@sekiui/elements 0.0.56 → 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.
Files changed (256) hide show
  1. package/dist/cdn/index.js +1053 -91
  2. package/dist/cdn/p-BJCq8m2o.js +138 -0
  3. package/dist/cdn/p-BfRJQMIU.js +111 -0
  4. package/dist/cdn/{p-bMBhrs0a.js → p-Bp7tjKwQ.js} +427 -7
  5. package/dist/cdn/seki-button.js +1 -1
  6. package/dist/cdn/seki-card-action.js +1 -1
  7. package/dist/cdn/seki-card-content.js +1 -1
  8. package/dist/cdn/seki-card-description.js +1 -1
  9. package/dist/cdn/seki-card-footer.js +1 -1
  10. package/dist/cdn/seki-card-header.js +1 -1
  11. package/dist/cdn/seki-card-title.js +1 -1
  12. package/dist/cdn/seki-card.js +1 -1
  13. package/dist/cdn/seki-field-description.js +1 -1
  14. package/dist/cdn/seki-field-error.js +1 -1
  15. package/dist/cdn/seki-field-group.js +1 -1
  16. package/dist/cdn/seki-field-label.js +1 -1
  17. package/dist/cdn/seki-field-legend.js +1 -1
  18. package/dist/cdn/seki-field.js +1 -1
  19. package/dist/cdn/seki-fieldset.js +1 -1
  20. package/dist/cdn/seki-input.js +1 -1
  21. package/dist/cdn/seki-select-content.js +1 -1
  22. package/dist/cdn/seki-select-group.js +1 -1
  23. package/dist/cdn/seki-select-option.js +1 -1
  24. package/dist/cdn/seki-select-trigger.js +1 -1
  25. package/dist/cdn/seki-select.js +1 -1
  26. package/dist/cdn/seki-sidebar-content.d.ts +11 -0
  27. package/dist/cdn/seki-sidebar-content.js +38 -0
  28. package/dist/cdn/seki-sidebar-footer.d.ts +11 -0
  29. package/dist/cdn/seki-sidebar-footer.js +38 -0
  30. package/dist/cdn/seki-sidebar-group.d.ts +11 -0
  31. package/dist/cdn/seki-sidebar-group.js +131 -0
  32. package/dist/cdn/seki-sidebar-header.d.ts +11 -0
  33. package/dist/cdn/seki-sidebar-header.js +38 -0
  34. package/dist/cdn/seki-sidebar-menu-item.d.ts +11 -0
  35. package/dist/cdn/seki-sidebar-menu-item.js +200 -0
  36. package/dist/cdn/seki-sidebar-menu-sub.d.ts +11 -0
  37. package/dist/cdn/seki-sidebar-menu-sub.js +123 -0
  38. package/dist/cdn/seki-sidebar-menu.d.ts +11 -0
  39. package/dist/cdn/seki-sidebar-menu.js +38 -0
  40. package/dist/cdn/seki-sidebar-trigger.d.ts +11 -0
  41. package/dist/cdn/seki-sidebar-trigger.js +109 -0
  42. package/dist/cdn/seki-sidebar.d.ts +11 -0
  43. package/dist/cdn/seki-sidebar.js +380 -0
  44. package/dist/cdn/seki-skeleton.js +2 -2
  45. package/dist/cdn/seki-switch.js +1 -1
  46. package/dist/cdn/seki-tooltip.js +1 -1
  47. package/dist/cjs/{index-Dd6_-KaR.js → index-tQYksITZ.js} +426 -6
  48. package/dist/cjs/index.cjs.js +1115 -63
  49. package/dist/cjs/keyboard-Cjl5HYES.js +142 -0
  50. package/dist/cjs/loader.cjs.js +2 -2
  51. package/dist/cjs/seki-button.cjs.entry.js +1 -1
  52. package/dist/cjs/seki-card-action.cjs.entry.js +1 -1
  53. package/dist/cjs/seki-card-content.cjs.entry.js +1 -1
  54. package/dist/cjs/seki-card-description.cjs.entry.js +1 -1
  55. package/dist/cjs/seki-card-footer.cjs.entry.js +1 -1
  56. package/dist/cjs/seki-card-header.cjs.entry.js +1 -1
  57. package/dist/cjs/seki-card-title.cjs.entry.js +1 -1
  58. package/dist/cjs/seki-card.cjs.entry.js +1 -1
  59. package/dist/cjs/seki-field-description.cjs.entry.js +1 -1
  60. package/dist/cjs/seki-field-error.cjs.entry.js +1 -1
  61. package/dist/cjs/seki-field-group.cjs.entry.js +1 -1
  62. package/dist/cjs/seki-field-label.cjs.entry.js +1 -1
  63. package/dist/cjs/seki-field-legend.cjs.entry.js +1 -1
  64. package/dist/cjs/seki-field.cjs.entry.js +1 -1
  65. package/dist/cjs/seki-fieldset.cjs.entry.js +1 -1
  66. package/dist/cjs/seki-input.cjs.entry.js +1 -1
  67. package/dist/cjs/seki-select-content.cjs.entry.js +1 -1
  68. package/dist/cjs/seki-select-group.cjs.entry.js +1 -1
  69. package/dist/cjs/seki-select-option.cjs.entry.js +1 -1
  70. package/dist/cjs/seki-select-trigger.cjs.entry.js +1 -1
  71. package/dist/cjs/seki-select.cjs.entry.js +1 -1
  72. package/dist/cjs/seki-sidebar-content.cjs.entry.js +20 -0
  73. package/dist/cjs/seki-sidebar-footer.cjs.entry.js +20 -0
  74. package/dist/cjs/seki-sidebar-group.cjs.entry.js +105 -0
  75. package/dist/cjs/seki-sidebar-header.cjs.entry.js +20 -0
  76. package/dist/cjs/seki-sidebar-menu-item.cjs.entry.js +174 -0
  77. package/dist/cjs/seki-sidebar-menu-sub.cjs.entry.js +99 -0
  78. package/dist/cjs/seki-sidebar-menu.cjs.entry.js +20 -0
  79. package/dist/cjs/seki-sidebar-trigger.cjs.entry.js +86 -0
  80. package/dist/cjs/seki-sidebar.cjs.entry.js +342 -0
  81. package/dist/cjs/seki-skeleton.cjs.entry.js +2 -2
  82. package/dist/cjs/seki-switch.cjs.entry.js +81 -3
  83. package/dist/cjs/seki-tooltip.cjs.entry.js +1 -1
  84. package/dist/cjs/sekiui.cjs.js +2 -2
  85. package/dist/collection/collection-manifest.json +9 -0
  86. package/dist/collection/components/sidebar/seki-sidebar-content.css +82 -0
  87. package/dist/collection/components/sidebar/seki-sidebar-content.js +33 -0
  88. package/dist/collection/components/sidebar/seki-sidebar-footer.css +44 -0
  89. package/dist/collection/components/sidebar/seki-sidebar-footer.js +31 -0
  90. package/dist/collection/components/sidebar/seki-sidebar-group.css +158 -0
  91. package/dist/collection/components/sidebar/seki-sidebar-group.js +300 -0
  92. package/dist/collection/components/sidebar/seki-sidebar-header.css +44 -0
  93. package/dist/collection/components/sidebar/seki-sidebar-header.js +32 -0
  94. package/dist/collection/components/sidebar/seki-sidebar-menu-item.css +196 -0
  95. package/dist/collection/components/sidebar/seki-sidebar-menu-item.js +403 -0
  96. package/dist/collection/components/sidebar/seki-sidebar-menu-sub.css +357 -0
  97. package/dist/collection/components/sidebar/seki-sidebar-menu-sub.js +256 -0
  98. package/dist/collection/components/sidebar/seki-sidebar-menu.css +25 -0
  99. package/dist/collection/components/sidebar/seki-sidebar-menu.js +32 -0
  100. package/dist/collection/components/sidebar/seki-sidebar-trigger.css +68 -0
  101. package/dist/collection/components/sidebar/seki-sidebar-trigger.js +175 -0
  102. package/dist/collection/components/sidebar/seki-sidebar.css +352 -0
  103. package/dist/collection/components/sidebar/seki-sidebar.js +812 -0
  104. package/dist/collection/components/sidebar/types.js +18 -0
  105. package/dist/collection/components/skeleton/seki-skeleton.js +1 -1
  106. package/dist/collection/index.js +7 -0
  107. package/dist/collection/services/focus.js +192 -0
  108. package/dist/collection/services/index.js +7 -0
  109. package/dist/collection/services/keyboard.js +136 -0
  110. package/dist/collection/services/media-query.js +254 -0
  111. package/dist/collection/types.js +41 -0
  112. package/dist/collection/utils/a11y.js +291 -0
  113. package/dist/collection/utils/common.js +286 -0
  114. package/dist/components/index.js +1053 -91
  115. package/dist/components/p-BJCq8m2o.js +138 -0
  116. package/dist/components/{p-QhPshhKB.js → p-BzYKy7d3.js} +427 -7
  117. package/dist/components/p-DwTISp-i.js +111 -0
  118. package/dist/components/seki-button.js +1 -1
  119. package/dist/components/seki-card-action.js +1 -1
  120. package/dist/components/seki-card-content.js +1 -1
  121. package/dist/components/seki-card-description.js +1 -1
  122. package/dist/components/seki-card-footer.js +1 -1
  123. package/dist/components/seki-card-header.js +1 -1
  124. package/dist/components/seki-card-title.js +1 -1
  125. package/dist/components/seki-card.js +1 -1
  126. package/dist/components/seki-field-description.js +1 -1
  127. package/dist/components/seki-field-error.js +1 -1
  128. package/dist/components/seki-field-group.js +1 -1
  129. package/dist/components/seki-field-label.js +1 -1
  130. package/dist/components/seki-field-legend.js +1 -1
  131. package/dist/components/seki-field.js +1 -1
  132. package/dist/components/seki-fieldset.js +1 -1
  133. package/dist/components/seki-input.js +1 -1
  134. package/dist/components/seki-select-content.js +1 -1
  135. package/dist/components/seki-select-group.js +1 -1
  136. package/dist/components/seki-select-option.js +1 -1
  137. package/dist/components/seki-select-trigger.js +1 -1
  138. package/dist/components/seki-select.js +1 -1
  139. package/dist/components/seki-sidebar-content.d.ts +11 -0
  140. package/dist/components/seki-sidebar-content.js +38 -0
  141. package/dist/components/seki-sidebar-footer.d.ts +11 -0
  142. package/dist/components/seki-sidebar-footer.js +38 -0
  143. package/dist/components/seki-sidebar-group.d.ts +11 -0
  144. package/dist/components/seki-sidebar-group.js +131 -0
  145. package/dist/components/seki-sidebar-header.d.ts +11 -0
  146. package/dist/components/seki-sidebar-header.js +38 -0
  147. package/dist/components/seki-sidebar-menu-item.d.ts +11 -0
  148. package/dist/components/seki-sidebar-menu-item.js +200 -0
  149. package/dist/components/seki-sidebar-menu-sub.d.ts +11 -0
  150. package/dist/components/seki-sidebar-menu-sub.js +123 -0
  151. package/dist/components/seki-sidebar-menu.d.ts +11 -0
  152. package/dist/components/seki-sidebar-menu.js +38 -0
  153. package/dist/components/seki-sidebar-trigger.d.ts +11 -0
  154. package/dist/components/seki-sidebar-trigger.js +109 -0
  155. package/dist/components/seki-sidebar.d.ts +11 -0
  156. package/dist/components/seki-sidebar.js +380 -0
  157. package/dist/components/seki-skeleton.js +2 -2
  158. package/dist/components/seki-switch.js +1 -1
  159. package/dist/components/seki-tooltip.js +1 -1
  160. package/dist/esm/{index-CuXbV_yz.js → index-Dfzpqq0a.js} +426 -6
  161. package/dist/esm/index.js +1053 -63
  162. package/dist/esm/keyboard-BJCq8m2o.js +138 -0
  163. package/dist/esm/loader.js +3 -3
  164. package/dist/esm/seki-button.entry.js +1 -1
  165. package/dist/esm/seki-card-action.entry.js +1 -1
  166. package/dist/esm/seki-card-content.entry.js +1 -1
  167. package/dist/esm/seki-card-description.entry.js +1 -1
  168. package/dist/esm/seki-card-footer.entry.js +1 -1
  169. package/dist/esm/seki-card-header.entry.js +1 -1
  170. package/dist/esm/seki-card-title.entry.js +1 -1
  171. package/dist/esm/seki-card.entry.js +1 -1
  172. package/dist/esm/seki-field-description.entry.js +1 -1
  173. package/dist/esm/seki-field-error.entry.js +1 -1
  174. package/dist/esm/seki-field-group.entry.js +1 -1
  175. package/dist/esm/seki-field-label.entry.js +1 -1
  176. package/dist/esm/seki-field-legend.entry.js +1 -1
  177. package/dist/esm/seki-field.entry.js +1 -1
  178. package/dist/esm/seki-fieldset.entry.js +1 -1
  179. package/dist/esm/seki-input.entry.js +1 -1
  180. package/dist/esm/seki-select-content.entry.js +1 -1
  181. package/dist/esm/seki-select-group.entry.js +1 -1
  182. package/dist/esm/seki-select-option.entry.js +1 -1
  183. package/dist/esm/seki-select-trigger.entry.js +1 -1
  184. package/dist/esm/seki-select.entry.js +1 -1
  185. package/dist/esm/seki-sidebar-content.entry.js +18 -0
  186. package/dist/esm/seki-sidebar-footer.entry.js +18 -0
  187. package/dist/esm/seki-sidebar-group.entry.js +103 -0
  188. package/dist/esm/seki-sidebar-header.entry.js +18 -0
  189. package/dist/esm/seki-sidebar-menu-item.entry.js +172 -0
  190. package/dist/esm/seki-sidebar-menu-sub.entry.js +97 -0
  191. package/dist/esm/seki-sidebar-menu.entry.js +18 -0
  192. package/dist/esm/seki-sidebar-trigger.entry.js +84 -0
  193. package/dist/esm/seki-sidebar.entry.js +340 -0
  194. package/dist/esm/seki-skeleton.entry.js +2 -2
  195. package/dist/esm/seki-switch.entry.js +84 -2
  196. package/dist/esm/seki-tooltip.entry.js +1 -1
  197. package/dist/esm/sekiui.js +3 -3
  198. package/dist/sekiui/index.esm.js +1 -1
  199. package/dist/sekiui/p-01cfb4e7.entry.js +1 -0
  200. package/dist/sekiui/{p-97e6e5ce.entry.js → p-042ec460.entry.js} +1 -1
  201. package/dist/sekiui/{p-3e088b5a.entry.js → p-10c008fc.entry.js} +1 -1
  202. package/dist/sekiui/{p-ed440425.entry.js → p-352bd295.entry.js} +1 -1
  203. package/dist/sekiui/p-37c5f4d9.entry.js +1 -0
  204. package/dist/sekiui/p-40fb71d6.entry.js +1 -0
  205. package/dist/sekiui/{p-eefbc037.entry.js → p-44191aed.entry.js} +1 -1
  206. package/dist/sekiui/{p-81709fc2.entry.js → p-4423d621.entry.js} +1 -1
  207. package/dist/sekiui/{p-9af5286b.entry.js → p-4636588f.entry.js} +1 -1
  208. package/dist/sekiui/p-471b97a5.entry.js +1 -0
  209. package/dist/sekiui/{p-0544d787.entry.js → p-56f0d754.entry.js} +1 -1
  210. package/dist/sekiui/{p-43f7c542.entry.js → p-5bc0f5aa.entry.js} +1 -1
  211. package/dist/sekiui/{p-eedf44b5.entry.js → p-6164cd8a.entry.js} +1 -1
  212. package/dist/sekiui/{p-b479935d.entry.js → p-635f4098.entry.js} +1 -1
  213. package/dist/sekiui/{p-35f8f9c4.entry.js → p-743fc6d9.entry.js} +1 -1
  214. package/dist/sekiui/p-83e65cbe.entry.js +1 -0
  215. package/dist/sekiui/p-8b7bd061.entry.js +1 -0
  216. package/dist/sekiui/{p-009183ab.entry.js → p-8d9a4878.entry.js} +1 -1
  217. package/dist/sekiui/p-9cb9cdfe.entry.js +1 -0
  218. package/dist/sekiui/p-9f2d95d7.entry.js +1 -0
  219. package/dist/sekiui/p-BJCq8m2o.js +1 -0
  220. package/dist/sekiui/p-Dfzpqq0a.js +2 -0
  221. package/dist/sekiui/{p-b22df79e.entry.js → p-a1a71958.entry.js} +1 -1
  222. package/dist/sekiui/{p-dd1e3e87.entry.js → p-a71e0c55.entry.js} +1 -1
  223. package/dist/sekiui/{p-1607dc4d.entry.js → p-ae227955.entry.js} +1 -1
  224. package/dist/sekiui/{p-cf11115c.entry.js → p-b365f5fb.entry.js} +1 -1
  225. package/dist/sekiui/{p-6bde807e.entry.js → p-b387a2a5.entry.js} +1 -1
  226. package/dist/sekiui/p-b8590f4d.entry.js +1 -0
  227. package/dist/sekiui/{p-88f91658.entry.js → p-c98b6d6a.entry.js} +1 -1
  228. package/dist/sekiui/{p-e71ad432.entry.js → p-d73cdb9a.entry.js} +1 -1
  229. package/dist/sekiui/{p-b10d81a6.entry.js → p-d96e770e.entry.js} +1 -1
  230. package/dist/sekiui/p-e62dd89b.entry.js +1 -0
  231. package/dist/sekiui/{p-d4c92041.entry.js → p-e7bb140c.entry.js} +1 -1
  232. package/dist/sekiui/{p-6e238adf.entry.js → p-eecc18f3.entry.js} +1 -1
  233. package/dist/sekiui/{p-4d57c6ea.entry.js → p-ff636955.entry.js} +1 -1
  234. package/dist/sekiui/sekiui.esm.js +1 -1
  235. package/dist/types/components/sidebar/seki-sidebar-content.d.ts +18 -0
  236. package/dist/types/components/sidebar/seki-sidebar-footer.d.ts +16 -0
  237. package/dist/types/components/sidebar/seki-sidebar-group.d.ts +81 -0
  238. package/dist/types/components/sidebar/seki-sidebar-header.d.ts +17 -0
  239. package/dist/types/components/sidebar/seki-sidebar-menu-item.d.ts +104 -0
  240. package/dist/types/components/sidebar/seki-sidebar-menu-sub.d.ts +81 -0
  241. package/dist/types/components/sidebar/seki-sidebar-menu.d.ts +17 -0
  242. package/dist/types/components/sidebar/seki-sidebar-trigger.d.ts +53 -0
  243. package/dist/types/components/sidebar/seki-sidebar.d.ts +185 -0
  244. package/dist/types/components/sidebar/types.d.ts +245 -0
  245. package/dist/types/components.d.ts +508 -0
  246. package/dist/types/index.d.ts +4 -0
  247. package/dist/types/services/focus.d.ts +74 -0
  248. package/dist/types/services/index.d.ts +7 -0
  249. package/dist/types/services/keyboard.d.ts +74 -0
  250. package/dist/types/services/media-query.d.ts +121 -0
  251. package/dist/types/types.d.ts +105 -0
  252. package/dist/types/utils/a11y.d.ts +130 -0
  253. package/dist/types/utils/common.d.ts +142 -0
  254. package/package.json +2 -1
  255. package/dist/sekiui/p-9fe07f6e.entry.js +0 -1
  256. 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
+ }