11import { getConfig } from '../../scripts/ak.js';
22import { getComponentProps } from '../../scripts/utils/extraction-status.js';
3+ import '../../deps/se/se.js';
34
45const config = getConfig();
56
@@ -40,6 +41,7 @@ const EXCLUDED_SOURCES = new Set([
4041// `kind`/`values`/`optional` back the playground's controls; this table shows the
4142// human-readable `type` instead.
4243const EXCLUDED_COLUMNS = new Set(['status', 'since', 'kind', 'values', 'optional']);
44+ const TYPE_DISCLOSURE_THRESHOLD = 500;
4345
4446/**
4547 * The column-header text repeated inside a body cell. Only the narrow stacked layout
@@ -62,6 +64,30 @@ const buildCellValue = () => {
6264 return value;
6365};
6466
67+ const buildDescription = (text) => {
68+ const content = document.createDocumentFragment();
69+ const tokens = /(?<!`)(`+)(?!`)([\s\S]*?)(?<!`)\1(?!`)|(?<!!)\[([^\]]+)\]\((https?:\/\/(?:[^()\s]|\([^()\s]*\))+)\)/gi;
70+ let offset = 0;
71+ for (const match of text.matchAll(tokens)) {
72+ const [markdown, backticks, codeText, label, href] = match;
73+ let node;
74+ if (backticks) {
75+ node = document.createElement('code');
76+ node.textContent = codeText;
77+ } else if (label.trim() && URL.canParse(href)) {
78+ node = document.createElement('a');
79+ node.href = href;
80+ node.append(buildDescription(label));
81+ }
82+ if (node) {
83+ content.append(text.slice(offset, match.index), node);
84+ offset = match.index + markdown.length;
85+ }
86+ }
87+ content.append(text.slice(offset));
88+ return content;
89+ };
90+
6591const buildTableElement = (headerCells, dataCells) => {
6692 const tableHead = document.createElement('thead');
6793 tableHead.classList.add('header-row');
@@ -152,14 +178,68 @@ const buildDataTable = async (href) => {
152178 const dataCells = rows.map((props) => properties.map((key) => {
153179 const tableCell = document.createElement('td');
154180 const value = buildCellValue();
155- value.textContent = props[key] || '-';
181+ const resolvedValue = props[key] || '-';
182+ if (key === 'property'
183+ || key === 'attribute'
184+ || key === 'type'
185+ || (key === 'default' && resolvedValue !== '-')) {
186+ tableCell.classList.add('table-type-cell');
187+ }
188+ if (key === 'type' && typeof props[key] === 'string' && props[key].length > TYPE_DISCLOSURE_THRESHOLD) {
189+ const disclosure = document.createElement('se-disclosure');
190+ disclosure.setAttribute('preview', '');
191+ disclosure.setAttribute('label', `Expand to view more about ${props.attribute || props.property || 'this property'}`);
192+ disclosure.textContent = resolvedValue;
193+ value.append(disclosure);
194+ } else if (key === 'description' && typeof resolvedValue === 'string') {
195+ value.append(buildDescription(resolvedValue));
196+ } else {
197+ value.textContent = resolvedValue;
198+ }
156199 tableCell.append(value);
157200 return tableCell;
158201 }));
159202
160203 return buildTableElement(headerCells, dataCells);
161204};
162205
206+ const buildScroller = (table, labelIds) => {
207+ const scroller = document.createElement('div');
208+ scroller.className = 'table-scroll';
209+ scroller.tabIndex = 0;
210+ if (labelIds.length) {
211+ scroller.role = 'region';
212+ scroller.setAttribute('aria-labelledby', labelIds.join(' '));
213+ }
214+ scroller.append(table);
215+ return scroller;
216+ };
217+
218+ const watchScrollHint = (el, scroller, instructions) => {
219+ const update = () => {
220+ const overflow = scroller.scrollWidth - scroller.clientWidth;
221+ const remaining = overflow - Math.abs(scroller.scrollLeft);
222+ el.toggleAttribute('data-scroll-hint', remaining > 1);
223+ const descriptionId = overflow > 1 ? instructions.id : null;
224+ if (scroller.getAttribute('aria-describedby') !== descriptionId) {
225+ instructions.hidden = !descriptionId;
226+ if (descriptionId) {
227+ scroller.setAttribute('aria-describedby', descriptionId);
228+ } else {
229+ scroller.removeAttribute('aria-describedby');
230+ }
231+ }
232+ };
233+ update();
234+ // Measure again when styles, content, or the surrounding layout change.
235+ const observer = new ResizeObserver(update);
236+ observer.observe(scroller);
237+ observer.observe(scroller.querySelector('table'));
238+ scroller.addEventListener('scroll', update, { passive: true });
239+ window.addEventListener('resize', update, { passive: true });
240+ document.fonts?.ready.then(update);
241+ };
242+
163243export default async function init(el) {
164244 const dataHref = el.querySelector('a[href$=".json"]')?.href;
165245
@@ -187,5 +267,14 @@ export default async function init(el) {
187267 return heading.id;
188268 });
189269 if (table && labelIds.length) { table.setAttribute('aria-labelledby', labelIds.join(' ')); }
190- el.tabIndex = 0;
270+ if (table) {
271+ const scroller = buildScroller(table, labelIds);
272+ const instructions = document.createElement('span');
273+ instructions.id = `table-scroll-instructions-${Math.random().toString(36).slice(2)}`;
274+ instructions.className = 'visually-hidden';
275+ instructions.textContent = 'Scrollable table. Use the left and right arrow keys to view more.';
276+ instructions.hidden = true;
277+ el.replaceChildren(scroller, instructions);
278+ watchScrollHint(el, scroller, instructions);
279+ }
191280}
0 commit comments