@birdapi/velinstyle 0.8.0 → 0.9.0

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 (313) hide show
  1. package/README.de.md +135 -246
  2. package/README.md +134 -245
  3. package/cli/blueprints/onboarding.html +2 -2
  4. package/cli/cli-manifest.json +97 -0
  5. package/cli/docgen/extract-a11y.js +89 -0
  6. package/cli/docgen/extract-attributes.js +95 -0
  7. package/cli/docgen/extract-cli.js +50 -0
  8. package/cli/docgen/extract-components.js +175 -0
  9. package/cli/docgen/extract-meta.js +32 -0
  10. package/cli/docgen/extract-rules.js +76 -0
  11. package/cli/docgen/extract-tokens.js +79 -0
  12. package/cli/docgen/extract-utilities.js +75 -0
  13. package/cli/docgen/markdown.js +43 -0
  14. package/cli/docs-generate.js +196 -0
  15. package/cli/index.js +121 -5
  16. package/cli/meta.js +97 -0
  17. package/cli/perf-audit.js +174 -0
  18. package/cli/pii-scanner.js +134 -0
  19. package/cli/scanner-rules-data.js +62 -0
  20. package/cli/scanner.js +68 -1
  21. package/cli/search-index.js +163 -0
  22. package/cli/tokens-build.js +21 -1
  23. package/cli/tokens-validate.js +69 -0
  24. package/components/a11y-entry.js +42 -0
  25. package/components/a11y-utils.js +71 -0
  26. package/components/focus-manager.js +45 -8
  27. package/components/index.js +22 -12
  28. package/components/runtime/component-loaders.js +43 -0
  29. package/components/runtime/index.js +77 -0
  30. package/components/runtime-entry.js +10 -0
  31. package/components/sanitize.js +135 -54
  32. package/components/velin-accordion.js +12 -0
  33. package/components/velin-bottom-nav.js +19 -0
  34. package/components/velin-carousel.js +2 -2
  35. package/components/velin-code-block.js +142 -0
  36. package/components/velin-command.js +15 -0
  37. package/components/velin-copy.js +18 -5
  38. package/components/velin-counter.js +3 -0
  39. package/components/velin-dialog.js +9 -1
  40. package/components/velin-email.js +94 -0
  41. package/components/velin-icon.js +65 -22
  42. package/components/velin-lightbox.js +8 -3
  43. package/components/velin-live-dot.js +5 -0
  44. package/components/velin-modal.js +6 -3
  45. package/components/velin-popover.js +34 -2
  46. package/components/velin-reveal.js +7 -65
  47. package/components/velin-scroll-top.js +1 -1
  48. package/components/velin-search.js +267 -0
  49. package/components/velin-secure-field.js +99 -0
  50. package/components/velin-sparkline.js +27 -7
  51. package/components/{velin-stepper-wc.js → velin-stepper.js} +37 -8
  52. package/components/velin-theme-toggle.js +12 -5
  53. package/components/velin-toast.js +4 -0
  54. package/components/{velin-tooltip-wc.js → velin-tooltip.js} +22 -3
  55. package/core/a11y/component-contracts.json +221 -0
  56. package/core/attributes/index.js +6 -0
  57. package/core/attributes/registry.js +268 -0
  58. package/core/highlight/highlight.js +114 -0
  59. package/core/highlight/index.js +29 -0
  60. package/core/highlight/languages/_utils.js +52 -0
  61. package/core/highlight/languages/blade.js +16 -0
  62. package/core/highlight/languages/css.js +26 -0
  63. package/core/highlight/languages/html.js +17 -0
  64. package/core/highlight/languages/js.js +58 -0
  65. package/core/highlight/languages/json.js +14 -0
  66. package/core/highlight/languages/markdown.js +16 -0
  67. package/core/highlight/languages/php.js +39 -0
  68. package/core/highlight/languages/plain.js +7 -0
  69. package/core/highlight/languages/shell.js +33 -0
  70. package/core/highlight/languages/sql.js +20 -0
  71. package/core/highlight/languages/typescript.js +28 -0
  72. package/core/highlight/observe.js +53 -0
  73. package/core/highlight/registry.js +107 -0
  74. package/core/highlight/render.js +35 -0
  75. package/core/highlight/types.js +28 -0
  76. package/core/meta/build.js +293 -0
  77. package/core/meta/index.js +17 -0
  78. package/core/meta/schema.js +49 -0
  79. package/core/motion/effects.js +54 -0
  80. package/core/motion/index.js +76 -0
  81. package/core/motion/scheduler.js +72 -0
  82. package/core/motion/scroll.js +32 -0
  83. package/core/motion/stagger.js +24 -0
  84. package/core/search/docs-url.js +96 -0
  85. package/core/search/engine.js +139 -0
  86. package/core/search/highlight.js +48 -0
  87. package/core/search/index.js +126 -0
  88. package/core/search/providers.js +36 -0
  89. package/core/search/types.js +61 -0
  90. package/core/search/worker-client.js +56 -0
  91. package/core/search/worker.js +20 -0
  92. package/dist/chunks/a11y-utils-IRC7LOPF.js +16 -0
  93. package/dist/chunks/attributes-3BZLI7XF.js +364 -0
  94. package/dist/chunks/attributes-4N5JP4CG.js +364 -0
  95. package/dist/chunks/attributes-5Q2WXGRC.js +374 -0
  96. package/dist/chunks/attributes-AIR7SVLK.js +364 -0
  97. package/dist/chunks/attributes-BGRN5XZO.js +364 -0
  98. package/dist/chunks/attributes-BVSQAYLR.js +364 -0
  99. package/dist/chunks/attributes-CDYFUY7Y.js +364 -0
  100. package/dist/chunks/attributes-JMYJZBPE.js +364 -0
  101. package/dist/chunks/attributes-R5JY3X4O.js +374 -0
  102. package/dist/chunks/attributes-STYCS3O5.js +391 -0
  103. package/dist/chunks/attributes-TNSPQTHX.js +374 -0
  104. package/dist/chunks/attributes-TPANLMLF.js +391 -0
  105. package/dist/chunks/blade-LIQLVKZQ.js +19 -0
  106. package/dist/chunks/browser-MRKYZPEH.js +1149 -0
  107. package/dist/chunks/chunk-2354NEBP.js +109 -0
  108. package/dist/chunks/chunk-2VNA7G7S.js +109 -0
  109. package/dist/chunks/chunk-42VVPW6N.js +132 -0
  110. package/dist/chunks/chunk-7FFAMRFV.js +44 -0
  111. package/dist/chunks/chunk-7MCGPHVC.js +69 -0
  112. package/dist/chunks/chunk-AQLRJSGG.js +109 -0
  113. package/dist/chunks/chunk-EWPSDMQT.js +109 -0
  114. package/dist/chunks/chunk-F7OLHTGX.js +22 -0
  115. package/dist/chunks/chunk-J472ERN2.js +242 -0
  116. package/dist/chunks/chunk-LA7264K4.js +85 -0
  117. package/dist/chunks/chunk-M5CHQVP6.js +61 -0
  118. package/dist/chunks/chunk-MKZP2TBH.js +89 -0
  119. package/dist/chunks/chunk-QTJJ3DPO.js +109 -0
  120. package/dist/chunks/chunk-RDE3QE7Z.js +89 -0
  121. package/dist/chunks/chunk-RSMRR27R.js +109 -0
  122. package/dist/chunks/chunk-RZQVWFHW.js +109 -0
  123. package/dist/chunks/chunk-SRL6EWHP.js +109 -0
  124. package/dist/chunks/chunk-U4XHMZ7D.js +109 -0
  125. package/dist/chunks/chunk-V5YIUA6G.js +109 -0
  126. package/dist/chunks/chunk-XIET2VSZ.js +109 -0
  127. package/dist/chunks/chunk-XYLJ2GCB.js +205 -0
  128. package/dist/chunks/chunk-Z4JYQ2XX.js +51 -0
  129. package/dist/chunks/chunk-ZPSPKUYD.js +109 -0
  130. package/dist/chunks/css-MQ5YU77P.js +28 -0
  131. package/dist/chunks/highlight-GJHHV4KM.js +37 -0
  132. package/dist/chunks/highlight-XTHRKBA3.js +37 -0
  133. package/dist/chunks/html-ZODC4DLQ.js +20 -0
  134. package/dist/chunks/js-MNSNYTJU.js +7 -0
  135. package/dist/chunks/json-VEM2HFH3.js +17 -0
  136. package/dist/chunks/markdown-E7OPGS3S.js +19 -0
  137. package/dist/chunks/php-MLL7T3LT.js +41 -0
  138. package/dist/chunks/plain-LJJDC5TN.js +8 -0
  139. package/dist/chunks/runtime-entry.js +14 -0
  140. package/dist/chunks/shell-QTRK4U4E.js +35 -0
  141. package/dist/chunks/sql-PPWLHGJ2.js +23 -0
  142. package/dist/chunks/typescript-JT3CCYWL.js +30 -0
  143. package/dist/chunks/velin-accordion-FBIPAEXG.js +105 -0
  144. package/dist/chunks/velin-accordion-QAQO7BI7.js +117 -0
  145. package/dist/chunks/velin-announcer-PVTXXCWR.js +33 -0
  146. package/dist/chunks/velin-bottom-nav-2N2TYWOF.js +86 -0
  147. package/dist/chunks/velin-bottom-nav-7GEMPIM6.js +86 -0
  148. package/dist/chunks/velin-bottom-nav-CTU6FFZV.js +101 -0
  149. package/dist/chunks/velin-bottom-nav-N3DFQR66.js +86 -0
  150. package/dist/chunks/velin-carousel-Y5VCOABZ.js +213 -0
  151. package/dist/chunks/velin-carousel-ZTBANFQQ.js +213 -0
  152. package/dist/chunks/velin-code-block-ASUBTAQV.js +119 -0
  153. package/dist/chunks/velin-code-block-CIAURDRJ.js +132 -0
  154. package/dist/chunks/velin-code-block-TSUKPY4C.js +123 -0
  155. package/dist/chunks/velin-code-block-Y6CM726Q.js +119 -0
  156. package/dist/chunks/velin-collapse-5XYBUIID.js +80 -0
  157. package/dist/chunks/velin-combobox-2KDTCQ7N.js +155 -0
  158. package/dist/chunks/velin-combobox-NXS4QB4R.js +155 -0
  159. package/dist/chunks/velin-combobox-UGA56HP3.js +155 -0
  160. package/dist/chunks/velin-command-CKDNO4BW.js +148 -0
  161. package/dist/chunks/velin-command-M7RNHUPR.js +136 -0
  162. package/dist/chunks/velin-command-ONKO2A3J.js +136 -0
  163. package/dist/chunks/velin-command-TMLSRGIU.js +136 -0
  164. package/dist/chunks/velin-copy-G2TZ5YBB.js +81 -0
  165. package/dist/chunks/velin-copy-NYDBOKXT.js +81 -0
  166. package/dist/chunks/velin-copy-T5KDTDXP.js +81 -0
  167. package/dist/chunks/velin-countdown-4ZVDGK4R.js +101 -0
  168. package/dist/chunks/velin-countdown-5XSTN3BH.js +101 -0
  169. package/dist/chunks/velin-countdown-7AIY66CU.js +101 -0
  170. package/dist/chunks/velin-counter-ISCTEAHI.js +118 -0
  171. package/dist/chunks/velin-counter-L5FYJF7A.js +121 -0
  172. package/dist/chunks/velin-dialog-D2GUHSCD.js +144 -0
  173. package/dist/chunks/velin-dialog-ELFOZIXZ.js +152 -0
  174. package/dist/chunks/velin-dialog-GTTZI564.js +147 -0
  175. package/dist/chunks/velin-dialog-IJLFDXAJ.js +144 -0
  176. package/dist/chunks/velin-dialog-TNS3WXMB.js +144 -0
  177. package/dist/chunks/velin-drawer-BE6TYN3H.js +130 -0
  178. package/dist/chunks/velin-drawer-DLL2GLT6.js +130 -0
  179. package/dist/chunks/velin-drawer-MJS6WCF7.js +130 -0
  180. package/dist/chunks/velin-dropdown-PLUIFA5M.js +172 -0
  181. package/dist/chunks/velin-email-N5B5ZYTE.js +90 -0
  182. package/dist/chunks/velin-email-NTM6DG3V.js +90 -0
  183. package/dist/chunks/velin-email-RBX6ZR4S.js +90 -0
  184. package/dist/chunks/velin-haptic-4QQTM3RK.js +50 -0
  185. package/dist/chunks/velin-icon-7M76DOPB.js +164 -0
  186. package/dist/chunks/velin-icon-KZO3IEHG.js +164 -0
  187. package/dist/chunks/velin-icon-WTFPG2DD.js +191 -0
  188. package/dist/chunks/velin-icon-ZTSKUOVN.js +193 -0
  189. package/dist/chunks/velin-lightbox-6E7CAATP.js +149 -0
  190. package/dist/chunks/velin-lightbox-I75U6JF4.js +140 -0
  191. package/dist/chunks/velin-lightbox-QOTB4PRL.js +140 -0
  192. package/dist/chunks/velin-lightbox-UCD6N7HF.js +140 -0
  193. package/dist/chunks/velin-live-dot-RJHX2DGY.js +74 -0
  194. package/dist/chunks/velin-live-dot-XHUZCIDZ.js +67 -0
  195. package/dist/chunks/velin-menubar-I426AED2.js +86 -0
  196. package/dist/chunks/velin-menubar-J66JXRWW.js +86 -0
  197. package/dist/chunks/velin-menubar-SFEXJE56.js +86 -0
  198. package/dist/chunks/velin-modal-2HOBXE6F.js +176 -0
  199. package/dist/chunks/velin-modal-5HYNNNUI.js +176 -0
  200. package/dist/chunks/velin-modal-AEOTZ67O.js +177 -0
  201. package/dist/chunks/velin-modal-GLI2XZZB.js +176 -0
  202. package/dist/chunks/velin-persist-DFPL3QP4.js +110 -0
  203. package/dist/chunks/velin-popover-EXINRIGJ.js +145 -0
  204. package/dist/chunks/velin-popover-HQW6Q2Y6.js +172 -0
  205. package/dist/chunks/velin-popover-OXX7MRZ6.js +145 -0
  206. package/dist/chunks/velin-popover-T72HK3DF.js +151 -0
  207. package/dist/chunks/velin-popover-YKCCROJG.js +145 -0
  208. package/dist/chunks/velin-progress-ring-E4YRLZHH.js +66 -0
  209. package/dist/chunks/velin-rating-6CDBI6DG.js +91 -0
  210. package/dist/chunks/velin-rating-N4DI2XNL.js +91 -0
  211. package/dist/chunks/velin-rating-XRQJV4LO.js +91 -0
  212. package/dist/chunks/velin-scroll-top-F2FCZNYD.js +58 -0
  213. package/dist/chunks/velin-scroll-top-OHYVVHHY.js +58 -0
  214. package/dist/chunks/velin-scrollspy-OKCT4FDV.js +50 -0
  215. package/dist/chunks/velin-search-2XRQFBCS.js +556 -0
  216. package/dist/chunks/velin-search-4YHJWYSS.js +556 -0
  217. package/dist/chunks/velin-search-7B4KJ6TX.js +557 -0
  218. package/dist/chunks/velin-search-LWHY23Q5.js +474 -0
  219. package/dist/chunks/velin-secure-field-6PV7RZDJ.js +95 -0
  220. package/dist/chunks/velin-secure-field-H7FSBUJW.js +95 -0
  221. package/dist/chunks/velin-secure-field-OFNAXCDP.js +95 -0
  222. package/dist/chunks/velin-segmented-control-EKL4E3BL.js +109 -0
  223. package/dist/chunks/velin-segmented-control-GYD5BIXQ.js +109 -0
  224. package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +109 -0
  225. package/dist/chunks/velin-sheet-3OGI4HD7.js +119 -0
  226. package/dist/chunks/velin-sheet-OUK6572T.js +119 -0
  227. package/dist/chunks/velin-sheet-R2Y67X6H.js +119 -0
  228. package/dist/chunks/velin-sparkline-GVEANJVW.js +184 -0
  229. package/dist/chunks/velin-sparkline-KDA3GQAN.js +162 -0
  230. package/dist/chunks/velin-sparkline-XVTFC6GE.js +181 -0
  231. package/dist/chunks/velin-stepper-2ZIHLKGM.js +141 -0
  232. package/dist/chunks/velin-stepper-7ECRUYHD.js +148 -0
  233. package/dist/chunks/velin-stepper-FYEUEWE6.js +141 -0
  234. package/dist/chunks/velin-stepper-X54WGHDG.js +141 -0
  235. package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +133 -0
  236. package/dist/chunks/velin-tabs-5UQ5ZVMV.js +138 -0
  237. package/dist/chunks/velin-theme-toggle-32J2CBNC.js +291 -0
  238. package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +285 -0
  239. package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +285 -0
  240. package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +291 -0
  241. package/dist/chunks/velin-toast-OMMOA2DK.js +109 -0
  242. package/dist/chunks/velin-toast-RCZ4CSUC.js +109 -0
  243. package/dist/chunks/velin-toast-RSF2IHT3.js +113 -0
  244. package/dist/chunks/velin-toast-WEUBTJG6.js +109 -0
  245. package/dist/chunks/velin-tooltip-664IOHZA.js +112 -0
  246. package/dist/chunks/velin-tooltip-BDHGRTQB.js +112 -0
  247. package/dist/chunks/velin-tooltip-HVUOKNVX.js +112 -0
  248. package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +103 -0
  249. package/dist/chunks/worker-client-R6DBYPFT.js +46 -0
  250. package/dist/llms.test.txt +40 -0
  251. package/dist/llms.txt +40 -0
  252. package/dist/search-index.json +1773 -0
  253. package/dist/search-index.test.json +1773 -0
  254. package/dist/themes/brutalist.min.css +1 -1
  255. package/dist/themes/midnight.min.css +1 -1
  256. package/dist/themes/pastel.min.css +1 -1
  257. package/dist/themes/retro.min.css +1 -1
  258. package/dist/themes/sunset.min.css +1 -1
  259. package/dist/velin-agent.json +684 -0
  260. package/dist/velin-agent.test.json +684 -0
  261. package/dist/velinstyle-components.iife.js +7715 -3515
  262. package/dist/velinstyle-components.js +7087 -2859
  263. package/dist/velinstyle-components.min.js +196 -109
  264. package/dist/velinstyle.css +604 -65
  265. package/dist/velinstyle.d.ts +54 -0
  266. package/dist/velinstyle.min.css +1 -1
  267. package/examples/search-index.schema.json +33 -0
  268. package/package.json +56 -10
  269. package/src/a11y/authentication.css +37 -0
  270. package/src/a11y/consistent-help.css +22 -0
  271. package/src/a11y/dragging-alternatives.css +30 -0
  272. package/src/a11y/focus-appearance.css +20 -0
  273. package/src/a11y/high-contrast-aaa.css +16 -26
  274. package/src/a11y/skip-link.css +3 -1
  275. package/src/a11y/sr-only.css +1 -1
  276. package/src/base/reset.css +0 -36
  277. package/src/base/wc-placeholder.css +102 -0
  278. package/src/components/attributes.css +42 -0
  279. package/src/components/highlight.css +182 -0
  280. package/src/components/input-group.css +1 -2
  281. package/src/components/nav.css +2 -3
  282. package/src/components/search.css +95 -0
  283. package/src/components/stepper.css +2 -4
  284. package/src/components/timeline.css +1 -2
  285. package/src/components/tooltip.css +2 -4
  286. package/src/layout/container.css +5 -5
  287. package/src/layout/grid.css +8 -8
  288. package/src/layout/patterns.css +5 -5
  289. package/src/themes/brutalist.css +2 -11
  290. package/src/themes/corporate.css +0 -9
  291. package/src/themes/earth.css +0 -9
  292. package/src/themes/forest.css +0 -9
  293. package/src/themes/midnight.css +1 -10
  294. package/src/themes/neon.css +0 -9
  295. package/src/themes/nordic.css +0 -9
  296. package/src/themes/ocean.css +0 -9
  297. package/src/themes/pastel.css +1 -10
  298. package/src/themes/retro.css +2 -11
  299. package/src/themes/sharp.css +0 -7
  300. package/src/themes/soft.css +0 -7
  301. package/src/themes/sunset.css +1 -10
  302. package/src/tokens/color.css +45 -46
  303. package/src/tokens/fonts.css +10 -0
  304. package/src/tokens/typography.css +4 -5
  305. package/src/utilities/animation.css +2 -2
  306. package/src/utilities/display.css +3 -3
  307. package/src/utilities/divide.css +2 -6
  308. package/src/utilities/responsive.css +4 -4
  309. package/src/utilities/safe-area.css +1 -1
  310. package/src/utilities/scroll-animation.css +34 -0
  311. package/src/utilities/spacing.css +17 -8
  312. package/src/utilities/text.css +4 -1
  313. package/src/velinstyle.css +9 -0
@@ -98,14 +98,17 @@ class VelinModal extends HTMLElement {
98
98
  }
99
99
 
100
100
  connectedCallback() {
101
- const title = this.getAttribute('title') || '';
101
+ const title = (this.getAttribute('title') || '').trim();
102
102
  const safeTitle = escapeHTML(title);
103
+ const dialogLabel = title
104
+ ? 'aria-labelledby="velin-modal-title"'
105
+ : `aria-label="${escapeHTML(this.getAttribute('aria-label') || 'Dialog')}"`;
103
106
  this.shadowRoot.innerHTML = `
104
107
  <style>${styles}</style>
105
108
  <div class="overlay" part="overlay">
106
- <div class="dialog" role="dialog" aria-modal="true" aria-labelledby="velin-modal-title" part="dialog">
109
+ <div class="dialog" role="dialog" aria-modal="true" ${dialogLabel} part="dialog">
107
110
  <div class="header" part="header">
108
- <h2 class="title" id="velin-modal-title">${safeTitle}</h2>
111
+ <h2 class="title" id="velin-modal-title"${title ? '' : ' class="velin-sr-only"'}>${safeTitle || 'Dialog'}</h2>
109
112
  <button class="close-btn" aria-label="Close" part="close">&#215;</button>
110
113
  </div>
111
114
  <div class="body" part="body"><slot></slot></div>
@@ -50,24 +50,52 @@ class VelinPopover extends HTMLElement {
50
50
  const role = triggerType === 'hover' ? 'tooltip' : 'dialog';
51
51
  this._isDialog = role === 'dialog';
52
52
 
53
+ const titleId = title ? `${this._popoverId}-title` : '';
53
54
  this.shadowRoot.innerHTML = `
54
55
  <style>${styles}</style>
55
56
  <slot name="trigger"></slot>
56
- <div class="popover popover--${placement}" id="${this._popoverId}" role="${role}" part="popover">
57
- ${title ? `<div class="popover__title" part="title">${escapeHTML(title)}</div>` : ''}
57
+ <div class="popover popover--${placement}" id="${this._popoverId}" role="${role}" part="popover"${titleId ? ` aria-labelledby="${titleId}"` : ''}>
58
+ ${title ? `<div class="popover__title" id="${titleId}" part="title">${escapeHTML(title)}</div>` : ''}
58
59
  <slot></slot>
59
60
  </div>
60
61
  `;
62
+ const popoverEl = this.shadowRoot.querySelector('.popover');
63
+ if (!title && popoverEl) {
64
+ const fallback =
65
+ this.getAttribute('aria-label') ||
66
+ (role === 'tooltip' ? 'Tooltip' : 'Popover');
67
+ popoverEl.setAttribute('aria-label', fallback);
68
+ }
61
69
 
62
70
  const triggerSlot = this.shadowRoot.querySelector('slot[name="trigger"]');
63
71
  triggerSlot.addEventListener('slotchange', () => this._wireTrigger(triggerType));
64
72
  this._wireTrigger(triggerType);
65
73
  }
66
74
 
75
+ _ensureTriggerInteractive(trigger) {
76
+ const tag = trigger.tagName;
77
+ if (tag !== 'BUTTON' && tag !== 'A' && trigger.getAttribute('role') !== 'button') {
78
+ trigger.setAttribute('role', 'button');
79
+ if (!trigger.hasAttribute('tabindex')) trigger.setAttribute('tabindex', '0');
80
+ }
81
+ }
82
+
83
+ _onTriggerKey(e) {
84
+ if (e.key === 'Escape') {
85
+ this.close();
86
+ return;
87
+ }
88
+ if (e.key === 'Enter' || e.key === ' ') {
89
+ e.preventDefault();
90
+ this.toggle();
91
+ }
92
+ }
93
+
67
94
  _wireTrigger(triggerType) {
68
95
  const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
69
96
  if (!trigger) return;
70
97
 
98
+ this._ensureTriggerInteractive(trigger);
71
99
  const isHover = triggerType === 'hover';
72
100
  trigger.setAttribute('aria-haspopup', isHover ? 'true' : 'dialog');
73
101
  trigger.setAttribute('aria-expanded', this.hasAttribute('open') ? 'true' : 'false');
@@ -75,6 +103,10 @@ class VelinPopover extends HTMLElement {
75
103
  trigger.setAttribute('aria-controls', this._popoverId);
76
104
  }
77
105
 
106
+ trigger.removeEventListener('keydown', this._onTriggerKeyBound);
107
+ this._onTriggerKeyBound = this._onTriggerKeyBound || this._onTriggerKey.bind(this);
108
+ trigger.addEventListener('keydown', this._onTriggerKeyBound);
109
+
78
110
  if (triggerType === 'click') {
79
111
  trigger.onclick = () => this.toggle();
80
112
  } else if (triggerType === 'hover') {
@@ -1,73 +1,15 @@
1
1
  /*
2
- * velin-reveal.js — reusable scroll-into-view reveal helper.
3
- *
4
- * Replaces inline IntersectionObserver snippets that every project ends up
5
- * copy-pasting. Just call `initReveal()` once, or opt into auto-init by
6
- * adding `data-velin-reveal-auto` to <html>. Elements with the class
7
- * `.velin-animate-on-scroll` get `is-visible` toggled when they enter the
8
- * viewport.
9
- *
10
- * Honours `prefers-reduced-motion: reduce` by revealing immediately without
11
- * animation, and falls back to immediate reveal if IntersectionObserver is
12
- * unavailable.
13
- *
14
- * API:
15
- * initReveal(options?) -> () => void teardown function.
16
- * options.selector CSS selector (default '.velin-animate-on-scroll').
17
- * options.threshold IO threshold (default 0.1).
18
- * options.rootMargin IO rootMargin (default '0px 0px -40px 0px').
19
- * options.once if true (default) stops observing once visible.
20
- * options.visibleClass class added on intersect (default 'is-visible').
2
+ * velin-reveal.js — scroll-into-view reveal (delegates to core/motion).
21
3
  */
4
+ import { initMotion, initReveal as motionInitReveal, velinMotion } from '../core/motion/index.js';
22
5
 
23
- const DEFAULTS = {
24
- selector: '.velin-animate-on-scroll',
25
- threshold: 0.1,
26
- rootMargin: '0px 0px -40px 0px',
27
- once: true,
28
- visibleClass: 'is-visible',
29
- };
30
-
31
- const _activeObservers = new WeakMap();
32
-
33
- export function initReveal(options = {}) {
34
- if (typeof document === 'undefined') return () => {};
35
- const opts = { ...DEFAULTS, ...options };
36
- const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
37
- const targets = Array.from(document.querySelectorAll(opts.selector));
38
-
39
- if (reduced || typeof IntersectionObserver === 'undefined') {
40
- targets.forEach((el) => el.classList.add(opts.visibleClass));
41
- return () => {};
42
- }
43
-
44
- const observer = new IntersectionObserver(
45
- (entries) => {
46
- for (const entry of entries) {
47
- if (!entry.isIntersecting) continue;
48
- entry.target.classList.add(opts.visibleClass);
49
- if (opts.once) observer.unobserve(entry.target);
50
- }
51
- },
52
- { threshold: opts.threshold, rootMargin: opts.rootMargin },
53
- );
54
-
55
- targets.forEach((el) => {
56
- if (_activeObservers.has(el)) return;
57
- _activeObservers.set(el, observer);
58
- observer.observe(el);
59
- });
60
-
61
- return () => {
62
- observer.disconnect();
63
- targets.forEach((el) => _activeObservers.delete(el));
64
- };
65
- }
6
+ export { initMotion, velinMotion };
7
+ export const initReveal = motionInitReveal;
66
8
 
67
9
  if (typeof document !== 'undefined') {
68
10
  const autoInit = () => {
69
- if (document.documentElement && document.documentElement.hasAttribute('data-velin-reveal-auto')) {
70
- initReveal();
11
+ if (document.documentElement?.hasAttribute('data-velin-reveal-auto')) {
12
+ initMotion();
71
13
  }
72
14
  };
73
15
  if (document.readyState === 'loading') {
@@ -77,4 +19,4 @@ if (typeof document !== 'undefined') {
77
19
  }
78
20
  }
79
21
 
80
- export default { initReveal };
22
+ export default { initReveal, initMotion };
@@ -30,7 +30,7 @@ class VelinScrollTop extends HTMLElement {
30
30
  this.shadowRoot.innerHTML = `
31
31
  <style>${styles}</style>
32
32
  <button part="button" aria-label="Scroll to top">
33
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
33
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
34
34
  <line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/>
35
35
  </svg>
36
36
  </button>
@@ -0,0 +1,267 @@
1
+ import { createSearch, highlightHtml, SEARCH_CATEGORIES, resolveDocsSearchUrl } from '../core/search/index.js';
2
+ import { escapeHTMLAttribute } from './sanitize.js';
3
+
4
+ const CATEGORY_LABELS = {
5
+ docs: 'Documentation',
6
+ components: 'Components',
7
+ api: 'API',
8
+ examples: 'Examples',
9
+ };
10
+
11
+ class VelinSearch extends HTMLElement {
12
+ static get observedAttributes() {
13
+ return ['index', 'categories', 'min-chars', 'fuzzy', 'placeholder', 'debounce'];
14
+ }
15
+
16
+ constructor() {
17
+ super();
18
+ this._search = createSearch();
19
+ this._debounceTimer = null;
20
+ this._activeIndex = -1;
21
+ this._flatResults = [];
22
+ this._indexLoaded = false;
23
+ this._listId = `velin-search-listbox-${Math.random().toString(36).slice(2, 9)}`;
24
+ this._mounted = false;
25
+ }
26
+
27
+ connectedCallback() {
28
+ if (this._mounted) return;
29
+ this._mounted = true;
30
+ this._ensureMarkup();
31
+ this._loadIndex();
32
+ this._bindEvents();
33
+ }
34
+
35
+ disconnectedCallback() {
36
+ this._teardown?.();
37
+ this._mounted = false;
38
+ }
39
+
40
+ get indexUrl() {
41
+ return this.getAttribute('index') || '/search-index.json';
42
+ }
43
+
44
+ get minChars() {
45
+ return parseInt(this.getAttribute('min-chars') || '2', 10);
46
+ }
47
+
48
+ get fuzzy() {
49
+ const v = this.getAttribute('fuzzy');
50
+ return v === null ? 0.2 : parseFloat(v) || 0;
51
+ }
52
+
53
+ get categories() {
54
+ const raw = this.getAttribute('categories');
55
+ if (!raw) return null;
56
+ return raw.split(',').map((c) => c.trim()).filter((c) => SEARCH_CATEGORIES.includes(c));
57
+ }
58
+
59
+ get debounceMs() {
60
+ return parseInt(this.getAttribute('debounce') || '120', 10);
61
+ }
62
+
63
+ _ensureMarkup() {
64
+ if (this.querySelector('[data-velin-search-input]')) return;
65
+ const ph = escapeHTMLAttribute(this.getAttribute('placeholder') || 'Search…');
66
+ const label = escapeHTMLAttribute(this.getAttribute('aria-label') || 'Search');
67
+ this.innerHTML = `
68
+ <div class="velin-search">
69
+ <div class="velin-search__field">
70
+ <input type="search" class="velin-search__input" data-velin-search-input
71
+ placeholder="${ph}" autocomplete="off" role="combobox" aria-expanded="false"
72
+ aria-label="${label}" aria-controls="${this._listId}" aria-autocomplete="list" />
73
+ </div>
74
+ <div class="velin-search__status velin-visually-hidden" data-velin-search-status aria-live="polite" aria-atomic="true"></div>
75
+ <div class="velin-search__results" data-velin-search-results role="listbox"
76
+ id="${this._listId}" hidden></div>
77
+ </div>
78
+ `;
79
+ }
80
+
81
+ _inputEl() {
82
+ return this.querySelector('[data-velin-search-input]');
83
+ }
84
+
85
+ _resultsEl() {
86
+ return this.querySelector('[data-velin-search-results]');
87
+ }
88
+
89
+ async _loadIndex() {
90
+ try {
91
+ await this._search.loadIndex(this.indexUrl);
92
+ this._indexLoaded = true;
93
+ } catch {
94
+ this._indexLoaded = false;
95
+ }
96
+ }
97
+
98
+ _bindEvents() {
99
+ const input = this._inputEl();
100
+ const panel = this._resultsEl();
101
+ if (!input || !panel) return;
102
+
103
+ input.addEventListener('input', () => {
104
+ clearTimeout(this._debounceTimer);
105
+ this._debounceTimer = setTimeout(() => this._runQuery(input.value), this.debounceMs);
106
+ });
107
+ input.addEventListener('focus', () => {
108
+ if (input.value.trim().length >= this.minChars) this._runQuery(input.value);
109
+ });
110
+ input.addEventListener('keydown', (e) => this._onKeydown(e, input, panel));
111
+
112
+ const onDocClick = (e) => {
113
+ if (!this.contains(e.target)) this._hide(panel, input);
114
+ };
115
+ document.addEventListener('click', onDocClick);
116
+ this._teardown = () => {
117
+ document.removeEventListener('click', onDocClick);
118
+ clearTimeout(this._debounceTimer);
119
+ };
120
+ }
121
+
122
+ async _runQuery(raw) {
123
+ const input = this._inputEl();
124
+ const panel = this._resultsEl();
125
+ if (!input || !panel) return;
126
+ const q = raw.trim();
127
+ if (q.length < this.minChars) {
128
+ this._hide(panel, input);
129
+ return;
130
+ }
131
+ if (!this._indexLoaded) await this._loadIndex();
132
+ const { groups } = this._search.query(q, {
133
+ minChars: this.minChars,
134
+ fuzzy: this.fuzzy,
135
+ categories: this.categories || undefined,
136
+ limit: 12,
137
+ });
138
+ this._activeIndex = -1;
139
+ this._renderResults(panel, input, q, groups);
140
+ if (this._flatResults.length) {
141
+ panel.hidden = false;
142
+ panel.classList.add('velin-search__results--open');
143
+ input.setAttribute('aria-expanded', 'true');
144
+ } else {
145
+ this._hide(panel, input);
146
+ }
147
+ }
148
+
149
+ _renderResults(panel, input, q, groups) {
150
+ panel.innerHTML = '';
151
+ const order = this.categories || [...SEARCH_CATEGORIES];
152
+ this._flatResults = [];
153
+ let globalIdx = 0;
154
+ for (const cat of order) {
155
+ const items = groups[cat];
156
+ if (!items?.length) continue;
157
+ const groupId = `${this._listId}-group-${cat}`;
158
+ const group = document.createElement('div');
159
+ group.setAttribute('role', 'group');
160
+ group.setAttribute('aria-labelledby', groupId);
161
+ const heading = document.createElement('div');
162
+ heading.className = 'velin-search__group-label';
163
+ heading.id = groupId;
164
+ heading.textContent = CATEGORY_LABELS[cat] || cat;
165
+ group.appendChild(heading);
166
+ for (const item of items) {
167
+ const a = document.createElement('a');
168
+ const navHref = resolveDocsSearchUrl(item.url);
169
+ if (navHref) a.href = navHref;
170
+ a.className = 'velin-search__item';
171
+ a.setAttribute('role', 'option');
172
+ a.id = `velin-search-opt-${globalIdx}`;
173
+ a.innerHTML =
174
+ `<span class="velin-search__title">${highlightHtml(item.title, q)}</span>` +
175
+ `<span class="velin-search__excerpt">${highlightHtml(item.excerpt || '', q)}</span>`;
176
+ a.addEventListener(
177
+ 'click',
178
+ (e) => {
179
+ e.preventDefault();
180
+ if (navHref) window.location.assign(navHref);
181
+ },
182
+ true,
183
+ );
184
+ a.addEventListener('mouseenter', () => this._setActive(globalIdx, panel, input));
185
+ group.appendChild(a);
186
+ this._flatResults.push(item);
187
+ globalIdx++;
188
+ }
189
+ panel.appendChild(group);
190
+ }
191
+ const status = this._statusEl();
192
+ if (status) {
193
+ status.textContent = this._flatResults.length
194
+ ? `${this._flatResults.length} results`
195
+ : 'No results';
196
+ }
197
+ }
198
+
199
+ _statusEl() {
200
+ return this.querySelector('[data-velin-search-status]');
201
+ }
202
+
203
+ _setActive(idx, panel, input) {
204
+ this._activeIndex = idx;
205
+ panel.querySelectorAll('.velin-search__item').forEach((el, i) => {
206
+ el.classList.toggle('velin-search__item--active', i === idx);
207
+ if (i === idx) {
208
+ input.setAttribute('aria-activedescendant', el.id);
209
+ el.scrollIntoView({ block: 'nearest' });
210
+ }
211
+ });
212
+ }
213
+
214
+ _onKeydown(e, input, panel) {
215
+ if (e.key === 'Escape') {
216
+ this._hide(panel, input);
217
+ return;
218
+ }
219
+ if (!this._flatResults.length) return;
220
+ if (e.key === 'ArrowDown') {
221
+ e.preventDefault();
222
+ const next = this._activeIndex < this._flatResults.length - 1 ? this._activeIndex + 1 : 0;
223
+ this._setActive(next, panel, input);
224
+ } else if (e.key === 'ArrowUp') {
225
+ e.preventDefault();
226
+ const prev = this._activeIndex > 0 ? this._activeIndex - 1 : this._flatResults.length - 1;
227
+ this._setActive(prev, panel, input);
228
+ } else if (e.key === 'Enter') {
229
+ const target = this._activeIndex >= 0 ? this._flatResults[this._activeIndex] : this._flatResults[0];
230
+ const navHref = target?.url ? resolveDocsSearchUrl(target.url) : '';
231
+ if (navHref) {
232
+ e.preventDefault();
233
+ window.location.assign(navHref);
234
+ }
235
+ }
236
+ }
237
+
238
+ _hide(panel, input) {
239
+ panel.hidden = true;
240
+ panel.classList.remove('velin-search__results--open');
241
+ input.setAttribute('aria-expanded', 'false');
242
+ input.removeAttribute('aria-activedescendant');
243
+ this._activeIndex = -1;
244
+ }
245
+ }
246
+
247
+ export default VelinSearch;
248
+
249
+ if (typeof HTMLElement !== 'undefined' && typeof customElements !== 'undefined' && !customElements.get('velin-search')) {
250
+ customElements.define('velin-search', VelinSearch);
251
+ }
252
+
253
+ export function bindDeclarativeSearch(root = document) {
254
+ root.querySelectorAll('[velin-search-input]').forEach((input) => {
255
+ if (input.dataset.velinSearchBound) return;
256
+ input.dataset.velinSearchBound = '1';
257
+ let host = input.closest('velin-search');
258
+ if (!host) {
259
+ host = document.createElement('velin-search');
260
+ host.setAttribute('index', input.getAttribute('data-search-index') || '/search-index.json');
261
+ const results = input.parentElement?.querySelector('[velin-search-results]');
262
+ input.before(host);
263
+ host.appendChild(input);
264
+ if (results) host.appendChild(results);
265
+ }
266
+ });
267
+ }
@@ -0,0 +1,99 @@
1
+ import { escapeHTML, escapeHTMLAttribute, sanitizeInputType } from './sanitize.js';
2
+
3
+ const styles = `
4
+ :host { display: block; }
5
+ label { display: block; font-size: var(--velin-text-sm); margin-block-end: var(--velin-space-1); }
6
+ input {
7
+ width: 100%;
8
+ min-height: 2.75rem;
9
+ padding: var(--velin-space-2) var(--velin-space-3);
10
+ font: inherit;
11
+ border: 1px solid var(--velin-color-border);
12
+ border-radius: var(--velin-radius-md);
13
+ }
14
+ input:focus-visible {
15
+ outline: 3px solid var(--velin-color-focus);
16
+ outline-offset: 2px;
17
+ }
18
+ .velin-field-help {
19
+ font-size: var(--velin-text-xs);
20
+ color: var(--velin-color-text-muted);
21
+ margin-block-start: var(--velin-space-1);
22
+ }
23
+ `;
24
+
25
+ async function encryptValue(plain, mode) {
26
+ if (mode !== 'aes-gcm' || !globalThis.crypto?.subtle) {
27
+ return JSON.stringify({ encoding: 'base64', payload: btoa(unescape(encodeURIComponent(plain))) });
28
+ }
29
+ const iv = crypto.getRandomValues(new Uint8Array(12));
30
+ const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt']);
31
+ const encoded = new TextEncoder().encode(plain);
32
+ const cipher = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);
33
+ return JSON.stringify({
34
+ encoding: 'aes-gcm',
35
+ iv: Array.from(iv),
36
+ cipher: Array.from(new Uint8Array(cipher)),
37
+ });
38
+ }
39
+
40
+ class VelinSecureField extends HTMLElement {
41
+ static get observedAttributes() {
42
+ return ['type', 'name', 'label', 'mode', 'autocomplete'];
43
+ }
44
+
45
+ constructor() {
46
+ super();
47
+ this.attachShadow({ mode: 'open' });
48
+ this._onChange = null;
49
+ }
50
+
51
+ connectedCallback() {
52
+ if (!this._onChange) this.render();
53
+ }
54
+
55
+ disconnectedCallback() {
56
+ const input = this.shadowRoot?.querySelector('input');
57
+ if (input && this._onChange) input.removeEventListener('change', this._onChange);
58
+ this._onChange = null;
59
+ }
60
+
61
+ attributeChangedCallback() {
62
+ if (this.isConnected) this.render();
63
+ }
64
+
65
+ render() {
66
+ const type = sanitizeInputType(this.getAttribute('type'));
67
+ const name = this.getAttribute('name') || 'secure';
68
+ const label = this.getAttribute('label') || 'Secure field';
69
+ const autocomplete = escapeHTMLAttribute(this.getAttribute('autocomplete') || 'off');
70
+ const id = `velin-secure-${Math.random().toString(36).slice(2, 9)}`;
71
+
72
+ const input = this.shadowRoot?.querySelector('input');
73
+ if (input && this._onChange) input.removeEventListener('change', this._onChange);
74
+
75
+ this.shadowRoot.innerHTML = `
76
+ <style>${styles}</style>
77
+ <label for="${id}">${escapeHTML(label)}</label>
78
+ <input id="${id}" type="${type}" autocomplete="${autocomplete}" part="input" />
79
+ <p class="velin-field-help">Demo-only client encoding — use HTTPS and server-side crypto for real secrets.</p>
80
+ `;
81
+
82
+ const newInput = this.shadowRoot.querySelector('input');
83
+ this._onChange = async () => {
84
+ const mode = this.getAttribute('mode') || 'aes-gcm';
85
+ const payload = await encryptValue(newInput.value, mode);
86
+ this.dispatchEvent(new CustomEvent('velin-secure-submit', {
87
+ bubbles: true,
88
+ detail: { name, payload },
89
+ }));
90
+ };
91
+ newInput.addEventListener('change', this._onChange);
92
+ }
93
+ }
94
+
95
+ if (!customElements.get('velin-secure-field')) {
96
+ customElements.define('velin-secure-field', VelinSecureField);
97
+ }
98
+
99
+ export default VelinSecureField;
@@ -120,7 +120,6 @@ class VelinSparkline extends HTMLElement {
120
120
  const wantsArea = this.hasAttribute('area') && this.getAttribute('area') !== 'false';
121
121
  const wantsGlow = this.hasAttribute('glow') && this.getAttribute('glow') !== 'false';
122
122
  const animate = (this.getAttribute('animate') || 'draw').toLowerCase();
123
- const label = this.getAttribute('label');
124
123
 
125
124
  const points = buildPoints(values, w, h, min, max);
126
125
  const linePath = pointsToPath(points);
@@ -133,12 +132,7 @@ class VelinSparkline extends HTMLElement {
133
132
  svg.style.display = 'block';
134
133
  svg.style.width = '100%';
135
134
  svg.style.height = '100%';
136
- if (label) {
137
- svg.setAttribute('role', 'img');
138
- svg.setAttribute('aria-label', label);
139
- } else {
140
- svg.setAttribute('aria-hidden', 'true');
141
- }
135
+ this._applyA11y(svg);
142
136
 
143
137
  if (wantsArea) {
144
138
  const defs = document.createElementNS(NS, 'defs');
@@ -198,6 +192,32 @@ class VelinSparkline extends HTMLElement {
198
192
  this.classList.add('velin-spark-tick');
199
193
  }
200
194
  }
195
+
196
+ _applyA11y(svg) {
197
+ const label = (this.getAttribute('label') || this.getAttribute('aria-label') || '').trim();
198
+ if (label) {
199
+ svg.setAttribute('role', 'img');
200
+ svg.setAttribute('aria-label', label);
201
+ svg.removeAttribute('aria-hidden');
202
+ svg.removeAttribute('aria-labelledby');
203
+ return;
204
+ }
205
+ const cap = this.closest('figure')?.querySelector('figcaption');
206
+ const capText = cap?.textContent?.trim();
207
+ if (capText) {
208
+ if (!cap.id) cap.id = `velin-sparkline-cap-${Math.random().toString(36).slice(2, 9)}`;
209
+ svg.setAttribute('role', 'img');
210
+ svg.setAttribute('aria-labelledby', cap.id);
211
+ svg.removeAttribute('aria-hidden');
212
+ svg.removeAttribute('aria-label');
213
+ return;
214
+ }
215
+ svg.setAttribute('role', 'presentation');
216
+ svg.setAttribute('aria-hidden', 'true');
217
+ svg.setAttribute('focusable', 'false');
218
+ svg.removeAttribute('aria-label');
219
+ svg.removeAttribute('aria-labelledby');
220
+ }
201
221
  }
202
222
 
203
223
  if (typeof customElements !== 'undefined' && !customElements.get('velin-sparkline')) {
@@ -49,10 +49,14 @@ const styles = `
49
49
  .panels ::slotted(*) { display: none; }
50
50
  .panels ::slotted([data-active]) { display: block; }
51
51
  @media (prefers-reduced-motion: reduce) { .step__marker { transition: none; } }
52
+ .velin-sr-only {
53
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
54
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
55
+ }
52
56
  `;
53
57
 
54
- class VelinStepperWC extends HTMLElement {
55
- static get observedAttributes() { return ['active']; }
58
+ class VelinStepper extends HTMLElement {
59
+ static get observedAttributes() { return ['active', 'aria-label', 'labels']; }
56
60
 
57
61
  constructor() {
58
62
  super();
@@ -67,19 +71,25 @@ class VelinStepperWC extends HTMLElement {
67
71
 
68
72
  _buildSteps() {
69
73
  const labels = (this.getAttribute('labels') || '').split(',').map(s => s.trim());
74
+ const listLabel = this.getAttribute('aria-label') || 'Progress';
70
75
  const stepsHTML = labels.map((label, i) => {
71
76
  const state = i < this._current ? 'completed' : i === this._current ? 'active' : '';
72
- const marker = i < this._current ? '&#10003;' : (i + 1);
77
+ const status =
78
+ i < this._current ? 'completed' : i === this._current ? 'current step' : 'upcoming';
79
+ const marker =
80
+ i < this._current
81
+ ? '<span class="velin-sr-only">Completed</span><span aria-hidden="true">&#10003;</span>'
82
+ : `<span aria-hidden="true">${i + 1}</span>`;
73
83
  const ariaCurrent = i === this._current ? ' aria-current="step"' : '';
74
- return `<div class="step ${state}" role="listitem"${ariaCurrent}><span class="step__marker" aria-hidden="true">${marker}</span><span class="step__label">${escapeHTML(label)}</span></div>`;
84
+ const itemLabel = label ? `${label}, ${status}` : `Step ${i + 1}, ${status}`;
85
+ return `<div class="step ${state}" role="listitem" aria-label="${escapeHTML(itemLabel)}"${ariaCurrent}><span class="step__marker">${marker}</span><span class="step__label">${escapeHTML(label)}</span></div>`;
75
86
  }).join('');
76
87
 
77
88
  this.shadowRoot.innerHTML = `
78
89
  <style>${styles}</style>
79
- <div class="steps" role="list" aria-label="Progress" part="steps">${stepsHTML}</div>
90
+ <div class="steps" role="list" aria-label="${escapeHTML(listLabel)}" part="steps">${stepsHTML}</div>
80
91
  <div class="panels" part="panels"><slot></slot></div>
81
92
  `;
82
- this._updatePanels();
83
93
  }
84
94
 
85
95
  _updatePanels() {
@@ -124,5 +134,24 @@ class VelinStepperWC extends HTMLElement {
124
134
  }
125
135
  }
126
136
 
127
- customElements.define('velin-stepper-wc', VelinStepperWC);
128
- export default VelinStepperWC;
137
+ /** @deprecated Use `<velin-stepper>` */
138
+ class VelinStepperWC extends VelinStepper {
139
+ static _warned = false;
140
+
141
+ connectedCallback() {
142
+ if (!VelinStepperWC._warned && typeof console !== 'undefined' && console.warn) {
143
+ VelinStepperWC._warned = true;
144
+ console.warn('[velinstyle] <velin-stepper-wc> is deprecated; use <velin-stepper> instead.');
145
+ }
146
+ super.connectedCallback();
147
+ }
148
+ }
149
+
150
+ if (!customElements.get('velin-stepper')) {
151
+ customElements.define('velin-stepper', VelinStepper);
152
+ }
153
+ if (!customElements.get('velin-stepper-wc')) {
154
+ customElements.define('velin-stepper-wc', VelinStepperWC);
155
+ }
156
+ export default VelinStepper;
157
+ export { VelinStepper, VelinStepperWC };