@birdapi/velinstyle 0.7.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 (336) hide show
  1. package/README.de.md +135 -224
  2. package/README.md +134 -223
  3. package/cli/blueprint.js +8 -0
  4. package/cli/blueprints/bottom-nav-mobile.html +17 -0
  5. package/cli/blueprints/cookie-consent.html +9 -0
  6. package/cli/blueprints/empty-state.html +5 -0
  7. package/cli/blueprints/filter-bar.html +15 -0
  8. package/cli/blueprints/notification-center.html +13 -0
  9. package/cli/blueprints/onboarding.html +23 -0
  10. package/cli/blueprints/pricing-table.html +20 -0
  11. package/cli/blueprints/settings-panel.html +20 -0
  12. package/cli/cli-manifest.json +97 -0
  13. package/cli/docgen/extract-a11y.js +89 -0
  14. package/cli/docgen/extract-attributes.js +95 -0
  15. package/cli/docgen/extract-cli.js +50 -0
  16. package/cli/docgen/extract-components.js +175 -0
  17. package/cli/docgen/extract-meta.js +32 -0
  18. package/cli/docgen/extract-rules.js +76 -0
  19. package/cli/docgen/extract-tokens.js +79 -0
  20. package/cli/docgen/extract-utilities.js +75 -0
  21. package/cli/docgen/markdown.js +43 -0
  22. package/cli/docs-generate.js +196 -0
  23. package/cli/index.js +236 -5
  24. package/cli/layout-audit.js +325 -0
  25. package/cli/meta.js +97 -0
  26. package/cli/perf-audit.js +174 -0
  27. package/cli/pii-scanner.js +134 -0
  28. package/cli/scaffold-recipes.json +70 -0
  29. package/cli/scaffold.js +155 -0
  30. package/cli/scanner-rules-data.js +62 -0
  31. package/cli/scanner.js +136 -1
  32. package/cli/search-index.js +163 -0
  33. package/cli/tokens-build.js +21 -1
  34. package/cli/tokens-validate.js +69 -0
  35. package/components/a11y-entry.js +42 -0
  36. package/components/a11y-utils.js +71 -0
  37. package/components/focus-manager.js +45 -8
  38. package/components/index.js +40 -11
  39. package/components/runtime/component-loaders.js +43 -0
  40. package/components/runtime/index.js +77 -0
  41. package/components/runtime-entry.js +10 -0
  42. package/components/sanitize.js +135 -28
  43. package/components/shadow-a11y-styles.js +18 -0
  44. package/components/velin-accordion.js +12 -0
  45. package/components/velin-announcer.js +35 -0
  46. package/components/velin-bottom-nav.js +108 -0
  47. package/components/velin-carousel.js +2 -2
  48. package/components/velin-code-block.js +142 -0
  49. package/components/velin-combobox.js +149 -0
  50. package/components/velin-command.js +142 -0
  51. package/components/velin-copy.js +18 -5
  52. package/components/velin-counter.js +155 -0
  53. package/components/velin-dialog.js +9 -1
  54. package/components/velin-email.js +94 -0
  55. package/components/velin-flip.js +220 -0
  56. package/components/velin-icon.js +99 -22
  57. package/components/velin-lightbox.js +8 -3
  58. package/components/velin-live-dot.js +90 -0
  59. package/components/velin-menubar.js +83 -0
  60. package/components/velin-modal.js +6 -3
  61. package/components/velin-popover.js +34 -2
  62. package/components/velin-rating.js +91 -0
  63. package/components/velin-reveal.js +22 -0
  64. package/components/velin-scroll-top.js +1 -1
  65. package/components/velin-search.js +267 -0
  66. package/components/velin-secure-field.js +99 -0
  67. package/components/velin-segmented-control.js +108 -0
  68. package/components/velin-sheet.js +107 -0
  69. package/components/velin-sparkline.js +227 -0
  70. package/components/{velin-stepper-wc.js → velin-stepper.js} +37 -8
  71. package/components/velin-theme-toggle.js +284 -60
  72. package/components/velin-toast.js +4 -0
  73. package/components/{velin-tooltip-wc.js → velin-tooltip.js} +22 -3
  74. package/core/a11y/component-contracts.json +221 -0
  75. package/core/attributes/index.js +6 -0
  76. package/core/attributes/registry.js +268 -0
  77. package/core/highlight/highlight.js +114 -0
  78. package/core/highlight/index.js +29 -0
  79. package/core/highlight/languages/_utils.js +52 -0
  80. package/core/highlight/languages/blade.js +16 -0
  81. package/core/highlight/languages/css.js +26 -0
  82. package/core/highlight/languages/html.js +17 -0
  83. package/core/highlight/languages/js.js +58 -0
  84. package/core/highlight/languages/json.js +14 -0
  85. package/core/highlight/languages/markdown.js +16 -0
  86. package/core/highlight/languages/php.js +39 -0
  87. package/core/highlight/languages/plain.js +7 -0
  88. package/core/highlight/languages/shell.js +33 -0
  89. package/core/highlight/languages/sql.js +20 -0
  90. package/core/highlight/languages/typescript.js +28 -0
  91. package/core/highlight/observe.js +53 -0
  92. package/core/highlight/registry.js +107 -0
  93. package/core/highlight/render.js +35 -0
  94. package/core/highlight/types.js +28 -0
  95. package/core/meta/build.js +293 -0
  96. package/core/meta/index.js +17 -0
  97. package/core/meta/schema.js +49 -0
  98. package/core/motion/effects.js +54 -0
  99. package/core/motion/index.js +76 -0
  100. package/core/motion/scheduler.js +72 -0
  101. package/core/motion/scroll.js +32 -0
  102. package/core/motion/stagger.js +24 -0
  103. package/core/search/docs-url.js +96 -0
  104. package/core/search/engine.js +139 -0
  105. package/core/search/highlight.js +48 -0
  106. package/core/search/index.js +126 -0
  107. package/core/search/providers.js +36 -0
  108. package/core/search/types.js +61 -0
  109. package/core/search/worker-client.js +56 -0
  110. package/core/search/worker.js +20 -0
  111. package/dist/chunks/a11y-utils-IRC7LOPF.js +16 -0
  112. package/dist/chunks/attributes-3BZLI7XF.js +364 -0
  113. package/dist/chunks/attributes-4N5JP4CG.js +364 -0
  114. package/dist/chunks/attributes-5Q2WXGRC.js +374 -0
  115. package/dist/chunks/attributes-AIR7SVLK.js +364 -0
  116. package/dist/chunks/attributes-BGRN5XZO.js +364 -0
  117. package/dist/chunks/attributes-BVSQAYLR.js +364 -0
  118. package/dist/chunks/attributes-CDYFUY7Y.js +364 -0
  119. package/dist/chunks/attributes-JMYJZBPE.js +364 -0
  120. package/dist/chunks/attributes-R5JY3X4O.js +374 -0
  121. package/dist/chunks/attributes-STYCS3O5.js +391 -0
  122. package/dist/chunks/attributes-TNSPQTHX.js +374 -0
  123. package/dist/chunks/attributes-TPANLMLF.js +391 -0
  124. package/dist/chunks/blade-LIQLVKZQ.js +19 -0
  125. package/dist/chunks/browser-MRKYZPEH.js +1149 -0
  126. package/dist/chunks/chunk-2354NEBP.js +109 -0
  127. package/dist/chunks/chunk-2VNA7G7S.js +109 -0
  128. package/dist/chunks/chunk-42VVPW6N.js +132 -0
  129. package/dist/chunks/chunk-7FFAMRFV.js +44 -0
  130. package/dist/chunks/chunk-7MCGPHVC.js +69 -0
  131. package/dist/chunks/chunk-AQLRJSGG.js +109 -0
  132. package/dist/chunks/chunk-EWPSDMQT.js +109 -0
  133. package/dist/chunks/chunk-F7OLHTGX.js +22 -0
  134. package/dist/chunks/chunk-J472ERN2.js +242 -0
  135. package/dist/chunks/chunk-LA7264K4.js +85 -0
  136. package/dist/chunks/chunk-M5CHQVP6.js +61 -0
  137. package/dist/chunks/chunk-MKZP2TBH.js +89 -0
  138. package/dist/chunks/chunk-QTJJ3DPO.js +109 -0
  139. package/dist/chunks/chunk-RDE3QE7Z.js +89 -0
  140. package/dist/chunks/chunk-RSMRR27R.js +109 -0
  141. package/dist/chunks/chunk-RZQVWFHW.js +109 -0
  142. package/dist/chunks/chunk-SRL6EWHP.js +109 -0
  143. package/dist/chunks/chunk-U4XHMZ7D.js +109 -0
  144. package/dist/chunks/chunk-V5YIUA6G.js +109 -0
  145. package/dist/chunks/chunk-XIET2VSZ.js +109 -0
  146. package/dist/chunks/chunk-XYLJ2GCB.js +205 -0
  147. package/dist/chunks/chunk-Z4JYQ2XX.js +51 -0
  148. package/dist/chunks/chunk-ZPSPKUYD.js +109 -0
  149. package/dist/chunks/css-MQ5YU77P.js +28 -0
  150. package/dist/chunks/highlight-GJHHV4KM.js +37 -0
  151. package/dist/chunks/highlight-XTHRKBA3.js +37 -0
  152. package/dist/chunks/html-ZODC4DLQ.js +20 -0
  153. package/dist/chunks/js-MNSNYTJU.js +7 -0
  154. package/dist/chunks/json-VEM2HFH3.js +17 -0
  155. package/dist/chunks/markdown-E7OPGS3S.js +19 -0
  156. package/dist/chunks/php-MLL7T3LT.js +41 -0
  157. package/dist/chunks/plain-LJJDC5TN.js +8 -0
  158. package/dist/chunks/runtime-entry.js +14 -0
  159. package/dist/chunks/shell-QTRK4U4E.js +35 -0
  160. package/dist/chunks/sql-PPWLHGJ2.js +23 -0
  161. package/dist/chunks/typescript-JT3CCYWL.js +30 -0
  162. package/dist/chunks/velin-accordion-FBIPAEXG.js +105 -0
  163. package/dist/chunks/velin-accordion-QAQO7BI7.js +117 -0
  164. package/dist/chunks/velin-announcer-PVTXXCWR.js +33 -0
  165. package/dist/chunks/velin-bottom-nav-2N2TYWOF.js +86 -0
  166. package/dist/chunks/velin-bottom-nav-7GEMPIM6.js +86 -0
  167. package/dist/chunks/velin-bottom-nav-CTU6FFZV.js +101 -0
  168. package/dist/chunks/velin-bottom-nav-N3DFQR66.js +86 -0
  169. package/dist/chunks/velin-carousel-Y5VCOABZ.js +213 -0
  170. package/dist/chunks/velin-carousel-ZTBANFQQ.js +213 -0
  171. package/dist/chunks/velin-code-block-ASUBTAQV.js +119 -0
  172. package/dist/chunks/velin-code-block-CIAURDRJ.js +132 -0
  173. package/dist/chunks/velin-code-block-TSUKPY4C.js +123 -0
  174. package/dist/chunks/velin-code-block-Y6CM726Q.js +119 -0
  175. package/dist/chunks/velin-collapse-5XYBUIID.js +80 -0
  176. package/dist/chunks/velin-combobox-2KDTCQ7N.js +155 -0
  177. package/dist/chunks/velin-combobox-NXS4QB4R.js +155 -0
  178. package/dist/chunks/velin-combobox-UGA56HP3.js +155 -0
  179. package/dist/chunks/velin-command-CKDNO4BW.js +148 -0
  180. package/dist/chunks/velin-command-M7RNHUPR.js +136 -0
  181. package/dist/chunks/velin-command-ONKO2A3J.js +136 -0
  182. package/dist/chunks/velin-command-TMLSRGIU.js +136 -0
  183. package/dist/chunks/velin-copy-G2TZ5YBB.js +81 -0
  184. package/dist/chunks/velin-copy-NYDBOKXT.js +81 -0
  185. package/dist/chunks/velin-copy-T5KDTDXP.js +81 -0
  186. package/dist/chunks/velin-countdown-4ZVDGK4R.js +101 -0
  187. package/dist/chunks/velin-countdown-5XSTN3BH.js +101 -0
  188. package/dist/chunks/velin-countdown-7AIY66CU.js +101 -0
  189. package/dist/chunks/velin-counter-ISCTEAHI.js +118 -0
  190. package/dist/chunks/velin-counter-L5FYJF7A.js +121 -0
  191. package/dist/chunks/velin-dialog-D2GUHSCD.js +144 -0
  192. package/dist/chunks/velin-dialog-ELFOZIXZ.js +152 -0
  193. package/dist/chunks/velin-dialog-GTTZI564.js +147 -0
  194. package/dist/chunks/velin-dialog-IJLFDXAJ.js +144 -0
  195. package/dist/chunks/velin-dialog-TNS3WXMB.js +144 -0
  196. package/dist/chunks/velin-drawer-BE6TYN3H.js +130 -0
  197. package/dist/chunks/velin-drawer-DLL2GLT6.js +130 -0
  198. package/dist/chunks/velin-drawer-MJS6WCF7.js +130 -0
  199. package/dist/chunks/velin-dropdown-PLUIFA5M.js +172 -0
  200. package/dist/chunks/velin-email-N5B5ZYTE.js +90 -0
  201. package/dist/chunks/velin-email-NTM6DG3V.js +90 -0
  202. package/dist/chunks/velin-email-RBX6ZR4S.js +90 -0
  203. package/dist/chunks/velin-haptic-4QQTM3RK.js +50 -0
  204. package/dist/chunks/velin-icon-7M76DOPB.js +164 -0
  205. package/dist/chunks/velin-icon-KZO3IEHG.js +164 -0
  206. package/dist/chunks/velin-icon-WTFPG2DD.js +191 -0
  207. package/dist/chunks/velin-icon-ZTSKUOVN.js +193 -0
  208. package/dist/chunks/velin-lightbox-6E7CAATP.js +149 -0
  209. package/dist/chunks/velin-lightbox-I75U6JF4.js +140 -0
  210. package/dist/chunks/velin-lightbox-QOTB4PRL.js +140 -0
  211. package/dist/chunks/velin-lightbox-UCD6N7HF.js +140 -0
  212. package/dist/chunks/velin-live-dot-RJHX2DGY.js +74 -0
  213. package/dist/chunks/velin-live-dot-XHUZCIDZ.js +67 -0
  214. package/dist/chunks/velin-menubar-I426AED2.js +86 -0
  215. package/dist/chunks/velin-menubar-J66JXRWW.js +86 -0
  216. package/dist/chunks/velin-menubar-SFEXJE56.js +86 -0
  217. package/dist/chunks/velin-modal-2HOBXE6F.js +176 -0
  218. package/dist/chunks/velin-modal-5HYNNNUI.js +176 -0
  219. package/dist/chunks/velin-modal-AEOTZ67O.js +177 -0
  220. package/dist/chunks/velin-modal-GLI2XZZB.js +176 -0
  221. package/dist/chunks/velin-persist-DFPL3QP4.js +110 -0
  222. package/dist/chunks/velin-popover-EXINRIGJ.js +145 -0
  223. package/dist/chunks/velin-popover-HQW6Q2Y6.js +172 -0
  224. package/dist/chunks/velin-popover-OXX7MRZ6.js +145 -0
  225. package/dist/chunks/velin-popover-T72HK3DF.js +151 -0
  226. package/dist/chunks/velin-popover-YKCCROJG.js +145 -0
  227. package/dist/chunks/velin-progress-ring-E4YRLZHH.js +66 -0
  228. package/dist/chunks/velin-rating-6CDBI6DG.js +91 -0
  229. package/dist/chunks/velin-rating-N4DI2XNL.js +91 -0
  230. package/dist/chunks/velin-rating-XRQJV4LO.js +91 -0
  231. package/dist/chunks/velin-scroll-top-F2FCZNYD.js +58 -0
  232. package/dist/chunks/velin-scroll-top-OHYVVHHY.js +58 -0
  233. package/dist/chunks/velin-scrollspy-OKCT4FDV.js +50 -0
  234. package/dist/chunks/velin-search-2XRQFBCS.js +556 -0
  235. package/dist/chunks/velin-search-4YHJWYSS.js +556 -0
  236. package/dist/chunks/velin-search-7B4KJ6TX.js +557 -0
  237. package/dist/chunks/velin-search-LWHY23Q5.js +474 -0
  238. package/dist/chunks/velin-secure-field-6PV7RZDJ.js +95 -0
  239. package/dist/chunks/velin-secure-field-H7FSBUJW.js +95 -0
  240. package/dist/chunks/velin-secure-field-OFNAXCDP.js +95 -0
  241. package/dist/chunks/velin-segmented-control-EKL4E3BL.js +109 -0
  242. package/dist/chunks/velin-segmented-control-GYD5BIXQ.js +109 -0
  243. package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +109 -0
  244. package/dist/chunks/velin-sheet-3OGI4HD7.js +119 -0
  245. package/dist/chunks/velin-sheet-OUK6572T.js +119 -0
  246. package/dist/chunks/velin-sheet-R2Y67X6H.js +119 -0
  247. package/dist/chunks/velin-sparkline-GVEANJVW.js +184 -0
  248. package/dist/chunks/velin-sparkline-KDA3GQAN.js +162 -0
  249. package/dist/chunks/velin-sparkline-XVTFC6GE.js +181 -0
  250. package/dist/chunks/velin-stepper-2ZIHLKGM.js +141 -0
  251. package/dist/chunks/velin-stepper-7ECRUYHD.js +148 -0
  252. package/dist/chunks/velin-stepper-FYEUEWE6.js +141 -0
  253. package/dist/chunks/velin-stepper-X54WGHDG.js +141 -0
  254. package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +133 -0
  255. package/dist/chunks/velin-tabs-5UQ5ZVMV.js +138 -0
  256. package/dist/chunks/velin-theme-toggle-32J2CBNC.js +291 -0
  257. package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +285 -0
  258. package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +285 -0
  259. package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +291 -0
  260. package/dist/chunks/velin-toast-OMMOA2DK.js +109 -0
  261. package/dist/chunks/velin-toast-RCZ4CSUC.js +109 -0
  262. package/dist/chunks/velin-toast-RSF2IHT3.js +113 -0
  263. package/dist/chunks/velin-toast-WEUBTJG6.js +109 -0
  264. package/dist/chunks/velin-tooltip-664IOHZA.js +112 -0
  265. package/dist/chunks/velin-tooltip-BDHGRTQB.js +112 -0
  266. package/dist/chunks/velin-tooltip-HVUOKNVX.js +112 -0
  267. package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +103 -0
  268. package/dist/chunks/worker-client-R6DBYPFT.js +46 -0
  269. package/dist/llms.test.txt +40 -0
  270. package/dist/llms.txt +40 -0
  271. package/dist/search-index.json +1773 -0
  272. package/dist/search-index.test.json +1773 -0
  273. package/dist/themes/brutalist.min.css +1 -1
  274. package/dist/themes/midnight.min.css +1 -1
  275. package/dist/themes/pastel.min.css +1 -1
  276. package/dist/themes/retro.min.css +1 -1
  277. package/dist/themes/sunset.min.css +1 -1
  278. package/dist/velin-agent.json +684 -0
  279. package/dist/velin-agent.test.json +684 -0
  280. package/dist/velinstyle-components.iife.js +7959 -2199
  281. package/dist/velinstyle-components.js +7982 -2174
  282. package/dist/velinstyle-components.min.js +521 -90
  283. package/dist/velinstyle.css +1290 -282
  284. package/dist/velinstyle.d.ts +54 -0
  285. package/dist/velinstyle.min.css +1 -1
  286. package/examples/search-index.schema.json +33 -0
  287. package/package.json +57 -10
  288. package/src/a11y/authentication.css +37 -0
  289. package/src/a11y/consistent-help.css +22 -0
  290. package/src/a11y/dragging-alternatives.css +30 -0
  291. package/src/a11y/focus-appearance.css +20 -0
  292. package/src/a11y/high-contrast-aaa.css +16 -26
  293. package/src/a11y/security.css +18 -0
  294. package/src/a11y/skip-link.css +3 -1
  295. package/src/a11y/sr-only.css +1 -1
  296. package/src/base/reset.css +0 -25
  297. package/src/base/wc-placeholder.css +102 -0
  298. package/src/components/attributes.css +42 -0
  299. package/src/components/highlight.css +182 -0
  300. package/src/components/input-group.css +1 -2
  301. package/src/components/nav.css +151 -151
  302. package/src/components/search.css +95 -0
  303. package/src/components/stepper.css +2 -4
  304. package/src/components/timeline.css +1 -2
  305. package/src/components/tooltip.css +2 -4
  306. package/src/layout/container.css +5 -5
  307. package/src/layout/grid.css +8 -8
  308. package/src/layout/patterns.css +5 -5
  309. package/src/themes/brutalist.css +2 -11
  310. package/src/themes/corporate.css +0 -9
  311. package/src/themes/earth.css +0 -9
  312. package/src/themes/forest.css +0 -9
  313. package/src/themes/midnight.css +1 -10
  314. package/src/themes/neon.css +0 -9
  315. package/src/themes/nordic.css +0 -9
  316. package/src/themes/ocean.css +0 -9
  317. package/src/themes/pastel.css +1 -10
  318. package/src/themes/retro.css +2 -11
  319. package/src/themes/sharp.css +0 -7
  320. package/src/themes/soft.css +0 -7
  321. package/src/themes/sunset.css +1 -10
  322. package/src/tokens/color.css +45 -46
  323. package/src/tokens/fonts.css +10 -0
  324. package/src/tokens/motion.css +7 -0
  325. package/src/tokens/typography.css +4 -5
  326. package/src/utilities/animation.css +99 -3
  327. package/src/utilities/chart-animation.css +101 -0
  328. package/src/utilities/display.css +3 -3
  329. package/src/utilities/divide.css +2 -6
  330. package/src/utilities/filter-effects.css +103 -0
  331. package/src/utilities/responsive.css +4 -4
  332. package/src/utilities/safe-area.css +39 -0
  333. package/src/utilities/scroll-animation.css +34 -0
  334. package/src/utilities/spacing.css +17 -8
  335. package/src/utilities/text.css +4 -1
  336. package/src/velinstyle.css +12 -0
@@ -0,0 +1,94 @@
1
+ import { escapeHTML, escapeHTMLAttribute } from './sanitize.js';
2
+
3
+ const styles = `
4
+ :host { display: inline; }
5
+ button {
6
+ font: inherit;
7
+ color: var(--velin-color-primary, #4338ca);
8
+ background: none;
9
+ border: none;
10
+ padding: 0;
11
+ cursor: pointer;
12
+ text-decoration: underline;
13
+ text-underline-offset: 0.15em;
14
+ min-height: 2.75rem;
15
+ min-width: 2.75rem;
16
+ }
17
+ button:focus-visible {
18
+ outline: 3px solid var(--velin-color-focus, #2563eb);
19
+ outline-offset: 2px;
20
+ border-radius: 2px;
21
+ }
22
+ .revealed { text-decoration: none; user-select: text; }
23
+ `;
24
+
25
+ function decodeObfuscated(raw, method) {
26
+ if (!raw) return '';
27
+ if (method === 'rot13') {
28
+ return raw.replace(/[a-zA-Z]/g, (c) => {
29
+ const base = c <= 'Z' ? 65 : 97;
30
+ return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
31
+ });
32
+ }
33
+ try {
34
+ return atob(raw);
35
+ } catch {
36
+ return raw;
37
+ }
38
+ }
39
+
40
+ class VelinEmail extends HTMLElement {
41
+ static get observedAttributes() {
42
+ return ['value', 'obfuscate', 'label'];
43
+ }
44
+
45
+ constructor() {
46
+ super();
47
+ this.attachShadow({ mode: 'open' });
48
+ this._revealed = false;
49
+ }
50
+
51
+ connectedCallback() {
52
+ this.render();
53
+ }
54
+
55
+ attributeChangedCallback() {
56
+ if (this.isConnected) this.render();
57
+ }
58
+
59
+ get email() {
60
+ const method = this.getAttribute('obfuscate') || '';
61
+ const raw = this.getAttribute('value') || '';
62
+ return method ? decodeObfuscated(raw, method) : raw;
63
+ }
64
+
65
+ render() {
66
+ const label = this.getAttribute('label') || 'Show email address';
67
+ const email = this.email;
68
+ const safeEmail = escapeHTML(email);
69
+
70
+ if (this._revealed) {
71
+ this.shadowRoot.innerHTML = `
72
+ <style>${styles}</style>
73
+ <span class="revealed" part="email"><a href="mailto:${escapeHTMLAttribute(email)}">${safeEmail}</a></span>
74
+ `;
75
+ return;
76
+ }
77
+
78
+ this.shadowRoot.innerHTML = `
79
+ <style>${styles}</style>
80
+ <button type="button" part="reveal" aria-label="${escapeHTMLAttribute(label)}">${escapeHTML(label)}</button>
81
+ `;
82
+ this.shadowRoot.querySelector('button').addEventListener('click', () => {
83
+ this._revealed = true;
84
+ this.render();
85
+ this.dispatchEvent(new CustomEvent('velin-email-reveal', { bubbles: true, detail: { email } }));
86
+ });
87
+ }
88
+ }
89
+
90
+ if (!customElements.get('velin-email')) {
91
+ customElements.define('velin-email', VelinEmail);
92
+ }
93
+
94
+ export default VelinEmail;
@@ -0,0 +1,220 @@
1
+ /*
2
+ * velin-flip.js — FLIP-style reorder + filter helper.
3
+ *
4
+ * FLIP (First, Last, Invert, Play) measures item rects before and after a
5
+ * DOM mutation, then animates the delta. Used for sorting/filtering UIs
6
+ * where rows reorder visibly. Pair it with the [data-velin-flip] attribute
7
+ * on a container plus [data-velin-filter-value] chips or [data-velin-filter-input]
8
+ * inputs to wire chip/search filtering with zero JS in your demo.
9
+ *
10
+ * API:
11
+ * flipReorder(container, mutateFn, opts?)
12
+ * filterList(container, predicateFn, opts?)
13
+ *
14
+ * opts.duration ms (default 250); reduced-motion forces 0
15
+ * opts.easing CSS timing function (default expo-out token)
16
+ * opts.itemSelector children selector (default ':scope > *')
17
+ *
18
+ * Auto-init:
19
+ * <ul data-velin-flip data-velin-filter-attr="tags" id="myList">
20
+ * <li data-tags="a b">..</li> ...
21
+ * </ul>
22
+ * <button data-velin-filter-value="a" data-velin-filter-target="#myList">A</button>
23
+ * <input data-velin-filter-input data-velin-filter-target="#myList">
24
+ *
25
+ * Any chip or input with data-velin-filter-target pointing at a flip
26
+ * container is wired automatically. The active chip carries
27
+ * data-velin-filter-active so you can style it.
28
+ */
29
+
30
+ const REDUCED_MOTION_MQ =
31
+ typeof window !== 'undefined' && window.matchMedia
32
+ ? window.matchMedia('(prefers-reduced-motion: reduce)')
33
+ : null;
34
+
35
+ const DEFAULTS = {
36
+ duration: 250,
37
+ easing: 'var(--velin-ease-expo-out, cubic-bezier(0.16, 1, 0.3, 1))',
38
+ itemSelector: ':scope > *',
39
+ };
40
+
41
+ function getItems(container, selector) {
42
+ return Array.from(container.querySelectorAll(selector));
43
+ }
44
+
45
+ export function flipReorder(container, mutateFn, options = {}) {
46
+ if (!container || typeof mutateFn !== 'function') return;
47
+ const opts = { ...DEFAULTS, ...options };
48
+ const reduced = REDUCED_MOTION_MQ && REDUCED_MOTION_MQ.matches;
49
+
50
+ const items = getItems(container, opts.itemSelector);
51
+ const before = new Map();
52
+ items.forEach((el) => {
53
+ if (!el.hidden) before.set(el, el.getBoundingClientRect());
54
+ });
55
+
56
+ mutateFn();
57
+
58
+ if (reduced) return;
59
+
60
+ const items2 = getItems(container, opts.itemSelector);
61
+ items2.forEach((el) => {
62
+ if (el.hidden) return;
63
+ const prev = before.get(el);
64
+ const next = el.getBoundingClientRect();
65
+ if (!prev) {
66
+ if (typeof el.animate !== 'function') return;
67
+ el.animate(
68
+ [
69
+ { opacity: 0, transform: 'scale(0.96)' },
70
+ { opacity: 1, transform: 'scale(1)' },
71
+ ],
72
+ { duration: opts.duration, easing: opts.easing, fill: 'both' },
73
+ );
74
+ return;
75
+ }
76
+ const dx = prev.left - next.left;
77
+ const dy = prev.top - next.top;
78
+ if (dx === 0 && dy === 0) return;
79
+ if (typeof el.animate !== 'function') return;
80
+ el.animate(
81
+ [
82
+ { transform: `translate(${dx}px, ${dy}px)` },
83
+ { transform: 'translate(0, 0)' },
84
+ ],
85
+ { duration: opts.duration, easing: opts.easing, fill: 'both' },
86
+ );
87
+ });
88
+ }
89
+
90
+ export function filterList(container, predicate, options = {}) {
91
+ if (!container || typeof predicate !== 'function') return;
92
+ const opts = { ...DEFAULTS, ...options };
93
+ flipReorder(
94
+ container,
95
+ () => {
96
+ getItems(container, opts.itemSelector).forEach((el) => {
97
+ el.hidden = !predicate(el);
98
+ });
99
+ },
100
+ opts,
101
+ );
102
+ }
103
+
104
+ function readTokens(value) {
105
+ if (!value) return [];
106
+ return String(value)
107
+ .toLowerCase()
108
+ .split(/[\s,|]+/)
109
+ .map((s) => s.trim())
110
+ .filter(Boolean);
111
+ }
112
+
113
+ function matchTokens(itemTokens, queryTokens, mode) {
114
+ if (!queryTokens.length) return true;
115
+ if (mode === 'all') return queryTokens.every((q) => itemTokens.includes(q));
116
+ return queryTokens.some((q) => itemTokens.includes(q));
117
+ }
118
+
119
+ function matchSearch(item, query) {
120
+ if (!query) return true;
121
+ const haystack =
122
+ (item.getAttribute('data-tags') || '') +
123
+ ' ' +
124
+ (item.getAttribute('data-search') || '') +
125
+ ' ' +
126
+ (item.textContent || '');
127
+ return haystack.toLowerCase().includes(query.toLowerCase());
128
+ }
129
+
130
+ class FilterController {
131
+ constructor(container) {
132
+ this.container = container;
133
+ this.tag = '';
134
+ this.search = '';
135
+ this.matchMode = container.getAttribute('data-velin-filter-mode') === 'all' ? 'all' : 'any';
136
+ this.itemSelector = container.getAttribute('data-velin-filter-item') || ':scope > *';
137
+ }
138
+
139
+ apply() {
140
+ const queryTokens = readTokens(this.tag);
141
+ const term = this.search;
142
+ filterList(
143
+ this.container,
144
+ (el) => {
145
+ const tokens = readTokens(el.getAttribute('data-tags'));
146
+ return matchTokens(tokens, queryTokens, this.matchMode) && matchSearch(el, term);
147
+ },
148
+ { itemSelector: this.itemSelector },
149
+ );
150
+ }
151
+ }
152
+
153
+ const _controllers = new WeakMap();
154
+
155
+ function getController(container) {
156
+ let ctrl = _controllers.get(container);
157
+ if (!ctrl) {
158
+ ctrl = new FilterController(container);
159
+ _controllers.set(container, ctrl);
160
+ }
161
+ return ctrl;
162
+ }
163
+
164
+ function resolveTarget(triggerEl) {
165
+ const sel = triggerEl.getAttribute('data-velin-filter-target');
166
+ if (!sel) return null;
167
+ try {
168
+ return document.querySelector(sel);
169
+ } catch {
170
+ return null;
171
+ }
172
+ }
173
+
174
+ function highlightActive(group, active) {
175
+ if (!group) return;
176
+ group.querySelectorAll('[data-velin-filter-value]').forEach((btn) => {
177
+ if (btn === active) btn.setAttribute('data-velin-filter-active', '');
178
+ else btn.removeAttribute('data-velin-filter-active');
179
+ });
180
+ }
181
+
182
+ function autoInit() {
183
+ if (typeof document === 'undefined') return;
184
+
185
+ document.addEventListener('click', (event) => {
186
+ const target = event.target.closest('[data-velin-filter-value]');
187
+ if (!target) return;
188
+ const container = resolveTarget(target);
189
+ if (!container) return;
190
+ const group = target.closest('[data-velin-filter-group]') || target.parentElement;
191
+ highlightActive(group, target);
192
+ const ctrl = getController(container);
193
+ ctrl.tag = target.getAttribute('data-velin-filter-value') || '';
194
+ if (ctrl.tag.toLowerCase() === 'all' || ctrl.tag === '*') ctrl.tag = '';
195
+ ctrl.apply();
196
+ });
197
+
198
+ const handleInput = (event) => {
199
+ const input = event.target.closest('[data-velin-filter-input]');
200
+ if (!input) return;
201
+ const container = resolveTarget(input);
202
+ if (!container) return;
203
+ const ctrl = getController(container);
204
+ const raw = input.value || (typeof input.getAttribute === 'function' ? input.getAttribute('value') : '');
205
+ ctrl.search = (raw || '').trim();
206
+ ctrl.apply();
207
+ };
208
+ document.addEventListener('input', handleInput);
209
+ document.addEventListener('change', handleInput);
210
+ }
211
+
212
+ if (typeof document !== 'undefined') {
213
+ if (document.readyState === 'loading') {
214
+ document.addEventListener('DOMContentLoaded', autoInit, { once: true });
215
+ } else {
216
+ autoInit();
217
+ }
218
+ }
219
+
220
+ export default { flipReorder, filterList };
@@ -1,16 +1,41 @@
1
+ import { sanitizeURL, sanitizeSVG } from './sanitize.js';
2
+
1
3
  const PROVIDER_CDNS = {
2
4
  lucide: 'https://unpkg.com/lucide-static@latest/icons/{name}.svg',
3
5
  heroicons: 'https://unpkg.com/heroicons@2/24/outline/{name}.svg',
4
6
  bootstrap: 'https://unpkg.com/bootstrap-icons@latest/icons/{name}.svg',
5
7
  material: 'https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/{name}/default/24px.svg',
6
- fontawesome: 'https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/regular/{name}.svg',
8
+ fontawesome:
9
+ 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/svgs/solid/{name}.svg',
10
+ };
11
+
12
+ const PROVIDER_VARIANTS = {
13
+ fontawesome: {
14
+ regular:
15
+ 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/svgs/regular/{name}.svg',
16
+ solid:
17
+ 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/svgs/solid/{name}.svg',
18
+ brands:
19
+ 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/svgs/brands/{name}.svg',
20
+ },
21
+ heroicons: {
22
+ outline: 'https://unpkg.com/heroicons@2/24/outline/{name}.svg',
23
+ solid: 'https://unpkg.com/heroicons@2/24/solid/{name}.svg',
24
+ mini: 'https://unpkg.com/heroicons@2/20/solid/{name}.svg',
25
+ },
7
26
  };
8
27
 
28
+ function resolveProviderUrl(provider, variant) {
29
+ const variants = PROVIDER_VARIANTS[provider];
30
+ if (variant && variants?.[variant]) return variants[variant];
31
+ return PROVIDER_CDNS[provider];
32
+ }
33
+
9
34
  const _svgCache = new Map();
10
35
 
11
36
  class VelinIcon extends HTMLElement {
12
37
  static get observedAttributes() {
13
- return ['name', 'size', 'label', 'provider', 'sprite'];
38
+ return ['name', 'size', 'label', 'provider', 'variant', 'sprite'];
14
39
  }
15
40
 
16
41
  constructor() {
@@ -29,23 +54,31 @@ class VelinIcon extends HTMLElement {
29
54
  _render() {
30
55
  const name = this.getAttribute('name');
31
56
  const size = this.getAttribute('size') || '24';
32
- const label = this.getAttribute('label');
57
+ const a11y = this._resolveA11y();
33
58
  const provider = this.getAttribute('provider');
59
+ const variant = this.getAttribute('variant');
34
60
 
35
61
  if (!name) {
36
62
  this.innerHTML = '';
37
63
  return;
38
64
  }
39
65
 
40
- if (provider && PROVIDER_CDNS[provider]) {
41
- this._renderFromCDN(name, size, label, provider);
66
+ if (provider && (PROVIDER_CDNS[provider] || PROVIDER_VARIANTS[provider])) {
67
+ this._renderFromCDN(name, size, a11y, provider, variant);
42
68
  return;
43
69
  }
44
70
 
45
- this._renderFromSprite(name, size, label);
71
+ this._renderFromSprite(name, size, a11y);
46
72
  }
47
73
 
48
- _renderFromSprite(name, size, label) {
74
+ _resolveA11y() {
75
+ const label = (this.getAttribute('label') || this.getAttribute('aria-label') || '').trim();
76
+ if (label) return { label, hidden: false };
77
+ const hidden = this.getAttribute('aria-hidden') !== 'false';
78
+ return { label: '', hidden };
79
+ }
80
+
81
+ _renderFromSprite(name, size, a11y) {
49
82
  const svgNS = 'http://www.w3.org/2000/svg';
50
83
  const svg = document.createElementNS(svgNS, 'svg');
51
84
  svg.setAttribute('width', size);
@@ -57,50 +90,79 @@ class VelinIcon extends HTMLElement {
57
90
  svg.setAttribute('stroke-linecap', 'round');
58
91
  svg.setAttribute('stroke-linejoin', 'round');
59
92
  this._applyStyle(svg);
60
- this._applyA11y(svg, label);
61
93
 
62
94
  const use = document.createElementNS(svgNS, 'use');
63
- const spriteUrl = this.getAttribute('sprite') || 'velin-icons.svg';
64
- use.setAttribute('href', `${spriteUrl}#${name}`);
95
+ const spriteAttr = this.getAttribute('sprite');
96
+ const localSymbol = document.getElementById(name);
97
+ const isLocalSymbol = localSymbol && localSymbol.tagName && localSymbol.tagName.toLowerCase() === 'symbol';
98
+ let href;
99
+ if (spriteAttr === '' || (spriteAttr == null && isLocalSymbol)) {
100
+ href = `#${name}`;
101
+ } else {
102
+ const spriteUrl = sanitizeURL(spriteAttr || '/dist/velin-icons.svg') || '/dist/velin-icons.svg';
103
+ href = `${spriteUrl}#${name}`;
104
+ }
105
+ use.setAttribute('href', href);
65
106
  svg.appendChild(use);
107
+ this._applyA11y(svg, a11y);
66
108
 
67
109
  this.innerHTML = '';
68
110
  this.appendChild(svg);
69
111
  this._rendered = true;
70
112
  }
71
113
 
72
- async _renderFromCDN(name, size, label, provider) {
73
- const cacheKey = `${provider}:${name}`;
114
+ async _renderFromCDN(name, size, a11y, provider, variant) {
115
+ const cacheKey = `${provider}:${variant || 'default'}:${name}`;
74
116
 
75
117
  if (_svgCache.has(cacheKey)) {
76
- this._injectSVG(_svgCache.get(cacheKey), size, label);
118
+ this._injectSVG(_svgCache.get(cacheKey), size, a11y);
77
119
  return;
78
120
  }
79
121
 
80
- const url = PROVIDER_CDNS[provider].replace('{name}', name);
122
+ const template = resolveProviderUrl(provider, variant);
123
+ if (!template) {
124
+ this._renderFromSprite(name, size, a11y);
125
+ return;
126
+ }
127
+ const url = sanitizeURL(template.replace('{name}', name));
128
+ if (!url) {
129
+ this._renderFromSprite(name, size, a11y);
130
+ return;
131
+ }
81
132
  try {
82
133
  const res = await fetch(url);
83
134
  if (!res.ok) throw new Error(`${res.status}`);
84
135
  const text = await res.text();
85
136
  if (!text.includes('<svg')) throw new Error('Not SVG');
86
- _svgCache.set(cacheKey, text);
87
- this._injectSVG(text, size, label);
137
+ const clean = await sanitizeSVG(text);
138
+ _svgCache.set(cacheKey, clean);
139
+ this._injectSVG(clean, size, a11y);
88
140
  } catch {
89
- this._renderFromSprite(name, size, label);
141
+ this._renderFromSprite(name, size, a11y);
90
142
  }
91
143
  }
92
144
 
93
- _injectSVG(svgText, size, label) {
145
+ _stripForeignA11y(svg) {
146
+ svg.removeAttribute('role');
147
+ svg.removeAttribute('aria-label');
148
+ svg.removeAttribute('aria-labelledby');
149
+ svg.removeAttribute('aria-hidden');
150
+ svg.querySelector('title')?.remove();
151
+ svg.querySelector('desc')?.remove();
152
+ }
153
+
154
+ _injectSVG(svgText, size, a11y) {
94
155
  const parser = new DOMParser();
95
156
  const doc = parser.parseFromString(svgText, 'image/svg+xml');
96
157
  const svg = doc.querySelector('svg');
97
158
  if (!svg) { this.innerHTML = ''; return; }
98
159
 
160
+ this._stripForeignA11y(svg);
99
161
  svg.setAttribute('width', size);
100
162
  svg.setAttribute('height', size);
101
163
  if (!svg.getAttribute('viewBox')) svg.setAttribute('viewBox', '0 0 24 24');
102
164
  this._applyStyle(svg);
103
- this._applyA11y(svg, label);
165
+ this._applyA11y(svg, a11y);
104
166
 
105
167
  this.innerHTML = '';
106
168
  this.appendChild(document.importNode(svg, true));
@@ -113,19 +175,34 @@ class VelinIcon extends HTMLElement {
113
175
  svg.style.flexShrink = '0';
114
176
  }
115
177
 
116
- _applyA11y(svg, label) {
178
+ _applyA11y(svg, a11y) {
179
+ const { label, hidden } = a11y;
180
+ const use = svg.querySelector('use');
117
181
  if (label) {
118
182
  svg.setAttribute('role', 'img');
119
183
  svg.setAttribute('aria-label', label);
120
- } else {
184
+ svg.removeAttribute('aria-hidden');
185
+ svg.removeAttribute('focusable');
186
+ if (use) use.removeAttribute('aria-hidden');
187
+ } else if (hidden) {
188
+ svg.setAttribute('role', 'presentation');
121
189
  svg.setAttribute('aria-hidden', 'true');
190
+ svg.setAttribute('focusable', 'false');
191
+ svg.removeAttribute('aria-label');
192
+ if (use) {
193
+ use.setAttribute('aria-hidden', 'true');
194
+ use.setAttribute('focusable', 'false');
195
+ }
122
196
  }
123
197
  }
124
198
 
125
199
  static get providers() { return Object.keys(PROVIDER_CDNS); }
126
200
 
127
- static registerProvider(name, urlTemplate) {
201
+ static registerProvider(name, urlTemplate, variantTemplates) {
128
202
  PROVIDER_CDNS[name] = urlTemplate;
203
+ if (variantTemplates && PROVIDER_VARIANTS[name]) {
204
+ Object.assign(PROVIDER_VARIANTS[name], variantTemplates);
205
+ }
129
206
  }
130
207
  }
131
208
 
@@ -1,4 +1,5 @@
1
- import { trapFocus } from './focus-manager.js';
1
+ import { trapFocus, setBackgroundInert, clearBackgroundInert } from './focus-manager.js';
2
+ import { announce } from './a11y-utils.js';
2
3
  import { escapeHTML, sanitizeURL } from './sanitize.js';
3
4
 
4
5
  const styles = `
@@ -61,7 +62,7 @@ class VelinLightbox extends HTMLElement {
61
62
  <div class="content" part="content"></div>
62
63
  <button class="nav nav--next" aria-label="Next">&#8250;</button>
63
64
  <button class="close" aria-label="Close">&times;</button>
64
- <div class="counter" part="counter"></div>
65
+ <div class="counter" part="counter" aria-live="polite" aria-atomic="true"></div>
65
66
  </div>
66
67
  `;
67
68
 
@@ -89,6 +90,7 @@ class VelinLightbox extends HTMLElement {
89
90
  this._previousFocus = document.activeElement;
90
91
  this._index = index;
91
92
  this.setAttribute('open', '');
93
+ setBackgroundInert(this);
92
94
  this._render();
93
95
  document.body.style.overflow = 'hidden';
94
96
  const overlay = this.shadowRoot.querySelector('.overlay');
@@ -99,6 +101,7 @@ class VelinLightbox extends HTMLElement {
99
101
 
100
102
  close() {
101
103
  this.removeAttribute('open');
104
+ clearBackgroundInert();
102
105
  document.body.style.overflow = '';
103
106
  if (this._previousFocus) {
104
107
  this._previousFocus.focus();
@@ -122,7 +125,9 @@ class VelinLightbox extends HTMLElement {
122
125
  container.innerHTML = `<img src="${escapeHTML(src)}" alt="${alt}">`;
123
126
  }
124
127
  const counter = this.shadowRoot.querySelector('.counter');
125
- counter.textContent = `${this._index + 1} / ${this._items.length}`;
128
+ const label = `${this._index + 1} / ${this._items.length}`;
129
+ counter.textContent = label;
130
+ announce(`Slide ${label}`, 'polite');
126
131
  }
127
132
  }
128
133
 
@@ -0,0 +1,90 @@
1
+ /*
2
+ * <velin-live-dot status="live">Realtime</velin-live-dot>
3
+ *
4
+ * Tiny status indicator: a coloured dot with an optional concentric pulse
5
+ * (driven by the velin-live-pulse keyframe in chart-animation.css). Slot
6
+ * children render after the dot for an inline "Live - Streaming" label.
7
+ *
8
+ * Attributes:
9
+ * status "live" (default) | "paused" | "warning" | "error" | "muted"
10
+ * Determines dot colour via the CSS custom prop --velin-live-color.
11
+ * pulse "true" (default) | "false" Disables the looped pulse.
12
+ */
13
+
14
+ import { liveDotLabel } from './a11y-utils.js';
15
+
16
+ const STATUS_COLORS = {
17
+ live: 'var(--velin-color-success, oklch(60% 0.16 145))',
18
+ paused: 'var(--velin-color-text-muted, oklch(60% 0.02 240))',
19
+ warning: 'var(--velin-color-warning, oklch(75% 0.16 80))',
20
+ error: 'var(--velin-color-danger, oklch(60% 0.2 25))',
21
+ muted: 'var(--velin-color-border, oklch(85% 0.01 240))',
22
+ };
23
+
24
+ const styles = `
25
+ :host {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: var(--velin-space-2, 0.5rem);
29
+ font-size: inherit;
30
+ color: inherit;
31
+ line-height: 1.2;
32
+ }
33
+ .dot {
34
+ inline-size: 0.55rem;
35
+ block-size: 0.55rem;
36
+ border-radius: 50%;
37
+ background: var(--velin-live-color);
38
+ flex-shrink: 0;
39
+ }
40
+ :host([pulse="false"]) .dot { animation: none; }
41
+ :host(:not([pulse="false"])) .dot { animation: velin-live-pulse 1.8s var(--velin-ease-out, ease-out) infinite; }
42
+ @media (prefers-reduced-motion: reduce) {
43
+ .dot { animation: none !important; }
44
+ }
45
+ `;
46
+
47
+ const KEYFRAMES_FALLBACK = `
48
+ @keyframes velin-live-pulse {
49
+ 0% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--velin-live-color) 65%, transparent); }
50
+ 70% { box-shadow: 0 0 0 0.6rem color-mix(in oklch, var(--velin-live-color) 0%, transparent); }
51
+ 100% { box-shadow: 0 0 0 0 transparent; }
52
+ }`;
53
+
54
+ class VelinLiveDot extends HTMLElement {
55
+ static get observedAttributes() {
56
+ return ['status', 'pulse'];
57
+ }
58
+
59
+ constructor() {
60
+ super();
61
+ this.attachShadow({ mode: 'open' });
62
+ }
63
+
64
+ connectedCallback() {
65
+ this._render();
66
+ }
67
+
68
+ attributeChangedCallback() {
69
+ if (this.shadowRoot) this._render();
70
+ }
71
+
72
+ _render() {
73
+ const status = this.getAttribute('status') || 'live';
74
+ const color = STATUS_COLORS[status] || STATUS_COLORS.live;
75
+ const label = this.getAttribute('aria-label') || liveDotLabel(status);
76
+ this.setAttribute('role', 'status');
77
+ this.setAttribute('aria-label', label);
78
+ this.style.setProperty('--velin-live-color', color);
79
+ this.shadowRoot.innerHTML = `
80
+ <style>${styles}${KEYFRAMES_FALLBACK}</style>
81
+ <span class="dot" aria-hidden="true"></span><slot></slot>
82
+ `;
83
+ }
84
+ }
85
+
86
+ if (typeof customElements !== 'undefined' && !customElements.get('velin-live-dot')) {
87
+ customElements.define('velin-live-dot', VelinLiveDot);
88
+ }
89
+
90
+ export default VelinLiveDot;