@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.
@@ -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 {};
@@ -0,0 +1,3 @@
1
+ import { defineTable } from "./index.js";
2
+
3
+ defineTable();
@@ -4,7 +4,7 @@ export interface TabsTabState {
4
4
  }
5
5
 
6
6
  export type TabsActivation = "automatic" | "manual";
7
- export type TabsOrientation = "horizontal" | "vertical";
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 unsupported orientation values back to `"horizontal"`.
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 value?.trim().toLowerCase() === VERTICAL_ORIENTATION
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
- for (let index = 0; index < tabStates.length; index += 1) {
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
- if (orientation === DEFAULT_ORIENTATION) {
177
- nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, 1);
178
- }
175
+ nextIndex = findNextEnabledTabIndex(tabStates, currentIndex, 1);
179
176
  break;
180
177
  case "ArrowLeft":
181
- if (orientation === DEFAULT_ORIENTATION) {
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 = getInitialSelectedTabIndex(tabStates);
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", 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];