접근성과 Shadow DOM
ARIA grid 구조, 스크린리더가 읽는 포커스 셀, Shadow DOM 안의 그리드.
이 데모의 코드
import { createGrid } from '@freegridjs/core';
import css from '@freegridjs/core/styles.css?inline';
import { demoColumns, makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';
export default function mount(ctx: DemoContext) {
const { lang } = ctx;
const h = (en: string, ko: string) => tr(lang, { en, ko });
// the grid lives in a shadow root, as in a web component; the stylesheet goes into the shadow root
const shadow = ctx.host.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = css;
const box = document.createElement('div');
box.style.height = '100%';
shadow.append(style, box);
const grid = createGrid(box, { columns: demoColumns(lang), data: makeRows(200), locale: lang, rowSelection: 'multiple', groupPanel: true });
grid.on('focusChange', e => {
// what a screen reader announces: the focused cell, through aria-activedescendant
const gridEl = shadow.querySelector('.fg-grid')!;
const cell = shadow.getElementById(gridEl.getAttribute('aria-activedescendant') ?? '');
ctx.log(`${h('focus', '포커스')} → ${h('row', '행')} ${e.rowIndex + 1}, ${e.colId}: ${cell?.textContent ?? ''}`);
});
text(ctx, h('This grid sits inside a Shadow DOM. Tab into it and use the arrow keys: the log shows what a screen reader reads. Menus and filters open inside the shadow root.',
'이 그리드는 Shadow DOM 안에 있습니다. Tab으로 들어가 방향키로 움직이면 스크린리더가 읽는 내용이 아래에 표시됩니다. 메뉴와 필터도 shadow root 안에서 열립니다.'));
separator(ctx);
button(ctx, h('Show ARIA roles', 'ARIA 역할 보기'), () => {
const g = shadow.querySelector('.fg-grid')!;
ctx.log(`role=${g.getAttribute('role')} aria-rowcount=${g.getAttribute('aria-rowcount')} aria-colcount=${g.getAttribute('aria-colcount')} aria-multiselectable=${g.getAttribute('aria-multiselectable')}`);
ctx.log(`${h('header rows', '헤더 행')}: ${shadow.querySelectorAll('.fg-header [role=row]').length}, ${h('column headers', '열 머리글')}: ${shadow.querySelectorAll('[role=columnheader]').length}`);
});
button(ctx, h('Group by country (becomes a treegrid)', '국가별 묶기 (treegrid로 바뀜)'), () => {
grid.setGroupBy(grid.getGroupBy().length ? [] : ['country']);
setTimeout(() => ctx.log(`role=${shadow.querySelector('.fg-grid')!.getAttribute('role')}`), 50);
});
return grid;
}