@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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility Utilities
|
|
3
|
+
* Provides helpers for ARIA attributes, keyboard navigation, and accessibility features
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate a unique ID for ARIA relationships
|
|
7
|
+
* @param prefix - Optional prefix for the ID
|
|
8
|
+
* @param suffix - Optional suffix for the ID
|
|
9
|
+
*/
|
|
10
|
+
export function generateAriaId(prefix = 'aria', suffix) {
|
|
11
|
+
const timestamp = Date.now().toString(36);
|
|
12
|
+
const random = Math.random().toString(36).substr(2, 9);
|
|
13
|
+
const finalSuffix = suffix ? `-${suffix}` : '';
|
|
14
|
+
return `${prefix}-${timestamp}-${random}${finalSuffix}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Set ARIA attributes on an element
|
|
18
|
+
* @param element - The element to set attributes on
|
|
19
|
+
* @param attributes - Object with attribute names and values
|
|
20
|
+
*/
|
|
21
|
+
export function setAriaAttributes(element, attributes) {
|
|
22
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
23
|
+
if (value === null || value === false) {
|
|
24
|
+
element.removeAttribute(`aria-${key}`);
|
|
25
|
+
}
|
|
26
|
+
else if (value === true) {
|
|
27
|
+
element.setAttribute(`aria-${key}`, 'true');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
element.setAttribute(`aria-${key}`, String(value));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Add accessible label to an element
|
|
36
|
+
* @param element - The element to label
|
|
37
|
+
* @param label - The label text or element ID
|
|
38
|
+
* @param useAriaLabel - If true, uses aria-label; if false, uses aria-labelledby
|
|
39
|
+
*/
|
|
40
|
+
export function addAriaLabel(element, label, useAriaLabel = true) {
|
|
41
|
+
if (useAriaLabel) {
|
|
42
|
+
element.setAttribute('aria-label', label);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
element.setAttribute('aria-labelledby', label);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Add accessible description to an element
|
|
50
|
+
* @param element - The element to describe
|
|
51
|
+
* @param description - The description text or element ID
|
|
52
|
+
* @param useAriaDescription - If true, uses aria-description; if false, uses aria-describedby
|
|
53
|
+
*/
|
|
54
|
+
export function addAriaDescription(element, description, useAriaDescription = false) {
|
|
55
|
+
if (useAriaDescription) {
|
|
56
|
+
element.setAttribute('aria-description', description);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
element.setAttribute('aria-describedby', description);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Set ARIA live region announcement
|
|
64
|
+
* @param element - The element to make a live region
|
|
65
|
+
* @param message - The message to announce
|
|
66
|
+
* @param politeness - 'polite' (default), 'assertive', or 'off'
|
|
67
|
+
* @param atomic - Whether to announce the entire region
|
|
68
|
+
*/
|
|
69
|
+
export function announceAriaLive(element, message, politeness = 'polite', atomic = false) {
|
|
70
|
+
element.setAttribute('aria-live', politeness);
|
|
71
|
+
if (atomic) {
|
|
72
|
+
element.setAttribute('aria-atomic', 'true');
|
|
73
|
+
}
|
|
74
|
+
element.textContent = message;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Mark an element as disabled with ARIA attributes
|
|
78
|
+
* @param element - The element to mark as disabled
|
|
79
|
+
* @param disabled - Whether the element is disabled
|
|
80
|
+
*/
|
|
81
|
+
export function setAriaDisabled(element, disabled) {
|
|
82
|
+
if (disabled) {
|
|
83
|
+
element.setAttribute('aria-disabled', 'true');
|
|
84
|
+
element.style.pointerEvents = 'none';
|
|
85
|
+
element.style.opacity = '0.5';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
element.removeAttribute('aria-disabled');
|
|
89
|
+
element.style.pointerEvents = '';
|
|
90
|
+
element.style.opacity = '';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Mark an element as expanded/collapsed (for collapsible sections)
|
|
95
|
+
* @param element - The element to mark
|
|
96
|
+
* @param expanded - Whether the element is expanded
|
|
97
|
+
* @param targetId - ID of the element being controlled (optional)
|
|
98
|
+
*/
|
|
99
|
+
export function setAriaExpanded(element, expanded, targetId) {
|
|
100
|
+
element.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
101
|
+
if (targetId) {
|
|
102
|
+
element.setAttribute('aria-controls', targetId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Mark an element as selected
|
|
107
|
+
* @param element - The element to mark
|
|
108
|
+
* @param selected - Whether the element is selected
|
|
109
|
+
*/
|
|
110
|
+
export function setAriaSelected(element, selected) {
|
|
111
|
+
element.setAttribute('aria-selected', selected ? 'true' : 'false');
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Mark an element as checked (for checkboxes and radio buttons)
|
|
115
|
+
* @param element - The element to mark
|
|
116
|
+
* @param checked - The checked state ('true', 'false', or 'mixed')
|
|
117
|
+
*/
|
|
118
|
+
export function setAriaChecked(element, checked) {
|
|
119
|
+
element.setAttribute('aria-checked', checked);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Set the current value of an element (for sliders, spinbuttons, etc.)
|
|
123
|
+
* @param element - The element
|
|
124
|
+
* @param current - Current value
|
|
125
|
+
* @param min - Minimum value
|
|
126
|
+
* @param max - Maximum value
|
|
127
|
+
* @param text - Optional text description of the value
|
|
128
|
+
*/
|
|
129
|
+
export function setAriaValueNow(element, current, min, max, text) {
|
|
130
|
+
element.setAttribute('aria-valuenow', String(current));
|
|
131
|
+
if (min !== undefined)
|
|
132
|
+
element.setAttribute('aria-valuemin', String(min));
|
|
133
|
+
if (max !== undefined)
|
|
134
|
+
element.setAttribute('aria-valuemax', String(max));
|
|
135
|
+
if (text !== undefined)
|
|
136
|
+
element.setAttribute('aria-valuetext', text);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Mark an element as having a popup (for menus, popovers, etc.)
|
|
140
|
+
* @param element - The element with the popup trigger
|
|
141
|
+
* @param type - Type of popup: 'menu', 'listbox', 'tree', 'grid', 'dialog'
|
|
142
|
+
* @param popupId - ID of the popup element
|
|
143
|
+
*/
|
|
144
|
+
export function setAriaPopup(element, type = 'menu', popupId) {
|
|
145
|
+
element.setAttribute('aria-haspopup', type);
|
|
146
|
+
if (popupId) {
|
|
147
|
+
element.setAttribute('aria-owns', popupId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Set role for an element
|
|
152
|
+
* @param element - The element
|
|
153
|
+
* @param role - The role (button, menu, menuitem, etc.)
|
|
154
|
+
*/
|
|
155
|
+
export function setRole(element, role) {
|
|
156
|
+
element.setAttribute('role', role);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the accessible name of an element
|
|
160
|
+
* Follows ARIA naming convention: aria-labelledby > aria-label > title > textContent
|
|
161
|
+
*/
|
|
162
|
+
export function getAccessibleName(element) {
|
|
163
|
+
// Check aria-labelledby
|
|
164
|
+
const labelledby = element.getAttribute('aria-labelledby');
|
|
165
|
+
if (labelledby) {
|
|
166
|
+
const labelElement = document.getElementById(labelledby);
|
|
167
|
+
if (labelElement)
|
|
168
|
+
return labelElement.textContent || '';
|
|
169
|
+
}
|
|
170
|
+
// Check aria-label
|
|
171
|
+
const ariaLabel = element.getAttribute('aria-label');
|
|
172
|
+
if (ariaLabel)
|
|
173
|
+
return ariaLabel;
|
|
174
|
+
// Check title
|
|
175
|
+
const title = element.getAttribute('title');
|
|
176
|
+
if (title)
|
|
177
|
+
return title;
|
|
178
|
+
// Fall back to textContent
|
|
179
|
+
return element.textContent || '';
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create a skip link for keyboard navigation
|
|
183
|
+
* @param text - Link text
|
|
184
|
+
* @param targetId - ID of element to skip to
|
|
185
|
+
*/
|
|
186
|
+
export function createSkipLink(text, targetId) {
|
|
187
|
+
const link = document.createElement('a');
|
|
188
|
+
link.href = `#${targetId}`;
|
|
189
|
+
link.textContent = text;
|
|
190
|
+
link.className = 'sr-only'; // Visually hidden but accessible to screen readers
|
|
191
|
+
link.style.position = 'absolute';
|
|
192
|
+
link.style.top = '-9999px';
|
|
193
|
+
link.style.left = '-9999px';
|
|
194
|
+
// Show on focus
|
|
195
|
+
link.addEventListener('focus', () => {
|
|
196
|
+
link.style.top = '0';
|
|
197
|
+
link.style.left = '0';
|
|
198
|
+
});
|
|
199
|
+
// Hide on blur
|
|
200
|
+
link.addEventListener('blur', () => {
|
|
201
|
+
link.style.top = '-9999px';
|
|
202
|
+
link.style.left = '-9999px';
|
|
203
|
+
});
|
|
204
|
+
return link;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if element should be hidden from screen readers
|
|
208
|
+
*/
|
|
209
|
+
export function isAriaHidden(element) {
|
|
210
|
+
return element.getAttribute('aria-hidden') === 'true';
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Hide element from screen readers but keep visually visible
|
|
214
|
+
*/
|
|
215
|
+
export function setAriaHidden(element, hidden) {
|
|
216
|
+
if (hidden) {
|
|
217
|
+
element.setAttribute('aria-hidden', 'true');
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
element.removeAttribute('aria-hidden');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Announce a message to screen readers immediately (without ARIA live regions)
|
|
225
|
+
* @param message - The message to announce
|
|
226
|
+
* @param politeness - 'polite' (default) or 'assertive'
|
|
227
|
+
*/
|
|
228
|
+
export function announceToScreenReader(message, politeness = 'polite') {
|
|
229
|
+
if (typeof document === 'undefined')
|
|
230
|
+
return;
|
|
231
|
+
const announcement = document.createElement('div');
|
|
232
|
+
announcement.setAttribute('aria-live', politeness);
|
|
233
|
+
announcement.setAttribute('aria-atomic', 'true');
|
|
234
|
+
announcement.className = 'sr-only';
|
|
235
|
+
announcement.style.position = 'absolute';
|
|
236
|
+
announcement.style.left = '-9999px';
|
|
237
|
+
announcement.style.top = '-9999px';
|
|
238
|
+
announcement.textContent = message;
|
|
239
|
+
document.body.appendChild(announcement);
|
|
240
|
+
// Remove after announcement is made
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
announcement.remove();
|
|
243
|
+
}, 1000);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Set proper heading hierarchy
|
|
247
|
+
* @param element - The heading element
|
|
248
|
+
* @param level - Heading level (1-6)
|
|
249
|
+
*/
|
|
250
|
+
export function setHeadingLevel(element, level) {
|
|
251
|
+
const tag = `h${level}`;
|
|
252
|
+
if (element.tagName.toLowerCase() !== tag) {
|
|
253
|
+
// If element is not correct tag, set role as fallback
|
|
254
|
+
element.setAttribute('role', tag);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Enable keyboard navigation for a custom component
|
|
259
|
+
* @param element - The element that should handle keyboard
|
|
260
|
+
* @param keys - Map of key to handler function
|
|
261
|
+
*/
|
|
262
|
+
export function enableKeyboardNavigation(element, keys) {
|
|
263
|
+
const handleKeyDown = (event) => {
|
|
264
|
+
const handler = keys[event.key.toLowerCase()];
|
|
265
|
+
if (handler) {
|
|
266
|
+
event.preventDefault();
|
|
267
|
+
handler(event);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
element.addEventListener('keydown', handleKeyDown);
|
|
271
|
+
// Return cleanup function
|
|
272
|
+
return () => {
|
|
273
|
+
element.removeEventListener('keydown', handleKeyDown);
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Check if element has accessible focus styling
|
|
278
|
+
*/
|
|
279
|
+
export function hasAccessibleFocus(element) {
|
|
280
|
+
const styles = window.getComputedStyle(element);
|
|
281
|
+
return !!(styles.outline || styles.boxShadow || styles.border);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Ensure focus visible styling (for focus-visible pseudo-class)
|
|
285
|
+
*/
|
|
286
|
+
export function ensureFocusVisible(element) {
|
|
287
|
+
if (!hasAccessibleFocus(element)) {
|
|
288
|
+
element.style.outline = '2px solid #4A90E2';
|
|
289
|
+
element.style.outlineOffset = '2px';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Utilities
|
|
3
|
+
* Shared utility functions used across the component library
|
|
4
|
+
*/
|
|
5
|
+
/* eslint-disable no-undef, @typescript-eslint/no-explicit-any */
|
|
6
|
+
let idCounter = 0;
|
|
7
|
+
/**
|
|
8
|
+
* Generate a unique ID with optional prefix
|
|
9
|
+
* @param prefix - Optional prefix for the ID (default: 'id')
|
|
10
|
+
*/
|
|
11
|
+
export function generateUniqueId(prefix = 'id') {
|
|
12
|
+
return `${prefix}-${++idCounter}-${Math.random().toString(36).substr(2, 9)}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reset ID counter (useful for testing)
|
|
16
|
+
*/
|
|
17
|
+
export function resetIdCounter() {
|
|
18
|
+
idCounter = 0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Debounce a function
|
|
22
|
+
* @param fn - Function to debounce
|
|
23
|
+
* @param delay - Delay in milliseconds
|
|
24
|
+
*/
|
|
25
|
+
export function debounce(fn, delay) {
|
|
26
|
+
let timeoutId = null;
|
|
27
|
+
return (...args) => {
|
|
28
|
+
if (timeoutId)
|
|
29
|
+
clearTimeout(timeoutId);
|
|
30
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Throttle a function
|
|
35
|
+
* @param fn - Function to throttle
|
|
36
|
+
* @param delay - Delay in milliseconds
|
|
37
|
+
*/
|
|
38
|
+
export function throttle(fn, delay) {
|
|
39
|
+
let lastCall = 0;
|
|
40
|
+
let timeoutId = null;
|
|
41
|
+
return (...args) => {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (now - lastCall >= delay) {
|
|
44
|
+
lastCall = now;
|
|
45
|
+
fn(...args);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (timeoutId)
|
|
49
|
+
clearTimeout(timeoutId);
|
|
50
|
+
timeoutId = setTimeout(() => {
|
|
51
|
+
lastCall = Date.now();
|
|
52
|
+
fn(...args);
|
|
53
|
+
}, delay - (now - lastCall));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add a class to an element
|
|
59
|
+
* @param element - The element
|
|
60
|
+
* @param className - Class name to add
|
|
61
|
+
*/
|
|
62
|
+
export function addClass(element, className) {
|
|
63
|
+
element.classList.add(className);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Remove a class from an element
|
|
67
|
+
* @param element - The element
|
|
68
|
+
* @param className - Class name to remove
|
|
69
|
+
*/
|
|
70
|
+
export function removeClass(element, className) {
|
|
71
|
+
element.classList.remove(className);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Toggle a class on an element
|
|
75
|
+
* @param element - The element
|
|
76
|
+
* @param className - Class name to toggle
|
|
77
|
+
* @param force - Optional force add/remove
|
|
78
|
+
*/
|
|
79
|
+
export function toggleClass(element, className, force) {
|
|
80
|
+
element.classList.toggle(className, force);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if element has a class
|
|
84
|
+
* @param element - The element
|
|
85
|
+
* @param className - Class name to check
|
|
86
|
+
*/
|
|
87
|
+
export function hasClass(element, className) {
|
|
88
|
+
return element.classList.contains(className);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get all data attributes from an element as an object
|
|
92
|
+
*/
|
|
93
|
+
export function getDataAttributes(element) {
|
|
94
|
+
const data = {};
|
|
95
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
96
|
+
if (attr.name.startsWith('data-')) {
|
|
97
|
+
const key = attr.name.slice(5); // Remove 'data-' prefix
|
|
98
|
+
data[key] = attr.value;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Set a data attribute on an element
|
|
105
|
+
* @param element - The element
|
|
106
|
+
* @param key - Data attribute name (without 'data-' prefix)
|
|
107
|
+
* @param value - The value to set
|
|
108
|
+
*/
|
|
109
|
+
export function setDataAttribute(element, key, value) {
|
|
110
|
+
if (value === null) {
|
|
111
|
+
element.removeAttribute(`data-${key}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
element.setAttribute(`data-${key}`, value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get the value of a data attribute
|
|
119
|
+
* @param element - The element
|
|
120
|
+
* @param key - Data attribute name (without 'data-' prefix)
|
|
121
|
+
*/
|
|
122
|
+
export function getDataAttribute(element, key) {
|
|
123
|
+
return element.getAttribute(`data-${key}`) || undefined;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Dispatch a custom event from an element
|
|
127
|
+
* @param element - The element to dispatch from
|
|
128
|
+
* @param eventName - Name of the custom event
|
|
129
|
+
* @param detail - Optional detail object to include in the event
|
|
130
|
+
*/
|
|
131
|
+
export function dispatchCustomEvent(element, eventName, detail) {
|
|
132
|
+
const event = new CustomEvent(eventName, {
|
|
133
|
+
detail,
|
|
134
|
+
bubbles: true,
|
|
135
|
+
cancelable: true,
|
|
136
|
+
composed: true,
|
|
137
|
+
});
|
|
138
|
+
element.dispatchEvent(event);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Listen for a custom event
|
|
142
|
+
* @param element - The element to listen on
|
|
143
|
+
* @param eventName - Name of the custom event
|
|
144
|
+
* @param handler - Callback function
|
|
145
|
+
* @returns Cleanup function to remove listener
|
|
146
|
+
*/
|
|
147
|
+
export function onCustomEvent(element, eventName, handler) {
|
|
148
|
+
const listener = (event) => {
|
|
149
|
+
handler(event.detail);
|
|
150
|
+
};
|
|
151
|
+
element.addEventListener(eventName, listener);
|
|
152
|
+
return () => {
|
|
153
|
+
element.removeEventListener(eventName, listener);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get the offset position of an element relative to the viewport
|
|
158
|
+
*/
|
|
159
|
+
export function getElementOffset(element) {
|
|
160
|
+
const rect = element.getBoundingClientRect();
|
|
161
|
+
return {
|
|
162
|
+
top: rect.top + window.scrollY,
|
|
163
|
+
left: rect.left + window.scrollX,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if an element is in the viewport
|
|
168
|
+
*/
|
|
169
|
+
export function isElementInViewport(element) {
|
|
170
|
+
const rect = element.getBoundingClientRect();
|
|
171
|
+
return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Scroll element into view
|
|
175
|
+
* @param element - The element to scroll into view
|
|
176
|
+
* @param options - Scroll behavior options
|
|
177
|
+
*/
|
|
178
|
+
export function scrollIntoView(element, options = {}) {
|
|
179
|
+
element.scrollIntoView(Object.assign({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }, options));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get computed style value
|
|
183
|
+
*/
|
|
184
|
+
export function getComputedValue(element, property) {
|
|
185
|
+
return window.getComputedStyle(element).getPropertyValue(property);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Wait for a specific time
|
|
189
|
+
* @param ms - Time in milliseconds
|
|
190
|
+
*/
|
|
191
|
+
export function wait(ms) {
|
|
192
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Wait for an element to appear in the DOM
|
|
196
|
+
* @param selector - CSS selector to wait for
|
|
197
|
+
* @param timeout - Timeout in milliseconds (default: 5000)
|
|
198
|
+
*/
|
|
199
|
+
export function waitForElement(selector, timeout = 5000) {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const element = document.querySelector(selector);
|
|
202
|
+
if (element) {
|
|
203
|
+
resolve(element);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const observer = new MutationObserver(() => {
|
|
207
|
+
const el = document.querySelector(selector);
|
|
208
|
+
if (el) {
|
|
209
|
+
observer.disconnect();
|
|
210
|
+
resolve(el);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
observer.disconnect();
|
|
216
|
+
reject(new Error(`Element "${selector}" not found within ${timeout}ms`));
|
|
217
|
+
}, timeout);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Clone an object deeply
|
|
222
|
+
*/
|
|
223
|
+
export function deepClone(obj) {
|
|
224
|
+
if (obj === null || typeof obj !== 'object')
|
|
225
|
+
return obj;
|
|
226
|
+
if (obj instanceof Date)
|
|
227
|
+
return new Date(obj.getTime());
|
|
228
|
+
if (obj instanceof Array)
|
|
229
|
+
return obj.map((item) => deepClone(item));
|
|
230
|
+
if (obj instanceof Object) {
|
|
231
|
+
const cloned = {};
|
|
232
|
+
Object.keys(obj).forEach((key) => {
|
|
233
|
+
cloned[key] = deepClone(obj[key]);
|
|
234
|
+
});
|
|
235
|
+
return cloned;
|
|
236
|
+
}
|
|
237
|
+
return obj;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Merge objects
|
|
241
|
+
*/
|
|
242
|
+
export function mergeObjects(target, ...sources) {
|
|
243
|
+
return sources.reduce((result, source) => (Object.assign(Object.assign({}, result), source)), target);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get the prefixed CSS property name (for vendor prefixes)
|
|
247
|
+
*/
|
|
248
|
+
export function getPrefixedProperty(property) {
|
|
249
|
+
const element = document.createElement('div');
|
|
250
|
+
const style = element.style;
|
|
251
|
+
if (property in style)
|
|
252
|
+
return property;
|
|
253
|
+
if (`webkit${property[0].toUpperCase()}${property.slice(1)}` in style)
|
|
254
|
+
return `webkit${property[0].toUpperCase()}${property.slice(1)}`;
|
|
255
|
+
if (`moz${property[0].toUpperCase()}${property.slice(1)}` in style)
|
|
256
|
+
return `moz${property[0].toUpperCase()}${property.slice(1)}`;
|
|
257
|
+
if (`ms${property[0].toUpperCase()}${property.slice(1)}` in style)
|
|
258
|
+
return `ms${property[0].toUpperCase()}${property.slice(1)}`;
|
|
259
|
+
return property;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if browser supports a CSS feature
|
|
263
|
+
*/
|
|
264
|
+
export function supportsCSSFeature(property, value) {
|
|
265
|
+
const element = document.createElement('div');
|
|
266
|
+
element.style.setProperty(property, value);
|
|
267
|
+
return element.style.getPropertyValue(property) !== '';
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Request animation frame with promise
|
|
271
|
+
*/
|
|
272
|
+
export function requestAnimationFramePromise() {
|
|
273
|
+
return new Promise((resolve) => requestAnimationFrame(resolve));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Convert camelCase to kebab-case
|
|
277
|
+
*/
|
|
278
|
+
export function camelToKebab(str) {
|
|
279
|
+
return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Convert kebab-case to camelCase
|
|
283
|
+
*/
|
|
284
|
+
export function kebabToCamel(str) {
|
|
285
|
+
return str.replace(/-./g, (x) => x[1].toUpperCase());
|
|
286
|
+
}
|