@sekiui/elements 0.0.55 → 0.0.57
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/dist/cdn/index.js +1053 -91
- package/dist/cdn/p-BJCq8m2o.js +138 -0
- package/dist/cdn/p-BfRJQMIU.js +111 -0
- package/dist/cdn/{p-bMBhrs0a.js → p-Bp7tjKwQ.js} +427 -7
- package/dist/cdn/seki-button.js +1 -1
- package/dist/cdn/seki-card-action.d.ts +11 -0
- package/dist/cdn/seki-card-action.js +38 -0
- package/dist/cdn/seki-card-content.d.ts +11 -0
- package/dist/cdn/seki-card-content.js +38 -0
- package/dist/cdn/seki-card-description.d.ts +11 -0
- package/dist/cdn/seki-card-description.js +38 -0
- package/dist/cdn/seki-card-footer.d.ts +11 -0
- package/dist/cdn/seki-card-footer.js +44 -0
- package/dist/cdn/seki-card-header.d.ts +11 -0
- package/dist/cdn/seki-card-header.js +38 -0
- package/dist/cdn/seki-card-title.d.ts +11 -0
- package/dist/cdn/seki-card-title.js +59 -0
- package/dist/cdn/seki-card.d.ts +11 -0
- package/dist/cdn/seki-card.js +39 -0
- package/dist/cdn/seki-field-description.js +2 -2
- package/dist/cdn/seki-field-error.js +2 -2
- package/dist/cdn/seki-field-group.js +2 -2
- package/dist/cdn/seki-field-label.js +2 -2
- package/dist/cdn/seki-field-legend.js +2 -2
- package/dist/cdn/seki-field.js +2 -2
- package/dist/cdn/seki-fieldset.js +2 -2
- package/dist/cdn/seki-input.js +2 -2
- package/dist/cdn/seki-select-content.js +1 -1
- package/dist/cdn/seki-select-group.js +2 -2
- package/dist/cdn/seki-select-option.js +2 -2
- package/dist/cdn/seki-select-trigger.js +1 -1
- package/dist/cdn/seki-select.js +2 -2
- package/dist/cdn/seki-sidebar-content.d.ts +11 -0
- package/dist/cdn/seki-sidebar-content.js +38 -0
- package/dist/cdn/seki-sidebar-footer.d.ts +11 -0
- package/dist/cdn/seki-sidebar-footer.js +38 -0
- package/dist/cdn/seki-sidebar-group.d.ts +11 -0
- package/dist/cdn/seki-sidebar-group.js +131 -0
- package/dist/cdn/seki-sidebar-header.d.ts +11 -0
- package/dist/cdn/seki-sidebar-header.js +38 -0
- package/dist/cdn/seki-sidebar-menu-item.d.ts +11 -0
- package/dist/cdn/seki-sidebar-menu-item.js +200 -0
- package/dist/cdn/seki-sidebar-menu-sub.d.ts +11 -0
- package/dist/cdn/seki-sidebar-menu-sub.js +123 -0
- package/dist/cdn/seki-sidebar-menu.d.ts +11 -0
- package/dist/cdn/seki-sidebar-menu.js +38 -0
- package/dist/cdn/seki-sidebar-trigger.d.ts +11 -0
- package/dist/cdn/seki-sidebar-trigger.js +109 -0
- package/dist/cdn/seki-sidebar.d.ts +11 -0
- package/dist/cdn/seki-sidebar.js +380 -0
- package/dist/cdn/seki-skeleton.js +2 -2
- package/dist/cdn/seki-switch.js +1 -1
- package/dist/cdn/seki-tooltip.js +1 -1
- package/dist/cjs/{index-Dd6_-KaR.js → index-tQYksITZ.js} +426 -6
- package/dist/cjs/index.cjs.js +1115 -63
- package/dist/cjs/keyboard-Cjl5HYES.js +142 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/seki-button.cjs.entry.js +1 -1
- package/dist/cjs/seki-card-action.cjs.entry.js +17 -0
- package/dist/cjs/seki-card-content.cjs.entry.js +17 -0
- package/dist/cjs/seki-card-description.cjs.entry.js +17 -0
- package/dist/cjs/seki-card-footer.cjs.entry.js +22 -0
- package/dist/cjs/seki-card-header.cjs.entry.js +17 -0
- package/dist/cjs/seki-card-title.cjs.entry.js +37 -0
- package/dist/cjs/seki-card.cjs.entry.js +17 -0
- package/dist/cjs/seki-field-description.cjs.entry.js +2 -2
- package/dist/cjs/seki-field-error.cjs.entry.js +2 -2
- package/dist/cjs/seki-field-group.cjs.entry.js +2 -2
- package/dist/cjs/seki-field-label.cjs.entry.js +2 -2
- package/dist/cjs/seki-field-legend.cjs.entry.js +2 -2
- package/dist/cjs/seki-field.cjs.entry.js +2 -2
- package/dist/cjs/seki-fieldset.cjs.entry.js +2 -2
- package/dist/cjs/seki-input.cjs.entry.js +2 -2
- package/dist/cjs/seki-select-content.cjs.entry.js +1 -1
- package/dist/cjs/seki-select-group.cjs.entry.js +2 -2
- package/dist/cjs/seki-select-option.cjs.entry.js +2 -2
- package/dist/cjs/seki-select-trigger.cjs.entry.js +1 -1
- package/dist/cjs/seki-select.cjs.entry.js +2 -2
- package/dist/cjs/seki-sidebar-content.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-footer.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-group.cjs.entry.js +105 -0
- package/dist/cjs/seki-sidebar-header.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-menu-item.cjs.entry.js +174 -0
- package/dist/cjs/seki-sidebar-menu-sub.cjs.entry.js +99 -0
- package/dist/cjs/seki-sidebar-menu.cjs.entry.js +20 -0
- package/dist/cjs/seki-sidebar-trigger.cjs.entry.js +86 -0
- package/dist/cjs/seki-sidebar.cjs.entry.js +342 -0
- package/dist/cjs/seki-skeleton.cjs.entry.js +2 -2
- package/dist/cjs/seki-switch.cjs.entry.js +81 -3
- package/dist/cjs/seki-tooltip.cjs.entry.js +1 -1
- package/dist/cjs/sekiui.cjs.js +2 -2
- package/dist/collection/collection-manifest.json +16 -0
- package/dist/collection/components/card/seki-card-action.js +50 -0
- package/dist/collection/components/card/seki-card-content.js +51 -0
- package/dist/collection/components/card/seki-card-description.js +48 -0
- package/dist/collection/components/card/seki-card-footer.js +83 -0
- package/dist/collection/components/card/seki-card-header.js +54 -0
- package/dist/collection/components/card/seki-card-title.js +95 -0
- package/dist/collection/components/card/seki-card.a11y.js +363 -0
- package/dist/collection/components/card/seki-card.css +608 -0
- package/dist/collection/components/card/seki-card.js +72 -0
- package/dist/collection/components/field/seki-field.js +1 -1
- package/dist/collection/components/field-description/seki-field-description.js +1 -1
- package/dist/collection/components/field-error/seki-field-error.js +1 -1
- package/dist/collection/components/field-group/seki-field-group.js +1 -1
- package/dist/collection/components/field-label/seki-field-label.js +1 -1
- package/dist/collection/components/field-legend/seki-field-legend.js +1 -1
- package/dist/collection/components/fieldset/seki-fieldset.js +1 -1
- package/dist/collection/components/input/seki-input.js +1 -1
- package/dist/collection/components/select/seki-select.js +1 -1
- package/dist/collection/components/select-group/seki-select-group.js +1 -1
- package/dist/collection/components/select-option/seki-select-option.js +1 -1
- package/dist/collection/components/sidebar/seki-sidebar-content.css +82 -0
- package/dist/collection/components/sidebar/seki-sidebar-content.js +33 -0
- package/dist/collection/components/sidebar/seki-sidebar-footer.css +44 -0
- package/dist/collection/components/sidebar/seki-sidebar-footer.js +31 -0
- package/dist/collection/components/sidebar/seki-sidebar-group.css +158 -0
- package/dist/collection/components/sidebar/seki-sidebar-group.js +300 -0
- package/dist/collection/components/sidebar/seki-sidebar-header.css +44 -0
- package/dist/collection/components/sidebar/seki-sidebar-header.js +32 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-item.css +196 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-item.js +403 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-sub.css +357 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu-sub.js +256 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu.css +25 -0
- package/dist/collection/components/sidebar/seki-sidebar-menu.js +32 -0
- package/dist/collection/components/sidebar/seki-sidebar-trigger.css +68 -0
- package/dist/collection/components/sidebar/seki-sidebar-trigger.js +175 -0
- package/dist/collection/components/sidebar/seki-sidebar.css +352 -0
- package/dist/collection/components/sidebar/seki-sidebar.js +812 -0
- package/dist/collection/components/sidebar/types.js +18 -0
- package/dist/collection/components/skeleton/seki-skeleton.js +1 -1
- package/dist/collection/index.js +7 -0
- package/dist/collection/services/focus.js +192 -0
- package/dist/collection/services/index.js +7 -0
- package/dist/collection/services/keyboard.js +136 -0
- package/dist/collection/services/media-query.js +254 -0
- package/dist/collection/types.js +41 -0
- package/dist/collection/utils/a11y.js +291 -0
- package/dist/collection/utils/common.js +286 -0
- package/dist/components/index.js +1053 -91
- package/dist/components/p-BJCq8m2o.js +138 -0
- package/dist/components/{p-QhPshhKB.js → p-BzYKy7d3.js} +427 -7
- package/dist/components/p-DwTISp-i.js +111 -0
- package/dist/components/seki-button.js +1 -1
- package/dist/components/seki-card-action.d.ts +11 -0
- package/dist/components/seki-card-action.js +38 -0
- package/dist/components/seki-card-content.d.ts +11 -0
- package/dist/components/seki-card-content.js +38 -0
- package/dist/components/seki-card-description.d.ts +11 -0
- package/dist/components/seki-card-description.js +38 -0
- package/dist/components/seki-card-footer.d.ts +11 -0
- package/dist/components/seki-card-footer.js +44 -0
- package/dist/components/seki-card-header.d.ts +11 -0
- package/dist/components/seki-card-header.js +38 -0
- package/dist/components/seki-card-title.d.ts +11 -0
- package/dist/components/seki-card-title.js +59 -0
- package/dist/components/seki-card.d.ts +11 -0
- package/dist/components/seki-card.js +39 -0
- package/dist/components/seki-field-description.js +2 -2
- package/dist/components/seki-field-error.js +2 -2
- package/dist/components/seki-field-group.js +2 -2
- package/dist/components/seki-field-label.js +2 -2
- package/dist/components/seki-field-legend.js +2 -2
- package/dist/components/seki-field.js +2 -2
- package/dist/components/seki-fieldset.js +2 -2
- package/dist/components/seki-input.js +2 -2
- package/dist/components/seki-select-content.js +1 -1
- package/dist/components/seki-select-group.js +2 -2
- package/dist/components/seki-select-option.js +2 -2
- package/dist/components/seki-select-trigger.js +1 -1
- package/dist/components/seki-select.js +2 -2
- package/dist/components/seki-sidebar-content.d.ts +11 -0
- package/dist/components/seki-sidebar-content.js +38 -0
- package/dist/components/seki-sidebar-footer.d.ts +11 -0
- package/dist/components/seki-sidebar-footer.js +38 -0
- package/dist/components/seki-sidebar-group.d.ts +11 -0
- package/dist/components/seki-sidebar-group.js +131 -0
- package/dist/components/seki-sidebar-header.d.ts +11 -0
- package/dist/components/seki-sidebar-header.js +38 -0
- package/dist/components/seki-sidebar-menu-item.d.ts +11 -0
- package/dist/components/seki-sidebar-menu-item.js +200 -0
- package/dist/components/seki-sidebar-menu-sub.d.ts +11 -0
- package/dist/components/seki-sidebar-menu-sub.js +123 -0
- package/dist/components/seki-sidebar-menu.d.ts +11 -0
- package/dist/components/seki-sidebar-menu.js +38 -0
- package/dist/components/seki-sidebar-trigger.d.ts +11 -0
- package/dist/components/seki-sidebar-trigger.js +109 -0
- package/dist/components/seki-sidebar.d.ts +11 -0
- package/dist/components/seki-sidebar.js +380 -0
- package/dist/components/seki-skeleton.js +2 -2
- package/dist/components/seki-switch.js +1 -1
- package/dist/components/seki-tooltip.js +1 -1
- package/dist/esm/{index-CuXbV_yz.js → index-Dfzpqq0a.js} +426 -6
- package/dist/esm/index.js +1053 -63
- package/dist/esm/keyboard-BJCq8m2o.js +138 -0
- package/dist/esm/loader.js +3 -3
- package/dist/esm/seki-button.entry.js +1 -1
- package/dist/esm/seki-card-action.entry.js +15 -0
- package/dist/esm/seki-card-content.entry.js +15 -0
- package/dist/esm/seki-card-description.entry.js +15 -0
- package/dist/esm/seki-card-footer.entry.js +20 -0
- package/dist/esm/seki-card-header.entry.js +15 -0
- package/dist/esm/seki-card-title.entry.js +35 -0
- package/dist/esm/seki-card.entry.js +15 -0
- package/dist/esm/seki-field-description.entry.js +2 -2
- package/dist/esm/seki-field-error.entry.js +2 -2
- package/dist/esm/seki-field-group.entry.js +2 -2
- package/dist/esm/seki-field-label.entry.js +2 -2
- package/dist/esm/seki-field-legend.entry.js +2 -2
- package/dist/esm/seki-field.entry.js +2 -2
- package/dist/esm/seki-fieldset.entry.js +2 -2
- package/dist/esm/seki-input.entry.js +2 -2
- package/dist/esm/seki-select-content.entry.js +1 -1
- package/dist/esm/seki-select-group.entry.js +2 -2
- package/dist/esm/seki-select-option.entry.js +2 -2
- package/dist/esm/seki-select-trigger.entry.js +1 -1
- package/dist/esm/seki-select.entry.js +2 -2
- package/dist/esm/seki-sidebar-content.entry.js +18 -0
- package/dist/esm/seki-sidebar-footer.entry.js +18 -0
- package/dist/esm/seki-sidebar-group.entry.js +103 -0
- package/dist/esm/seki-sidebar-header.entry.js +18 -0
- package/dist/esm/seki-sidebar-menu-item.entry.js +172 -0
- package/dist/esm/seki-sidebar-menu-sub.entry.js +97 -0
- package/dist/esm/seki-sidebar-menu.entry.js +18 -0
- package/dist/esm/seki-sidebar-trigger.entry.js +84 -0
- package/dist/esm/seki-sidebar.entry.js +340 -0
- package/dist/esm/seki-skeleton.entry.js +2 -2
- package/dist/esm/seki-switch.entry.js +84 -2
- package/dist/esm/seki-tooltip.entry.js +1 -1
- package/dist/esm/sekiui.js +3 -3
- package/dist/sekiui/index.esm.js +1 -1
- package/dist/sekiui/p-01cfb4e7.entry.js +1 -0
- package/dist/sekiui/{p-431f46a1.entry.js → p-042ec460.entry.js} +1 -1
- package/dist/sekiui/{p-a36f1ac4.entry.js → p-10c008fc.entry.js} +1 -1
- package/dist/sekiui/p-352bd295.entry.js +1 -0
- package/dist/sekiui/p-37c5f4d9.entry.js +1 -0
- package/dist/sekiui/p-40fb71d6.entry.js +1 -0
- package/dist/sekiui/{p-eefbc037.entry.js → p-44191aed.entry.js} +1 -1
- package/dist/sekiui/{p-81709fc2.entry.js → p-4423d621.entry.js} +1 -1
- package/dist/sekiui/{p-e679d501.entry.js → p-4636588f.entry.js} +1 -1
- package/dist/sekiui/p-471b97a5.entry.js +1 -0
- package/dist/sekiui/{p-de4735fb.entry.js → p-56f0d754.entry.js} +1 -1
- package/dist/sekiui/p-5bc0f5aa.entry.js +1 -0
- package/dist/sekiui/p-6164cd8a.entry.js +1 -0
- package/dist/sekiui/{p-0fba4e2d.entry.js → p-635f4098.entry.js} +1 -1
- package/dist/sekiui/{p-434be19f.entry.js → p-743fc6d9.entry.js} +1 -1
- package/dist/sekiui/p-83e65cbe.entry.js +1 -0
- package/dist/sekiui/p-8b7bd061.entry.js +1 -0
- package/dist/sekiui/p-8d9a4878.entry.js +1 -0
- package/dist/sekiui/p-9cb9cdfe.entry.js +1 -0
- package/dist/sekiui/p-9f2d95d7.entry.js +1 -0
- package/dist/sekiui/p-BJCq8m2o.js +1 -0
- package/dist/sekiui/p-Dfzpqq0a.js +2 -0
- package/dist/sekiui/p-a1a71958.entry.js +1 -0
- package/dist/sekiui/{p-bf942ad5.entry.js → p-a71e0c55.entry.js} +1 -1
- package/dist/sekiui/{p-6ff91f7f.entry.js → p-ae227955.entry.js} +1 -1
- package/dist/sekiui/{p-80a41058.entry.js → p-b365f5fb.entry.js} +1 -1
- package/dist/sekiui/{p-6bde807e.entry.js → p-b387a2a5.entry.js} +1 -1
- package/dist/sekiui/p-b8590f4d.entry.js +1 -0
- package/dist/sekiui/p-c98b6d6a.entry.js +1 -0
- package/dist/sekiui/{p-7c2245be.entry.js → p-d73cdb9a.entry.js} +1 -1
- package/dist/sekiui/{p-a56602b3.entry.js → p-d96e770e.entry.js} +1 -1
- package/dist/sekiui/p-e62dd89b.entry.js +1 -0
- package/dist/sekiui/{p-386c00ac.entry.js → p-e7bb140c.entry.js} +1 -1
- package/dist/sekiui/{p-6e238adf.entry.js → p-eecc18f3.entry.js} +1 -1
- package/dist/sekiui/p-ff636955.entry.js +1 -0
- package/dist/sekiui/sekiui.esm.js +1 -1
- package/dist/types/components/card/seki-card-action.d.ts +16 -0
- package/dist/types/components/card/seki-card-content.d.ts +17 -0
- package/dist/types/components/card/seki-card-description.d.ts +14 -0
- package/dist/types/components/card/seki-card-footer.d.ts +24 -0
- package/dist/types/components/card/seki-card-header.d.ts +20 -0
- package/dist/types/components/card/seki-card-title.d.ts +20 -0
- package/dist/types/components/card/seki-card.a11y.d.ts +1 -0
- package/dist/types/components/card/seki-card.d.ts +23 -0
- package/dist/types/components/sidebar/seki-sidebar-content.d.ts +18 -0
- package/dist/types/components/sidebar/seki-sidebar-footer.d.ts +16 -0
- package/dist/types/components/sidebar/seki-sidebar-group.d.ts +81 -0
- package/dist/types/components/sidebar/seki-sidebar-header.d.ts +17 -0
- package/dist/types/components/sidebar/seki-sidebar-menu-item.d.ts +104 -0
- package/dist/types/components/sidebar/seki-sidebar-menu-sub.d.ts +81 -0
- package/dist/types/components/sidebar/seki-sidebar-menu.d.ts +17 -0
- package/dist/types/components/sidebar/seki-sidebar-trigger.d.ts +53 -0
- package/dist/types/components/sidebar/seki-sidebar.d.ts +185 -0
- package/dist/types/components/sidebar/types.d.ts +245 -0
- package/dist/types/components.d.ts +899 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/services/focus.d.ts +74 -0
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/services/keyboard.d.ts +74 -0
- package/dist/types/services/media-query.d.ts +121 -0
- package/dist/types/types.d.ts +105 -0
- package/dist/types/utils/a11y.d.ts +130 -0
- package/dist/types/utils/common.d.ts +142 -0
- package/package.json +2 -1
- package/dist/sekiui/p-9fe07f6e.entry.js +0 -1
- package/dist/sekiui/p-CuXbV_yz.js +0 -2
package/dist/cdn/index.js
CHANGED
|
@@ -1,112 +1,1074 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
export { g as getAssetPath, r as render, s as setAssetPath, a as setNonce, b as setPlatformOptions } from './p-Bp7tjKwQ.js';
|
|
2
|
+
export { S as SekiSwitch } from './p-BfRJQMIU.js';
|
|
3
|
+
export { K as KeyboardService, c as createKeyboardService, g as getKeyboardService } from './p-BJCq8m2o.js';
|
|
3
4
|
|
|
4
|
-
const sekiSwitchCss = ":host{--seki-neutral-50:hsl(0, 0%, 98%);--seki-neutral-100:hsl(0, 0%, 96.1%);--seki-neutral-200:hsl(0, 0%, 89.8%);--seki-neutral-300:hsl(0, 0%, 83.1%);--seki-neutral-400:hsl(0, 0%, 63.9%);--seki-neutral-500:hsl(0, 0%, 45.1%);--seki-neutral-600:hsl(0, 0%, 32.2%);--seki-neutral-700:hsl(0, 0%, 25.1%);--seki-neutral-800:hsl(0, 0%, 14.9%);--seki-neutral-900:hsl(0, 0%, 9%);--seki-neutral-950:hsl(0, 0%, 3.9%);--seki-primary-50:hsl(210, 100%, 97%);--seki-primary-100:hsl(210, 95%, 93%);--seki-primary-200:hsl(210, 95%, 85%);--seki-primary-300:hsl(210, 90%, 73%);--seki-primary-400:hsl(210, 85%, 58%);--seki-primary-500:hsl(210, 80%, 48%);--seki-primary-600:hsl(210, 75%, 40%);--seki-primary-700:hsl(210, 70%, 32%);--seki-primary-800:hsl(210, 65%, 25%);--seki-primary-900:hsl(210, 60%, 18%);--seki-primary-950:hsl(210, 55%, 10%);--seki-success-500:hsl(142, 71%, 45%);--seki-success-600:hsl(142, 71%, 35%);--seki-warning-500:hsl(38, 92%, 50%);--seki-warning-600:hsl(38, 92%, 40%);--seki-destructive-500:hsl(0, 84.2%, 60.2%);--seki-destructive-600:hsl(0, 72%, 41%)}:host{--seki-background:hsl(0, 0%, 100%);--seki-foreground:hsl(0, 0%, 3.9%);--seki-muted:hsl(0, 0%, 96.1%);--seki-muted-foreground:hsl(0, 0%, 45.1%);--seki-card:hsl(0, 0%, 100%);--seki-card-foreground:hsl(0, 0%, 3.9%);--seki-popover:hsl(0, 0%, 100%);--seki-popover-foreground:hsl(0, 0%, 3.9%);--seki-border:hsl(0, 0%, 89.8%);--seki-input:hsl(0, 0%, 89.8%);--seki-primary:hsl(0, 0%, 9%);--seki-primary-foreground:hsl(0, 0%, 98%);--seki-secondary:hsl(0, 0%, 96.1%);--seki-secondary-foreground:hsl(0, 0%, 9%);--seki-accent:hsl(0, 0%, 96.1%);--seki-accent-foreground:hsl(0, 0%, 9%);--seki-destructive:hsl(0, 84.2%, 60.2%);--seki-destructive-foreground:hsl(0, 0%, 98%);--seki-ring:hsl(0, 0%, 3.9%);--seki-bg-primary:var(--seki-background);--seki-bg-secondary:var(--seki-muted);--seki-bg-tertiary:var(--seki-accent);--seki-text-primary:var(--seki-foreground);--seki-text-secondary:var(--seki-muted-foreground);--seki-text-tertiary:var(--seki-neutral-400);--seki-border-default:var(--seki-border);--seki-border-emphasis:var(--seki-neutral-300)}:host([data-theme='dark']){--seki-background:hsl(0, 0%, 3.9%);--seki-foreground:hsl(0, 0%, 98%);--seki-muted:hsl(0, 0%, 14.9%);--seki-muted-foreground:hsl(0, 0%, 63.9%);--seki-card:hsl(0, 0%, 3.9%);--seki-card-foreground:hsl(0, 0%, 98%);--seki-popover:hsl(0, 0%, 3.9%);--seki-popover-foreground:hsl(0, 0%, 98%);--seki-border:hsl(0, 0%, 14.9%);--seki-input:hsl(0, 0%, 14.9%);--seki-primary:hsl(0, 0%, 98%);--seki-primary-foreground:hsl(0, 0%, 9%);--seki-secondary:hsl(0, 0%, 14.9%);--seki-secondary-foreground:hsl(0, 0%, 98%);--seki-accent:hsl(0, 0%, 14.9%);--seki-accent-foreground:hsl(0, 0%, 98%);--seki-destructive:hsl(0, 62.8%, 30.6%);--seki-destructive-foreground:hsl(0, 0%, 98%);--seki-ring:hsl(0, 0%, 83.1%);--seki-bg-primary:var(--seki-background);--seki-bg-secondary:var(--seki-muted);--seki-bg-tertiary:var(--seki-accent);--seki-text-primary:var(--seki-foreground);--seki-text-secondary:var(--seki-muted-foreground);--seki-text-tertiary:var(--seki-neutral-600);--seki-border-default:var(--seki-border);--seki-border-emphasis:var(--seki-neutral-700);--seki-button-primary-hover-bg:hsl(0, 0%, 98%, 0.9);--seki-button-secondary-hover-bg:hsl(0, 0%, 14.9%, 0.8)}:host{--seki-button-primary-bg:var(--seki-primary);--seki-button-primary-text:var(--seki-primary-foreground);--seki-button-primary-border:transparent;--seki-button-primary-hover-bg:hsl(0, 0%, 9%, 0.9);--seki-button-primary-hover-text:var(--seki-primary-foreground);--seki-button-primary-hover-border:transparent;--seki-button-secondary-bg:var(--seki-secondary);--seki-button-secondary-text:var(--seki-secondary-foreground);--seki-button-secondary-border:transparent;--seki-button-secondary-hover-bg:hsl(0, 0%, 96.1%, 0.8);--seki-button-secondary-hover-text:var(--seki-secondary-foreground);--seki-button-secondary-hover-border:transparent;--seki-button-outline-bg:transparent;--seki-button-outline-text:var(--seki-foreground);--seki-button-outline-border:var(--seki-input);--seki-button-outline-hover-bg:var(--seki-accent);--seki-button-outline-hover-text:var(--seki-accent-foreground);--seki-button-outline-hover-border:var(--seki-input);--seki-button-ghost-bg:transparent;--seki-button-ghost-text:var(--seki-foreground);--seki-button-ghost-border:transparent;--seki-button-ghost-hover-bg:var(--seki-accent);--seki-button-ghost-hover-text:var(--seki-accent-foreground);--seki-button-ghost-hover-border:transparent;--seki-button-destructive-bg:var(--seki-destructive);--seki-button-destructive-text:var(--seki-destructive-foreground);--seki-button-destructive-border:transparent;--seki-button-destructive-hover-bg:hsl(0, 84.2%, 60.2%, 0.9);--seki-button-destructive-hover-border:transparent;--seki-button-sm-padding-x:var(--seki-spacing-3, 0.75rem);--seki-button-sm-padding-y:0.375rem;--seki-button-sm-font-size:var(--seki-text-sm, 0.875rem);--seki-button-sm-height:2.25rem;--seki-button-md-padding-x:var(--seki-spacing-4, 1rem);--seki-button-md-padding-y:var(--seki-spacing-2, 0.5rem);--seki-button-md-font-size:var(--seki-text-base, 1rem);--seki-button-md-height:2.5rem;--seki-button-lg-padding-x:2rem;--seki-button-lg-padding-y:0.5rem;--seki-button-lg-font-size:1rem;--seki-button-lg-height:2.75rem;--seki-button-icon-sm-size:2rem;--seki-button-icon-size:2.5rem;--seki-button-icon-lg-size:3rem;--seki-button-icon-gap:0.5rem;--seki-button-link-bg:transparent;--seki-button-link-text:var(--seki-primary);--seki-button-link-border:transparent;--seki-button-link-hover-bg:transparent;--seki-button-link-hover-text:var(--seki-primary);--seki-button-link-hover-border:transparent;--seki-button-radius:var(--seki-radius-md, 0.375rem);--seki-button-font-weight:var(--seki-font-medium, 500);--seki-button-transition-duration:150ms;--seki-button-transition-timing:ease-in-out;--seki-button-shadow:var(--seki-shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));--seki-button-shadow-hover:var(--seki-shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1));--seki-button-disabled-opacity:0.5;--seki-input-bg:var(--seki-background);--seki-input-border:var(--seki-border);--seki-input-text:var(--seki-foreground);--seki-input-placeholder:var(--seki-muted-foreground);--seki-input-ring:var(--seki-ring);--seki-input-invalid-border:var(--seki-destructive);--seki-input-invalid-ring:var(--seki-destructive);--seki-input-disabled-opacity:0.5;--seki-input-radius:var(--seki-radius-md);--seki-input-ring-offset:2px;--seki-select-trigger-bg:var(--seki-background);--seki-select-trigger-border:1px solid var(--seki-border);--seki-select-trigger-bg-hover:var(--seki-muted);--seki-select-trigger-radius:var(--seki-radius-md);--seki-select-trigger-min-height:2.5rem;--seki-select-trigger-padding:0.5rem 0.75rem;--seki-select-trigger-gap:0.5rem;--seki-select-content-bg:var(--seki-popover);--seki-select-content-border:1px solid var(--seki-border);--seki-select-content-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1);--seki-select-content-radius:var(--seki-radius-md);--seki-select-content-padding:0.5rem 0;--seki-select-option-padding:0.5rem 0.75rem;--seki-select-option-gap:0.5rem;--seki-select-option-radius:var(--seki-radius-sm);--seki-select-option-bg-hover:var(--seki-muted);--seki-select-option-bg-selected:var(--seki-muted);--seki-select-group-label-padding:0.5rem 0.75rem;--seki-select-group-label-font-size:var(--seki-font-size-xs);--seki-select-group-label-font-weight:var(--seki-font-semibold);--seki-select-group-label-color:var(--seki-muted-foreground);--seki-select-group-separator:1px solid var(--seki-border)}:host{--seki-spacing-0:0;--seki-spacing-0-5:0.125rem;--seki-spacing-1:0.25rem;--seki-spacing-1-5:0.375rem;--seki-spacing-2:0.5rem;--seki-spacing-2-5:0.625rem;--seki-spacing-3:0.75rem;--seki-spacing-3-5:0.875rem;--seki-spacing-4:1rem;--seki-spacing-5:1.25rem;--seki-spacing-6:1.5rem;--seki-spacing-8:2rem;--seki-spacing-10:2.5rem;--seki-spacing-12:3rem;--seki-spacing-16:4rem;--seki-spacing-20:5rem;--seki-spacing-24:6rem;--seki-input-height-sm:2rem;--seki-input-height-md:2.5rem;--seki-input-height-lg:3rem;--seki-input-padding-x:0.75rem;--seki-input-padding-y:0.5rem;--seki-input-font-size:0.875rem;--seki-input-ring-offset:2px}:host{--seki-shadow-sm:0 1px 2px 0 rgb(0 0 0 / 0.05);--seki-shadow-md:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--seki-shadow-lg:0 10px 15px -3px rgb(0 0 0 / 0.15), 0 4px 6px -4px rgb(0 0 0 / 0.1)}:host{--seki-font-sans:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',\n Arial, sans-serif;--seki-font-mono:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;--seki-text-xs:0.75rem;--seki-text-sm:0.875rem;--seki-text-base:1rem;--seki-text-lg:1.125rem;--seki-text-xl:1.25rem;--seki-text-2xl:1.5rem;--seki-text-3xl:1.875rem;--seki-text-4xl:2.25rem;--seki-font-normal:400;--seki-font-medium:500;--seki-font-semibold:600;--seki-font-bold:700;--seki-leading-tight:1.25;--seki-leading-normal:1.5;--seki-leading-relaxed:1.75}:host{--seki-radius-sm:0.25rem;--seki-radius-md:0.375rem;--seki-radius-lg:0.5rem;--seki-radius-xl:1rem;}:host{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px}.switch{width:var(--seki-switch-width, 2.75rem);height:var(--seki-switch-height, 1.5rem);position:relative;display:inline-flex;align-items:center;flex-shrink:0;background-color:var(--seki-switch-bg-unchecked, hsl(var(--muted, 240 4.8% 95.9%)));border-radius:9999px;cursor:pointer;transition:background-color var(--seki-switch-transition-duration, 150ms) ease-out;outline:none}.switch:focus-visible{outline:2px solid var(--seki-switch-focus-ring, hsl(var(--ring, 240 5% 64.9%)));outline-offset:2px}:host([data-state=\"checked\"]) .switch{background-color:var(--seki-switch-bg-checked, hsl(var(--primary, 240 5.9% 10%)))}:host([data-disabled]) .switch{background-color:var(--seki-switch-bg-disabled, hsl(var(--muted, 240 4.8% 95.9%) / 0.5));cursor:not-allowed;opacity:0.5}.thumb{width:var(--seki-switch-thumb-size, 1.25rem);height:var(--seki-switch-thumb-size, 1.25rem);background-color:var(--seki-switch-thumb-bg, hsl(var(--background, 0 0% 100%)));position:absolute;left:0.125rem;border-radius:50%;box-shadow:0 1px 2px 0 rgb(0 0 0 / 0.05);transition:transform var(--seki-switch-transition-duration, 150ms) ease-out;pointer-events:none}:host([data-state=\"checked\"]) .thumb{transform:translateX(calc(var(--seki-switch-width, 2.75rem) - var(--seki-switch-thumb-size, 1.25rem) - 0.25rem))}@media (prefers-color-scheme: dark){.switch{--seki-switch-bg-unchecked:hsl(var(--muted, 240 3.7% 15.9%))}.thumb{--seki-switch-thumb-bg:hsl(var(--background, 240 10% 3.9%))}}";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Focus Management Service
|
|
7
|
+
* Provides utilities for focus management, focus traps, and focus restoration
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* FocusService - Centralized focus management
|
|
11
|
+
* Provides:
|
|
12
|
+
* - Focus trap implementation
|
|
13
|
+
* - Focus restoration
|
|
14
|
+
* - Focusable element detection
|
|
15
|
+
* - Focus utilities for accessibility
|
|
16
|
+
*/
|
|
17
|
+
class FocusService {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.focusTrapStack = new Map();
|
|
20
|
+
this.previouslyFocusedElement = null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get all focusable elements within a container
|
|
24
|
+
* Includes buttons, links, inputs, textareas, selects, and elements with tabindex
|
|
25
|
+
*/
|
|
26
|
+
getFocusableElements(container = document) {
|
|
27
|
+
const focusableSelectors = [
|
|
28
|
+
'button:not([disabled])',
|
|
29
|
+
'a[href]',
|
|
30
|
+
'input:not([disabled])',
|
|
31
|
+
'textarea:not([disabled])',
|
|
32
|
+
'select:not([disabled])',
|
|
33
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
34
|
+
].join(',');
|
|
35
|
+
const elements = Array.from(container.querySelectorAll(focusableSelectors));
|
|
36
|
+
return elements.filter((el) => {
|
|
37
|
+
// Exclude hidden elements
|
|
38
|
+
const style = window.getComputedStyle(el);
|
|
39
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if an element is focusable
|
|
44
|
+
*/
|
|
45
|
+
isFocusable(element) {
|
|
46
|
+
const focusableSelectors = [
|
|
47
|
+
'button',
|
|
48
|
+
'a[href]',
|
|
49
|
+
'input',
|
|
50
|
+
'textarea',
|
|
51
|
+
'select',
|
|
52
|
+
'[tabindex]',
|
|
53
|
+
];
|
|
54
|
+
return focusableSelectors.some((selector) => element.matches(selector)) && !element.hasAttribute('disabled');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Set focus to an element with optional callbacks
|
|
58
|
+
*/
|
|
59
|
+
setFocus(element, options) {
|
|
60
|
+
var _a;
|
|
61
|
+
if (!element)
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
element.focus({ preventScroll: (_a = options === null || options === void 0 ? void 0 : options.preventScroll) !== null && _a !== void 0 ? _a : false });
|
|
65
|
+
return document.activeElement === element;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.warn('Failed to set focus:', error);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Trap focus within a container (keyboard navigation stays within bounds)
|
|
74
|
+
* @param container - The element to trap focus within
|
|
75
|
+
* @param options - Focus trap options
|
|
76
|
+
*/
|
|
77
|
+
createFocusTrap(container, options = {}) {
|
|
78
|
+
if (typeof window === 'undefined')
|
|
79
|
+
return;
|
|
80
|
+
const { initialFocus, restoreFocus = true } = options;
|
|
81
|
+
// Store the previously focused element for restoration
|
|
82
|
+
if (restoreFocus) {
|
|
83
|
+
this.previouslyFocusedElement = document.activeElement;
|
|
84
|
+
}
|
|
85
|
+
// Set initial focus
|
|
86
|
+
if (initialFocus) {
|
|
87
|
+
this.setFocus(initialFocus);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const focusableElements = this.getFocusableElements(container);
|
|
91
|
+
if (focusableElements.length > 0) {
|
|
92
|
+
this.setFocus(focusableElements[0]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Handle Tab key for focus trap
|
|
96
|
+
const handleKeyDown = (event) => {
|
|
97
|
+
if (event.key !== 'Tab')
|
|
98
|
+
return;
|
|
99
|
+
const focusableElements = this.getFocusableElements(container);
|
|
100
|
+
if (focusableElements.length === 0)
|
|
39
101
|
return;
|
|
102
|
+
const currentFocusIndex = focusableElements.indexOf(document.activeElement);
|
|
103
|
+
if (event.shiftKey) {
|
|
104
|
+
// Shift+Tab - move backwards
|
|
105
|
+
const previousIndex = currentFocusIndex - 1;
|
|
106
|
+
const targetElement = previousIndex >= 0 ? focusableElements[previousIndex] : focusableElements[focusableElements.length - 1];
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
this.setFocus(targetElement);
|
|
40
109
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
110
|
+
else {
|
|
111
|
+
// Tab - move forwards
|
|
112
|
+
const nextIndex = currentFocusIndex + 1;
|
|
113
|
+
const targetElement = nextIndex < focusableElements.length ? focusableElements[nextIndex] : focusableElements[0];
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
this.setFocus(targetElement);
|
|
45
116
|
}
|
|
46
|
-
// Always emit event (for both controlled and uncontrolled)
|
|
47
|
-
this.sekiChange.emit({ checked: newCheckedState });
|
|
48
117
|
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
118
|
+
container.addEventListener('keydown', handleKeyDown);
|
|
119
|
+
this.focusTrapStack.set(container, this.previouslyFocusedElement);
|
|
120
|
+
// Store the handler for potential later cleanup
|
|
121
|
+
// Note: Manual cleanup should be done via releaseFocusTrap()
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Release focus trap from a container
|
|
125
|
+
*/
|
|
126
|
+
releaseFocusTrap(container, restoreFocus = true) {
|
|
127
|
+
const previousElement = this.focusTrapStack.get(container);
|
|
128
|
+
this.focusTrapStack.delete(container);
|
|
129
|
+
if (restoreFocus && previousElement) {
|
|
130
|
+
this.setFocus(previousElement);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get the first focusable element in a container
|
|
135
|
+
*/
|
|
136
|
+
getFirstFocusable(container = document) {
|
|
137
|
+
const focusable = this.getFocusableElements(container);
|
|
138
|
+
return focusable.length > 0 ? focusable[0] : null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get the last focusable element in a container
|
|
142
|
+
*/
|
|
143
|
+
getLastFocusable(container = document) {
|
|
144
|
+
const focusable = this.getFocusableElements(container);
|
|
145
|
+
return focusable.length > 0 ? focusable[focusable.length - 1] : null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Move focus to the next focusable element
|
|
149
|
+
*/
|
|
150
|
+
focusNext(container = document) {
|
|
151
|
+
const focusable = this.getFocusableElements(container);
|
|
152
|
+
const currentIndex = focusable.indexOf(document.activeElement);
|
|
153
|
+
const nextIndex = (currentIndex + 1) % focusable.length;
|
|
154
|
+
if (focusable.length > 0) {
|
|
155
|
+
this.setFocus(focusable[nextIndex]);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Move focus to the previous focusable element
|
|
162
|
+
*/
|
|
163
|
+
focusPrevious(container = document) {
|
|
164
|
+
const focusable = this.getFocusableElements(container);
|
|
165
|
+
const currentIndex = focusable.indexOf(document.activeElement);
|
|
166
|
+
const previousIndex = (currentIndex - 1 + focusable.length) % focusable.length;
|
|
167
|
+
if (focusable.length > 0) {
|
|
168
|
+
this.setFocus(focusable[previousIndex]);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clear all focus traps
|
|
175
|
+
*/
|
|
176
|
+
clearFocusTraps() {
|
|
177
|
+
this.focusTrapStack.clear();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Singleton instance
|
|
181
|
+
let focusServiceInstance = null;
|
|
182
|
+
/**
|
|
183
|
+
* Get the singleton FocusService instance
|
|
184
|
+
*/
|
|
185
|
+
function getFocusService() {
|
|
186
|
+
if (!focusServiceInstance) {
|
|
187
|
+
focusServiceInstance = new FocusService();
|
|
188
|
+
}
|
|
189
|
+
return focusServiceInstance;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Create a new isolated FocusService instance
|
|
193
|
+
*/
|
|
194
|
+
function createFocusService() {
|
|
195
|
+
return new FocusService();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Media Query Service
|
|
200
|
+
* Provides utilities for responsive design and media query management
|
|
201
|
+
*/
|
|
202
|
+
/**
|
|
203
|
+
* Default breakpoints (Tailwind-inspired)
|
|
204
|
+
*/
|
|
205
|
+
const DEFAULT_BREAKPOINTS = {
|
|
206
|
+
sm: '640px',
|
|
207
|
+
md: '768px',
|
|
208
|
+
lg: '1024px',
|
|
209
|
+
xl: '1280px',
|
|
210
|
+
'2xl': '1536px',
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* MediaQueryService - Centralized media query management
|
|
214
|
+
* Provides:
|
|
215
|
+
* - Media query listener registration
|
|
216
|
+
* - Breakpoint detection
|
|
217
|
+
* - Window resize handling
|
|
218
|
+
* - Current viewport information
|
|
219
|
+
*/
|
|
220
|
+
class MediaQueryService {
|
|
221
|
+
constructor(breakpoints = DEFAULT_BREAKPOINTS) {
|
|
222
|
+
this.listeners = new Map();
|
|
223
|
+
this.resizeObserver = null;
|
|
224
|
+
this.resizeListeners = new Map();
|
|
225
|
+
this.breakpoints = breakpoints;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Register a media query listener
|
|
229
|
+
* @param id - Unique identifier for this listener
|
|
230
|
+
* @param query - CSS media query string
|
|
231
|
+
* @param handler - Callback when media query matches/unmatches
|
|
232
|
+
*/
|
|
233
|
+
registerMediaQuery(id, query, handler) {
|
|
234
|
+
if (typeof window === 'undefined')
|
|
235
|
+
return;
|
|
236
|
+
try {
|
|
237
|
+
const matcher = window.matchMedia(query);
|
|
238
|
+
const listener = { query, handler, matcher };
|
|
239
|
+
// Call handler with initial state
|
|
240
|
+
handler(matcher.matches);
|
|
241
|
+
// Listen for changes
|
|
242
|
+
matcher.addEventListener('change', (e) => handler(e.matches));
|
|
243
|
+
this.listeners.set(id, listener);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.warn(`Failed to register media query "${query}":`, error);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Register a breakpoint listener (easier than raw media queries)
|
|
251
|
+
* @param id - Unique identifier
|
|
252
|
+
* @param breakpoint - Breakpoint name (sm, md, lg, xl, 2xl)
|
|
253
|
+
* @param type - Type of match: 'min' (mobile-first) or 'max' (desktop-first)
|
|
254
|
+
* @param handler - Callback when breakpoint matches
|
|
255
|
+
*/
|
|
256
|
+
registerBreakpoint(id, breakpoint, type = 'min', handler) {
|
|
257
|
+
const size = this.breakpoints[breakpoint];
|
|
258
|
+
if (!size) {
|
|
259
|
+
console.warn(`Unknown breakpoint: ${breakpoint}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const query = type === 'min' ? `(min-width: ${size})` : `(max-width: ${size})`;
|
|
263
|
+
this.registerMediaQuery(id, query, handler);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Unregister a media query listener
|
|
267
|
+
*/
|
|
268
|
+
unregisterMediaQuery(id) {
|
|
269
|
+
const listener = this.listeners.get(id);
|
|
270
|
+
if (listener && listener.matcher) {
|
|
271
|
+
listener.matcher.removeEventListener('change', () => { });
|
|
272
|
+
}
|
|
273
|
+
this.listeners.delete(id);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Check if a media query currently matches
|
|
277
|
+
*/
|
|
278
|
+
isMatching(query) {
|
|
279
|
+
if (typeof window === 'undefined')
|
|
280
|
+
return false;
|
|
281
|
+
try {
|
|
282
|
+
return window.matchMedia(query).matches;
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
console.warn(`Failed to check media query "${query}":`, error);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if a breakpoint is currently active
|
|
291
|
+
*/
|
|
292
|
+
isBreakpointActive(breakpoint, type = 'min') {
|
|
293
|
+
const size = this.breakpoints[breakpoint];
|
|
294
|
+
if (!size)
|
|
295
|
+
return false;
|
|
296
|
+
const query = type === 'min' ? `(min-width: ${size})` : `(max-width: ${size})`;
|
|
297
|
+
return this.isMatching(query);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get the current viewport width
|
|
301
|
+
*/
|
|
302
|
+
getViewportWidth() {
|
|
303
|
+
if (typeof window === 'undefined')
|
|
304
|
+
return 0;
|
|
305
|
+
return window.innerWidth;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get the current viewport height
|
|
309
|
+
*/
|
|
310
|
+
getViewportHeight() {
|
|
311
|
+
if (typeof window === 'undefined')
|
|
312
|
+
return 0;
|
|
313
|
+
return window.innerHeight;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Check if viewport is mobile-sized (< 768px)
|
|
317
|
+
*/
|
|
318
|
+
isMobile() {
|
|
319
|
+
return this.getViewportWidth() < 768;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Check if viewport is tablet-sized (768px - 1024px)
|
|
323
|
+
*/
|
|
324
|
+
isTablet() {
|
|
325
|
+
const width = this.getViewportWidth();
|
|
326
|
+
return width >= 768 && width < 1024;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Check if viewport is desktop-sized (>= 1024px)
|
|
330
|
+
*/
|
|
331
|
+
isDesktop() {
|
|
332
|
+
return this.getViewportWidth() >= 1024;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Check if prefers-reduced-motion is enabled
|
|
336
|
+
*/
|
|
337
|
+
prefersReducedMotion() {
|
|
338
|
+
if (typeof window === 'undefined')
|
|
339
|
+
return false;
|
|
340
|
+
try {
|
|
341
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
342
|
+
}
|
|
343
|
+
catch (_a) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check if dark mode is preferred
|
|
349
|
+
*/
|
|
350
|
+
prefersDarkMode() {
|
|
351
|
+
if (typeof window === 'undefined')
|
|
352
|
+
return false;
|
|
353
|
+
try {
|
|
354
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
355
|
+
}
|
|
356
|
+
catch (_a) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get the current device orientation
|
|
362
|
+
*/
|
|
363
|
+
getOrientation() {
|
|
364
|
+
if (typeof window === 'undefined')
|
|
365
|
+
return 'portrait';
|
|
366
|
+
return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Register a window resize listener
|
|
370
|
+
* @param id - Unique identifier
|
|
371
|
+
* @param handler - Callback with (width, height)
|
|
372
|
+
* @param debounceMs - Debounce delay in milliseconds
|
|
373
|
+
*/
|
|
374
|
+
registerResizeListener(id, handler, debounceMs = 0) {
|
|
375
|
+
if (typeof window === 'undefined')
|
|
376
|
+
return;
|
|
377
|
+
let timeoutId = null;
|
|
378
|
+
const eventListener = () => {
|
|
379
|
+
if (timeoutId)
|
|
380
|
+
clearTimeout(timeoutId);
|
|
381
|
+
if (debounceMs > 0) {
|
|
382
|
+
timeoutId = setTimeout(() => {
|
|
383
|
+
handler(this.getViewportWidth(), this.getViewportHeight());
|
|
384
|
+
}, debounceMs);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
handler(this.getViewportWidth(), this.getViewportHeight());
|
|
388
|
+
}
|
|
54
389
|
};
|
|
390
|
+
this.resizeListeners.set(id, { handler, eventListener });
|
|
391
|
+
window.addEventListener('resize', eventListener);
|
|
392
|
+
// Call handler with initial dimensions
|
|
393
|
+
handler(this.getViewportWidth(), this.getViewportHeight());
|
|
55
394
|
}
|
|
56
395
|
/**
|
|
57
|
-
*
|
|
396
|
+
* Unregister a resize listener
|
|
58
397
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
398
|
+
unregisterResizeListener(id) {
|
|
399
|
+
if (typeof window === 'undefined')
|
|
400
|
+
return;
|
|
401
|
+
const entry = this.resizeListeners.get(id);
|
|
402
|
+
if (entry) {
|
|
403
|
+
window.removeEventListener('resize', entry.eventListener);
|
|
404
|
+
this.resizeListeners.delete(id);
|
|
405
|
+
}
|
|
61
406
|
}
|
|
62
407
|
/**
|
|
63
|
-
*
|
|
408
|
+
* Clean up all listeners
|
|
64
409
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
410
|
+
cleanup() {
|
|
411
|
+
this.listeners.forEach((listener) => {
|
|
412
|
+
if (listener.matcher) {
|
|
413
|
+
listener.matcher.removeEventListener('change', () => { });
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
this.listeners.clear();
|
|
417
|
+
if (typeof window !== 'undefined') {
|
|
418
|
+
this.resizeListeners.forEach((entry) => {
|
|
419
|
+
window.removeEventListener('resize', entry.eventListener);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
this.resizeListeners.clear();
|
|
423
|
+
if (this.resizeObserver) {
|
|
424
|
+
this.resizeObserver.disconnect();
|
|
425
|
+
this.resizeObserver = null;
|
|
426
|
+
}
|
|
67
427
|
}
|
|
68
428
|
/**
|
|
69
|
-
*
|
|
429
|
+
* Update breakpoint configuration
|
|
70
430
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
431
|
+
setBreakpoints(breakpoints) {
|
|
432
|
+
this.breakpoints = Object.assign(Object.assign({}, DEFAULT_BREAKPOINTS), breakpoints);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Singleton instance
|
|
436
|
+
let mediaQueryServiceInstance = null;
|
|
437
|
+
/**
|
|
438
|
+
* Get the singleton MediaQueryService instance
|
|
439
|
+
*/
|
|
440
|
+
function getMediaQueryService(breakpoints) {
|
|
441
|
+
if (!mediaQueryServiceInstance) {
|
|
442
|
+
mediaQueryServiceInstance = new MediaQueryService(breakpoints);
|
|
443
|
+
}
|
|
444
|
+
return mediaQueryServiceInstance;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Create a new isolated MediaQueryService instance
|
|
448
|
+
*/
|
|
449
|
+
function createMediaQueryService(breakpoints) {
|
|
450
|
+
return new MediaQueryService(breakpoints || DEFAULT_BREAKPOINTS);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Common Utilities
|
|
455
|
+
* Shared utility functions used across the component library
|
|
456
|
+
*/
|
|
457
|
+
/* eslint-disable no-undef, @typescript-eslint/no-explicit-any */
|
|
458
|
+
let idCounter = 0;
|
|
459
|
+
/**
|
|
460
|
+
* Generate a unique ID with optional prefix
|
|
461
|
+
* @param prefix - Optional prefix for the ID (default: 'id')
|
|
462
|
+
*/
|
|
463
|
+
function generateUniqueId(prefix = 'id') {
|
|
464
|
+
return `${prefix}-${++idCounter}-${Math.random().toString(36).substr(2, 9)}`;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Reset ID counter (useful for testing)
|
|
468
|
+
*/
|
|
469
|
+
function resetIdCounter() {
|
|
470
|
+
idCounter = 0;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Debounce a function
|
|
474
|
+
* @param fn - Function to debounce
|
|
475
|
+
* @param delay - Delay in milliseconds
|
|
476
|
+
*/
|
|
477
|
+
function debounce(fn, delay) {
|
|
478
|
+
let timeoutId = null;
|
|
479
|
+
return (...args) => {
|
|
480
|
+
if (timeoutId)
|
|
481
|
+
clearTimeout(timeoutId);
|
|
482
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Throttle a function
|
|
487
|
+
* @param fn - Function to throttle
|
|
488
|
+
* @param delay - Delay in milliseconds
|
|
489
|
+
*/
|
|
490
|
+
function throttle(fn, delay) {
|
|
491
|
+
let lastCall = 0;
|
|
492
|
+
let timeoutId = null;
|
|
493
|
+
return (...args) => {
|
|
494
|
+
const now = Date.now();
|
|
495
|
+
if (now - lastCall >= delay) {
|
|
496
|
+
lastCall = now;
|
|
497
|
+
fn(...args);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
if (timeoutId)
|
|
501
|
+
clearTimeout(timeoutId);
|
|
502
|
+
timeoutId = setTimeout(() => {
|
|
503
|
+
lastCall = Date.now();
|
|
504
|
+
fn(...args);
|
|
505
|
+
}, delay - (now - lastCall));
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Add a class to an element
|
|
511
|
+
* @param element - The element
|
|
512
|
+
* @param className - Class name to add
|
|
513
|
+
*/
|
|
514
|
+
function addClass(element, className) {
|
|
515
|
+
element.classList.add(className);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Remove a class from an element
|
|
519
|
+
* @param element - The element
|
|
520
|
+
* @param className - Class name to remove
|
|
521
|
+
*/
|
|
522
|
+
function removeClass(element, className) {
|
|
523
|
+
element.classList.remove(className);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Toggle a class on an element
|
|
527
|
+
* @param element - The element
|
|
528
|
+
* @param className - Class name to toggle
|
|
529
|
+
* @param force - Optional force add/remove
|
|
530
|
+
*/
|
|
531
|
+
function toggleClass(element, className, force) {
|
|
532
|
+
element.classList.toggle(className, force);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Check if element has a class
|
|
536
|
+
* @param element - The element
|
|
537
|
+
* @param className - Class name to check
|
|
538
|
+
*/
|
|
539
|
+
function hasClass(element, className) {
|
|
540
|
+
return element.classList.contains(className);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Get all data attributes from an element as an object
|
|
544
|
+
*/
|
|
545
|
+
function getDataAttributes(element) {
|
|
546
|
+
const data = {};
|
|
547
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
548
|
+
if (attr.name.startsWith('data-')) {
|
|
549
|
+
const key = attr.name.slice(5); // Remove 'data-' prefix
|
|
550
|
+
data[key] = attr.value;
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
return data;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Set a data attribute on an element
|
|
557
|
+
* @param element - The element
|
|
558
|
+
* @param key - Data attribute name (without 'data-' prefix)
|
|
559
|
+
* @param value - The value to set
|
|
560
|
+
*/
|
|
561
|
+
function setDataAttribute(element, key, value) {
|
|
562
|
+
if (value === null) {
|
|
563
|
+
element.removeAttribute(`data-${key}`);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
element.setAttribute(`data-${key}`, value);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get the value of a data attribute
|
|
571
|
+
* @param element - The element
|
|
572
|
+
* @param key - Data attribute name (without 'data-' prefix)
|
|
573
|
+
*/
|
|
574
|
+
function getDataAttribute(element, key) {
|
|
575
|
+
return element.getAttribute(`data-${key}`) || undefined;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Dispatch a custom event from an element
|
|
579
|
+
* @param element - The element to dispatch from
|
|
580
|
+
* @param eventName - Name of the custom event
|
|
581
|
+
* @param detail - Optional detail object to include in the event
|
|
582
|
+
*/
|
|
583
|
+
function dispatchCustomEvent(element, eventName, detail) {
|
|
584
|
+
const event = new CustomEvent(eventName, {
|
|
585
|
+
detail,
|
|
586
|
+
bubbles: true,
|
|
587
|
+
cancelable: true,
|
|
588
|
+
composed: true,
|
|
589
|
+
});
|
|
590
|
+
element.dispatchEvent(event);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Listen for a custom event
|
|
594
|
+
* @param element - The element to listen on
|
|
595
|
+
* @param eventName - Name of the custom event
|
|
596
|
+
* @param handler - Callback function
|
|
597
|
+
* @returns Cleanup function to remove listener
|
|
598
|
+
*/
|
|
599
|
+
function onCustomEvent(element, eventName, handler) {
|
|
600
|
+
const listener = (event) => {
|
|
601
|
+
handler(event.detail);
|
|
602
|
+
};
|
|
603
|
+
element.addEventListener(eventName, listener);
|
|
604
|
+
return () => {
|
|
605
|
+
element.removeEventListener(eventName, listener);
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get the offset position of an element relative to the viewport
|
|
610
|
+
*/
|
|
611
|
+
function getElementOffset(element) {
|
|
612
|
+
const rect = element.getBoundingClientRect();
|
|
613
|
+
return {
|
|
614
|
+
top: rect.top + window.scrollY,
|
|
615
|
+
left: rect.left + window.scrollX,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Check if an element is in the viewport
|
|
620
|
+
*/
|
|
621
|
+
function isElementInViewport(element) {
|
|
622
|
+
const rect = element.getBoundingClientRect();
|
|
623
|
+
return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Scroll element into view
|
|
627
|
+
* @param element - The element to scroll into view
|
|
628
|
+
* @param options - Scroll behavior options
|
|
629
|
+
*/
|
|
630
|
+
function scrollIntoView(element, options = {}) {
|
|
631
|
+
element.scrollIntoView(Object.assign({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }, options));
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Get computed style value
|
|
635
|
+
*/
|
|
636
|
+
function getComputedValue(element, property) {
|
|
637
|
+
return window.getComputedStyle(element).getPropertyValue(property);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Wait for a specific time
|
|
641
|
+
* @param ms - Time in milliseconds
|
|
642
|
+
*/
|
|
643
|
+
function wait(ms) {
|
|
644
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Wait for an element to appear in the DOM
|
|
648
|
+
* @param selector - CSS selector to wait for
|
|
649
|
+
* @param timeout - Timeout in milliseconds (default: 5000)
|
|
650
|
+
*/
|
|
651
|
+
function waitForElement(selector, timeout = 5000) {
|
|
652
|
+
return new Promise((resolve, reject) => {
|
|
653
|
+
const element = document.querySelector(selector);
|
|
654
|
+
if (element) {
|
|
655
|
+
resolve(element);
|
|
73
656
|
return;
|
|
74
657
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
658
|
+
const observer = new MutationObserver(() => {
|
|
659
|
+
const el = document.querySelector(selector);
|
|
660
|
+
if (el) {
|
|
661
|
+
observer.disconnect();
|
|
662
|
+
resolve(el);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
666
|
+
setTimeout(() => {
|
|
667
|
+
observer.disconnect();
|
|
668
|
+
reject(new Error(`Element "${selector}" not found within ${timeout}ms`));
|
|
669
|
+
}, timeout);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Clone an object deeply
|
|
674
|
+
*/
|
|
675
|
+
function deepClone(obj) {
|
|
676
|
+
if (obj === null || typeof obj !== 'object')
|
|
677
|
+
return obj;
|
|
678
|
+
if (obj instanceof Date)
|
|
679
|
+
return new Date(obj.getTime());
|
|
680
|
+
if (obj instanceof Array)
|
|
681
|
+
return obj.map((item) => deepClone(item));
|
|
682
|
+
if (obj instanceof Object) {
|
|
683
|
+
const cloned = {};
|
|
684
|
+
Object.keys(obj).forEach((key) => {
|
|
685
|
+
cloned[key] = deepClone(obj[key]);
|
|
686
|
+
});
|
|
687
|
+
return cloned;
|
|
688
|
+
}
|
|
689
|
+
return obj;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Merge objects
|
|
693
|
+
*/
|
|
694
|
+
function mergeObjects(target, ...sources) {
|
|
695
|
+
return sources.reduce((result, source) => (Object.assign(Object.assign({}, result), source)), target);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Get the prefixed CSS property name (for vendor prefixes)
|
|
699
|
+
*/
|
|
700
|
+
function getPrefixedProperty(property) {
|
|
701
|
+
const element = document.createElement('div');
|
|
702
|
+
const style = element.style;
|
|
703
|
+
if (property in style)
|
|
704
|
+
return property;
|
|
705
|
+
if (`webkit${property[0].toUpperCase()}${property.slice(1)}` in style)
|
|
706
|
+
return `webkit${property[0].toUpperCase()}${property.slice(1)}`;
|
|
707
|
+
if (`moz${property[0].toUpperCase()}${property.slice(1)}` in style)
|
|
708
|
+
return `moz${property[0].toUpperCase()}${property.slice(1)}`;
|
|
709
|
+
if (`ms${property[0].toUpperCase()}${property.slice(1)}` in style)
|
|
710
|
+
return `ms${property[0].toUpperCase()}${property.slice(1)}`;
|
|
711
|
+
return property;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Check if browser supports a CSS feature
|
|
715
|
+
*/
|
|
716
|
+
function supportsCSSFeature(property, value) {
|
|
717
|
+
const element = document.createElement('div');
|
|
718
|
+
element.style.setProperty(property, value);
|
|
719
|
+
return element.style.getPropertyValue(property) !== '';
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Request animation frame with promise
|
|
723
|
+
*/
|
|
724
|
+
function requestAnimationFramePromise() {
|
|
725
|
+
return new Promise((resolve) => requestAnimationFrame(resolve));
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Convert camelCase to kebab-case
|
|
729
|
+
*/
|
|
730
|
+
function camelToKebab(str) {
|
|
731
|
+
return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Convert kebab-case to camelCase
|
|
735
|
+
*/
|
|
736
|
+
function kebabToCamel(str) {
|
|
737
|
+
return str.replace(/-./g, (x) => x[1].toUpperCase());
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Accessibility Utilities
|
|
742
|
+
* Provides helpers for ARIA attributes, keyboard navigation, and accessibility features
|
|
743
|
+
*/
|
|
744
|
+
/**
|
|
745
|
+
* Generate a unique ID for ARIA relationships
|
|
746
|
+
* @param prefix - Optional prefix for the ID
|
|
747
|
+
* @param suffix - Optional suffix for the ID
|
|
748
|
+
*/
|
|
749
|
+
function generateAriaId(prefix = 'aria', suffix) {
|
|
750
|
+
const timestamp = Date.now().toString(36);
|
|
751
|
+
const random = Math.random().toString(36).substr(2, 9);
|
|
752
|
+
const finalSuffix = suffix ? `-${suffix}` : '';
|
|
753
|
+
return `${prefix}-${timestamp}-${random}${finalSuffix}`;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Set ARIA attributes on an element
|
|
757
|
+
* @param element - The element to set attributes on
|
|
758
|
+
* @param attributes - Object with attribute names and values
|
|
759
|
+
*/
|
|
760
|
+
function setAriaAttributes(element, attributes) {
|
|
761
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
762
|
+
if (value === null || value === false) {
|
|
763
|
+
element.removeAttribute(`aria-${key}`);
|
|
764
|
+
}
|
|
765
|
+
else if (value === true) {
|
|
766
|
+
element.setAttribute(`aria-${key}`, 'true');
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
element.setAttribute(`aria-${key}`, String(value));
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Add accessible label to an element
|
|
775
|
+
* @param element - The element to label
|
|
776
|
+
* @param label - The label text or element ID
|
|
777
|
+
* @param useAriaLabel - If true, uses aria-label; if false, uses aria-labelledby
|
|
778
|
+
*/
|
|
779
|
+
function addAriaLabel(element, label, useAriaLabel = true) {
|
|
780
|
+
if (useAriaLabel) {
|
|
781
|
+
element.setAttribute('aria-label', label);
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
element.setAttribute('aria-labelledby', label);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Add accessible description to an element
|
|
789
|
+
* @param element - The element to describe
|
|
790
|
+
* @param description - The description text or element ID
|
|
791
|
+
* @param useAriaDescription - If true, uses aria-description; if false, uses aria-describedby
|
|
792
|
+
*/
|
|
793
|
+
function addAriaDescription(element, description, useAriaDescription = false) {
|
|
794
|
+
if (useAriaDescription) {
|
|
795
|
+
element.setAttribute('aria-description', description);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
element.setAttribute('aria-describedby', description);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Set ARIA live region announcement
|
|
803
|
+
* @param element - The element to make a live region
|
|
804
|
+
* @param message - The message to announce
|
|
805
|
+
* @param politeness - 'polite' (default), 'assertive', or 'off'
|
|
806
|
+
* @param atomic - Whether to announce the entire region
|
|
807
|
+
*/
|
|
808
|
+
function announceAriaLive(element, message, politeness = 'polite', atomic = false) {
|
|
809
|
+
element.setAttribute('aria-live', politeness);
|
|
810
|
+
if (atomic) {
|
|
811
|
+
element.setAttribute('aria-atomic', 'true');
|
|
812
|
+
}
|
|
813
|
+
element.textContent = message;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Mark an element as disabled with ARIA attributes
|
|
817
|
+
* @param element - The element to mark as disabled
|
|
818
|
+
* @param disabled - Whether the element is disabled
|
|
819
|
+
*/
|
|
820
|
+
function setAriaDisabled(element, disabled) {
|
|
821
|
+
if (disabled) {
|
|
822
|
+
element.setAttribute('aria-disabled', 'true');
|
|
823
|
+
element.style.pointerEvents = 'none';
|
|
824
|
+
element.style.opacity = '0.5';
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
element.removeAttribute('aria-disabled');
|
|
828
|
+
element.style.pointerEvents = '';
|
|
829
|
+
element.style.opacity = '';
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Mark an element as expanded/collapsed (for collapsible sections)
|
|
834
|
+
* @param element - The element to mark
|
|
835
|
+
* @param expanded - Whether the element is expanded
|
|
836
|
+
* @param targetId - ID of the element being controlled (optional)
|
|
837
|
+
*/
|
|
838
|
+
function setAriaExpanded(element, expanded, targetId) {
|
|
839
|
+
element.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
840
|
+
if (targetId) {
|
|
841
|
+
element.setAttribute('aria-controls', targetId);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Mark an element as selected
|
|
846
|
+
* @param element - The element to mark
|
|
847
|
+
* @param selected - Whether the element is selected
|
|
848
|
+
*/
|
|
849
|
+
function setAriaSelected(element, selected) {
|
|
850
|
+
element.setAttribute('aria-selected', selected ? 'true' : 'false');
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Mark an element as checked (for checkboxes and radio buttons)
|
|
854
|
+
* @param element - The element to mark
|
|
855
|
+
* @param checked - The checked state ('true', 'false', or 'mixed')
|
|
856
|
+
*/
|
|
857
|
+
function setAriaChecked(element, checked) {
|
|
858
|
+
element.setAttribute('aria-checked', checked);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Set the current value of an element (for sliders, spinbuttons, etc.)
|
|
862
|
+
* @param element - The element
|
|
863
|
+
* @param current - Current value
|
|
864
|
+
* @param min - Minimum value
|
|
865
|
+
* @param max - Maximum value
|
|
866
|
+
* @param text - Optional text description of the value
|
|
867
|
+
*/
|
|
868
|
+
function setAriaValueNow(element, current, min, max, text) {
|
|
869
|
+
element.setAttribute('aria-valuenow', String(current));
|
|
870
|
+
if (min !== undefined)
|
|
871
|
+
element.setAttribute('aria-valuemin', String(min));
|
|
872
|
+
if (max !== undefined)
|
|
873
|
+
element.setAttribute('aria-valuemax', String(max));
|
|
874
|
+
if (text !== undefined)
|
|
875
|
+
element.setAttribute('aria-valuetext', text);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Mark an element as having a popup (for menus, popovers, etc.)
|
|
879
|
+
* @param element - The element with the popup trigger
|
|
880
|
+
* @param type - Type of popup: 'menu', 'listbox', 'tree', 'grid', 'dialog'
|
|
881
|
+
* @param popupId - ID of the popup element
|
|
882
|
+
*/
|
|
883
|
+
function setAriaPopup(element, type = 'menu', popupId) {
|
|
884
|
+
element.setAttribute('aria-haspopup', type);
|
|
885
|
+
if (popupId) {
|
|
886
|
+
element.setAttribute('aria-owns', popupId);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Set role for an element
|
|
891
|
+
* @param element - The element
|
|
892
|
+
* @param role - The role (button, menu, menuitem, etc.)
|
|
893
|
+
*/
|
|
894
|
+
function setRole(element, role) {
|
|
895
|
+
element.setAttribute('role', role);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Get the accessible name of an element
|
|
899
|
+
* Follows ARIA naming convention: aria-labelledby > aria-label > title > textContent
|
|
900
|
+
*/
|
|
901
|
+
function getAccessibleName(element) {
|
|
902
|
+
// Check aria-labelledby
|
|
903
|
+
const labelledby = element.getAttribute('aria-labelledby');
|
|
904
|
+
if (labelledby) {
|
|
905
|
+
const labelElement = document.getElementById(labelledby);
|
|
906
|
+
if (labelElement)
|
|
907
|
+
return labelElement.textContent || '';
|
|
908
|
+
}
|
|
909
|
+
// Check aria-label
|
|
910
|
+
const ariaLabel = element.getAttribute('aria-label');
|
|
911
|
+
if (ariaLabel)
|
|
912
|
+
return ariaLabel;
|
|
913
|
+
// Check title
|
|
914
|
+
const title = element.getAttribute('title');
|
|
915
|
+
if (title)
|
|
916
|
+
return title;
|
|
917
|
+
// Fall back to textContent
|
|
918
|
+
return element.textContent || '';
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Create a skip link for keyboard navigation
|
|
922
|
+
* @param text - Link text
|
|
923
|
+
* @param targetId - ID of element to skip to
|
|
924
|
+
*/
|
|
925
|
+
function createSkipLink(text, targetId) {
|
|
926
|
+
const link = document.createElement('a');
|
|
927
|
+
link.href = `#${targetId}`;
|
|
928
|
+
link.textContent = text;
|
|
929
|
+
link.className = 'sr-only'; // Visually hidden but accessible to screen readers
|
|
930
|
+
link.style.position = 'absolute';
|
|
931
|
+
link.style.top = '-9999px';
|
|
932
|
+
link.style.left = '-9999px';
|
|
933
|
+
// Show on focus
|
|
934
|
+
link.addEventListener('focus', () => {
|
|
935
|
+
link.style.top = '0';
|
|
936
|
+
link.style.left = '0';
|
|
937
|
+
});
|
|
938
|
+
// Hide on blur
|
|
939
|
+
link.addEventListener('blur', () => {
|
|
940
|
+
link.style.top = '-9999px';
|
|
941
|
+
link.style.left = '-9999px';
|
|
942
|
+
});
|
|
943
|
+
return link;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Check if element should be hidden from screen readers
|
|
947
|
+
*/
|
|
948
|
+
function isAriaHidden(element) {
|
|
949
|
+
return element.getAttribute('aria-hidden') === 'true';
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Hide element from screen readers but keep visually visible
|
|
953
|
+
*/
|
|
954
|
+
function setAriaHidden(element, hidden) {
|
|
955
|
+
if (hidden) {
|
|
956
|
+
element.setAttribute('aria-hidden', 'true');
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
element.removeAttribute('aria-hidden');
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Announce a message to screen readers immediately (without ARIA live regions)
|
|
964
|
+
* @param message - The message to announce
|
|
965
|
+
* @param politeness - 'polite' (default) or 'assertive'
|
|
966
|
+
*/
|
|
967
|
+
function announceToScreenReader(message, politeness = 'polite') {
|
|
968
|
+
if (typeof document === 'undefined')
|
|
99
969
|
return;
|
|
970
|
+
const announcement = document.createElement('div');
|
|
971
|
+
announcement.setAttribute('aria-live', politeness);
|
|
972
|
+
announcement.setAttribute('aria-atomic', 'true');
|
|
973
|
+
announcement.className = 'sr-only';
|
|
974
|
+
announcement.style.position = 'absolute';
|
|
975
|
+
announcement.style.left = '-9999px';
|
|
976
|
+
announcement.style.top = '-9999px';
|
|
977
|
+
announcement.textContent = message;
|
|
978
|
+
document.body.appendChild(announcement);
|
|
979
|
+
// Remove after announcement is made
|
|
980
|
+
setTimeout(() => {
|
|
981
|
+
announcement.remove();
|
|
982
|
+
}, 1000);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Set proper heading hierarchy
|
|
986
|
+
* @param element - The heading element
|
|
987
|
+
* @param level - Heading level (1-6)
|
|
988
|
+
*/
|
|
989
|
+
function setHeadingLevel(element, level) {
|
|
990
|
+
const tag = `h${level}`;
|
|
991
|
+
if (element.tagName.toLowerCase() !== tag) {
|
|
992
|
+
// If element is not correct tag, set role as fallback
|
|
993
|
+
element.setAttribute('role', tag);
|
|
100
994
|
}
|
|
101
|
-
const components = ["seki-switch"];
|
|
102
|
-
components.forEach(tagName => { switch (tagName) {
|
|
103
|
-
case "seki-switch":
|
|
104
|
-
if (!customElements.get(tagName)) {
|
|
105
|
-
customElements.define(tagName, SekiSwitch);
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
} });
|
|
109
995
|
}
|
|
110
|
-
|
|
996
|
+
/**
|
|
997
|
+
* Enable keyboard navigation for a custom component
|
|
998
|
+
* @param element - The element that should handle keyboard
|
|
999
|
+
* @param keys - Map of key to handler function
|
|
1000
|
+
*/
|
|
1001
|
+
function enableKeyboardNavigation(element, keys) {
|
|
1002
|
+
const handleKeyDown = (event) => {
|
|
1003
|
+
const handler = keys[event.key.toLowerCase()];
|
|
1004
|
+
if (handler) {
|
|
1005
|
+
event.preventDefault();
|
|
1006
|
+
handler(event);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
element.addEventListener('keydown', handleKeyDown);
|
|
1010
|
+
// Return cleanup function
|
|
1011
|
+
return () => {
|
|
1012
|
+
element.removeEventListener('keydown', handleKeyDown);
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Check if element has accessible focus styling
|
|
1017
|
+
*/
|
|
1018
|
+
function hasAccessibleFocus(element) {
|
|
1019
|
+
const styles = window.getComputedStyle(element);
|
|
1020
|
+
return !!(styles.outline || styles.boxShadow || styles.border);
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Ensure focus visible styling (for focus-visible pseudo-class)
|
|
1024
|
+
*/
|
|
1025
|
+
function ensureFocusVisible(element) {
|
|
1026
|
+
if (!hasAccessibleFocus(element)) {
|
|
1027
|
+
element.style.outline = '2px solid #4A90E2';
|
|
1028
|
+
element.style.outlineOffset = '2px';
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* SekiUI Type Definitions
|
|
1034
|
+
* Central export point for all type definitions across components and services
|
|
1035
|
+
*/
|
|
1036
|
+
// ============================================================================
|
|
1037
|
+
// NOTE: Types from ./components/sidebar/types and ./utils/select/types
|
|
1038
|
+
// are already re-exported at the top of this file via the main index.ts
|
|
1039
|
+
// ============================================================================
|
|
1040
|
+
// ============================================================================
|
|
1041
|
+
// TYPE GUARDS AND UTILITY TYPES
|
|
1042
|
+
// ============================================================================
|
|
1043
|
+
/**
|
|
1044
|
+
* Type guard to check if a value is a valid sidebar variant
|
|
1045
|
+
*/
|
|
1046
|
+
function isSidebarVariant(value) {
|
|
1047
|
+
return ['sidebar', 'floating', 'inset'].includes(value);
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Type guard to check if a value is a valid collapse mode
|
|
1051
|
+
*/
|
|
1052
|
+
function isCollapseMode(value) {
|
|
1053
|
+
return ['offcanvas', 'icon', 'none'].includes(value);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Type guard to check if a value is a valid button size
|
|
1057
|
+
*/
|
|
1058
|
+
function isButtonSize(value) {
|
|
1059
|
+
return ['sm', 'md', 'lg', 'icon-sm', 'icon', 'icon-lg'].includes(value);
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Type guard to check if a value is a valid button variant
|
|
1063
|
+
*/
|
|
1064
|
+
function isButtonVariant(value) {
|
|
1065
|
+
return ['primary', 'secondary', 'outline', 'ghost', 'destructive', 'link'].includes(value);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Type guard to check if a value is a valid tooltip side
|
|
1069
|
+
*/
|
|
1070
|
+
function isTooltipSide(value) {
|
|
1071
|
+
return ['top', 'right', 'bottom', 'left'].includes(value);
|
|
1072
|
+
}
|
|
111
1073
|
|
|
112
|
-
export {
|
|
1074
|
+
export { DEFAULT_BREAKPOINTS, FocusService, MediaQueryService, addAriaDescription, addAriaLabel, addClass, announceAriaLive, announceToScreenReader, camelToKebab, createFocusService, createMediaQueryService, createSkipLink, debounce, deepClone, dispatchCustomEvent, enableKeyboardNavigation, ensureFocusVisible, generateAriaId, generateUniqueId, getAccessibleName, getComputedValue, getDataAttribute, getDataAttributes, getElementOffset, getFocusService, getMediaQueryService, getPrefixedProperty, hasAccessibleFocus, hasClass, isAriaHidden, isButtonSize, isButtonVariant, isCollapseMode, isElementInViewport, isSidebarVariant, isTooltipSide, kebabToCamel, mergeObjects, onCustomEvent, removeClass, requestAnimationFramePromise, resetIdCounter, scrollIntoView, setAriaAttributes, setAriaChecked, setAriaDisabled, setAriaExpanded, setAriaHidden, setAriaPopup, setAriaSelected, setAriaValueNow, setDataAttribute, setHeadingLevel, setRole, supportsCSSFeature, throttle, toggleClass, wait, waitForElement };
|