SHA256
Связи (pixel-aquarium, 10.06): партия 3 (лаборатория) — общие связи + доступность
Вариант 2 (всё в лаборатории, реальный путь /network-view не трогаем). - Общие связи: среди друзей человека один помечен как «общий» (он и твой друг тоже) — золотой ободок + ★ (CSS .fg-node.is-common). В лаб-генерации addDeepLevels подставляет узнаваемого друга Ивана. - Доступность: визуально скрытый (sr-only) текстовый список графа .fg-a11y (центр + связи 1-го уровня) для скринридеров; обновляется в updateA11y при перестроении (role=region, aria-label). Автопроверки расширены до 19 ассертов (добавлены «общие связи ★» и sr-only список) — прогон 19/19 PASS. Бамп client.version → 1.2.150. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,7 +149,12 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
edgesSvg.setAttribute('class', 'fg-edges');
|
||||
const world = document.createElement('div');
|
||||
world.className = 'fg-world';
|
||||
stage.append(edgesSvg, world);
|
||||
// Доступность: визуально скрытый текстовый список графа для скринридеров (граф сам по себе им не читается).
|
||||
const a11y = document.createElement('div');
|
||||
a11y.className = 'fg-a11y';
|
||||
a11y.setAttribute('role', 'region');
|
||||
a11y.setAttribute('aria-label', 'Карта связей — список');
|
||||
stage.append(edgesSvg, world, a11y);
|
||||
ensureShineFilter(); // SVG-фильтр свечения «сияющих» (нужен до первой отрисовки узлов)
|
||||
|
||||
// Состояние камеры (панорамирование + зум)
|
||||
@@ -433,6 +438,7 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
`is-${src.relationType || 'contact'}`,
|
||||
tier === 2 ? 'is-tier2' : '', // друг друзей (вдвое меньше, полупрозрачный)
|
||||
tier >= 2 ? 'is-secondary' : '',
|
||||
src.common ? 'is-common' : '', // «общая связь» — этот человек и твой друг тоже (золотой ободок ★)
|
||||
].filter(Boolean).join(' ');
|
||||
el.dataset.nodeId = String(src.id);
|
||||
|
||||
@@ -472,6 +478,16 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
}
|
||||
}
|
||||
|
||||
// Доступность: текстовое представление графа для скринридеров (центр + связи 1-го уровня списком).
|
||||
function updateA11y() {
|
||||
const focus = nodes.find((n) => n.isFocus);
|
||||
const tier1 = nodes.filter((n) => n.tier === 1 && !n.isFocus);
|
||||
const rel = { family: 'семья', friend: 'друг', business: 'бизнес', contact: 'контакт' };
|
||||
const items = tier1.map((n) => `<li>${escapeHtml(n.name || n.login || String(n.id))}${n.shining ? ' — сияющий' : ''} (${rel[n.relationType] || 'связь'})</li>`).join('');
|
||||
a11y.innerHTML = `<p>Центр: ${escapeHtml(focus ? (focus.name || focus.login || '') : '')}. Связей 1-го уровня: ${tier1.length}.</p><ul>${items}</ul>`;
|
||||
}
|
||||
function escapeHtml(s) { return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); }
|
||||
|
||||
// Переиспользование узла при диффинге: сохраняем x/y/скорость, задаём новую роль и цель.
|
||||
function updateNodeRole(node, spec) {
|
||||
const src = spec.src;
|
||||
@@ -502,7 +518,7 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
// обновляем классы элемента (роль/тип/свечение/уровень) — без пересоздания DOM
|
||||
node.el.className = spec.dotOnly
|
||||
? ['fg-node', 'fg-dot', tier >= 3 ? 'is-tier3' : '', src.shining ? 'is-shine' : '', `is-${src.relationType || 'contact'}`].filter(Boolean).join(' ')
|
||||
: ['fg-node', spec.isFocus ? 'is-focus' : '', src.shining ? 'is-shine' : '', `is-${src.relationType || 'contact'}`, tier === 2 ? 'is-tier2' : '', tier >= 2 ? 'is-secondary' : ''].filter(Boolean).join(' ');
|
||||
: ['fg-node', spec.isFocus ? 'is-focus' : '', src.shining ? 'is-shine' : '', `is-${src.relationType || 'contact'}`, tier === 2 ? 'is-tier2' : '', tier >= 2 ? 'is-secondary' : '', src.common ? 'is-common' : ''].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
// --- Рендер ----------------------------------------------------------------
|
||||
@@ -1540,6 +1556,7 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
diveTargetId = null; surfacing = false; zoom = 1; // перестроение графа сбрасывает погружение и зум
|
||||
rebuildIndex(); // обновляем nodeById/hasDeep под новый набор узлов
|
||||
updateBadges(); // бейджи-счётчики связей под новый набор
|
||||
updateA11y(); // текстовый список графа для скринридеров
|
||||
layoutDeep(); // сразу ставим глубокие уровни на орбиты вокруг родителей
|
||||
renderDeepNodes(); // и показываем их (CSS-bloom их элементы не трогает)
|
||||
emitDiveChange(); // сбрасываем хлебные крошки (новый граф = верхний уровень)
|
||||
@@ -1622,6 +1639,7 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
if (ro) ro.disconnect();
|
||||
edgesSvg.remove();
|
||||
world.remove();
|
||||
a11y.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user