luxen-ui 0.21.0 → 0.21.2
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/cdn/custom-elements.json +175 -175
- package/cdn/elements/tabs/tabs.d.ts +33 -0
- package/cdn/elements/tabs/tabs.d.ts.map +1 -1
- package/cdn/elements/tabs/tabs.js +1 -1
- package/cdn/elements/tabs/tabs.js.map +1 -1
- package/cdn/translations/ko.d.ts +4 -0
- package/cdn/translations/ko.d.ts.map +1 -0
- package/cdn/translations/ko.js +2 -0
- package/cdn/translations/ko.js.map +1 -0
- package/dist/custom-elements.json +175 -175
- package/dist/elements/tabs/tabs.d.ts +33 -0
- package/dist/elements/tabs/tabs.d.ts.map +1 -1
- package/dist/elements/tabs/tabs.js +249 -1
- package/dist/metadata/index.json +1 -1
- package/dist/templates/elements/tabs.md +45 -2
- package/dist/translations/ko.d.ts +4 -0
- package/dist/translations/ko.d.ts.map +1 -0
- package/dist/translations/ko.js +51 -0
- package/package.json +1 -1
package/cdn/custom-elements.json
CHANGED
|
@@ -172,181 +172,6 @@
|
|
|
172
172
|
}
|
|
173
173
|
]
|
|
174
174
|
},
|
|
175
|
-
{
|
|
176
|
-
"kind": "javascript-module",
|
|
177
|
-
"path": "src/html/elements/avatar/avatar.ts",
|
|
178
|
-
"declarations": [
|
|
179
|
-
{
|
|
180
|
-
"kind": "class",
|
|
181
|
-
"description": "",
|
|
182
|
-
"name": "Avatar",
|
|
183
|
-
"cssProperties": [
|
|
184
|
-
{
|
|
185
|
-
"description": "Box size (any length). Set it inline (e.g. `style=\"--size: 20px\"`) for an arbitrary size beyond the `size` token scale; the font then follows proportionally. The `size` attribute sets it to the matching token.",
|
|
186
|
-
"name": "--size",
|
|
187
|
-
"default": "40px"
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"description": "Background fill color for initials and the default icon.",
|
|
191
|
-
"name": "--color"
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
"description": "Initials/icon color over `--color`. Defaults to an auto-derived readable color; set it to enforce a specific brand color (overrides the automatic choice).",
|
|
195
|
-
"name": "--text-color"
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
"description": "Set to `circle` for a fully circular avatar (default is a rounded square).",
|
|
199
|
-
"name": "--appearance"
|
|
200
|
-
}
|
|
201
|
-
],
|
|
202
|
-
"cssParts": [
|
|
203
|
-
{
|
|
204
|
-
"description": "The avatar container that paints `--color`; style it (e.g. `color`) to override the auto-derived text color.",
|
|
205
|
-
"name": "base"
|
|
206
|
-
}
|
|
207
|
-
],
|
|
208
|
-
"members": [
|
|
209
|
-
{
|
|
210
|
-
"kind": "field",
|
|
211
|
-
"name": "src",
|
|
212
|
-
"type": {
|
|
213
|
-
"text": "string"
|
|
214
|
-
},
|
|
215
|
-
"default": "''",
|
|
216
|
-
"description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
|
|
217
|
-
"attribute": "src"
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"kind": "field",
|
|
221
|
-
"name": "name",
|
|
222
|
-
"type": {
|
|
223
|
-
"text": "string"
|
|
224
|
-
},
|
|
225
|
-
"default": "''",
|
|
226
|
-
"description": "Name used as the accessible label and to derive the initials fallback.",
|
|
227
|
-
"attribute": "name"
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
"kind": "field",
|
|
231
|
-
"name": "size",
|
|
232
|
-
"type": {
|
|
233
|
-
"text": "string"
|
|
234
|
-
},
|
|
235
|
-
"default": "'md'",
|
|
236
|
-
"description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
|
|
237
|
-
"attribute": "size",
|
|
238
|
-
"reflects": true
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
"kind": "field",
|
|
242
|
-
"name": "badge",
|
|
243
|
-
"type": {
|
|
244
|
-
"text": "number"
|
|
245
|
-
},
|
|
246
|
-
"default": "0",
|
|
247
|
-
"description": "Count shown in the corner badge. `0` hides the badge.",
|
|
248
|
-
"attribute": "badge"
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
"kind": "field",
|
|
252
|
-
"name": "interactive",
|
|
253
|
-
"type": {
|
|
254
|
-
"text": "boolean"
|
|
255
|
-
},
|
|
256
|
-
"default": "false",
|
|
257
|
-
"description": "Render as a `<button>` with focus ring and hover states.",
|
|
258
|
-
"attribute": "interactive",
|
|
259
|
-
"reflects": true
|
|
260
|
-
}
|
|
261
|
-
],
|
|
262
|
-
"attributes": [
|
|
263
|
-
{
|
|
264
|
-
"name": "src",
|
|
265
|
-
"type": {
|
|
266
|
-
"text": "string"
|
|
267
|
-
},
|
|
268
|
-
"default": "''",
|
|
269
|
-
"description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
|
|
270
|
-
"fieldName": "src"
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
"name": "name",
|
|
274
|
-
"type": {
|
|
275
|
-
"text": "string"
|
|
276
|
-
},
|
|
277
|
-
"default": "''",
|
|
278
|
-
"description": "Name used as the accessible label and to derive the initials fallback.",
|
|
279
|
-
"fieldName": "name"
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
"name": "size",
|
|
283
|
-
"type": {
|
|
284
|
-
"text": "string"
|
|
285
|
-
},
|
|
286
|
-
"default": "'md'",
|
|
287
|
-
"description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
|
|
288
|
-
"fieldName": "size"
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
"name": "badge",
|
|
292
|
-
"type": {
|
|
293
|
-
"text": "number"
|
|
294
|
-
},
|
|
295
|
-
"default": "0",
|
|
296
|
-
"description": "Count shown in the corner badge. `0` hides the badge.",
|
|
297
|
-
"fieldName": "badge"
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
"name": "interactive",
|
|
301
|
-
"type": {
|
|
302
|
-
"text": "boolean"
|
|
303
|
-
},
|
|
304
|
-
"default": "false",
|
|
305
|
-
"description": "Render as a `<button>` with focus ring and hover states.",
|
|
306
|
-
"fieldName": "interactive"
|
|
307
|
-
}
|
|
308
|
-
],
|
|
309
|
-
"superclass": {
|
|
310
|
-
"name": "LuxenElement",
|
|
311
|
-
"module": "/src/html/shared/luxen-element.js"
|
|
312
|
-
},
|
|
313
|
-
"tagName": "l-avatar",
|
|
314
|
-
"customElement": true,
|
|
315
|
-
"summary": "An avatar component for displaying user images, initials, or a default icon.",
|
|
316
|
-
"_cssClasses": [
|
|
317
|
-
{
|
|
318
|
-
"name": ".l-avatar-group",
|
|
319
|
-
"description": "Flex wrapper that overlaps a row of stacked avatars."
|
|
320
|
-
}
|
|
321
|
-
]
|
|
322
|
-
}
|
|
323
|
-
],
|
|
324
|
-
"exports": [
|
|
325
|
-
{
|
|
326
|
-
"kind": "js",
|
|
327
|
-
"name": "Avatar",
|
|
328
|
-
"declaration": {
|
|
329
|
-
"name": "Avatar",
|
|
330
|
-
"module": "src/html/elements/avatar/avatar.ts"
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
]
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
"kind": "javascript-module",
|
|
337
|
-
"path": "src/html/elements/avatar/index.ts",
|
|
338
|
-
"declarations": [],
|
|
339
|
-
"exports": [
|
|
340
|
-
{
|
|
341
|
-
"kind": "js",
|
|
342
|
-
"name": "*",
|
|
343
|
-
"declaration": {
|
|
344
|
-
"name": "*",
|
|
345
|
-
"module": "src/html/elements/avatar/avatar.js"
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
]
|
|
349
|
-
},
|
|
350
175
|
{
|
|
351
176
|
"kind": "javascript-module",
|
|
352
177
|
"path": "src/html/elements/alert-dialog/alert-dialog.ts",
|
|
@@ -827,6 +652,181 @@
|
|
|
827
652
|
}
|
|
828
653
|
]
|
|
829
654
|
},
|
|
655
|
+
{
|
|
656
|
+
"kind": "javascript-module",
|
|
657
|
+
"path": "src/html/elements/avatar/avatar.ts",
|
|
658
|
+
"declarations": [
|
|
659
|
+
{
|
|
660
|
+
"kind": "class",
|
|
661
|
+
"description": "",
|
|
662
|
+
"name": "Avatar",
|
|
663
|
+
"cssProperties": [
|
|
664
|
+
{
|
|
665
|
+
"description": "Box size (any length). Set it inline (e.g. `style=\"--size: 20px\"`) for an arbitrary size beyond the `size` token scale; the font then follows proportionally. The `size` attribute sets it to the matching token.",
|
|
666
|
+
"name": "--size",
|
|
667
|
+
"default": "40px"
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
"description": "Background fill color for initials and the default icon.",
|
|
671
|
+
"name": "--color"
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
"description": "Initials/icon color over `--color`. Defaults to an auto-derived readable color; set it to enforce a specific brand color (overrides the automatic choice).",
|
|
675
|
+
"name": "--text-color"
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
"description": "Set to `circle` for a fully circular avatar (default is a rounded square).",
|
|
679
|
+
"name": "--appearance"
|
|
680
|
+
}
|
|
681
|
+
],
|
|
682
|
+
"cssParts": [
|
|
683
|
+
{
|
|
684
|
+
"description": "The avatar container that paints `--color`; style it (e.g. `color`) to override the auto-derived text color.",
|
|
685
|
+
"name": "base"
|
|
686
|
+
}
|
|
687
|
+
],
|
|
688
|
+
"members": [
|
|
689
|
+
{
|
|
690
|
+
"kind": "field",
|
|
691
|
+
"name": "src",
|
|
692
|
+
"type": {
|
|
693
|
+
"text": "string"
|
|
694
|
+
},
|
|
695
|
+
"default": "''",
|
|
696
|
+
"description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
|
|
697
|
+
"attribute": "src"
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
"kind": "field",
|
|
701
|
+
"name": "name",
|
|
702
|
+
"type": {
|
|
703
|
+
"text": "string"
|
|
704
|
+
},
|
|
705
|
+
"default": "''",
|
|
706
|
+
"description": "Name used as the accessible label and to derive the initials fallback.",
|
|
707
|
+
"attribute": "name"
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"kind": "field",
|
|
711
|
+
"name": "size",
|
|
712
|
+
"type": {
|
|
713
|
+
"text": "string"
|
|
714
|
+
},
|
|
715
|
+
"default": "'md'",
|
|
716
|
+
"description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
|
|
717
|
+
"attribute": "size",
|
|
718
|
+
"reflects": true
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
"kind": "field",
|
|
722
|
+
"name": "badge",
|
|
723
|
+
"type": {
|
|
724
|
+
"text": "number"
|
|
725
|
+
},
|
|
726
|
+
"default": "0",
|
|
727
|
+
"description": "Count shown in the corner badge. `0` hides the badge.",
|
|
728
|
+
"attribute": "badge"
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
"kind": "field",
|
|
732
|
+
"name": "interactive",
|
|
733
|
+
"type": {
|
|
734
|
+
"text": "boolean"
|
|
735
|
+
},
|
|
736
|
+
"default": "false",
|
|
737
|
+
"description": "Render as a `<button>` with focus ring and hover states.",
|
|
738
|
+
"attribute": "interactive",
|
|
739
|
+
"reflects": true
|
|
740
|
+
}
|
|
741
|
+
],
|
|
742
|
+
"attributes": [
|
|
743
|
+
{
|
|
744
|
+
"name": "src",
|
|
745
|
+
"type": {
|
|
746
|
+
"text": "string"
|
|
747
|
+
},
|
|
748
|
+
"default": "''",
|
|
749
|
+
"description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
|
|
750
|
+
"fieldName": "src"
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
"name": "name",
|
|
754
|
+
"type": {
|
|
755
|
+
"text": "string"
|
|
756
|
+
},
|
|
757
|
+
"default": "''",
|
|
758
|
+
"description": "Name used as the accessible label and to derive the initials fallback.",
|
|
759
|
+
"fieldName": "name"
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
"name": "size",
|
|
763
|
+
"type": {
|
|
764
|
+
"text": "string"
|
|
765
|
+
},
|
|
766
|
+
"default": "'md'",
|
|
767
|
+
"description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
|
|
768
|
+
"fieldName": "size"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
"name": "badge",
|
|
772
|
+
"type": {
|
|
773
|
+
"text": "number"
|
|
774
|
+
},
|
|
775
|
+
"default": "0",
|
|
776
|
+
"description": "Count shown in the corner badge. `0` hides the badge.",
|
|
777
|
+
"fieldName": "badge"
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
"name": "interactive",
|
|
781
|
+
"type": {
|
|
782
|
+
"text": "boolean"
|
|
783
|
+
},
|
|
784
|
+
"default": "false",
|
|
785
|
+
"description": "Render as a `<button>` with focus ring and hover states.",
|
|
786
|
+
"fieldName": "interactive"
|
|
787
|
+
}
|
|
788
|
+
],
|
|
789
|
+
"superclass": {
|
|
790
|
+
"name": "LuxenElement",
|
|
791
|
+
"module": "/src/html/shared/luxen-element.js"
|
|
792
|
+
},
|
|
793
|
+
"tagName": "l-avatar",
|
|
794
|
+
"customElement": true,
|
|
795
|
+
"summary": "An avatar component for displaying user images, initials, or a default icon.",
|
|
796
|
+
"_cssClasses": [
|
|
797
|
+
{
|
|
798
|
+
"name": ".l-avatar-group",
|
|
799
|
+
"description": "Flex wrapper that overlaps a row of stacked avatars."
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
"exports": [
|
|
805
|
+
{
|
|
806
|
+
"kind": "js",
|
|
807
|
+
"name": "Avatar",
|
|
808
|
+
"declaration": {
|
|
809
|
+
"name": "Avatar",
|
|
810
|
+
"module": "src/html/elements/avatar/avatar.ts"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
"kind": "javascript-module",
|
|
817
|
+
"path": "src/html/elements/avatar/index.ts",
|
|
818
|
+
"declarations": [],
|
|
819
|
+
"exports": [
|
|
820
|
+
{
|
|
821
|
+
"kind": "js",
|
|
822
|
+
"name": "*",
|
|
823
|
+
"declaration": {
|
|
824
|
+
"name": "*",
|
|
825
|
+
"module": "src/html/elements/avatar/avatar.js"
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
]
|
|
829
|
+
},
|
|
830
830
|
{
|
|
831
831
|
"kind": "javascript-module",
|
|
832
832
|
"path": "src/html/elements/badge/badge.ts",
|
|
@@ -56,6 +56,16 @@ export declare class Tabs extends LuxenElement {
|
|
|
56
56
|
private _tabs;
|
|
57
57
|
private _panels;
|
|
58
58
|
private _resizeObserver;
|
|
59
|
+
private _mutationObserver;
|
|
60
|
+
/** Custom-element tags already scheduled for a re-check once they upgrade. */
|
|
61
|
+
private _pendingTags;
|
|
62
|
+
/**
|
|
63
|
+
* Whether this element owns a panel's `tabindex`, decided the first time the
|
|
64
|
+
* panel is seen and never revisited: a panel that already carried one belongs
|
|
65
|
+
* to the consumer. Re-deciding on every setup would misread our own attribute
|
|
66
|
+
* after a detach/reattach and freeze the panel out of the sync.
|
|
67
|
+
*/
|
|
68
|
+
private _ownsPanelTabindex;
|
|
59
69
|
/** Visual variant. */
|
|
60
70
|
variant: TabsVariant;
|
|
61
71
|
/** Index of the active tab (0-based). */
|
|
@@ -70,6 +80,29 @@ export declare class Tabs extends LuxenElement {
|
|
|
70
80
|
private _trySetup;
|
|
71
81
|
updated(changed: Map<string, unknown>): void;
|
|
72
82
|
private _setup;
|
|
83
|
+
private _onMutation;
|
|
84
|
+
/** Have panels been added, removed or reordered since setup? */
|
|
85
|
+
private _structureChanged;
|
|
86
|
+
/**
|
|
87
|
+
* Rebuild the tab↔panel pairing after a structural change: a panel added
|
|
88
|
+
* later has no role, no id and no stop until it is wired.
|
|
89
|
+
*/
|
|
90
|
+
private _rewire;
|
|
91
|
+
/**
|
|
92
|
+
* A panel joins the tab sequence unless its first content already takes a tab
|
|
93
|
+
* stop of its own — then `Tab` out of the tablist reaches that control
|
|
94
|
+
* directly, instead of stopping on the panel box first. Per the APG Tabs
|
|
95
|
+
* pattern; `firstContentTakesTabStop` carries the reasoning.
|
|
96
|
+
*/
|
|
97
|
+
private _syncPanelTabStops;
|
|
98
|
+
private _onPanelFocusOut;
|
|
99
|
+
/**
|
|
100
|
+
* A custom element rendering into its shadow root produces no light-DOM
|
|
101
|
+
* mutation, so nothing would otherwise retake a verdict that was blocked on
|
|
102
|
+
* it. Wait for the definition, then a task for the first render (Lit schedules
|
|
103
|
+
* its initial update on a microtask).
|
|
104
|
+
*/
|
|
105
|
+
private _recheckOnUpgrade;
|
|
73
106
|
private _teardown;
|
|
74
107
|
private _selectTab;
|
|
75
108
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tabs/tabs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAG7D,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,CAAC;AAExD,gEAAgE;AAChE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;gBACjB,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAK/C;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,eAAe,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tabs/tabs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{uniqueId as e}from"../../registry.js";import{i as t,t as n}from"../../chunks/decorate.js";import{LuxenElement as r}from"../../shared/luxen-element.js";var i=class extends Event{constructor(e,t){super(`change`,{bubbles:!0,composed:!1,cancelable:!1}),this.index=e,this.name=t}},a=class extends r{constructor(...t){super(...t),this._initialized=!1,this._setupTimer=0,this._instanceId=e(`tabs`),this._tablistEl=null,this._tabs=[],this._panels=[],this._resizeObserver=null,this.variant=`line`,this.value=`0`,this.fullWidth=!1,this.orientation=`horizontal`,this._onClick=e=>{let t=e.target.closest(`[role="tab"]`);if(!t)return;let n=this._tabs.indexOf(t);n<0||this._isDisabled(t)||(this._selectTab(n),t.focus())},this._onKeyDown=e=>{let t=e.target;if(t.getAttribute(`role`)!==`tab`)return;let n=this.orientation===`horizontal`,r=n?`ArrowLeft`:`ArrowUp`,i=n?`ArrowRight`:`ArrowDown`,a=this._tabs.indexOf(t);if(a<0)return;let o;switch(e.key){case i:o=this._nextEnabled(a,1);break;case r:o=this._nextEnabled(a,-1);break;case`Home`:o=this._edgeEnabled(1);break;case`End`:o=this._edgeEnabled(-1);break;default:return}e.preventDefault(),this._isSelectable(o)&&(this._selectTab(o),this._tabs[o].focus())}}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this._trySetup()||(this._setupTimer=window.setTimeout(()=>this._trySetup(),0))}disconnectedCallback(){super.disconnectedCallback(),clearTimeout(this._setupTimer),this._teardown()}_trySetup(){return this._initialized||!this.isConnected?!0:this.children.length<2?!1:(this._setup(),!0)}updated(e){if(e.has(`value`)&&this._tabs.length){let e=Number(this.value);if(this._isSelectable(e))this._selectTab(e,!1);else{let e=this._activeIndex();queueMicrotask(()=>{this.value=String(e)})}}e.has(`orientation`)&&this._tablistEl&&this._tablistEl.setAttribute(`aria-orientation`,this.orientation)}_setup(){let e=Array.from(this.children);if(e.length<2)return;this._initialized=!0,this._tablistEl=e[0],this._tablistEl.setAttribute(`role`,`tablist`),this._tablistEl.setAttribute(`aria-orientation`,this.orientation),this._tabs=Array.from(this._tablistEl.querySelectorAll(`button`)),this._panels=e.slice(1);let t=this._resolveActiveIndex();for(let e=0;e<this._tabs.length;e++){let n=this._tabs[e],r=this._panels[e],i=n.getAttribute(`name`)??String(e),a=`${this._instanceId}-tab-${i}`,o=`${this._instanceId}-panel-${i}`;n.setAttribute(`role`,`tab`),n.setAttribute(`id`,a),n.setAttribute(`aria-selected`,String(e===t)),n.setAttribute(`tabindex`,e===t?`0`:`-1`),r&&(n.setAttribute(`aria-controls`,o),r.setAttribute(`role`,`tabpanel`),r.setAttribute(`id`,o),r.setAttribute(`aria-labelledby`,a),r.
|
|
1
|
+
import{uniqueId as e}from"../../registry.js";import{i as t,t as n}from"../../chunks/decorate.js";import{LuxenElement as r}from"../../shared/luxen-element.js";var i=class extends Event{constructor(e,t){super(`change`,{bubbles:!0,composed:!1,cancelable:!1}),this.index=e,this.name=t}},a=[`a[href]`,`area[href]`,`button`,`input:not([type="hidden"])`,`select`,`textarea`,`details > summary:first-of-type`,`audio[controls]`,`video[controls]`,`iframe`,`[contenteditable]:not([contenteditable="false"])`,`[tabindex]:not([tabindex^="-"])`].join(`,`),o=`img, picture, svg, canvas, video, object, embed, hr`,s=`[hidden], template, script, style`;function c(e){let t=e.checkVisibility;return typeof t==`function`?t.call(e,{checkVisibilityCSS:!0,visibilityProperty:!0}):e.getClientRects().length>0}function l(e){return e.matches(a)&&!e.matches(`:disabled`)?!0:!!e.shadowRoot?.querySelector(a)}function u(e,t){let n=e=>t?!c(e):e.matches(s),r=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,{acceptNode:e=>e.nodeType===Node.ELEMENT_NODE&&n(e)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}),i=null;for(let e=r.nextNode();e;e=r.nextNode()){if(e.nodeType===Node.TEXT_NODE){if(e.nodeValue?.trim())return{takesTabStop:!1,pendingTag:i};continue}let t=e;if(l(t))return{takesTabStop:!0,pendingTag:null};if(t.matches(`[inert]`)||t.matches(o))return{takesTabStop:!1,pendingTag:i};!i&&t.tagName.includes(`-`)&&!t.shadowRoot&&(i=t.tagName.toLowerCase())}return{takesTabStop:!1,pendingTag:i}}var d=class extends r{constructor(...t){super(...t),this._initialized=!1,this._setupTimer=0,this._instanceId=e(`tabs`),this._tablistEl=null,this._tabs=[],this._panels=[],this._resizeObserver=null,this._mutationObserver=null,this._pendingTags=new Set,this._ownsPanelTabindex=new WeakMap,this.variant=`line`,this.value=`0`,this.fullWidth=!1,this.orientation=`horizontal`,this._onPanelFocusOut=()=>this._syncPanelTabStops(),this._onClick=e=>{let t=e.target.closest(`[role="tab"]`);if(!t)return;let n=this._tabs.indexOf(t);n<0||this._isDisabled(t)||(this._selectTab(n),t.focus())},this._onKeyDown=e=>{let t=e.target;if(t.getAttribute(`role`)!==`tab`)return;let n=this.orientation===`horizontal`,r=n?`ArrowLeft`:`ArrowUp`,i=n?`ArrowRight`:`ArrowDown`,a=this._tabs.indexOf(t);if(a<0)return;let o;switch(e.key){case i:o=this._nextEnabled(a,1);break;case r:o=this._nextEnabled(a,-1);break;case`Home`:o=this._edgeEnabled(1);break;case`End`:o=this._edgeEnabled(-1);break;default:return}e.preventDefault(),this._isSelectable(o)&&(this._selectTab(o),this._tabs[o].focus())}}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this._trySetup()||(this._setupTimer=window.setTimeout(()=>this._trySetup(),0))}disconnectedCallback(){super.disconnectedCallback(),clearTimeout(this._setupTimer),this._teardown()}_trySetup(){return this._initialized||!this.isConnected?!0:this.children.length<2?!1:(this._setup(),!0)}updated(e){if(e.has(`value`)&&this._tabs.length){let e=Number(this.value);if(this._isSelectable(e))this._selectTab(e,!1);else{let e=this._activeIndex();queueMicrotask(()=>{this.value=String(e)})}}e.has(`orientation`)&&this._tablistEl&&this._tablistEl.setAttribute(`aria-orientation`,this.orientation)}_setup(){let e=Array.from(this.children);if(e.length<2)return;this._initialized=!0,this._tablistEl=e[0],this._tablistEl.setAttribute(`role`,`tablist`),this._tablistEl.setAttribute(`aria-orientation`,this.orientation),this._tabs=Array.from(this._tablistEl.querySelectorAll(`button`)),this._panels=e.slice(1);let t=this._resolveActiveIndex();for(let e=0;e<this._tabs.length;e++){let n=this._tabs[e],r=this._panels[e],i=n.getAttribute(`name`)??String(e),a=`${this._instanceId}-tab-${i}`,o=`${this._instanceId}-panel-${i}`;n.setAttribute(`role`,`tab`),n.setAttribute(`id`,a),n.setAttribute(`aria-selected`,String(e===t)),n.setAttribute(`tabindex`,e===t?`0`:`-1`),r&&(n.setAttribute(`aria-controls`,o),r.setAttribute(`role`,`tabpanel`),r.setAttribute(`id`,o),r.setAttribute(`aria-labelledby`,a),this._ownsPanelTabindex.has(r)||this._ownsPanelTabindex.set(r,!r.hasAttribute(`tabindex`)),e===t?r.hidden=!1:r.hidden=!0)}Number(this.value)!==t&&(this.value=String(t)),this._tablistEl.addEventListener(`click`,this._onClick),this._tablistEl.addEventListener(`keydown`,this._onKeyDown),this._updateIndicator(),this._resizeObserver=new ResizeObserver(()=>this._updateIndicator()),this._resizeObserver.observe(this._tablistEl),this._syncPanelTabStops(),this._mutationObserver=new MutationObserver(e=>this._onMutation(e)),this._mutationObserver.observe(this,{childList:!0});for(let e of this._panels)this._mutationObserver.observe(e,{childList:!0,subtree:!0})}_onMutation(e){if(e.some(e=>e.target===this)&&this._structureChanged()){this._rewire();return}this._syncPanelTabStops()}_structureChanged(){let e=Array.from(this.children);return e.length!==this._panels.length+1||e[0]!==this._tablistEl?!0:this._panels.some((t,n)=>e[n+1]!==t)}_rewire(){this.children.length<2||(this._teardown(),this._setup())}_syncPanelTabStops(){for(let e of this._panels){if(!this._ownsPanelTabindex.get(e))continue;let t=u(e,c(e));t.pendingTag&&this._recheckOnUpgrade(t.pendingTag);let n=t.takesTabStop?null:`0`;if(e.getAttribute(`tabindex`)!==n)if(n===null){if(document.activeElement===e){e.addEventListener(`focusout`,this._onPanelFocusOut,{once:!0});continue}e.removeAttribute(`tabindex`)}else e.setAttribute(`tabindex`,n)}}_recheckOnUpgrade(e){this._pendingTags.has(e)||(this._pendingTags.add(e),customElements.whenDefined(e).then(()=>{setTimeout(()=>{this._pendingTags.delete(e),this._initialized&&this._syncPanelTabStops()},0)}))}_teardown(){this._tablistEl?.removeEventListener(`click`,this._onClick),this._tablistEl?.removeEventListener(`keydown`,this._onKeyDown),this._resizeObserver?.disconnect(),this._resizeObserver=null,this._mutationObserver?.disconnect(),this._mutationObserver=null;for(let e of this._panels)e.removeEventListener(`focusout`,this._onPanelFocusOut);this._initialized=!1}_selectTab(e,t=!0){if(!this._isSelectable(e))return;let n=this._tabs[e].getAttribute(`aria-selected`)!==`true`;for(let t=0;t<this._tabs.length;t++){let n=this._tabs[t],r=this._panels[t],i=t===e;n.setAttribute(`aria-selected`,String(i)),n.setAttribute(`tabindex`,i?`0`:`-1`),r&&(r.hidden=!i)}if(this.value=String(e),this._updateIndicator(),this._syncPanelTabStops(),t&&n){let t=this._tabs[e]?.getAttribute(`name`)??null;this.dispatchEvent(new i(e,t))}}_isDisabled(e){return e.disabled||e.getAttribute(`aria-disabled`)===`true`}_isSelectable(e){return Number.isInteger(e)&&e>=0&&e<this._tabs.length&&!this._isDisabled(this._tabs[e])}_activeIndex(){let e=this._tabs.findIndex(e=>e.getAttribute(`aria-selected`)===`true`);return e>=0?e:0}_resolveActiveIndex(){let e=Number(this.value)||0;if(this._isSelectable(e))return e;let t=this._tabs.findIndex(e=>!this._isDisabled(e));return t>=0?t:0}_nextEnabled(e,t){let n=this._tabs.length;for(let r=1;r<=n;r++){let i=(e+t*r+n*r)%n;if(!this._isDisabled(this._tabs[i]))return i}return e}_edgeEnabled(e){let t=this._tabs.length,n=e===1?0:t-1;for(let r=n;r>=0&&r<t;r+=e)if(!this._isDisabled(this._tabs[r]))return r;return n}_updateIndicator(){if(!this._tablistEl)return;let e=this._tabs[this._activeIndex()];e&&(this.orientation===`vertical`?(this._tablistEl.style.setProperty(`--_indicator-top`,`${e.offsetTop}px`),this._tablistEl.style.setProperty(`--_indicator-height`,`${e.offsetHeight}px`)):(this._tablistEl.style.setProperty(`--_indicator-left`,`${e.offsetLeft}px`),this._tablistEl.style.setProperty(`--_indicator-width`,`${e.offsetWidth}px`)))}};n([t({reflect:!0})],d.prototype,`variant`,void 0),n([t({reflect:!0})],d.prototype,`value`,void 0),n([t({type:Boolean,reflect:!0,attribute:`full-width`})],d.prototype,`fullWidth`,void 0),n([t({reflect:!0})],d.prototype,`orientation`,void 0);export{d as Tabs,i as TabsChangeEvent};
|
|
2
2
|
//# sourceMappingURL=tabs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","names":[],"sources":["../../../src/html/elements/tabs/tabs.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { uniqueId } from '../../registry.js';\n\nexport type TabsVariant = 'enclosed' | 'line';\nexport type TabsOrientation = 'horizontal' | 'vertical';\n\n/** Fired when the active tab changes. Bubbles; not composed. */\nexport class TabsChangeEvent extends Event {\n readonly index: number;\n readonly name: string | null;\n constructor(index: number, name: string | null) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.index = index;\n this.name = name;\n }\n}\n\ninterface TabsEventMap {\n change: TabsChangeEvent;\n}\n\n/**\n * @summary A tabs component that progressively enhances light DOM markup\n * with ARIA roles, keyboard navigation, and animated indicators.\n *\n * @example\n * Mark the initially-active tab with `aria-selected=\"true\"` so it is styled as\n * selected before the script upgrades the element (progressive enhancement).\n * ```html\n * <l-tabs variant=\"enclosed\">\n * <div>\n * <button aria-selected=\"true\">Tab 1</button>\n * <button>Tab 2</button>\n * </div>\n * <div>Content 1</div>\n * <div>Content 2</div>\n * </l-tabs>\n * ```\n *\n * @example\n * A tab button marked `disabled` (or `aria-disabled=\"true\"`) is skipped by\n * arrow keys, `Home` and `End`, and cannot be selected by a click or by\n * `value` — it still owns its panel, so the tab/panel order is unchanged.\n * ```html\n * <button disabled>Billing</button>\n * ```\n *\n * @event change - Fired when the active tab changes. Bubbles. Not cancelable. Properties: `index: number`, `name: string | null`.\n *\n * @cssproperty [--indicator-color=var(--l-color-text-primary)] - `line` variant: color of the active underline that slides under the selected tab.\n * @cssproperty [--indicator-thickness=2px] - `line` variant: thickness of the active underline.\n * @cssproperty [--track-color=var(--l-color-border)] - `line` variant: color of the static bottom border the tabs sit on.\n * @cssproperty [--track-thickness=1px] - `line` variant: thickness of the static bottom border.\n * @cssproperty [--hover-color=var(--l-color-bg-state-hover)] - `line` variant: fill of the pill that appears behind a hovered tab label.\n * @cssproperty [--hover-inset=4px] - `line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.\n *\n * @customElement l-tabs\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Tabs extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n private _initialized = false;\n private _setupTimer = 0;\n private _instanceId = uniqueId('tabs');\n private _tablistEl: HTMLElement | null = null;\n private _tabs: HTMLButtonElement[] = [];\n private _panels: HTMLElement[] = [];\n private _resizeObserver: ResizeObserver | null = null;\n\n /** Visual variant. */\n @property({ reflect: true })\n variant: TabsVariant = 'line';\n\n /** Index of the active tab (0-based). */\n @property({ reflect: true })\n value = '0';\n\n /** Stretch tabs to fill container width. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n fullWidth = false;\n\n /** Tab orientation. */\n @property({ reflect: true })\n orientation: TabsOrientation = 'horizontal';\n\n // --- Lifecycle ---\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (this.children.length < 2) return false;\n this._setup();\n return true;\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value') && this._tabs.length) {\n const requested = Number(this.value);\n if (this._isSelectable(requested)) {\n this._selectTab(requested, false);\n } else {\n // The target is disabled or out of range, so the selection stays put —\n // and `value` must not keep claiming a tab that was never selected.\n // Reverting here would schedule an update from within an update, so\n // defer it (same reason as a vetoed dialog `show`).\n const current = this._activeIndex();\n queueMicrotask(() => {\n this.value = String(current);\n });\n }\n }\n if (changed.has('orientation') && this._tablistEl) {\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n }\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n const children = Array.from(this.children) as HTMLElement[];\n if (children.length < 2) return;\n this._initialized = true;\n\n // First child is the tablist container\n this._tablistEl = children[0];\n this._tablistEl.setAttribute('role', 'tablist');\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n\n // Buttons inside tablist become tabs\n this._tabs = Array.from(this._tablistEl.querySelectorAll('button'));\n\n // Remaining children become panels. Disabled buttons stay in `_tabs` so the\n // tab↔panel pairing keeps following DOM order — they are filtered out at\n // navigation time, not here.\n this._panels = children.slice(1);\n\n const activeIndex = this._resolveActiveIndex();\n\n // Enhance tabs\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const name = tab.getAttribute('name') ?? String(i);\n const tabId = `${this._instanceId}-tab-${name}`;\n const panelId = `${this._instanceId}-panel-${name}`;\n\n tab.setAttribute('role', 'tab');\n tab.setAttribute('id', tabId);\n tab.setAttribute('aria-selected', String(i === activeIndex));\n tab.setAttribute('tabindex', i === activeIndex ? '0' : '-1');\n\n if (panel) {\n tab.setAttribute('aria-controls', panelId);\n panel.setAttribute('role', 'tabpanel');\n panel.setAttribute('id', panelId);\n panel.setAttribute('aria-labelledby', tabId);\n panel.setAttribute('tabindex', '0');\n if (i !== activeIndex) {\n panel.hidden = true;\n } else {\n panel.hidden = false;\n }\n }\n }\n\n // `value` may have pointed at a disabled tab: canonicalize it to the tab\n // that is actually selected.\n if (Number(this.value) !== activeIndex) this.value = String(activeIndex);\n\n // Attach listeners\n this._tablistEl.addEventListener('click', this._onClick);\n this._tablistEl.addEventListener('keydown', this._onKeyDown);\n\n // Initial indicator position. Synchronous first (offset reads force\n // layout), then a ResizeObserver keeps it correct across hidden→visible\n // transitions and size changes — rAF is suspended in hidden documents,\n // so it must not be the only scheduling mechanism here.\n this._updateIndicator();\n this._resizeObserver = new ResizeObserver(() => this._updateIndicator());\n this._resizeObserver.observe(this._tablistEl);\n }\n\n private _teardown() {\n this._tablistEl?.removeEventListener('click', this._onClick);\n this._tablistEl?.removeEventListener('keydown', this._onKeyDown);\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n this._initialized = false;\n }\n\n // --- Tab selection ---\n\n private _selectTab(index: number, emitEvent = true) {\n // A disabled tab is never selectable: it cannot take focus, so selecting it\n // would move the roving tabindex entry point onto an unfocusable button and\n // drop the whole tablist out of the tab sequence (WCAG 2.1.1).\n if (!this._isSelectable(index)) return;\n\n // Whether this is an actual change — re-selecting the current tab must not\n // re-fire `change` (the event is documented as firing when the active tab\n // changes, and this matches native `change` on radios / `<select>`).\n const changed = this._tabs[index].getAttribute('aria-selected') !== 'true';\n\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const isActive = i === index;\n\n tab.setAttribute('aria-selected', String(isActive));\n tab.setAttribute('tabindex', isActive ? '0' : '-1');\n\n if (panel) {\n panel.hidden = !isActive;\n }\n }\n\n this.value = String(index);\n this._updateIndicator();\n\n if (emitEvent && changed) {\n const name = this._tabs[index]?.getAttribute('name') ?? null;\n this.dispatchEvent(new TabsChangeEvent(index, name));\n }\n }\n\n // --- Disabled tabs ---\n\n /**\n * Both spellings count, and they behave identically here. `aria-disabled`\n * leaves the button focusable in principle, but a roving tabindex hands\n * `tabindex=\"0\"` to the selected tab only — so a tab that can never be\n * selected is never in the tab sequence either, and the arrow keys skip it\n * like a native one. It is the escape hatch for consumers who cannot set the\n * native attribute (a framework binding, a non-`button` element), not a\n * \"reachable but inert\" variant.\n */\n private _isDisabled(tab: HTMLButtonElement): boolean {\n return tab.disabled || tab.getAttribute('aria-disabled') === 'true';\n }\n\n private _isSelectable(index: number): boolean {\n return (\n Number.isInteger(index) &&\n index >= 0 &&\n index < this._tabs.length &&\n !this._isDisabled(this._tabs[index])\n );\n }\n\n /** Index of the currently selected tab, read back from the DOM. */\n private _activeIndex(): number {\n const i = this._tabs.findIndex((t) => t.getAttribute('aria-selected') === 'true');\n return i >= 0 ? i : 0;\n }\n\n /** Resolve the initial selection from `value`, falling back to the first enabled tab. */\n private _resolveActiveIndex(): number {\n const requested = Number(this.value) || 0;\n if (this._isSelectable(requested)) return requested;\n const firstEnabled = this._tabs.findIndex((t) => !this._isDisabled(t));\n // Every tab disabled: nothing is focusable anyway, so index 0 is as good as\n // any and the tablist is simply unreachable — which is what was asked for.\n return firstEnabled >= 0 ? firstEnabled : 0;\n }\n\n /** Index of the next enabled tab from `from`, moving by `dir`, wrapping. */\n private _nextEnabled(from: number, dir: 1 | -1): number {\n const len = this._tabs.length;\n for (let step = 1; step <= len; step++) {\n const i = (from + dir * step + len * step) % len;\n if (!this._isDisabled(this._tabs[i])) return i;\n }\n return from;\n }\n\n /** Index of the first (`dir` 1) or last (`dir` -1) enabled tab. */\n private _edgeEnabled(dir: 1 | -1): number {\n const len = this._tabs.length;\n const start = dir === 1 ? 0 : len - 1;\n for (let i = start; i >= 0 && i < len; i += dir) {\n if (!this._isDisabled(this._tabs[i])) return i;\n }\n return start;\n }\n\n // --- Indicator ---\n\n private _updateIndicator() {\n if (!this._tablistEl) return;\n const activeTab = this._tabs[this._activeIndex()];\n if (!activeTab) return;\n\n const isVertical = this.orientation === 'vertical';\n\n if (isVertical) {\n this._tablistEl.style.setProperty('--_indicator-top', `${activeTab.offsetTop}px`);\n this._tablistEl.style.setProperty('--_indicator-height', `${activeTab.offsetHeight}px`);\n } else {\n this._tablistEl.style.setProperty('--_indicator-left', `${activeTab.offsetLeft}px`);\n this._tablistEl.style.setProperty('--_indicator-width', `${activeTab.offsetWidth}px`);\n }\n }\n\n // --- Event handlers ---\n\n private _onClick = (e: Event) => {\n const tab = (e.target as HTMLElement).closest<HTMLButtonElement>('[role=\"tab\"]');\n if (!tab) return;\n const index = this._tabs.indexOf(tab);\n // A native `disabled` button dispatches no click at all, but an\n // `aria-disabled` one does — `_selectTab` refuses it, and focus must not\n // follow either.\n if (index < 0 || this._isDisabled(tab)) return;\n this._selectTab(index);\n tab.focus();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n if (target.getAttribute('role') !== 'tab') return;\n\n const isHorizontal = this.orientation === 'horizontal';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n\n const current = this._tabs.indexOf(target as HTMLButtonElement);\n if (current < 0) return;\n\n // Every move lands on an enabled tab: selection follows focus here\n // (automatic activation), so a disabled target would split the two apart.\n let index: number;\n switch (e.key) {\n case nextKey:\n index = this._nextEnabled(current, 1);\n break;\n case prevKey:\n index = this._nextEnabled(current, -1);\n break;\n case 'Home':\n index = this._edgeEnabled(1);\n break;\n case 'End':\n index = this._edgeEnabled(-1);\n break;\n default:\n return;\n }\n\n e.preventDefault();\n // Only reachable when every tab is disabled — then there is nowhere to go\n // and focus must not be dragged onto a disabled tab.\n if (!this._isSelectable(index)) return;\n this._selectTab(index);\n this._tabs[index].focus();\n };\n}\n\n// Type `addEventListener('change', …)` on this element so the listener receives\n// a `TabsChangeEvent` (with `.index`/`.name`) instead of a bare `Event`, with no\n// cast. This is the canonical reference for the pattern used across the library.\n//\n// Why a same-named interface and not a global augmentation: `change` already\n// exists in lib.dom's `GlobalEventHandlersEventMap` (typed `Event`), and TS\n// forbids re-declaring an existing key with a different type — so colliding\n// names (`change`, `select`, `input`, `toggle`) can't be typed globally. Merging\n// a same-named `interface Tabs` into the `class Tabs` adds these overloads to\n// the element's own type only. (Non-colliding names like `show`/`story-change`\n// ARE augmented globally in their event-class module — no per-element block.)\n//\n// In `(type: K, listener: (ev: TabsEventMap[K]) => void)`, K is pinned to the\n// string literal you pass, and `TabsEventMap[K]` indexes the event type from it\n// (`'change'` → `TabsChangeEvent`). `this: Tabs` is a fake parameter that types\n// `this` inside a non-arrow listener; it emits no runtime code.\n//\n// Three load-bearing details:\n// 1. The `(type: string, …)` fallback overloads are REQUIRED: declaring our\n// own `addEventListener` hides ALL inherited `HTMLElement` overloads, so\n// without them `addEventListener('click', …)` would stop compiling. The\n// typed overload comes first so `'change'` resolves before the fallback.\n// 2. This interface sits AFTER the class: the CEM analyzer dedups on the first\n// declaration of the name, so an interface before the class would shadow it\n// and the manifest would extract nothing for this element.\n// 3. The merge is flagged by `no-unsafe-declaration-merging` (disabled on the\n// class above) — safe here because we merge only method overloads, never\n// uninitialized properties.\nexport interface Tabs {\n addEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tabs.js","names":[],"sources":["../../../src/html/elements/tabs/tabs.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { uniqueId } from '../../registry.js';\n\nexport type TabsVariant = 'enclosed' | 'line';\nexport type TabsOrientation = 'horizontal' | 'vertical';\n\n/** Fired when the active tab changes. Bubbles; not composed. */\nexport class TabsChangeEvent extends Event {\n readonly index: number;\n readonly name: string | null;\n constructor(index: number, name: string | null) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.index = index;\n this.name = name;\n }\n}\n\ninterface TabsEventMap {\n change: TabsChangeEvent;\n}\n\n/**\n * Elements that take a tab stop of their own. `tabindex=\"-1\"` is deliberately\n * absent: it makes an element programmatically focusable but leaves it out of\n * the tab sequence, and the tab sequence is what the APG rule below is about.\n *\n * A selector is the wrong shape for this question and the platform offers\n * nothing better yet. `el.tabIndex >= 0` looks like the answer and is not: it\n * reports 0 for `<a>` without `href`, `<input type=\"hidden\">` and `<video>`\n * without `controls`, none of which take focus, and -1 for `[contenteditable]`,\n * which does. `:focusable` / `:tabbable` would settle it, but they are not in\n * Selectors Level 4 and no engine ships them (checked against Chrome 152) —\n * when they land, this whole list collapses to `:tabbable` and `takesTabStop`\n * becomes one `matches` call. Probing with `focus()` is the only exact answer\n * available today, and it is unusable here: this runs inside a MutationObserver\n * callback, where moving focus would be destructive.\n *\n * Two clauses are narrower than their element name suggests:\n * - `input[type=\"hidden\"]` renders nothing and takes no focus, yet is a routine\n * first child of a form panel (a CSRF field).\n * - Only the first `summary` child of a `details` is the disclosure control; a\n * `summary` anywhere else is inert content.\n */\nconst TAB_STOP_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'button',\n 'input:not([type=\"hidden\"])',\n 'select',\n 'textarea',\n 'details > summary:first-of-type',\n 'audio[controls]',\n 'video[controls]',\n // Focus enters an iframe's content, so it is a tab stop rather than the\n // opaque box it looks like.\n 'iframe',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]:not([tabindex^=\"-\"])',\n].join(',');\n\n/**\n * Content that is not focusable and not text — the walk has to stop on these\n * rather than skip ahead, or a panel opening with an illustration would be read\n * as opening with whatever link happens to follow it.\n */\nconst OPAQUE_CONTENT_SELECTOR = 'img, picture, svg, canvas, video, object, embed, hr';\n\n/**\n * Markup that is never rendered. Only consulted for a panel that is itself\n * off-screen, where real visibility cannot be measured — see `panelIsRendered`.\n */\nconst NOT_RENDERED_SELECTOR = '[hidden], template, script, style';\n\n/**\n * Is this element actually rendered right now?\n *\n * Asking the platform rather than pattern-matching markup is what keeps\n * `display: none` content — a Tailwind `hidden` class, a framework's collapsed\n * block, a hidden input — from being mistaken for the panel's first content.\n * The option was renamed mid-standardisation and unknown keys are ignored, so\n * both spellings are passed; `getClientRects` covers engines without the method.\n */\nfunction isRendered(el: Element): boolean {\n const check = (el as Element & { checkVisibility?: (options?: object) => boolean })\n .checkVisibility;\n if (typeof check === 'function') {\n return check.call(el, { checkVisibilityCSS: true, visibilityProperty: true });\n }\n return el.getClientRects().length > 0;\n}\n\nfunction takesTabStop(el: Element): boolean {\n if (el.matches(TAB_STOP_SELECTOR) && !el.matches(':disabled')) return true;\n // A custom element keeps its control inside a shadow root, out of reach of a\n // light-DOM selector — `l-select` and `l-slider` both put their tab stop on a\n // shadow node.\n return Boolean(el.shadowRoot?.querySelector(TAB_STOP_SELECTOR));\n}\n\ninterface FirstContentVerdict {\n /** Whether the panel's first content already offers a tab stop. */\n takesTabStop: boolean;\n /**\n * Tag name of a custom element met before reaching a verdict whose shadow\n * root was not attached yet, so the verdict has to be retaken once it is.\n */\n pendingTag: string | null;\n}\n\n/**\n * Does the first thing a reader meets in this panel take a tab stop of its own?\n *\n * The APG asks for `tabindex=\"0\"` on a tabpanel \"when the tabpanel does not\n * contain any focusable elements **or the first element with content is not\n * focusable**\". One walk answers both clauses: stop at the first content and ask\n * whether it is focusable; a panel holding nothing focusable runs off the end\n * and returns false, which is the first clause.\n *\n * The walk descends through wrappers on purpose. The APG's own manual-activation\n * example is `<p><a href>…`, where the link is the first content even though the\n * `<p>` is visited first — and that example's panels carry no `tabindex`.\n *\n * `measured` says whether real visibility can be trusted. It cannot for a panel\n * that is itself hidden — every descendant would report invisible — so that case\n * falls back to the markup heuristic and is remeasured when the panel is shown.\n */\nfunction firstContentTakesTabStop(panel: Element, measured: boolean): FirstContentVerdict {\n const skipSubtree = (el: Element) =>\n measured ? !isRendered(el) : el.matches(NOT_RENDERED_SELECTOR);\n\n const walker = document.createTreeWalker(panel, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {\n acceptNode: (node) =>\n node.nodeType === Node.ELEMENT_NODE && skipSubtree(node as Element)\n ? // REJECT, not SKIP: a TreeWalker drops the whole subtree, so text\n // inside an unrendered block never reads as prose.\n NodeFilter.FILTER_REJECT\n : NodeFilter.FILTER_ACCEPT,\n });\n\n let pendingTag: string | null = null;\n\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.TEXT_NODE) {\n // Whitespace between tags is not content. Real text is — and is not focusable.\n if (node.nodeValue?.trim()) return { takesTabStop: false, pendingTag };\n continue;\n }\n const el = node as Element;\n if (takesTabStop(el)) return { takesTabStop: true, pendingTag: null };\n // Inert content is visible content that cannot be focused, so it settles the\n // question: this panel needs a stop of its own. (Unlike an unrendered block,\n // which is genuinely absent and is skipped above.)\n if (el.matches('[inert]')) return { takesTabStop: false, pendingTag };\n if (el.matches(OPAQUE_CONTENT_SELECTOR)) return { takesTabStop: false, pendingTag };\n // An un-upgraded custom element cannot be judged yet: its control may be\n // about to appear in a shadow root. Keep walking — it may be a light-DOM\n // element whose children answer the question — but remember to come back.\n if (!pendingTag && el.tagName.includes('-') && !el.shadowRoot) {\n pendingTag = el.tagName.toLowerCase();\n }\n }\n return { takesTabStop: false, pendingTag };\n}\n\n/**\n * @summary A tabs component that progressively enhances light DOM markup\n * with ARIA roles, keyboard navigation, and animated indicators.\n *\n * @example\n * Mark the initially-active tab with `aria-selected=\"true\"` so it is styled as\n * selected before the script upgrades the element (progressive enhancement).\n * ```html\n * <l-tabs variant=\"enclosed\">\n * <div>\n * <button aria-selected=\"true\">Tab 1</button>\n * <button>Tab 2</button>\n * </div>\n * <div>Content 1</div>\n * <div>Content 2</div>\n * </l-tabs>\n * ```\n *\n * @example\n * A tab button marked `disabled` (or `aria-disabled=\"true\"`) is skipped by\n * arrow keys, `Home` and `End`, and cannot be selected by a click or by\n * `value` — it still owns its panel, so the tab/panel order is unchanged.\n * ```html\n * <button disabled>Billing</button>\n * ```\n *\n * @event change - Fired when the active tab changes. Bubbles. Not cancelable. Properties: `index: number`, `name: string | null`.\n *\n * @cssproperty [--indicator-color=var(--l-color-text-primary)] - `line` variant: color of the active underline that slides under the selected tab.\n * @cssproperty [--indicator-thickness=2px] - `line` variant: thickness of the active underline.\n * @cssproperty [--track-color=var(--l-color-border)] - `line` variant: color of the static bottom border the tabs sit on.\n * @cssproperty [--track-thickness=1px] - `line` variant: thickness of the static bottom border.\n * @cssproperty [--hover-color=var(--l-color-bg-state-hover)] - `line` variant: fill of the pill that appears behind a hovered tab label.\n * @cssproperty [--hover-inset=4px] - `line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.\n *\n * @customElement l-tabs\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Tabs extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n private _initialized = false;\n private _setupTimer = 0;\n private _instanceId = uniqueId('tabs');\n private _tablistEl: HTMLElement | null = null;\n private _tabs: HTMLButtonElement[] = [];\n private _panels: HTMLElement[] = [];\n private _resizeObserver: ResizeObserver | null = null;\n private _mutationObserver: MutationObserver | null = null;\n /** Custom-element tags already scheduled for a re-check once they upgrade. */\n private _pendingTags = new Set<string>();\n /**\n * Whether this element owns a panel's `tabindex`, decided the first time the\n * panel is seen and never revisited: a panel that already carried one belongs\n * to the consumer. Re-deciding on every setup would misread our own attribute\n * after a detach/reattach and freeze the panel out of the sync.\n */\n private _ownsPanelTabindex = new WeakMap<Element, boolean>();\n\n /** Visual variant. */\n @property({ reflect: true })\n variant: TabsVariant = 'line';\n\n /** Index of the active tab (0-based). */\n @property({ reflect: true })\n value = '0';\n\n /** Stretch tabs to fill container width. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n fullWidth = false;\n\n /** Tab orientation. */\n @property({ reflect: true })\n orientation: TabsOrientation = 'horizontal';\n\n // --- Lifecycle ---\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (this.children.length < 2) return false;\n this._setup();\n return true;\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value') && this._tabs.length) {\n const requested = Number(this.value);\n if (this._isSelectable(requested)) {\n this._selectTab(requested, false);\n } else {\n // The target is disabled or out of range, so the selection stays put —\n // and `value` must not keep claiming a tab that was never selected.\n // Reverting here would schedule an update from within an update, so\n // defer it (same reason as a vetoed dialog `show`).\n const current = this._activeIndex();\n queueMicrotask(() => {\n this.value = String(current);\n });\n }\n }\n if (changed.has('orientation') && this._tablistEl) {\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n }\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n const children = Array.from(this.children) as HTMLElement[];\n if (children.length < 2) return;\n this._initialized = true;\n\n // First child is the tablist container\n this._tablistEl = children[0];\n this._tablistEl.setAttribute('role', 'tablist');\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n\n // Buttons inside tablist become tabs\n this._tabs = Array.from(this._tablistEl.querySelectorAll('button'));\n\n // Remaining children become panels. Disabled buttons stay in `_tabs` so the\n // tab↔panel pairing keeps following DOM order — they are filtered out at\n // navigation time, not here.\n this._panels = children.slice(1);\n\n const activeIndex = this._resolveActiveIndex();\n\n // Enhance tabs\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const name = tab.getAttribute('name') ?? String(i);\n const tabId = `${this._instanceId}-tab-${name}`;\n const panelId = `${this._instanceId}-panel-${name}`;\n\n tab.setAttribute('role', 'tab');\n tab.setAttribute('id', tabId);\n tab.setAttribute('aria-selected', String(i === activeIndex));\n tab.setAttribute('tabindex', i === activeIndex ? '0' : '-1');\n\n if (panel) {\n tab.setAttribute('aria-controls', panelId);\n panel.setAttribute('role', 'tabpanel');\n panel.setAttribute('id', panelId);\n panel.setAttribute('aria-labelledby', tabId);\n if (!this._ownsPanelTabindex.has(panel)) {\n this._ownsPanelTabindex.set(panel, !panel.hasAttribute('tabindex'));\n }\n if (i !== activeIndex) {\n panel.hidden = true;\n } else {\n panel.hidden = false;\n }\n }\n }\n\n // `value` may have pointed at a disabled tab: canonicalize it to the tab\n // that is actually selected.\n if (Number(this.value) !== activeIndex) this.value = String(activeIndex);\n\n // Attach listeners\n this._tablistEl.addEventListener('click', this._onClick);\n this._tablistEl.addEventListener('keydown', this._onKeyDown);\n\n // Initial indicator position. Synchronous first (offset reads force\n // layout), then a ResizeObserver keeps it correct across hidden→visible\n // transitions and size changes — rAF is suspended in hidden documents,\n // so it must not be the only scheduling mechanism here.\n this._updateIndicator();\n this._resizeObserver = new ResizeObserver(() => this._updateIndicator());\n this._resizeObserver.observe(this._tablistEl);\n\n // Panels are routinely filled after mount — a framework rendering into them\n // once its data lands — and whether a panel belongs in the tab sequence\n // depends on what arrives. So the decision is re-run on content changes\n // rather than frozen at setup. `childList` does not report attribute\n // mutations, so writing `tabindex` back cannot feed this.\n //\n // Two scopes, deliberately: our own child list (panels added or removed,\n // which needs the whole pairing rebuilt) and each panel's subtree (content\n // changes, which only need the stops retaken). The tablist is left out —\n // relabelling a tab cannot change any panel's verdict.\n this._syncPanelTabStops();\n this._mutationObserver = new MutationObserver((records) => this._onMutation(records));\n this._mutationObserver.observe(this, { childList: true });\n for (const panel of this._panels) {\n this._mutationObserver.observe(panel, { childList: true, subtree: true });\n }\n }\n\n private _onMutation(records: MutationRecord[]) {\n // Lit renders into this element's light DOM (`createRenderRoot` returns\n // `this`), so its marker comments land in our child list too — compare the\n // element children we wired instead of trusting the record's target.\n if (records.some((r) => r.target === this) && this._structureChanged()) {\n this._rewire();\n return;\n }\n this._syncPanelTabStops();\n }\n\n /** Have panels been added, removed or reordered since setup? */\n private _structureChanged(): boolean {\n const children = Array.from(this.children);\n if (children.length !== this._panels.length + 1) return true;\n if (children[0] !== this._tablistEl) return true;\n return this._panels.some((panel, i) => children[i + 1] !== panel);\n }\n\n /**\n * Rebuild the tab↔panel pairing after a structural change: a panel added\n * later has no role, no id and no stop until it is wired.\n */\n private _rewire() {\n // Too few children to pair up — keep the observer alive so the element\n // recovers when they arrive, rather than tearing down into an inert state.\n if (this.children.length < 2) return;\n this._teardown();\n this._setup();\n }\n\n /**\n * A panel joins the tab sequence unless its first content already takes a tab\n * stop of its own — then `Tab` out of the tablist reaches that control\n * directly, instead of stopping on the panel box first. Per the APG Tabs\n * pattern; `firstContentTakesTabStop` carries the reasoning.\n */\n private _syncPanelTabStops() {\n for (const panel of this._panels) {\n if (!this._ownsPanelTabindex.get(panel)) continue;\n\n const verdict = firstContentTakesTabStop(panel, isRendered(panel));\n if (verdict.pendingTag) this._recheckOnUpgrade(verdict.pendingTag);\n\n const wanted = verdict.takesTabStop ? null : '0';\n // Only write on a real change: an unconditional `setAttribute` would queue\n // a mutation record for every consumer observing this panel's attributes,\n // on every batch of DOM changes inside it.\n if (panel.getAttribute('tabindex') === wanted) continue;\n\n if (wanted === null) {\n // Taking `tabindex` off the focused panel blurs it and drops the user at\n // the top of the document. Keep the stop and retake the decision once\n // focus has moved on (WCAG 2.4.3).\n if (document.activeElement === panel) {\n panel.addEventListener('focusout', this._onPanelFocusOut, { once: true });\n continue;\n }\n panel.removeAttribute('tabindex');\n } else {\n panel.setAttribute('tabindex', wanted);\n }\n }\n }\n\n private _onPanelFocusOut = () => this._syncPanelTabStops();\n\n /**\n * A custom element rendering into its shadow root produces no light-DOM\n * mutation, so nothing would otherwise retake a verdict that was blocked on\n * it. Wait for the definition, then a task for the first render (Lit schedules\n * its initial update on a microtask).\n */\n private _recheckOnUpgrade(tag: string) {\n if (this._pendingTags.has(tag)) return;\n this._pendingTags.add(tag);\n void customElements.whenDefined(tag).then(() => {\n setTimeout(() => {\n this._pendingTags.delete(tag);\n if (this._initialized) this._syncPanelTabStops();\n }, 0);\n });\n }\n\n private _teardown() {\n this._tablistEl?.removeEventListener('click', this._onClick);\n this._tablistEl?.removeEventListener('keydown', this._onKeyDown);\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n this._mutationObserver?.disconnect();\n this._mutationObserver = null;\n for (const panel of this._panels) {\n panel.removeEventListener('focusout', this._onPanelFocusOut);\n }\n this._initialized = false;\n }\n\n // --- Tab selection ---\n\n private _selectTab(index: number, emitEvent = true) {\n // A disabled tab is never selectable: it cannot take focus, so selecting it\n // would move the roving tabindex entry point onto an unfocusable button and\n // drop the whole tablist out of the tab sequence (WCAG 2.1.1).\n if (!this._isSelectable(index)) return;\n\n // Whether this is an actual change — re-selecting the current tab must not\n // re-fire `change` (the event is documented as firing when the active tab\n // changes, and this matches native `change` on radios / `<select>`).\n const changed = this._tabs[index].getAttribute('aria-selected') !== 'true';\n\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const isActive = i === index;\n\n tab.setAttribute('aria-selected', String(isActive));\n tab.setAttribute('tabindex', isActive ? '0' : '-1');\n\n if (panel) {\n panel.hidden = !isActive;\n }\n }\n\n this.value = String(index);\n this._updateIndicator();\n // The panel just shown can be measured for real now; the one just hidden\n // keeps whatever it had, which is moot while `hidden` holds it out of the\n // tab sequence anyway.\n this._syncPanelTabStops();\n\n if (emitEvent && changed) {\n const name = this._tabs[index]?.getAttribute('name') ?? null;\n this.dispatchEvent(new TabsChangeEvent(index, name));\n }\n }\n\n // --- Disabled tabs ---\n\n /**\n * Both spellings count, and they behave identically here. `aria-disabled`\n * leaves the button focusable in principle, but a roving tabindex hands\n * `tabindex=\"0\"` to the selected tab only — so a tab that can never be\n * selected is never in the tab sequence either, and the arrow keys skip it\n * like a native one. It is the escape hatch for consumers who cannot set the\n * native attribute (a framework binding, a non-`button` element), not a\n * \"reachable but inert\" variant.\n */\n private _isDisabled(tab: HTMLButtonElement): boolean {\n return tab.disabled || tab.getAttribute('aria-disabled') === 'true';\n }\n\n private _isSelectable(index: number): boolean {\n return (\n Number.isInteger(index) &&\n index >= 0 &&\n index < this._tabs.length &&\n !this._isDisabled(this._tabs[index])\n );\n }\n\n /** Index of the currently selected tab, read back from the DOM. */\n private _activeIndex(): number {\n const i = this._tabs.findIndex((t) => t.getAttribute('aria-selected') === 'true');\n return i >= 0 ? i : 0;\n }\n\n /** Resolve the initial selection from `value`, falling back to the first enabled tab. */\n private _resolveActiveIndex(): number {\n const requested = Number(this.value) || 0;\n if (this._isSelectable(requested)) return requested;\n const firstEnabled = this._tabs.findIndex((t) => !this._isDisabled(t));\n // Every tab disabled: nothing is focusable anyway, so index 0 is as good as\n // any and the tablist is simply unreachable — which is what was asked for.\n return firstEnabled >= 0 ? firstEnabled : 0;\n }\n\n /** Index of the next enabled tab from `from`, moving by `dir`, wrapping. */\n private _nextEnabled(from: number, dir: 1 | -1): number {\n const len = this._tabs.length;\n for (let step = 1; step <= len; step++) {\n const i = (from + dir * step + len * step) % len;\n if (!this._isDisabled(this._tabs[i])) return i;\n }\n return from;\n }\n\n /** Index of the first (`dir` 1) or last (`dir` -1) enabled tab. */\n private _edgeEnabled(dir: 1 | -1): number {\n const len = this._tabs.length;\n const start = dir === 1 ? 0 : len - 1;\n for (let i = start; i >= 0 && i < len; i += dir) {\n if (!this._isDisabled(this._tabs[i])) return i;\n }\n return start;\n }\n\n // --- Indicator ---\n\n private _updateIndicator() {\n if (!this._tablistEl) return;\n const activeTab = this._tabs[this._activeIndex()];\n if (!activeTab) return;\n\n const isVertical = this.orientation === 'vertical';\n\n if (isVertical) {\n this._tablistEl.style.setProperty('--_indicator-top', `${activeTab.offsetTop}px`);\n this._tablistEl.style.setProperty('--_indicator-height', `${activeTab.offsetHeight}px`);\n } else {\n this._tablistEl.style.setProperty('--_indicator-left', `${activeTab.offsetLeft}px`);\n this._tablistEl.style.setProperty('--_indicator-width', `${activeTab.offsetWidth}px`);\n }\n }\n\n // --- Event handlers ---\n\n private _onClick = (e: Event) => {\n const tab = (e.target as HTMLElement).closest<HTMLButtonElement>('[role=\"tab\"]');\n if (!tab) return;\n const index = this._tabs.indexOf(tab);\n // A native `disabled` button dispatches no click at all, but an\n // `aria-disabled` one does — `_selectTab` refuses it, and focus must not\n // follow either.\n if (index < 0 || this._isDisabled(tab)) return;\n this._selectTab(index);\n tab.focus();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n if (target.getAttribute('role') !== 'tab') return;\n\n const isHorizontal = this.orientation === 'horizontal';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n\n const current = this._tabs.indexOf(target as HTMLButtonElement);\n if (current < 0) return;\n\n // Every move lands on an enabled tab: selection follows focus here\n // (automatic activation), so a disabled target would split the two apart.\n let index: number;\n switch (e.key) {\n case nextKey:\n index = this._nextEnabled(current, 1);\n break;\n case prevKey:\n index = this._nextEnabled(current, -1);\n break;\n case 'Home':\n index = this._edgeEnabled(1);\n break;\n case 'End':\n index = this._edgeEnabled(-1);\n break;\n default:\n return;\n }\n\n e.preventDefault();\n // Only reachable when every tab is disabled — then there is nowhere to go\n // and focus must not be dragged onto a disabled tab.\n if (!this._isSelectable(index)) return;\n this._selectTab(index);\n this._tabs[index].focus();\n };\n}\n\n// Type `addEventListener('change', …)` on this element so the listener receives\n// a `TabsChangeEvent` (with `.index`/`.name`) instead of a bare `Event`, with no\n// cast. This is the canonical reference for the pattern used across the library.\n//\n// Why a same-named interface and not a global augmentation: `change` already\n// exists in lib.dom's `GlobalEventHandlersEventMap` (typed `Event`), and TS\n// forbids re-declaring an existing key with a different type — so colliding\n// names (`change`, `select`, `input`, `toggle`) can't be typed globally. Merging\n// a same-named `interface Tabs` into the `class Tabs` adds these overloads to\n// the element's own type only. (Non-colliding names like `show`/`story-change`\n// ARE augmented globally in their event-class module — no per-element block.)\n//\n// In `(type: K, listener: (ev: TabsEventMap[K]) => void)`, K is pinned to the\n// string literal you pass, and `TabsEventMap[K]` indexes the event type from it\n// (`'change'` → `TabsChangeEvent`). `this: Tabs` is a fake parameter that types\n// `this` inside a non-arrow listener; it emits no runtime code.\n//\n// Three load-bearing details:\n// 1. The `(type: string, …)` fallback overloads are REQUIRED: declaring our\n// own `addEventListener` hides ALL inherited `HTMLElement` overloads, so\n// without them `addEventListener('click', …)` would stop compiling. The\n// typed overload comes first so `'change'` resolves before the fallback.\n// 2. This interface sits AFTER the class: the CEM analyzer dedups on the first\n// declaration of the name, so an interface before the class would shadow it\n// and the manifest would extract nothing for this element.\n// 3. The merge is flagged by `no-unsafe-declaration-merging` (disabled on the\n// class above) — safe here because we merge only method overloads, never\n// uninitialized properties.\nexport interface Tabs {\n addEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ko.d.ts","sourceRoot":"","sources":["../../src/html/translations/ko.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,QAAA,MAAM,EAAE,EAAE,gBAwDT,CAAC;AAIF,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{registerTranslation as e}from"../shared/localize.js";e({$code:`ko`,$name:`한국어`,$dir:`ltr`,loading:`로딩 중`,close:`닫기`,previousSlide:`이전 슬라이드`,nextSlide:`다음 슬라이드`,toggleFullscreen:`전체 화면 전환`,goToSlide:e=>`${e}번 슬라이드로 이동`,showPassword:`비밀번호 표시`,increaseValue:`값 늘리기`,decreaseValue:`값 줄이기`,rangeMinimum:`최솟값`,rangeMaximum:`최댓값`,clear:`지우기`,noResults:`결과 없음`,selectOption:`옵션을 선택하세요.`,suggestions:`제안`,search:`검색`,remove:`제거`,richTextEditor:`서식 있는 텍스트 편집기`,formatting:`서식`,heading1:`제목 1`,heading2:`제목 2`,heading3:`제목 3`,bold:`굵게`,italic:`기울임꼴`,underline:`밑줄`,strikethrough:`취소선`,highlight:`형광펜`,bulletList:`글머리 기호 목록`,orderedList:`번호 매기기 목록`,blockquote:`인용구`,codeBlock:`코드 블록`,horizontalRule:`가로줄`,link:`링크`,emoji:`이모지`,attachFile:`파일 첨부`,undo:`실행 취소`,redo:`다시 실행`,stories:`스토리`,storyProgress:`스토리 진행률`,previousStory:`이전 스토리`,nextStory:`다음 스토리`,mute:`음소거`,unmute:`음소거 해제`});
|
|
2
|
+
//# sourceMappingURL=ko.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ko.js","names":[],"sources":["../../src/html/translations/ko.ts"],"sourcesContent":["import { registerTranslation, type LuxenTranslation } from '../shared/localize.js';\n\nconst ko: LuxenTranslation = {\n $code: 'ko',\n $name: '한국어',\n $dir: 'ltr',\n\n loading: '로딩 중',\n close: '닫기',\n\n previousSlide: '이전 슬라이드',\n nextSlide: '다음 슬라이드',\n toggleFullscreen: '전체 화면 전환',\n goToSlide: (n) => `${n}번 슬라이드로 이동`,\n\n showPassword: '비밀번호 표시',\n\n increaseValue: '값 늘리기',\n decreaseValue: '값 줄이기',\n\n rangeMinimum: '최솟값',\n rangeMaximum: '최댓값',\n\n clear: '지우기',\n noResults: '결과 없음',\n selectOption: '옵션을 선택하세요.',\n suggestions: '제안',\n search: '검색',\n\n remove: '제거',\n\n richTextEditor: '서식 있는 텍스트 편집기',\n formatting: '서식',\n heading1: '제목 1',\n heading2: '제목 2',\n heading3: '제목 3',\n bold: '굵게',\n italic: '기울임꼴',\n underline: '밑줄',\n strikethrough: '취소선',\n highlight: '형광펜',\n bulletList: '글머리 기호 목록',\n orderedList: '번호 매기기 목록',\n blockquote: '인용구',\n codeBlock: '코드 블록',\n horizontalRule: '가로줄',\n link: '링크',\n emoji: '이모지',\n attachFile: '파일 첨부',\n undo: '실행 취소',\n redo: '다시 실행',\n\n stories: '스토리',\n storyProgress: '스토리 진행률',\n previousStory: '이전 스토리',\n nextStory: '다음 스토리',\n mute: '음소거',\n unmute: '음소거 해제',\n};\n\nregisterTranslation(ko);\n\nexport default ko;\n"],"mappings":"4DA4DA,EAAoB,CAzDlB,MAAO,KACP,MAAO,MACP,KAAM,MAEN,QAAS,OACT,MAAO,KAEP,cAAe,UACf,UAAW,UACX,iBAAkB,WAClB,UAAY,GAAM,GAAG,EAAE,YAEvB,aAAc,UAEd,cAAe,QACf,cAAe,QAEf,aAAc,MACd,aAAc,MAEd,MAAO,MACP,UAAW,QACX,aAAc,aACd,YAAa,KACb,OAAQ,KAER,OAAQ,KAER,eAAgB,gBAChB,WAAY,KACZ,SAAU,OACV,SAAU,OACV,SAAU,OACV,KAAM,KACN,OAAQ,OACR,UAAW,KACX,cAAe,MACf,UAAW,MACX,WAAY,YACZ,YAAa,YACb,WAAY,MACZ,UAAW,QACX,eAAgB,MAChB,KAAM,KACN,MAAO,MACP,WAAY,QACZ,KAAM,QACN,KAAM,QAEN,QAAS,MACT,cAAe,UACf,cAAe,SACf,UAAW,SACX,KAAM,MACN,OAAQ,QAGU,CAAE"}
|