@lmfaole/basics 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +296 -6
- package/components/basic-accordion/index.d.ts +50 -0
- package/components/basic-accordion/index.js +387 -0
- package/components/basic-accordion/register.d.ts +1 -0
- package/components/basic-accordion/register.js +3 -0
- package/components/basic-dialog/index.d.ts +36 -0
- package/components/basic-dialog/index.js +292 -0
- package/components/basic-dialog/register.d.ts +1 -0
- package/components/basic-dialog/register.js +3 -0
- package/components/basic-popover/index.d.ts +70 -0
- package/components/basic-popover/index.js +460 -0
- package/components/basic-popover/register.d.ts +1 -0
- package/components/basic-popover/register.js +3 -0
- package/components/basic-summary-table/index.d.ts +69 -0
- package/components/basic-summary-table/index.js +390 -0
- package/components/basic-summary-table/register.d.ts +1 -0
- package/components/basic-summary-table/register.js +3 -0
- package/components/basic-table/index.d.ts +75 -0
- package/components/basic-table/index.js +554 -0
- package/components/basic-table/register.d.ts +1 -0
- package/components/basic-table/register.js +3 -0
- package/components/basic-tabs/index.d.ts +2 -3
- package/components/basic-tabs/index.js +16 -38
- package/index.d.ts +5 -0
- package/index.js +5 -0
- package/package.json +138 -81
- package/readme.mdx +1 -1
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
const HTMLElementBase = globalThis.HTMLElement ?? class {};
|
|
2
|
+
const HTMLTableElementBase = globalThis.HTMLTableElement ?? class {};
|
|
3
|
+
const HTMLTableSectionElementBase = globalThis.HTMLTableSectionElement ?? class {};
|
|
4
|
+
const HTMLTableCellElementBase = globalThis.HTMLTableCellElement ?? class {};
|
|
5
|
+
|
|
6
|
+
export const TABLE_TAG_NAME = "basic-table";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LABEL = "Tabell";
|
|
9
|
+
const GENERATED_CAPTION_ATTRIBUTE = "data-basic-table-generated-caption";
|
|
10
|
+
const GENERATED_COLUMN_HEADER_ATTRIBUTE = "data-basic-table-generated-column-header";
|
|
11
|
+
const GENERATED_ROW_HEADER_ATTRIBUTE = "data-basic-table-generated-row-header";
|
|
12
|
+
const GENERATED_DESCRIPTION_ATTRIBUTE = "data-basic-table-generated-description";
|
|
13
|
+
const MANAGED_HEADERS_ATTRIBUTE = "data-basic-table-managed-headers";
|
|
14
|
+
const MANAGED_LABEL_ATTRIBUTE = "data-basic-table-managed-label";
|
|
15
|
+
|
|
16
|
+
let nextTableInstanceId = 1;
|
|
17
|
+
|
|
18
|
+
export function normalizeTableLabel(value) {
|
|
19
|
+
return value?.trim() || DEFAULT_LABEL;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function normalizeTableCaption(value) {
|
|
23
|
+
return value?.trim() || "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function normalizeTableDescription(value) {
|
|
27
|
+
return value?.trim() || "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeTableRowHeaders(value) {
|
|
31
|
+
if (typeof value === "string") {
|
|
32
|
+
const normalized = value.trim().toLowerCase();
|
|
33
|
+
return normalized !== "false" && normalized !== "0";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return Boolean(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function normalizeTableColumnHeaders(value) {
|
|
40
|
+
return normalizeTableRowHeaders(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function normalizeTableRowHeaderColumn(value) {
|
|
44
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
45
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeTableCellSpan(value) {
|
|
49
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
50
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function collectOwnedTables(root) {
|
|
54
|
+
return Array.from(root.querySelectorAll("table")).filter(
|
|
55
|
+
(table) => table instanceof HTMLTableElementBase && table.closest(root.tagName.toLowerCase()) === root,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function inferHeaderScope(
|
|
60
|
+
cell,
|
|
61
|
+
placement,
|
|
62
|
+
{ columnHeadersEnabled = false, rowHeadersEnabled = false, rowHeaderColumnIndex = 0 } = {},
|
|
63
|
+
) {
|
|
64
|
+
const explicitScope = cell.getAttribute("scope")?.trim().toLowerCase();
|
|
65
|
+
|
|
66
|
+
if (explicitScope) {
|
|
67
|
+
return explicitScope;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (cell.hasAttribute(GENERATED_COLUMN_HEADER_ATTRIBUTE)) {
|
|
71
|
+
return "col";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (placement.sectionTag === "thead") {
|
|
75
|
+
return "col";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (columnHeadersEnabled && placement.rowIndex === 0) {
|
|
79
|
+
return "col";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
rowHeadersEnabled
|
|
84
|
+
&& placement.sectionTag === "tbody"
|
|
85
|
+
&& placement.columnIndex <= rowHeaderColumnIndex
|
|
86
|
+
&& placement.columnIndex + placement.colSpan > rowHeaderColumnIndex
|
|
87
|
+
) {
|
|
88
|
+
return "row";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function ensureHeaderId(cell, baseId, nextHeaderIndex) {
|
|
95
|
+
if (!cell.id) {
|
|
96
|
+
cell.id = `${baseId}-header-${nextHeaderIndex}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return cell.id;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function sortPlacementsInDocumentOrder(left, right) {
|
|
103
|
+
if (left.rowIndex !== right.rowIndex) {
|
|
104
|
+
return left.rowIndex - right.rowIndex;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return left.columnIndex - right.columnIndex;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildTablePlacements(table) {
|
|
111
|
+
const rows = Array.from(table.rows);
|
|
112
|
+
const grid = [];
|
|
113
|
+
const placements = [];
|
|
114
|
+
|
|
115
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
116
|
+
const row = rows[rowIndex];
|
|
117
|
+
let columnIndex = 0;
|
|
118
|
+
|
|
119
|
+
grid[rowIndex] ??= [];
|
|
120
|
+
|
|
121
|
+
for (const cell of Array.from(row.cells)) {
|
|
122
|
+
if (!(cell instanceof HTMLTableCellElementBase)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
while (grid[rowIndex][columnIndex]) {
|
|
127
|
+
columnIndex += 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const rowSpan = normalizeTableCellSpan(cell.getAttribute("rowspan"));
|
|
131
|
+
const colSpan = normalizeTableCellSpan(cell.getAttribute("colspan"));
|
|
132
|
+
const section = row.parentElement;
|
|
133
|
+
const sectionTag = section instanceof HTMLTableSectionElementBase
|
|
134
|
+
? section.tagName.toLowerCase()
|
|
135
|
+
: "table";
|
|
136
|
+
|
|
137
|
+
const placement = {
|
|
138
|
+
cell,
|
|
139
|
+
rowIndex,
|
|
140
|
+
columnIndex,
|
|
141
|
+
rowSpan,
|
|
142
|
+
colSpan,
|
|
143
|
+
sectionTag,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
placements.push(placement);
|
|
147
|
+
|
|
148
|
+
for (let rowOffset = 0; rowOffset < rowSpan; rowOffset += 1) {
|
|
149
|
+
grid[rowIndex + rowOffset] ??= [];
|
|
150
|
+
|
|
151
|
+
for (let columnOffset = 0; columnOffset < colSpan; columnOffset += 1) {
|
|
152
|
+
grid[rowIndex + rowOffset][columnIndex + columnOffset] = placement;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
columnIndex += colSpan;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return placements;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function findCellAtColumnIndex(row, targetColumnIndex) {
|
|
164
|
+
let columnIndex = 0;
|
|
165
|
+
|
|
166
|
+
for (const cell of Array.from(row.cells)) {
|
|
167
|
+
if (!(cell instanceof HTMLTableCellElementBase)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const colSpan = normalizeTableCellSpan(cell.getAttribute("colspan"));
|
|
172
|
+
|
|
173
|
+
if (targetColumnIndex >= columnIndex && targetColumnIndex < columnIndex + colSpan) {
|
|
174
|
+
return cell;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
columnIndex += colSpan;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function replaceCellTag(cell, tagName, generatedAttributeName) {
|
|
184
|
+
const replacement = document.createElement(tagName);
|
|
185
|
+
|
|
186
|
+
for (const attribute of Array.from(cell.attributes)) {
|
|
187
|
+
if (attribute.name === "headers" || attribute.name === MANAGED_HEADERS_ATTRIBUTE) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
replacement.setAttribute(attribute.name, attribute.value);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
replacement.setAttribute(generatedAttributeName, "");
|
|
195
|
+
replacement.replaceChildren(...Array.from(cell.childNodes));
|
|
196
|
+
cell.parentElement?.replaceChild(replacement, cell);
|
|
197
|
+
|
|
198
|
+
return replacement;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function promoteFirstRowCellsToHeaders(table) {
|
|
202
|
+
const firstRow = table.rows[0];
|
|
203
|
+
|
|
204
|
+
if (!firstRow) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const cell of Array.from(firstRow.cells)) {
|
|
209
|
+
if (!(cell instanceof HTMLTableCellElementBase) || cell.tagName === "TH") {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
replaceCellTag(cell, "th", GENERATED_COLUMN_HEADER_ATTRIBUTE);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function promoteBodyCellsToRowHeaders(table, rowHeaderColumnIndex) {
|
|
218
|
+
for (const section of Array.from(table.tBodies)) {
|
|
219
|
+
for (const row of Array.from(section.rows)) {
|
|
220
|
+
const targetCell = findCellAtColumnIndex(row, rowHeaderColumnIndex);
|
|
221
|
+
|
|
222
|
+
if (!(targetCell instanceof HTMLTableCellElementBase) || targetCell.tagName === "TH") {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
replaceCellTag(targetCell, "th", GENERATED_ROW_HEADER_ATTRIBUTE);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function demoteManagedHeaders(table, generatedAttributeName) {
|
|
232
|
+
for (const cell of Array.from(table.querySelectorAll(`th[${generatedAttributeName}]`))) {
|
|
233
|
+
if (!(cell instanceof HTMLTableCellElementBase)) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const replacement = document.createElement("td");
|
|
238
|
+
|
|
239
|
+
for (const attribute of Array.from(cell.attributes)) {
|
|
240
|
+
if (attribute.name === generatedAttributeName || attribute.name === "scope") {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
replacement.setAttribute(attribute.name, attribute.value);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
replacement.replaceChildren(...Array.from(cell.childNodes));
|
|
248
|
+
cell.parentElement?.replaceChild(replacement, cell);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function createGeneratedCaption(table) {
|
|
253
|
+
const caption = document.createElement("caption");
|
|
254
|
+
caption.setAttribute(GENERATED_CAPTION_ATTRIBUTE, "");
|
|
255
|
+
table.insertBefore(caption, table.firstChild);
|
|
256
|
+
return caption;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function syncTableCaption(table, captionText) {
|
|
260
|
+
const existingCaption = table.caption;
|
|
261
|
+
const generatedCaption = existingCaption?.hasAttribute(GENERATED_CAPTION_ATTRIBUTE)
|
|
262
|
+
? existingCaption
|
|
263
|
+
: null;
|
|
264
|
+
|
|
265
|
+
if (!captionText) {
|
|
266
|
+
generatedCaption?.remove();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (existingCaption && !generatedCaption) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const caption = generatedCaption ?? createGeneratedCaption(table);
|
|
275
|
+
caption.textContent = captionText;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function syncFallbackAccessibleName(table, label) {
|
|
279
|
+
const hasCaption = Boolean(table.caption);
|
|
280
|
+
let hasManagedLabel = table.hasAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
281
|
+
|
|
282
|
+
if (hasManagedLabel && table.getAttribute("aria-label") !== label) {
|
|
283
|
+
table.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
284
|
+
hasManagedLabel = false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const hasOwnAriaLabel = table.hasAttribute("aria-label") && !hasManagedLabel;
|
|
288
|
+
const hasOwnLabelledBy = table.hasAttribute("aria-labelledby");
|
|
289
|
+
|
|
290
|
+
if (hasCaption || hasOwnAriaLabel || hasOwnLabelledBy) {
|
|
291
|
+
if (hasManagedLabel) {
|
|
292
|
+
table.removeAttribute("aria-label");
|
|
293
|
+
table.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
table.setAttribute("aria-label", label);
|
|
300
|
+
table.setAttribute(MANAGED_LABEL_ATTRIBUTE, "");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getGeneratedDescription(root) {
|
|
304
|
+
const description = root.querySelector(`[${GENERATED_DESCRIPTION_ATTRIBUTE}]`);
|
|
305
|
+
return description instanceof HTMLElementBase && description.closest(root.tagName.toLowerCase()) === root
|
|
306
|
+
? description
|
|
307
|
+
: null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function getAriaReferenceTokens(value) {
|
|
311
|
+
return value?.trim() ? value.trim().split(/\s+/) : [];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function syncTableDescription(root, table, descriptionText, baseId) {
|
|
315
|
+
const existingDescription = getGeneratedDescription(root);
|
|
316
|
+
|
|
317
|
+
if (!descriptionText) {
|
|
318
|
+
if (existingDescription?.id) {
|
|
319
|
+
const tokens = getAriaReferenceTokens(table.getAttribute("aria-describedby")).filter(
|
|
320
|
+
(token) => token !== existingDescription.id,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
if (tokens.length > 0) {
|
|
324
|
+
table.setAttribute("aria-describedby", tokens.join(" "));
|
|
325
|
+
} else {
|
|
326
|
+
table.removeAttribute("aria-describedby");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
existingDescription?.remove();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const description = existingDescription ?? document.createElement("p");
|
|
335
|
+
|
|
336
|
+
if (!existingDescription) {
|
|
337
|
+
description.setAttribute(GENERATED_DESCRIPTION_ATTRIBUTE, "");
|
|
338
|
+
description.hidden = true;
|
|
339
|
+
root.append(description);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!description.id) {
|
|
343
|
+
description.id = `${baseId}-description`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
description.textContent = descriptionText;
|
|
347
|
+
|
|
348
|
+
const tokens = getAriaReferenceTokens(table.getAttribute("aria-describedby")).filter(
|
|
349
|
+
(token) => token !== description.id,
|
|
350
|
+
);
|
|
351
|
+
tokens.push(description.id);
|
|
352
|
+
table.setAttribute("aria-describedby", tokens.join(" "));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export class TableElement extends HTMLElementBase {
|
|
356
|
+
static observedAttributes = [
|
|
357
|
+
"data-caption",
|
|
358
|
+
"data-column-headers",
|
|
359
|
+
"data-description",
|
|
360
|
+
"data-label",
|
|
361
|
+
"data-row-header-column",
|
|
362
|
+
"data-row-headers",
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
#instanceId = `${TABLE_TAG_NAME}-${nextTableInstanceId++}`;
|
|
366
|
+
#observer = null;
|
|
367
|
+
#scheduledFrame = 0;
|
|
368
|
+
|
|
369
|
+
connectedCallback() {
|
|
370
|
+
this.#syncObserver();
|
|
371
|
+
this.refresh();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
disconnectedCallback() {
|
|
375
|
+
this.#observer?.disconnect();
|
|
376
|
+
this.#observer = null;
|
|
377
|
+
|
|
378
|
+
if (this.#scheduledFrame !== 0 && typeof window !== "undefined") {
|
|
379
|
+
window.cancelAnimationFrame(this.#scheduledFrame);
|
|
380
|
+
this.#scheduledFrame = 0;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
attributeChangedCallback() {
|
|
385
|
+
this.#scheduleRefresh();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
refresh() {
|
|
389
|
+
const table = collectOwnedTables(this)[0] ?? null;
|
|
390
|
+
|
|
391
|
+
if (!(table instanceof HTMLTableElementBase)) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const label = normalizeTableLabel(this.getAttribute("data-label"));
|
|
396
|
+
const caption = normalizeTableCaption(this.getAttribute("data-caption"));
|
|
397
|
+
const description = normalizeTableDescription(this.getAttribute("data-description"));
|
|
398
|
+
const columnHeadersEnabled = normalizeTableColumnHeaders(
|
|
399
|
+
this.getAttribute("data-column-headers") ?? this.hasAttribute("data-column-headers"),
|
|
400
|
+
);
|
|
401
|
+
const rowHeaderColumnIndex = normalizeTableRowHeaderColumn(
|
|
402
|
+
this.getAttribute("data-row-header-column"),
|
|
403
|
+
) - 1;
|
|
404
|
+
const rowHeadersEnabled = normalizeTableRowHeaders(
|
|
405
|
+
this.getAttribute("data-row-headers") ?? this.hasAttribute("data-row-header-column"),
|
|
406
|
+
);
|
|
407
|
+
const baseId = this.id || this.#instanceId;
|
|
408
|
+
|
|
409
|
+
syncTableCaption(table, caption);
|
|
410
|
+
syncFallbackAccessibleName(table, label);
|
|
411
|
+
syncTableDescription(this, table, description, baseId);
|
|
412
|
+
|
|
413
|
+
if (columnHeadersEnabled) {
|
|
414
|
+
promoteFirstRowCellsToHeaders(table);
|
|
415
|
+
} else {
|
|
416
|
+
demoteManagedHeaders(table, GENERATED_COLUMN_HEADER_ATTRIBUTE);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (rowHeadersEnabled) {
|
|
420
|
+
demoteManagedHeaders(table, GENERATED_ROW_HEADER_ATTRIBUTE);
|
|
421
|
+
promoteBodyCellsToRowHeaders(table, rowHeaderColumnIndex);
|
|
422
|
+
} else {
|
|
423
|
+
demoteManagedHeaders(table, GENERATED_ROW_HEADER_ATTRIBUTE);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const placements = buildTablePlacements(table);
|
|
427
|
+
const headerPlacements = [];
|
|
428
|
+
let nextHeaderIndex = 1;
|
|
429
|
+
|
|
430
|
+
for (const placement of placements) {
|
|
431
|
+
if (placement.cell.tagName !== "TH") {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const scope = inferHeaderScope(placement.cell, placement, {
|
|
436
|
+
columnHeadersEnabled,
|
|
437
|
+
rowHeadersEnabled,
|
|
438
|
+
rowHeaderColumnIndex,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
if (scope) {
|
|
442
|
+
placement.cell.setAttribute("scope", scope);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
headerPlacements.push({
|
|
446
|
+
...placement,
|
|
447
|
+
scope,
|
|
448
|
+
id: ensureHeaderId(placement.cell, this.id || this.#instanceId, nextHeaderIndex),
|
|
449
|
+
});
|
|
450
|
+
nextHeaderIndex += 1;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
headerPlacements.sort(sortPlacementsInDocumentOrder);
|
|
454
|
+
|
|
455
|
+
for (const placement of placements) {
|
|
456
|
+
if (placement.cell.tagName !== "TD") {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const associatedHeaders = headerPlacements.filter((header) => {
|
|
461
|
+
switch (header.scope) {
|
|
462
|
+
case "col":
|
|
463
|
+
case "colgroup":
|
|
464
|
+
return (
|
|
465
|
+
header.rowIndex < placement.rowIndex
|
|
466
|
+
&& header.columnIndex < placement.columnIndex + placement.colSpan
|
|
467
|
+
&& header.columnIndex + header.colSpan > placement.columnIndex
|
|
468
|
+
);
|
|
469
|
+
case "row":
|
|
470
|
+
case "rowgroup":
|
|
471
|
+
return (
|
|
472
|
+
header.columnIndex < placement.columnIndex
|
|
473
|
+
&& header.rowIndex < placement.rowIndex + placement.rowSpan
|
|
474
|
+
&& header.rowIndex + header.rowSpan > placement.rowIndex
|
|
475
|
+
);
|
|
476
|
+
default:
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (associatedHeaders.length === 0) {
|
|
482
|
+
if (placement.cell.hasAttribute(MANAGED_HEADERS_ATTRIBUTE)) {
|
|
483
|
+
placement.cell.removeAttribute("headers");
|
|
484
|
+
placement.cell.removeAttribute(MANAGED_HEADERS_ATTRIBUTE);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (
|
|
491
|
+
placement.cell.hasAttribute("headers")
|
|
492
|
+
&& !placement.cell.hasAttribute(MANAGED_HEADERS_ATTRIBUTE)
|
|
493
|
+
) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
placement.cell.setAttribute(
|
|
498
|
+
"headers",
|
|
499
|
+
associatedHeaders.map((header) => header.id).join(" "),
|
|
500
|
+
);
|
|
501
|
+
placement.cell.setAttribute(MANAGED_HEADERS_ATTRIBUTE, "");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
#scheduleRefresh() {
|
|
506
|
+
if (this.#scheduledFrame !== 0 || typeof window === "undefined") {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this.#scheduledFrame = window.requestAnimationFrame(() => {
|
|
511
|
+
this.#scheduledFrame = 0;
|
|
512
|
+
this.refresh();
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
#syncObserver() {
|
|
517
|
+
if (this.#observer || typeof MutationObserver === "undefined") {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
this.#observer = new MutationObserver(() => {
|
|
522
|
+
this.#scheduleRefresh();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
this.#observer.observe(this, {
|
|
526
|
+
childList: true,
|
|
527
|
+
subtree: true,
|
|
528
|
+
characterData: true,
|
|
529
|
+
attributes: true,
|
|
530
|
+
attributeFilter: [
|
|
531
|
+
"aria-describedby",
|
|
532
|
+
"aria-label",
|
|
533
|
+
"aria-labelledby",
|
|
534
|
+
"colspan",
|
|
535
|
+
"headers",
|
|
536
|
+
"id",
|
|
537
|
+
"rowspan",
|
|
538
|
+
"scope",
|
|
539
|
+
],
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export function defineTable(registry = globalThis.customElements) {
|
|
545
|
+
if (!registry?.get || !registry?.define) {
|
|
546
|
+
return TableElement;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!registry.get(TABLE_TAG_NAME)) {
|
|
550
|
+
registry.define(TABLE_TAG_NAME, TableElement);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return TableElement;
|
|
554
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,7 +4,7 @@ export interface TabsTabState {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export type TabsActivation = "automatic" | "manual";
|
|
7
|
-
export type TabsOrientation = "horizontal"
|
|
7
|
+
export type TabsOrientation = "horizontal";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Public tag name registered by `defineTabs`.
|
|
@@ -12,7 +12,7 @@ export type TabsOrientation = "horizontal" | "vertical";
|
|
|
12
12
|
export const TABS_TAG_NAME: "basic-tabs";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Normalizes
|
|
15
|
+
* Normalizes orientation values back to `"horizontal"`.
|
|
16
16
|
*/
|
|
17
17
|
export function normalizeTabsOrientation(
|
|
18
18
|
value?: string | null,
|
|
@@ -48,7 +48,6 @@ export function findNextEnabledTabIndex(
|
|
|
48
48
|
*
|
|
49
49
|
* Attributes:
|
|
50
50
|
* - `data-label`: fallback accessible name when the tablist has no own label
|
|
51
|
-
* - `data-orientation`: sets the keyboard orientation and `aria-orientation`
|
|
52
51
|
* - `data-activation`: `automatic` or `manual`
|
|
53
52
|
* - `data-selected-index`: zero-based initially selected tab index
|
|
54
53
|
*/
|
|
@@ -8,7 +8,6 @@ const DEFAULT_LABEL = "Faner";
|
|
|
8
8
|
const DEFAULT_ACTIVATION = "automatic";
|
|
9
9
|
const DEFAULT_ORIENTATION = "horizontal";
|
|
10
10
|
const MANUAL_ACTIVATION = "manual";
|
|
11
|
-
const VERTICAL_ORIENTATION = "vertical";
|
|
12
11
|
const TABLIST_SELECTOR = "[data-tabs-list]";
|
|
13
12
|
const TAB_SELECTOR = "[data-tab]";
|
|
14
13
|
const PANEL_SELECTOR = "[data-tab-panel]";
|
|
@@ -16,9 +15,7 @@ const PANEL_SELECTOR = "[data-tab-panel]";
|
|
|
16
15
|
let nextTabsInstanceId = 1;
|
|
17
16
|
|
|
18
17
|
export function normalizeTabsOrientation(value) {
|
|
19
|
-
return
|
|
20
|
-
? VERTICAL_ORIENTATION
|
|
21
|
-
: DEFAULT_ORIENTATION;
|
|
18
|
+
return DEFAULT_ORIENTATION;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
export function normalizeTabsActivation(value) {
|
|
@@ -36,13 +33,7 @@ export function getInitialSelectedTabIndex(tabStates) {
|
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
if (!tabStates[index]?.disabled) {
|
|
41
|
-
return index;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return -1;
|
|
36
|
+
return findFirstEnabledTabIndex(tabStates);
|
|
46
37
|
}
|
|
47
38
|
|
|
48
39
|
export function findNextEnabledTabIndex(tabStates, startIndex, direction) {
|
|
@@ -80,6 +71,16 @@ function isTabDisabled(tab) {
|
|
|
80
71
|
return tab.hasAttribute("disabled") || tab.getAttribute("aria-disabled") === "true";
|
|
81
72
|
}
|
|
82
73
|
|
|
74
|
+
function findFirstEnabledTabIndex(tabStates) {
|
|
75
|
+
for (let index = 0; index < tabStates.length; index += 1) {
|
|
76
|
+
if (!tabStates[index]?.disabled) {
|
|
77
|
+
return index;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return -1;
|
|
82
|
+
}
|
|
83
|
+
|
|
83
84
|
function findLastEnabledTabIndex(tabStates) {
|
|
84
85
|
for (let index = tabStates.length - 1; index >= 0; index -= 1) {
|
|
85
86
|
if (!tabStates[index]?.disabled) {
|
|
@@ -94,7 +95,6 @@ export class TabsElement extends HTMLElementBase {
|
|
|
94
95
|
static observedAttributes = [
|
|
95
96
|
"data-activation",
|
|
96
97
|
"data-label",
|
|
97
|
-
"data-orientation",
|
|
98
98
|
"data-selected-index",
|
|
99
99
|
];
|
|
100
100
|
|
|
@@ -164,7 +164,6 @@ export class TabsElement extends HTMLElementBase {
|
|
|
164
164
|
const tabStates = this.#getTabStates();
|
|
165
165
|
const currentIndex = this.#tabs.indexOf(currentTab);
|
|
166
166
|
const activation = this.#getActivation();
|
|
167
|
-
const orientation = this.#getOrientation();
|
|
168
167
|
let nextIndex = -1;
|
|
169
168
|
|
|
170
169
|
if (currentIndex === -1 || currentIndex >= tabStates.length) {
|
|
@@ -173,27 +172,13 @@ export class TabsElement extends HTMLElementBase {
|
|
|
173
172
|
|
|
174
173
|
switch (event.key) {
|
|
175
174
|
case "ArrowRight":
|
|
176
|
-
|
|
177
|
-
nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, 1);
|
|
178
|
-
}
|
|
175
|
+
nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, 1);
|
|
179
176
|
break;
|
|
180
177
|
case "ArrowLeft":
|
|
181
|
-
|
|
182
|
-
nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, -1);
|
|
183
|
-
}
|
|
184
|
-
break;
|
|
185
|
-
case "ArrowDown":
|
|
186
|
-
if (orientation === VERTICAL_ORIENTATION) {
|
|
187
|
-
nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, 1);
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
case "ArrowUp":
|
|
191
|
-
if (orientation === VERTICAL_ORIENTATION) {
|
|
192
|
-
nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, -1);
|
|
193
|
-
}
|
|
178
|
+
nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, -1);
|
|
194
179
|
break;
|
|
195
180
|
case "Home":
|
|
196
|
-
nextIndex =
|
|
181
|
+
nextIndex = findFirstEnabledTabIndex(tabStates);
|
|
197
182
|
break;
|
|
198
183
|
case "End":
|
|
199
184
|
nextIndex = findLastEnabledTabIndex(tabStates);
|
|
@@ -248,12 +233,6 @@ export class TabsElement extends HTMLElementBase {
|
|
|
248
233
|
return this.getAttribute("data-label")?.trim() || DEFAULT_LABEL;
|
|
249
234
|
}
|
|
250
235
|
|
|
251
|
-
#getOrientation() {
|
|
252
|
-
return normalizeTabsOrientation(
|
|
253
|
-
this.getAttribute("data-orientation") ?? this.#tabList?.getAttribute("aria-orientation"),
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
236
|
#getTabStates(configuredSelectedIndex = null) {
|
|
258
237
|
const pairCount = Math.min(this.#tabs.length, this.#panels.length);
|
|
259
238
|
|
|
@@ -292,7 +271,6 @@ export class TabsElement extends HTMLElementBase {
|
|
|
292
271
|
return;
|
|
293
272
|
}
|
|
294
273
|
|
|
295
|
-
const orientation = this.#getOrientation();
|
|
296
274
|
const pairCount = Math.min(this.#tabs.length, this.#panels.length);
|
|
297
275
|
const baseId = this.id || this.#instanceId;
|
|
298
276
|
|
|
@@ -301,7 +279,7 @@ export class TabsElement extends HTMLElementBase {
|
|
|
301
279
|
}
|
|
302
280
|
|
|
303
281
|
this.#tabList.setAttribute("role", "tablist");
|
|
304
|
-
this.#tabList.setAttribute("aria-orientation",
|
|
282
|
+
this.#tabList.setAttribute("aria-orientation", DEFAULT_ORIENTATION);
|
|
305
283
|
|
|
306
284
|
for (let index = 0; index < this.#tabs.length; index += 1) {
|
|
307
285
|
const tab = this.#tabs[index];
|