tyrell-components 1.0.0-RC13 → 1.0.0-RC15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/css/tyrell-theme.css +192 -180
- package/css/tyrell.css +23 -0
- package/dist/tyrell-colors-static.css +1 -316
- package/dist/tyrell-theme.css +1 -1454
- package/dist/tyrell.css +1 -1371
- package/dist/tyrell.js +1 -1
- package/lib/base/ty-component.d.ts +20 -10
- package/lib/base/ty-component.js +196 -319
- package/lib/components/button.d.ts +3 -17
- package/lib/components/button.js +261 -295
- package/lib/components/calendar-month.d.ts +4 -50
- package/lib/components/calendar-month.js +443 -582
- package/lib/components/calendar-navigation.d.ts +3 -48
- package/lib/components/calendar-navigation.js +239 -362
- package/lib/components/calendar.d.ts +8 -83
- package/lib/components/calendar.js +616 -829
- package/lib/components/checkbox.d.ts +5 -72
- package/lib/components/checkbox.js +273 -416
- package/lib/components/copy.d.ts +4 -57
- package/lib/components/copy.js +280 -397
- package/lib/components/date-picker.d.ts +21 -163
- package/lib/components/date-picker.js +1143 -1674
- package/lib/components/file-upload.d.ts +9 -15
- package/lib/components/file-upload.js +404 -426
- package/lib/components/icon.d.ts +3 -24
- package/lib/components/icon.js +141 -230
- package/lib/components/input.d.ts +8 -108
- package/lib/components/input.js +593 -784
- package/lib/components/modal.d.ts +17 -26
- package/lib/components/modal.js +340 -484
- package/lib/components/option.d.ts +2 -9
- package/lib/components/option.js +168 -216
- package/lib/components/popup.d.ts +2 -17
- package/lib/components/popup.js +249 -380
- package/lib/components/radio.d.ts +5 -25
- package/lib/components/radio.js +407 -461
- package/lib/components/resize-observer.d.ts +3 -30
- package/lib/components/resize-observer.js +69 -103
- package/lib/components/scroll-container.d.ts +2 -12
- package/lib/components/scroll-container.js +293 -338
- package/lib/components/select.d.ts +87 -159
- package/lib/components/select.js +1609 -2090
- package/lib/components/selected-tags.d.ts +5 -48
- package/lib/components/selected-tags.js +188 -276
- package/lib/components/step.d.ts +1 -6
- package/lib/components/step.js +62 -68
- package/lib/components/switch.d.ts +1 -12
- package/lib/components/switch.js +214 -247
- package/lib/components/tab.d.ts +1 -6
- package/lib/components/tab.js +62 -69
- package/lib/components/tabs.d.ts +6 -63
- package/lib/components/tabs.js +545 -777
- package/lib/components/tag.d.ts +3 -37
- package/lib/components/tag.js +236 -335
- package/lib/components/textarea.d.ts +7 -87
- package/lib/components/textarea.js +457 -627
- package/lib/components/tooltip.d.ts +3 -11
- package/lib/components/tooltip.js +307 -443
- package/lib/components/wizard.d.ts +6 -54
- package/lib/components/wizard.js +405 -618
- package/lib/icons/fontawesome/brands.d.ts +0 -1
- package/lib/icons/fontawesome/brands.js +0 -8
- package/lib/icons/fontawesome/regular.d.ts +0 -1
- package/lib/icons/fontawesome/regular.js +0 -8
- package/lib/icons/fontawesome/solid.d.ts +0 -1
- package/lib/icons/fontawesome/solid.js +0 -8
- package/lib/icons/heroicons/micro.d.ts +0 -1
- package/lib/icons/heroicons/micro.js +392 -400
- package/lib/icons/heroicons/mini.d.ts +0 -1
- package/lib/icons/heroicons/mini.js +382 -390
- package/lib/icons/heroicons/outline.d.ts +0 -1
- package/lib/icons/heroicons/outline.js +337 -345
- package/lib/icons/heroicons/solid.d.ts +0 -1
- package/lib/icons/heroicons/solid.js +407 -415
- package/lib/icons/lucide.d.ts +0 -1
- package/lib/icons/lucide.js +24479 -26351
- package/lib/icons/material/filled.d.ts +0 -1
- package/lib/icons/material/filled.js +9653 -9663
- package/lib/icons/material/outlined.d.ts +0 -1
- package/lib/icons/material/outlined.js +10271 -10281
- package/lib/icons/material/round.d.ts +0 -1
- package/lib/icons/material/round.js +10495 -10505
- package/lib/icons/material/sharp.d.ts +0 -1
- package/lib/icons/material/sharp.js +9905 -9915
- package/lib/icons/material/two-tone.d.ts +0 -1
- package/lib/icons/material/two-tone.js +12792 -12802
- package/lib/index.d.ts +0 -1
- package/lib/index.js +21 -41
- package/lib/styles/button.d.ts +2 -20
- package/lib/styles/button.js +202 -404
- package/lib/styles/calendar-month.d.ts +1 -5
- package/lib/styles/calendar-month.js +127 -244
- package/lib/styles/calendar-navigation.d.ts +2 -6
- package/lib/styles/calendar-navigation.js +64 -104
- package/lib/styles/calendar.d.ts +1 -2
- package/lib/styles/calendar.js +9 -22
- package/lib/styles/checkbox.d.ts +0 -1
- package/lib/styles/checkbox.js +45 -94
- package/lib/styles/copy.d.ts +0 -1
- package/lib/styles/copy.js +67 -106
- package/lib/styles/custom-scrollbar.d.ts +1 -2
- package/lib/styles/custom-scrollbar.js +80 -115
- package/lib/styles/date-picker.d.ts +1 -5
- package/lib/styles/date-picker.js +261 -335
- package/lib/styles/field-size.d.ts +6 -0
- package/lib/styles/field-size.js +91 -0
- package/lib/styles/file-upload.d.ts +1 -2
- package/lib/styles/file-upload.js +143 -188
- package/lib/styles/icon.d.ts +2 -6
- package/lib/styles/icon.js +92 -170
- package/lib/styles/input.d.ts +1 -6
- package/lib/styles/input.js +125 -290
- package/lib/styles/modal.d.ts +1 -2
- package/lib/styles/modal.js +71 -108
- package/lib/styles/option.d.ts +2 -6
- package/lib/styles/option.js +49 -64
- package/lib/styles/popup.d.ts +1 -2
- package/lib/styles/popup.js +42 -77
- package/lib/styles/radio.d.ts +0 -1
- package/lib/styles/radio.js +69 -110
- package/lib/styles/resize-observer.d.ts +1 -2
- package/lib/styles/resize-observer.js +2 -12
- package/lib/styles/scroll-container.d.ts +1 -2
- package/lib/styles/scroll-container.js +100 -149
- package/lib/styles/select-base.d.ts +0 -1
- package/lib/styles/select-base.js +597 -785
- package/lib/styles/select.d.ts +1 -2
- package/lib/styles/select.js +140 -236
- package/lib/styles/step.d.ts +1 -2
- package/lib/styles/step.js +22 -33
- package/lib/styles/switch.d.ts +0 -1
- package/lib/styles/switch.js +34 -73
- package/lib/styles/tab.d.ts +1 -2
- package/lib/styles/tab.js +22 -34
- package/lib/styles/tabs.d.ts +3 -11
- package/lib/styles/tabs.js +190 -222
- package/lib/styles/tag.d.ts +1 -5
- package/lib/styles/tag.js +136 -218
- package/lib/styles/textarea.d.ts +1 -2
- package/lib/styles/textarea.js +210 -319
- package/lib/styles/tooltip.d.ts +0 -1
- package/lib/styles/tooltip.js +3 -13
- package/lib/styles/wizard.d.ts +5 -21
- package/lib/styles/wizard.js +170 -306
- package/lib/types/common.d.ts +2 -2
- package/lib/types/common.js +6 -17
- package/lib/utils/calendar-utils.d.ts +4 -21
- package/lib/utils/calendar-utils.js +100 -273
- package/lib/utils/custom-scrollbar.d.ts +6 -20
- package/lib/utils/custom-scrollbar.js +273 -317
- package/lib/utils/flavor-sheet.d.ts +0 -1
- package/lib/utils/flavor-sheet.js +14 -37
- package/lib/utils/icon-registry.d.ts +0 -2
- package/lib/utils/icon-registry.js +137 -243
- package/lib/utils/icons.d.ts +0 -1
- package/lib/utils/icons.js +0 -9
- package/lib/utils/loader-registry.d.ts +0 -1
- package/lib/utils/loader-registry.js +3 -35
- package/lib/utils/locale.d.ts +2 -23
- package/lib/utils/locale.js +34 -120
- package/lib/utils/mobile.d.ts +0 -1
- package/lib/utils/mobile.js +1 -18
- package/lib/utils/number-format.d.ts +6 -3
- package/lib/utils/number-format.js +54 -110
- package/lib/utils/positioning.d.ts +50 -7
- package/lib/utils/positioning.js +224 -234
- package/lib/utils/property-capture.d.ts +5 -27
- package/lib/utils/property-capture.js +36 -144
- package/lib/utils/property-manager.d.ts +0 -1
- package/lib/utils/property-manager.js +126 -196
- package/lib/utils/resize-observer.d.ts +0 -1
- package/lib/utils/resize-observer.js +23 -59
- package/lib/utils/scroll-lock.d.ts +4 -11
- package/lib/utils/scroll-lock.js +65 -172
- package/lib/utils/styles.d.ts +0 -1
- package/lib/utils/styles.js +17 -41
- package/lib/version.d.ts +1 -2
- package/lib/version.js +1 -11
- package/package.json +4 -4
- package/lib/base/ty-component.d.ts.map +0 -1
- package/lib/base/ty-component.js.map +0 -1
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js.map +0 -1
- package/lib/components/calendar-month.d.ts.map +0 -1
- package/lib/components/calendar-month.js.map +0 -1
- package/lib/components/calendar-navigation.d.ts.map +0 -1
- package/lib/components/calendar-navigation.js.map +0 -1
- package/lib/components/calendar.d.ts.map +0 -1
- package/lib/components/calendar.js.map +0 -1
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js.map +0 -1
- package/lib/components/copy.d.ts.map +0 -1
- package/lib/components/copy.js.map +0 -1
- package/lib/components/date-picker.d.ts.map +0 -1
- package/lib/components/date-picker.js.map +0 -1
- package/lib/components/file-upload.d.ts.map +0 -1
- package/lib/components/file-upload.js.map +0 -1
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js.map +0 -1
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js.map +0 -1
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js.map +0 -1
- package/lib/components/option.d.ts.map +0 -1
- package/lib/components/option.js.map +0 -1
- package/lib/components/popup.d.ts.map +0 -1
- package/lib/components/popup.js.map +0 -1
- package/lib/components/radio.d.ts.map +0 -1
- package/lib/components/radio.js.map +0 -1
- package/lib/components/resize-observer.d.ts.map +0 -1
- package/lib/components/resize-observer.js.map +0 -1
- package/lib/components/scroll-container.d.ts.map +0 -1
- package/lib/components/scroll-container.js.map +0 -1
- package/lib/components/select.d.ts.map +0 -1
- package/lib/components/select.js.map +0 -1
- package/lib/components/selected-tags.d.ts.map +0 -1
- package/lib/components/selected-tags.js.map +0 -1
- package/lib/components/step.d.ts.map +0 -1
- package/lib/components/step.js.map +0 -1
- package/lib/components/switch.d.ts.map +0 -1
- package/lib/components/switch.js.map +0 -1
- package/lib/components/tab.d.ts.map +0 -1
- package/lib/components/tab.js.map +0 -1
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js.map +0 -1
- package/lib/components/tag.d.ts.map +0 -1
- package/lib/components/tag.js.map +0 -1
- package/lib/components/textarea.d.ts.map +0 -1
- package/lib/components/textarea.js.map +0 -1
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js.map +0 -1
- package/lib/components/wizard.d.ts.map +0 -1
- package/lib/components/wizard.js.map +0 -1
- package/lib/icons/fontawesome/brands.d.ts.map +0 -1
- package/lib/icons/fontawesome/brands.js.map +0 -1
- package/lib/icons/fontawesome/regular.d.ts.map +0 -1
- package/lib/icons/fontawesome/regular.js.map +0 -1
- package/lib/icons/fontawesome/solid.d.ts.map +0 -1
- package/lib/icons/fontawesome/solid.js.map +0 -1
- package/lib/icons/heroicons/micro.d.ts.map +0 -1
- package/lib/icons/heroicons/micro.js.map +0 -1
- package/lib/icons/heroicons/mini.d.ts.map +0 -1
- package/lib/icons/heroicons/mini.js.map +0 -1
- package/lib/icons/heroicons/outline.d.ts.map +0 -1
- package/lib/icons/heroicons/outline.js.map +0 -1
- package/lib/icons/heroicons/solid.d.ts.map +0 -1
- package/lib/icons/heroicons/solid.js.map +0 -1
- package/lib/icons/lucide.d.ts.map +0 -1
- package/lib/icons/lucide.js.map +0 -1
- package/lib/icons/material/filled.d.ts.map +0 -1
- package/lib/icons/material/filled.js.map +0 -1
- package/lib/icons/material/outlined.d.ts.map +0 -1
- package/lib/icons/material/outlined.js.map +0 -1
- package/lib/icons/material/round.d.ts.map +0 -1
- package/lib/icons/material/round.js.map +0 -1
- package/lib/icons/material/sharp.d.ts.map +0 -1
- package/lib/icons/material/sharp.js.map +0 -1
- package/lib/icons/material/two-tone.d.ts.map +0 -1
- package/lib/icons/material/two-tone.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/styles/button.d.ts.map +0 -1
- package/lib/styles/button.js.map +0 -1
- package/lib/styles/calendar-month.d.ts.map +0 -1
- package/lib/styles/calendar-month.js.map +0 -1
- package/lib/styles/calendar-navigation.d.ts.map +0 -1
- package/lib/styles/calendar-navigation.js.map +0 -1
- package/lib/styles/calendar.d.ts.map +0 -1
- package/lib/styles/calendar.js.map +0 -1
- package/lib/styles/checkbox.d.ts.map +0 -1
- package/lib/styles/checkbox.js.map +0 -1
- package/lib/styles/copy.d.ts.map +0 -1
- package/lib/styles/copy.js.map +0 -1
- package/lib/styles/custom-scrollbar.d.ts.map +0 -1
- package/lib/styles/custom-scrollbar.js.map +0 -1
- package/lib/styles/date-picker.d.ts.map +0 -1
- package/lib/styles/date-picker.js.map +0 -1
- package/lib/styles/file-upload.d.ts.map +0 -1
- package/lib/styles/file-upload.js.map +0 -1
- package/lib/styles/icon.d.ts.map +0 -1
- package/lib/styles/icon.js.map +0 -1
- package/lib/styles/input.d.ts.map +0 -1
- package/lib/styles/input.js.map +0 -1
- package/lib/styles/modal.d.ts.map +0 -1
- package/lib/styles/modal.js.map +0 -1
- package/lib/styles/multiselect.d.ts +0 -8
- package/lib/styles/multiselect.d.ts.map +0 -1
- package/lib/styles/multiselect.js +0 -849
- package/lib/styles/multiselect.js.map +0 -1
- package/lib/styles/option.d.ts.map +0 -1
- package/lib/styles/option.js.map +0 -1
- package/lib/styles/popup.d.ts.map +0 -1
- package/lib/styles/popup.js.map +0 -1
- package/lib/styles/radio.d.ts.map +0 -1
- package/lib/styles/radio.js.map +0 -1
- package/lib/styles/resize-observer.d.ts.map +0 -1
- package/lib/styles/resize-observer.js.map +0 -1
- package/lib/styles/scroll-container.d.ts.map +0 -1
- package/lib/styles/scroll-container.js.map +0 -1
- package/lib/styles/select-base.d.ts.map +0 -1
- package/lib/styles/select-base.js.map +0 -1
- package/lib/styles/select.d.ts.map +0 -1
- package/lib/styles/select.js.map +0 -1
- package/lib/styles/step.d.ts.map +0 -1
- package/lib/styles/step.js.map +0 -1
- package/lib/styles/switch.d.ts.map +0 -1
- package/lib/styles/switch.js.map +0 -1
- package/lib/styles/tab.d.ts.map +0 -1
- package/lib/styles/tab.js.map +0 -1
- package/lib/styles/tabs.d.ts.map +0 -1
- package/lib/styles/tabs.js.map +0 -1
- package/lib/styles/tag.d.ts.map +0 -1
- package/lib/styles/tag.js.map +0 -1
- package/lib/styles/textarea.d.ts.map +0 -1
- package/lib/styles/textarea.js.map +0 -1
- package/lib/styles/tooltip.d.ts.map +0 -1
- package/lib/styles/tooltip.js.map +0 -1
- package/lib/styles/wizard.d.ts.map +0 -1
- package/lib/styles/wizard.js.map +0 -1
- package/lib/types/common.d.ts.map +0 -1
- package/lib/types/common.js.map +0 -1
- package/lib/utils/calendar-utils.d.ts.map +0 -1
- package/lib/utils/calendar-utils.js.map +0 -1
- package/lib/utils/custom-scrollbar.d.ts.map +0 -1
- package/lib/utils/custom-scrollbar.js.map +0 -1
- package/lib/utils/flavor-sheet.d.ts.map +0 -1
- package/lib/utils/flavor-sheet.js.map +0 -1
- package/lib/utils/icon-registry.d.ts.map +0 -1
- package/lib/utils/icon-registry.js.map +0 -1
- package/lib/utils/icons.d.ts.map +0 -1
- package/lib/utils/icons.js.map +0 -1
- package/lib/utils/loader-registry.d.ts.map +0 -1
- package/lib/utils/loader-registry.js.map +0 -1
- package/lib/utils/locale.d.ts.map +0 -1
- package/lib/utils/locale.js.map +0 -1
- package/lib/utils/mobile.d.ts.map +0 -1
- package/lib/utils/mobile.js.map +0 -1
- package/lib/utils/number-format.d.ts.map +0 -1
- package/lib/utils/number-format.js.map +0 -1
- package/lib/utils/parse-boolean.d.ts +0 -39
- package/lib/utils/parse-boolean.d.ts.map +0 -1
- package/lib/utils/parse-boolean.js +0 -58
- package/lib/utils/parse-boolean.js.map +0 -1
- package/lib/utils/positioning.d.ts.map +0 -1
- package/lib/utils/positioning.js.map +0 -1
- package/lib/utils/property-capture.d.ts.map +0 -1
- package/lib/utils/property-capture.js.map +0 -1
- package/lib/utils/property-manager.d.ts.map +0 -1
- package/lib/utils/property-manager.js.map +0 -1
- package/lib/utils/resize-observer.d.ts.map +0 -1
- package/lib/utils/resize-observer.js.map +0 -1
- package/lib/utils/scroll-lock.d.ts.map +0 -1
- package/lib/utils/scroll-lock.js.map +0 -1
- package/lib/utils/styles.d.ts.map +0 -1
- package/lib/utils/styles.js.map +0 -1
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js.map +0 -1
package/lib/components/tabs.js
CHANGED
|
@@ -1,818 +1,586 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tabs Component
|
|
3
|
-
*
|
|
4
|
-
* A carousel-based tabs component with smooth animations and fixed container dimensions.
|
|
5
|
-
* Features horizontal sliding transitions, smart positioning marker, and independent panel scrolling.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Carousel animation with smooth sliding transitions
|
|
9
|
-
* - Fixed dimensions prevent layout shift between tabs
|
|
10
|
-
* - Animated marker that follows active tab button
|
|
11
|
-
* - Rich label support via slots (icons, badges, custom content)
|
|
12
|
-
* - Custom marker slot for complete active tab styling control
|
|
13
|
-
* - Independent panel scrolling with scroll position reset
|
|
14
|
-
* - ResizeObserver for responsive percentage widths
|
|
15
|
-
* - Smart rendering - only updates DOM when necessary
|
|
16
|
-
* - Accessibility with ARIA roles and attributes
|
|
17
|
-
* - Top/bottom placement for tab buttons
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* <!-- Basic tabs with text labels -->
|
|
21
|
-
* <ty-tabs width="800px" height="600px" active="general">
|
|
22
|
-
* <ty-tab id="general" label="General Settings">
|
|
23
|
-
* <div class="p-6">General content...</div>
|
|
24
|
-
* </ty-tab>
|
|
25
|
-
* <ty-tab id="advanced" label="Advanced Settings">
|
|
26
|
-
* <div class="p-6">Advanced content...</div>
|
|
27
|
-
* </ty-tab>
|
|
28
|
-
* </ty-tabs>
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* <!-- Rich labels with icons and badges -->
|
|
32
|
-
* <ty-tabs width="800px" height="600px">
|
|
33
|
-
* <!-- Rich label as direct child -->
|
|
34
|
-
* <span slot="label-profile" class="flex items-center gap-2">
|
|
35
|
-
* <ty-icon name="user" size="sm"></ty-icon>
|
|
36
|
-
* Profile
|
|
37
|
-
* </span>
|
|
38
|
-
*
|
|
39
|
-
* <span slot="label-notifications" class="flex items-center gap-2">
|
|
40
|
-
* <ty-icon name="bell" size="sm"></ty-icon>
|
|
41
|
-
* Notifications
|
|
42
|
-
* <span class="ty-bg-danger ty-text-danger++ px-2 py-0.5 rounded-full text-xs">5</span>
|
|
43
|
-
* </span>
|
|
44
|
-
*
|
|
45
|
-
* <!-- Tab panels -->
|
|
46
|
-
* <ty-tab id="profile">...</ty-tab>
|
|
47
|
-
* <ty-tab id="notifications">...</ty-tab>
|
|
48
|
-
* </ty-tabs>
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* <!-- Custom marker styling -->
|
|
52
|
-
* <ty-tabs width="100%" height="400px">
|
|
53
|
-
* <!-- Gradient marker slot -->
|
|
54
|
-
* <div slot="marker" style="
|
|
55
|
-
* background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
56
|
-
* border-radius: 0.5rem;
|
|
57
|
-
* box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
58
|
-
* "></div>
|
|
59
|
-
*
|
|
60
|
-
* <ty-tab id="tab1" label="Dashboard">...</ty-tab>
|
|
61
|
-
* <ty-tab id="tab2" label="Analytics">...</ty-tab>
|
|
62
|
-
* </ty-tabs>
|
|
63
|
-
*/
|
|
64
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
65
2
|
import { tabsStyles } from '../styles/tabs.js';
|
|
66
|
-
// ============================================================================
|
|
67
|
-
// WeakMaps for State Management
|
|
68
|
-
// ============================================================================
|
|
69
3
|
const eventHandlers = new WeakMap();
|
|
70
4
|
const resizeObservers = new WeakMap();
|
|
71
|
-
|
|
72
|
-
// Helper Functions - Attribute Parsing
|
|
73
|
-
// ============================================================================
|
|
74
|
-
/**
|
|
75
|
-
* Extract tabs configuration from element attributes
|
|
76
|
-
*/
|
|
5
|
+
const pendingInitialSnap = new WeakSet();
|
|
77
6
|
function getTabsAttributes(el) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
* Get all ty-tab child elements
|
|
87
|
-
*/
|
|
7
|
+
return {
|
|
8
|
+
width: el.getAttribute('width') || '100%',
|
|
9
|
+
height: el.getAttribute('height') || '400px',
|
|
10
|
+
active: el.getAttribute('active'),
|
|
11
|
+
placement: (el.getAttribute('placement') || 'top'),
|
|
12
|
+
fixed: el.hasAttribute('fixed'),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
88
15
|
function getChildTabs(el) {
|
|
89
|
-
|
|
16
|
+
return Array.from(el.querySelectorAll('ty-tab'));
|
|
90
17
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Get ID from a ty-tab element
|
|
93
|
-
*/
|
|
94
18
|
function getTabId(tab) {
|
|
95
|
-
|
|
19
|
+
return tab.getAttribute('id');
|
|
20
|
+
}
|
|
21
|
+
function getSlotLabel(tabsEl, tabId) {
|
|
22
|
+
return tabsEl.querySelector(`:scope > [slot='label-${tabId}']`);
|
|
96
23
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Check if ty-tabs has a direct child label slot for this tab-id.
|
|
99
|
-
* Looks in ty-tabs' light DOM for slot='label-{tab-id}' elements.
|
|
100
|
-
*/
|
|
101
24
|
function hasSlotLabel(tabsEl, tabId) {
|
|
102
|
-
|
|
25
|
+
return getSlotLabel(tabsEl, tabId) !== null;
|
|
103
26
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Determine label type: 'slot' (rich content) or 'text' (simple attribute).
|
|
106
|
-
* Checks ty-tabs element for slot='label-{tab-id}' direct children.
|
|
107
|
-
*/
|
|
108
27
|
function getTabLabelType(tabsEl, tab) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Check if tab is disabled
|
|
117
|
-
*/
|
|
28
|
+
const tabId = getTabId(tab);
|
|
29
|
+
if (tabId && hasSlotLabel(tabsEl, tabId)) {
|
|
30
|
+
return 'slot';
|
|
31
|
+
}
|
|
32
|
+
return 'text';
|
|
33
|
+
}
|
|
118
34
|
function isTabDisabled(tab) {
|
|
119
|
-
|
|
35
|
+
return tab.hasAttribute('disabled');
|
|
120
36
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Check if user provided custom marker content via slot
|
|
123
|
-
*/
|
|
124
37
|
function hasCustomMarker(el) {
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// Helper Functions - Active Tab Management
|
|
129
|
-
// ============================================================================
|
|
130
|
-
/**
|
|
131
|
-
* Find index of tab with given ID
|
|
132
|
-
*/
|
|
38
|
+
return el.querySelector('[slot="marker"]') !== null;
|
|
39
|
+
}
|
|
133
40
|
function findTabIndex(tabs, tabId) {
|
|
134
|
-
|
|
135
|
-
|
|
41
|
+
const index = tabs.findIndex(tab => getTabId(tab) === tabId);
|
|
42
|
+
return index >= 0 ? index : undefined;
|
|
43
|
+
}
|
|
44
|
+
function currentActive(el) {
|
|
45
|
+
const tabs = getChildTabs(el);
|
|
46
|
+
const id = getActiveTabId(el, tabs);
|
|
47
|
+
return { id, index: (id ? findTabIndex(tabs, id) : undefined) ?? 0 };
|
|
136
48
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Get the active tab ID, defaulting to first tab if not specified
|
|
139
|
-
*/
|
|
140
49
|
function getActiveTabId(el, tabs) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Set the active tab by ID
|
|
153
|
-
*/
|
|
50
|
+
const activeAttr = el.getAttribute('active');
|
|
51
|
+
if (activeAttr && findTabIndex(tabs, activeAttr) !== undefined) {
|
|
52
|
+
return activeAttr;
|
|
53
|
+
}
|
|
54
|
+
if (tabs.length > 0) {
|
|
55
|
+
return getTabId(tabs[0]);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
154
59
|
function setActiveTab(el, tabId) {
|
|
155
|
-
|
|
60
|
+
el.setAttribute('active', tabId);
|
|
156
61
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Dispatch ty-tab-change event
|
|
159
|
-
*/
|
|
160
62
|
function dispatchTabChangeEvent(el, activeId, activeIndex, previousId, previousIndex) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
// ============================================================================
|
|
174
|
-
// Event Handlers - Tab Button Click
|
|
175
|
-
// ============================================================================
|
|
176
|
-
/**
|
|
177
|
-
* Handle tab button click
|
|
178
|
-
*/
|
|
63
|
+
const event = new CustomEvent('ty-tab-change', {
|
|
64
|
+
detail: {
|
|
65
|
+
activeId,
|
|
66
|
+
activeIndex,
|
|
67
|
+
previousId,
|
|
68
|
+
previousIndex,
|
|
69
|
+
},
|
|
70
|
+
bubbles: true,
|
|
71
|
+
cancelable: false,
|
|
72
|
+
});
|
|
73
|
+
el.dispatchEvent(event);
|
|
74
|
+
}
|
|
179
75
|
function handleTabClick(el, tabId, event) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Cleanup existing event listeners
|
|
187
|
-
*/
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
setActiveTab(el, tabId);
|
|
79
|
+
}
|
|
188
80
|
function cleanupEventListeners(el) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Setup event listeners for tab button clicks
|
|
213
|
-
*/
|
|
81
|
+
const handlers = eventHandlers.get(el);
|
|
82
|
+
if (!handlers)
|
|
83
|
+
return;
|
|
84
|
+
const shadowRoot = el.shadowRoot;
|
|
85
|
+
if (!shadowRoot)
|
|
86
|
+
return;
|
|
87
|
+
handlers.tabClickHandlers.forEach((handler, tabId) => {
|
|
88
|
+
const button = shadowRoot.querySelector(`[data-tab-id='${tabId}']`);
|
|
89
|
+
if (button) {
|
|
90
|
+
button.removeEventListener('pointerdown', handler);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
handlers.tabKeyClickHandlers.forEach((handler, tabId) => {
|
|
94
|
+
const button = shadowRoot.querySelector(`[data-tab-id='${tabId}']`);
|
|
95
|
+
if (button) {
|
|
96
|
+
button.removeEventListener('click', handler);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
handlers.tabClickHandlers.clear();
|
|
100
|
+
handlers.tabKeyClickHandlers.clear();
|
|
101
|
+
}
|
|
214
102
|
function setupEventListeners(el, shadowRoot, tabs) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
handleTabClick(el, tabId, e);
|
|
240
|
-
};
|
|
241
|
-
button.addEventListener('pointerdown', handler);
|
|
242
|
-
button.addEventListener('click', keyClickHandler);
|
|
243
|
-
handlers.tabClickHandlers.set(tabId, handler);
|
|
244
|
-
handlers.tabKeyClickHandlers.set(tabId, keyClickHandler);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
// Store handlers for cleanup
|
|
248
|
-
eventHandlers.set(el, handlers);
|
|
249
|
-
}
|
|
250
|
-
// ============================================================================
|
|
251
|
-
// Transform & Positioning Updates
|
|
252
|
-
// ============================================================================
|
|
253
|
-
/**
|
|
254
|
-
* Update the transform on panels-wrapper based on active index and measured width
|
|
255
|
-
*/
|
|
103
|
+
cleanupEventListeners(el);
|
|
104
|
+
const handlers = {
|
|
105
|
+
tabClickHandlers: new Map(),
|
|
106
|
+
tabKeyClickHandlers: new Map(),
|
|
107
|
+
};
|
|
108
|
+
tabs.forEach((tab) => {
|
|
109
|
+
const tabId = getTabId(tab);
|
|
110
|
+
if (!tabId)
|
|
111
|
+
return;
|
|
112
|
+
const button = shadowRoot.querySelector(`[data-tab-id='${tabId}']`);
|
|
113
|
+
if (button) {
|
|
114
|
+
const handler = (e) => handleTabClick(el, tabId, e);
|
|
115
|
+
const keyClickHandler = (e) => {
|
|
116
|
+
if (e.detail === 0)
|
|
117
|
+
handleTabClick(el, tabId, e);
|
|
118
|
+
};
|
|
119
|
+
button.addEventListener('pointerdown', handler);
|
|
120
|
+
button.addEventListener('click', keyClickHandler);
|
|
121
|
+
handlers.tabClickHandlers.set(tabId, handler);
|
|
122
|
+
handlers.tabKeyClickHandlers.set(tabId, keyClickHandler);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
eventHandlers.set(el, handlers);
|
|
126
|
+
}
|
|
256
127
|
function updateTransform(el, activeIndex) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
panelsWrapper.style.transform = `translateX(-${offsetPx}px)`;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Update ARIA attributes on tab buttons without re-rendering
|
|
271
|
-
*/
|
|
128
|
+
const shadowRoot = el.shadowRoot;
|
|
129
|
+
if (!shadowRoot)
|
|
130
|
+
return;
|
|
131
|
+
const panelsWrapper = shadowRoot.querySelector('.panels-wrapper');
|
|
132
|
+
if (!panelsWrapper)
|
|
133
|
+
return;
|
|
134
|
+
const containerWidth = el.offsetWidth;
|
|
135
|
+
const offsetPx = activeIndex * containerWidth;
|
|
136
|
+
panelsWrapper.style.transform = `translateX(-${offsetPx}px)`;
|
|
137
|
+
}
|
|
272
138
|
function updateAriaAttributes(el, shadowRoot, activeId) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Update pointer-events, opacity, and data-active on tab panels without re-rendering
|
|
295
|
-
*/
|
|
139
|
+
const tabs = getChildTabs(el);
|
|
140
|
+
tabs.forEach((tab, idx) => {
|
|
141
|
+
const tabId = getTabId(tab);
|
|
142
|
+
if (!tabId)
|
|
143
|
+
return;
|
|
144
|
+
const button = shadowRoot.querySelector(`[data-tab-id='${tabId}']`);
|
|
145
|
+
const isActive = tabId === activeId;
|
|
146
|
+
if (button) {
|
|
147
|
+
button.setAttribute('aria-selected', String(isActive));
|
|
148
|
+
button.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
149
|
+
button.setAttribute('data-active', String(isActive));
|
|
150
|
+
const slottedLabel = getSlotLabel(el, tabId);
|
|
151
|
+
if (slottedLabel) {
|
|
152
|
+
slottedLabel.setAttribute('data-active', String(isActive));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
296
157
|
function updatePanelInteraction(el, activeId) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Calculate marker position and dimensions based on active tab button.
|
|
317
|
-
* Returns position object with left, top, width, height in pixels, or null if button not found.
|
|
318
|
-
*/
|
|
158
|
+
const tabs = getChildTabs(el);
|
|
159
|
+
tabs.forEach((tab) => {
|
|
160
|
+
const tabId = getTabId(tab);
|
|
161
|
+
if (!tabId)
|
|
162
|
+
return;
|
|
163
|
+
const isActive = tabId === activeId;
|
|
164
|
+
tab.setAttribute('data-active', String(isActive));
|
|
165
|
+
if (isActive) {
|
|
166
|
+
tab.style.pointerEvents = 'auto';
|
|
167
|
+
tab.style.opacity = '1';
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
tab.style.pointerEvents = 'none';
|
|
171
|
+
tab.style.opacity = '0';
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
319
175
|
function calculateMarkerPosition(el, shadowRoot, activeId) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (snap)
|
|
353
|
-
marker.style.transition = 'none';
|
|
354
|
-
marker.style.left = `${position.left}px`;
|
|
355
|
-
marker.style.top = `${position.top}px`;
|
|
356
|
-
marker.style.width = `${position.width}px`;
|
|
357
|
-
marker.style.height = `${position.height}px`;
|
|
358
|
-
if (snap) {
|
|
359
|
-
marker.offsetWidth; // flush styles so the snap isn't animated
|
|
360
|
-
marker.style.transition = '';
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Update only the active tab state without re-rendering DOM.
|
|
365
|
-
* Called from `attributeChangedCallback` when the `active` attribute changes.
|
|
366
|
-
*
|
|
367
|
-
* `previousId` MUST be the value the attribute held *before* it was changed —
|
|
368
|
-
* never call `getActiveTabId(el, ...)` here, because the attribute has already
|
|
369
|
-
* been updated to `tabId` by the time this runs. Reading it back would always
|
|
370
|
-
* yield the new value, making the change-detection guard below trip on every
|
|
371
|
-
* click and silently suppress `ty-tab-change` dispatch.
|
|
372
|
-
*/
|
|
176
|
+
const button = shadowRoot.querySelector(`[data-tab-id='${activeId}']`);
|
|
177
|
+
const buttonsContainer = shadowRoot.querySelector('.tab-buttons');
|
|
178
|
+
if (!button || !buttonsContainer)
|
|
179
|
+
return null;
|
|
180
|
+
const left = button.offsetLeft;
|
|
181
|
+
const top = button.offsetTop;
|
|
182
|
+
const width = button.offsetWidth;
|
|
183
|
+
const height = button.offsetHeight;
|
|
184
|
+
return { left, top, width, height };
|
|
185
|
+
}
|
|
186
|
+
function updateMarker(el, activeId, forceSnap = false) {
|
|
187
|
+
const shadowRoot = el.shadowRoot;
|
|
188
|
+
if (!shadowRoot)
|
|
189
|
+
return;
|
|
190
|
+
const marker = shadowRoot.querySelector('.marker-wrapper');
|
|
191
|
+
if (!marker)
|
|
192
|
+
return;
|
|
193
|
+
const position = calculateMarkerPosition(el, shadowRoot, activeId);
|
|
194
|
+
if (!position)
|
|
195
|
+
return;
|
|
196
|
+
const snap = forceSnap || !marker.style.left || marker.offsetWidth === 0;
|
|
197
|
+
if (snap)
|
|
198
|
+
marker.style.transition = 'none';
|
|
199
|
+
marker.style.left = `${position.left}px`;
|
|
200
|
+
marker.style.top = `${position.top}px`;
|
|
201
|
+
marker.style.width = `${position.width}px`;
|
|
202
|
+
marker.style.height = `${position.height}px`;
|
|
203
|
+
if (snap) {
|
|
204
|
+
marker.offsetWidth;
|
|
205
|
+
marker.style.transition = '';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
373
208
|
function updateActiveTabState(el, tabId, previousId) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
newPanel.resetScroll();
|
|
397
|
-
}
|
|
398
|
-
// Dispatch change event
|
|
399
|
-
dispatchTabChangeEvent(el, tabId, newIndex, previousId, currentIndex ?? null);
|
|
400
|
-
}
|
|
401
|
-
// ============================================================================
|
|
402
|
-
// ResizeObserver for Responsive Width
|
|
403
|
-
// ============================================================================
|
|
404
|
-
/**
|
|
405
|
-
* Setup ResizeObserver for percentage widths and marker updates
|
|
406
|
-
*/
|
|
209
|
+
const tabs = getChildTabs(el);
|
|
210
|
+
const shadowRoot = el.shadowRoot;
|
|
211
|
+
if (!shadowRoot)
|
|
212
|
+
return;
|
|
213
|
+
const currentIndex = previousId ? findTabIndex(tabs, previousId) : undefined;
|
|
214
|
+
const newIndex = findTabIndex(tabs, tabId);
|
|
215
|
+
if (previousId === tabId || newIndex === undefined)
|
|
216
|
+
return;
|
|
217
|
+
el.style.setProperty('--active-index', String(newIndex));
|
|
218
|
+
updateTransform(el, newIndex);
|
|
219
|
+
updateAriaAttributes(el, shadowRoot, tabId);
|
|
220
|
+
updatePanelInteraction(el, tabId);
|
|
221
|
+
updateMarker(el, tabId, pendingInitialSnap.delete(el));
|
|
222
|
+
shadowRoot.querySelectorAll('.tab-overflow-item').forEach((item) => {
|
|
223
|
+
item.setAttribute('data-active', String(item.dataset.tabId === tabId));
|
|
224
|
+
});
|
|
225
|
+
const newPanel = tabs[newIndex];
|
|
226
|
+
if (newPanel?.resetScroll) {
|
|
227
|
+
newPanel.resetScroll();
|
|
228
|
+
}
|
|
229
|
+
dispatchTabChangeEvent(el, tabId, newIndex, previousId, currentIndex ?? null);
|
|
230
|
+
}
|
|
407
231
|
function setupResizeObserver(el) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
updateOverflow(el);
|
|
434
|
-
});
|
|
435
|
-
observer.observe(el);
|
|
436
|
-
resizeObservers.set(el, observer);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Cleanup ResizeObserver
|
|
440
|
-
*/
|
|
232
|
+
const oldObserver = resizeObservers.get(el);
|
|
233
|
+
if (oldObserver) {
|
|
234
|
+
oldObserver.disconnect();
|
|
235
|
+
}
|
|
236
|
+
const observer = new ResizeObserver((entries) => {
|
|
237
|
+
const entry = entries[0];
|
|
238
|
+
const { width } = getTabsAttributes(el);
|
|
239
|
+
const tabs = getChildTabs(el);
|
|
240
|
+
const activeId = getActiveTabId(el, tabs);
|
|
241
|
+
const activeIndex = activeId ? findTabIndex(tabs, activeId) : 0;
|
|
242
|
+
if (width.includes('%')) {
|
|
243
|
+
el.style.setProperty('--tabs-page-width', `${entry.contentRect.width}px`);
|
|
244
|
+
}
|
|
245
|
+
if (activeIndex !== undefined) {
|
|
246
|
+
updateTransform(el, activeIndex);
|
|
247
|
+
}
|
|
248
|
+
updateOverflow(el);
|
|
249
|
+
if (activeId) {
|
|
250
|
+
updateMarker(el, activeId);
|
|
251
|
+
scrollActiveIntoView(el, activeId, false);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
observer.observe(el);
|
|
255
|
+
resizeObservers.set(el, observer);
|
|
256
|
+
}
|
|
441
257
|
function cleanupResizeObserver(el) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
// ============================================================================
|
|
449
|
-
// Rendering Functions
|
|
450
|
-
// ============================================================================
|
|
451
|
-
/**
|
|
452
|
-
* Generate HTML for tab buttons using slots for rich labels.
|
|
453
|
-
* Slots are looked up as direct children of tabs-el with slot='label-{tab-id}'.
|
|
454
|
-
* Also renders marker wrapper with optional default underline (only if no custom marker).
|
|
455
|
-
*/
|
|
258
|
+
const observer = resizeObservers.get(el);
|
|
259
|
+
if (observer) {
|
|
260
|
+
observer.disconnect();
|
|
261
|
+
resizeObservers.delete(el);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
456
264
|
function renderTabButtons(tabsEl, tabs, activeId) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
* Used during smart updates to preserve the marker wrapper.
|
|
491
|
-
*/
|
|
265
|
+
const buttons = tabs.map((tab) => {
|
|
266
|
+
const tabId = getTabId(tab);
|
|
267
|
+
if (!tabId)
|
|
268
|
+
return '';
|
|
269
|
+
const labelType = getTabLabelType(tabsEl, tab);
|
|
270
|
+
const textLabel = labelType === 'text' ? (tab.getAttribute('label') || 'Tab') : '';
|
|
271
|
+
const disabled = isTabDisabled(tab);
|
|
272
|
+
const active = tabId === activeId;
|
|
273
|
+
return `<button
|
|
274
|
+
class="tab-button"
|
|
275
|
+
role="tab"
|
|
276
|
+
data-tab-id="${tabId}"
|
|
277
|
+
id="tab-${tabId}"
|
|
278
|
+
aria-selected="${active}"
|
|
279
|
+
tabindex="${active ? '0' : '-1'}"
|
|
280
|
+
data-active="${active}"
|
|
281
|
+
${disabled ? 'disabled aria-disabled="true"' : ''}
|
|
282
|
+
>
|
|
283
|
+
${labelType === 'slot' ? `<slot name="label-${tabId}"></slot>` : `<span class="tab-label">${textLabel}</span>`}
|
|
284
|
+
</button>`;
|
|
285
|
+
}).join('');
|
|
286
|
+
return `
|
|
287
|
+
<div class="tab-buttons" role="tablist" part="buttons-container">
|
|
288
|
+
<div class="tab-strip" part="strip">
|
|
289
|
+
<div class="marker-wrapper" part="marker-wrapper">
|
|
290
|
+
${!hasCustomMarker(tabsEl) ? '<div class="default-marker"></div>' : ''}
|
|
291
|
+
<slot name="marker"></slot>
|
|
292
|
+
</div>
|
|
293
|
+
${buttons}
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
492
298
|
function renderButtonsOnly(tabsEl, tabs, activeId) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
// ============================================================================
|
|
516
|
-
// Overflow Handling (collapse tabs that don't fit into a "more" menu)
|
|
517
|
-
// ============================================================================
|
|
518
|
-
const OVERFLOW_TRIGGER_WIDTH = 40;
|
|
519
|
-
/** Build a display label for a hidden tab's overflow-menu entry. */
|
|
299
|
+
return tabs.map((tab) => {
|
|
300
|
+
const tabId = getTabId(tab);
|
|
301
|
+
if (!tabId)
|
|
302
|
+
return '';
|
|
303
|
+
const labelType = getTabLabelType(tabsEl, tab);
|
|
304
|
+
const textLabel = labelType === 'text' ? (tab.getAttribute('label') || 'Tab') : '';
|
|
305
|
+
const disabled = isTabDisabled(tab);
|
|
306
|
+
const active = tabId === activeId;
|
|
307
|
+
return `<button
|
|
308
|
+
class="tab-button"
|
|
309
|
+
role="tab"
|
|
310
|
+
data-tab-id="${tabId}"
|
|
311
|
+
id="tab-${tabId}"
|
|
312
|
+
aria-selected="${active}"
|
|
313
|
+
tabindex="${active ? '0' : '-1'}"
|
|
314
|
+
data-active="${active}"
|
|
315
|
+
${disabled ? 'disabled aria-disabled="true"' : ''}
|
|
316
|
+
>
|
|
317
|
+
${labelType === 'slot' ? `<slot name="label-${tabId}"></slot>` : `<span class="tab-label">${textLabel}</span>`}
|
|
318
|
+
</button>`;
|
|
319
|
+
}).join('');
|
|
320
|
+
}
|
|
520
321
|
function getTabMenuLabel(tabsEl, tab) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
/** Remove the "more" trigger + its popup, if present. */
|
|
322
|
+
const tabId = getTabId(tab);
|
|
323
|
+
if (tabId && getTabLabelType(tabsEl, tab) === 'slot') {
|
|
324
|
+
const slotted = getSlotLabel(tabsEl, tabId);
|
|
325
|
+
return slotted?.textContent?.trim() || tabId;
|
|
326
|
+
}
|
|
327
|
+
return tab.getAttribute('label') || 'Tab';
|
|
328
|
+
}
|
|
529
329
|
function removeOverflowMenu(shadowRoot) {
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
menu.appendChild(item);
|
|
571
|
-
});
|
|
572
|
-
popup.appendChild(menu);
|
|
573
|
-
trigger.appendChild(popup);
|
|
574
|
-
container.appendChild(trigger);
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Measure the tab buttons against the available width and collapse whatever
|
|
578
|
-
* doesn't fit behind a "more" trigger + popup menu. The active tab is always
|
|
579
|
-
* kept visible — if it would be the one to overflow, the last visible button
|
|
580
|
-
* is hidden in its place instead.
|
|
581
|
-
*/
|
|
330
|
+
shadowRoot.querySelector('.tab-overflow-trigger')?.remove();
|
|
331
|
+
}
|
|
332
|
+
function renderOverflowMenu(el, container, allTabs) {
|
|
333
|
+
const activeId = getActiveTabId(el, allTabs);
|
|
334
|
+
const trigger = document.createElement('button');
|
|
335
|
+
trigger.className = 'tab-overflow-trigger';
|
|
336
|
+
trigger.type = 'button';
|
|
337
|
+
trigger.setAttribute('aria-label', `${allTabs.length} tabs`);
|
|
338
|
+
trigger.textContent = '⋯';
|
|
339
|
+
const popup = document.createElement('ty-popup');
|
|
340
|
+
popup.setAttribute('placement', 'bottom');
|
|
341
|
+
const menu = document.createElement('div');
|
|
342
|
+
menu.className = 'tab-overflow-menu';
|
|
343
|
+
menu.setAttribute('role', 'menu');
|
|
344
|
+
allTabs.forEach((tab) => {
|
|
345
|
+
const tabId = getTabId(tab);
|
|
346
|
+
if (!tabId)
|
|
347
|
+
return;
|
|
348
|
+
const item = document.createElement('button');
|
|
349
|
+
item.type = 'button';
|
|
350
|
+
item.className = 'tab-overflow-item';
|
|
351
|
+
item.setAttribute('role', 'menuitem');
|
|
352
|
+
item.setAttribute('data-tab-id', tabId);
|
|
353
|
+
item.setAttribute('data-active', String(tabId === activeId));
|
|
354
|
+
item.textContent = getTabMenuLabel(el, tab);
|
|
355
|
+
if (isTabDisabled(tab)) {
|
|
356
|
+
item.disabled = true;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
item.addEventListener('click', () => {
|
|
360
|
+
setActiveTab(el, tabId);
|
|
361
|
+
popup.closePopup?.();
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
menu.appendChild(item);
|
|
365
|
+
});
|
|
366
|
+
popup.appendChild(menu);
|
|
367
|
+
trigger.appendChild(popup);
|
|
368
|
+
container.appendChild(trigger);
|
|
369
|
+
}
|
|
582
370
|
function updateOverflow(el) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
371
|
+
const shadowRoot = el.shadowRoot;
|
|
372
|
+
if (!shadowRoot)
|
|
373
|
+
return;
|
|
374
|
+
const container = shadowRoot.querySelector('.tab-buttons');
|
|
375
|
+
const strip = shadowRoot.querySelector('.tab-strip');
|
|
376
|
+
if (!container || !strip)
|
|
377
|
+
return;
|
|
378
|
+
if (getTabsAttributes(el).fixed) {
|
|
379
|
+
removeOverflowMenu(shadowRoot);
|
|
380
|
+
strip.classList.add('unclipped');
|
|
381
|
+
strip.style.removeProperty('--fade-left');
|
|
382
|
+
strip.style.removeProperty('--fade-right');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const savedScrollLeft = strip.scrollLeft;
|
|
386
|
+
removeOverflowMenu(shadowRoot);
|
|
387
|
+
if (strip.clientWidth <= 0)
|
|
388
|
+
return;
|
|
389
|
+
const buttons = strip.querySelectorAll('.tab-button');
|
|
390
|
+
const last = buttons[buttons.length - 1];
|
|
391
|
+
const contentWidth = last ? last.offsetLeft + last.offsetWidth : 0;
|
|
392
|
+
const stripWidth = strip.clientWidth;
|
|
393
|
+
const hidden = contentWidth - stripWidth;
|
|
394
|
+
strip.classList.toggle('unclipped', hidden <= 1);
|
|
395
|
+
if (hidden <= 1) {
|
|
396
|
+
strip.scrollLeft = savedScrollLeft;
|
|
397
|
+
updateEdgeFades(strip);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
renderOverflowMenu(el, container, getChildTabs(el));
|
|
401
|
+
const triggerCost = container.clientWidth - strip.clientWidth;
|
|
402
|
+
if (hidden <= triggerCost) {
|
|
403
|
+
removeOverflowMenu(shadowRoot);
|
|
404
|
+
}
|
|
405
|
+
strip.scrollLeft = savedScrollLeft;
|
|
406
|
+
updateEdgeFades(strip);
|
|
407
|
+
}
|
|
408
|
+
function updateEdgeFades(strip) {
|
|
409
|
+
const maxScroll = strip.scrollWidth - strip.clientWidth;
|
|
410
|
+
const fade = (distance) => `${distance > 1 ? Math.min(28, distance) : 0}px`;
|
|
411
|
+
strip.style.setProperty('--fade-left', fade(strip.scrollLeft));
|
|
412
|
+
strip.style.setProperty('--fade-right', fade(maxScroll - strip.scrollLeft));
|
|
413
|
+
}
|
|
414
|
+
function scrollActiveIntoView(el, activeId, smooth) {
|
|
415
|
+
const shadowRoot = el.shadowRoot;
|
|
416
|
+
if (!shadowRoot)
|
|
417
|
+
return;
|
|
418
|
+
const strip = shadowRoot.querySelector('.tab-strip');
|
|
419
|
+
const button = shadowRoot.querySelector(`[data-tab-id='${activeId}']`);
|
|
420
|
+
if (!strip || !button || strip.clientWidth <= 0)
|
|
421
|
+
return;
|
|
422
|
+
const centered = button.offsetLeft + button.offsetWidth / 2 - strip.clientWidth / 2;
|
|
423
|
+
const target = Math.max(0, Math.min(centered, strip.scrollWidth - strip.clientWidth));
|
|
424
|
+
if (Math.round(target) !== Math.round(strip.scrollLeft)) {
|
|
425
|
+
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
426
|
+
strip.scrollTo({ left: target, behavior: smooth && !reduce ? 'smooth' : 'auto' });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
633
429
|
function render(el) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
updateOverflow(el);
|
|
740
|
-
});
|
|
741
|
-
// Setup event listeners
|
|
742
|
-
setupEventListeners(el, shadowRoot, tabs);
|
|
743
|
-
// Update ARIA and data-active attributes on initial render
|
|
744
|
-
updateAriaAttributes(el, shadowRoot, activeId || '');
|
|
745
|
-
// Setup ResizeObserver for responsive width
|
|
746
|
-
setupResizeObserver(el);
|
|
747
|
-
// Update tab panel states
|
|
748
|
-
if (activeId) {
|
|
749
|
-
updatePanelInteraction(el, activeId);
|
|
750
|
-
}
|
|
751
|
-
// Listen for marker slot changes to hide default marker
|
|
752
|
-
const markerSlot = shadowRoot.querySelector('slot[name="marker"]');
|
|
753
|
-
if (markerSlot) {
|
|
754
|
-
const updateDefaultMarkerVisibility = () => {
|
|
755
|
-
const defaultMarker = shadowRoot.querySelector('.default-marker');
|
|
756
|
-
if (defaultMarker) {
|
|
757
|
-
const hasCustom = markerSlot.assignedElements().length > 0;
|
|
758
|
-
defaultMarker.style.display = hasCustom ? 'none' : '';
|
|
759
|
-
}
|
|
760
|
-
};
|
|
761
|
-
markerSlot.addEventListener('slotchange', updateDefaultMarkerVisibility);
|
|
762
|
-
// Check immediately in case content is already slotted
|
|
763
|
-
updateDefaultMarkerVisibility();
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Cleanup when tabs component is disconnected
|
|
769
|
-
*/
|
|
430
|
+
const shadowRoot = el.shadowRoot;
|
|
431
|
+
if (!shadowRoot)
|
|
432
|
+
return;
|
|
433
|
+
const { width, height, placement, fixed } = getTabsAttributes(el);
|
|
434
|
+
const tabs = getChildTabs(el);
|
|
435
|
+
const activeId = getActiveTabId(el, tabs);
|
|
436
|
+
const activeIndex = activeId ? (findTabIndex(tabs, activeId) ?? 0) : 0;
|
|
437
|
+
tabs.forEach((tab) => {
|
|
438
|
+
const tabId = getTabId(tab);
|
|
439
|
+
if (!tabId)
|
|
440
|
+
return;
|
|
441
|
+
tab.setAttribute('role', 'tabpanel');
|
|
442
|
+
tab.setAttribute('aria-labelledby', `tab-${tabId}`);
|
|
443
|
+
tab.setAttribute('tabindex', '0');
|
|
444
|
+
});
|
|
445
|
+
const existingContainer = shadowRoot.querySelector('.tabs-container');
|
|
446
|
+
const existingButtons = shadowRoot.querySelector('.tab-buttons');
|
|
447
|
+
const existingStrip = shadowRoot.querySelector('.tab-strip');
|
|
448
|
+
const existingViewport = shadowRoot.querySelector('.panels-viewport');
|
|
449
|
+
ensureStyles(shadowRoot, { css: tabsStyles, id: 'ty-tabs' });
|
|
450
|
+
el.style.setProperty('--tabs-width', width.includes('%') ? '100%' : width);
|
|
451
|
+
el.style.setProperty('--tabs-height', height);
|
|
452
|
+
el.style.setProperty('--active-index', String(activeIndex));
|
|
453
|
+
if (existingContainer && existingButtons && existingStrip && existingViewport) {
|
|
454
|
+
existingContainer.setAttribute('data-placement', placement);
|
|
455
|
+
existingContainer.toggleAttribute('data-fixed', fixed);
|
|
456
|
+
const savedScrollLeft = existingStrip.scrollLeft;
|
|
457
|
+
existingButtons.querySelector('.tab-overflow-trigger')?.remove();
|
|
458
|
+
const allButtons = existingStrip.querySelectorAll('.tab-button');
|
|
459
|
+
allButtons.forEach(button => button.remove());
|
|
460
|
+
const buttonsHtml = renderButtonsOnly(el, tabs, activeId);
|
|
461
|
+
const tempDiv = document.createElement('div');
|
|
462
|
+
tempDiv.innerHTML = buttonsHtml;
|
|
463
|
+
Array.from(tempDiv.children).forEach(button => {
|
|
464
|
+
existingStrip.appendChild(button);
|
|
465
|
+
});
|
|
466
|
+
existingStrip.scrollLeft = savedScrollLeft;
|
|
467
|
+
setupEventListeners(el, shadowRoot, tabs);
|
|
468
|
+
updateAriaAttributes(el, shadowRoot, activeId || '');
|
|
469
|
+
requestAnimationFrame(() => {
|
|
470
|
+
const buttons = shadowRoot.querySelector('.tab-buttons');
|
|
471
|
+
if (buttons) {
|
|
472
|
+
const buttonsHeight = buttons.offsetHeight;
|
|
473
|
+
el.style.setProperty('--buttons-height', `${buttonsHeight}px`);
|
|
474
|
+
}
|
|
475
|
+
const current = currentActive(el);
|
|
476
|
+
updateTransform(el, current.index);
|
|
477
|
+
updateOverflow(el);
|
|
478
|
+
if (current.id) {
|
|
479
|
+
updateMarker(el, current.id);
|
|
480
|
+
scrollActiveIntoView(el, current.id, true);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
if (activeId) {
|
|
484
|
+
updatePanelInteraction(el, activeId);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
pendingInitialSnap.add(el);
|
|
489
|
+
shadowRoot.innerHTML = `
|
|
490
|
+
<div class="tabs-container" data-placement="${placement}"${fixed ? ' data-fixed' : ''}>
|
|
491
|
+
${renderTabButtons(el, tabs, activeId)}
|
|
492
|
+
<div class="panels-viewport" part="panels-container">
|
|
493
|
+
<div class="panels-wrapper">
|
|
494
|
+
<slot></slot>
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
`;
|
|
499
|
+
requestAnimationFrame(() => {
|
|
500
|
+
const buttons = shadowRoot.querySelector('.tab-buttons');
|
|
501
|
+
if (buttons) {
|
|
502
|
+
const buttonsHeight = buttons.offsetHeight;
|
|
503
|
+
el.style.setProperty('--buttons-height', `${buttonsHeight}px`);
|
|
504
|
+
}
|
|
505
|
+
const current = currentActive(el);
|
|
506
|
+
updateTransform(el, current.index);
|
|
507
|
+
updateOverflow(el);
|
|
508
|
+
if (current.id) {
|
|
509
|
+
updateMarker(el, current.id);
|
|
510
|
+
scrollActiveIntoView(el, current.id, false);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
setupEventListeners(el, shadowRoot, tabs);
|
|
514
|
+
updateAriaAttributes(el, shadowRoot, activeId || '');
|
|
515
|
+
setupResizeObserver(el);
|
|
516
|
+
const strip = shadowRoot.querySelector('.tab-strip');
|
|
517
|
+
strip?.addEventListener('scroll', () => updateEdgeFades(strip), { passive: true });
|
|
518
|
+
if (activeId) {
|
|
519
|
+
updatePanelInteraction(el, activeId);
|
|
520
|
+
}
|
|
521
|
+
const markerSlot = shadowRoot.querySelector('slot[name="marker"]');
|
|
522
|
+
if (markerSlot) {
|
|
523
|
+
const updateDefaultMarkerVisibility = () => {
|
|
524
|
+
const defaultMarker = shadowRoot.querySelector('.default-marker');
|
|
525
|
+
if (defaultMarker) {
|
|
526
|
+
const hasCustom = markerSlot.assignedElements().length > 0;
|
|
527
|
+
defaultMarker.style.display = hasCustom ? 'none' : '';
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
markerSlot.addEventListener('slotchange', updateDefaultMarkerVisibility);
|
|
531
|
+
updateDefaultMarkerVisibility();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
770
535
|
function cleanup(el) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
// ============================================================================
|
|
775
|
-
// Component Definition
|
|
776
|
-
// ============================================================================
|
|
777
|
-
/**
|
|
778
|
-
* TyTabs Web Component
|
|
779
|
-
*/
|
|
536
|
+
cleanupEventListeners(el);
|
|
537
|
+
cleanupResizeObserver(el);
|
|
538
|
+
}
|
|
780
539
|
export class TyTabs extends HTMLElement {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
|
|
540
|
+
static get observedAttributes() {
|
|
541
|
+
return ['width', 'height', 'active', 'placement', 'fixed'];
|
|
542
|
+
}
|
|
543
|
+
constructor() {
|
|
544
|
+
super();
|
|
545
|
+
this._childObserver = null;
|
|
546
|
+
this._childRenderQueued = false;
|
|
547
|
+
this.attachShadow({ mode: 'open' });
|
|
548
|
+
}
|
|
549
|
+
connectedCallback() {
|
|
550
|
+
render(this);
|
|
551
|
+
this._childObserver = new MutationObserver(() => {
|
|
552
|
+
if (this._childRenderQueued)
|
|
553
|
+
return;
|
|
554
|
+
this._childRenderQueued = true;
|
|
555
|
+
queueMicrotask(() => {
|
|
556
|
+
this._childRenderQueued = false;
|
|
557
|
+
if (this.isConnected)
|
|
558
|
+
render(this);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
this._childObserver.observe(this, { childList: true });
|
|
562
|
+
}
|
|
563
|
+
disconnectedCallback() {
|
|
564
|
+
this._childObserver?.disconnect();
|
|
565
|
+
this._childObserver = null;
|
|
566
|
+
cleanup(this);
|
|
567
|
+
}
|
|
568
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
569
|
+
if (name === 'active') {
|
|
570
|
+
if (!newValue)
|
|
571
|
+
return;
|
|
572
|
+
if (!this.shadowRoot?.querySelector(`[data-tab-id='${newValue}']`)) {
|
|
573
|
+
render(this);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
updateActiveTabState(this, newValue, oldValue);
|
|
577
|
+
scrollActiveIntoView(this, newValue, true);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
render(this);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
815
584
|
if (!customElements.get('ty-tabs')) {
|
|
816
|
-
|
|
585
|
+
customElements.define('ty-tabs', TyTabs);
|
|
817
586
|
}
|
|
818
|
-
//# sourceMappingURL=tabs.js.map
|