@boostdev/design-system-components 2.7.1 → 2.11.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 (535) hide show
  1. package/AGENTS.md +54 -46
  2. package/README.md +34 -20
  3. package/dist/client.cjs +1317 -779
  4. package/dist/client.css +1029 -812
  5. package/dist/client.d.cts +88 -26
  6. package/dist/client.d.ts +88 -26
  7. package/dist/client.js +1278 -732
  8. package/dist/index.cjs +1317 -779
  9. package/dist/index.css +1029 -812
  10. package/dist/index.d.cts +88 -26
  11. package/dist/index.d.ts +88 -26
  12. package/dist/index.js +1278 -732
  13. package/dist/web-components/chunk-2OLVQUBU.js +193 -0
  14. package/dist/web-components/chunk-3AY2YKCM.js +223 -0
  15. package/dist/web-components/chunk-3WAV6MDN.js +212 -0
  16. package/dist/web-components/{chunk-CZBIVR4V.js → chunk-47AT5KZD.js} +88 -16
  17. package/dist/web-components/chunk-4A2YYTYN.js +93 -0
  18. package/dist/web-components/{chunk-Q4T5BJT6.js → chunk-62NK3ZSC.js} +18 -3
  19. package/dist/web-components/chunk-66M6ETWD.js +77 -0
  20. package/dist/web-components/{chunk-NCPQ7AF3.js → chunk-6G22I2CK.js} +18 -3
  21. package/dist/web-components/{chunk-QDST57JC.js → chunk-7ZOJ7N4E.js} +14 -4
  22. package/dist/web-components/{chunk-5R6IFELV.js → chunk-AIBOHNDU.js} +11 -1
  23. package/dist/web-components/{chunk-ITHLKR7Q.js → chunk-AP4RDUL2.js} +16 -5
  24. package/dist/web-components/{chunk-QWPACK5L.js → chunk-BPYKZVDK.js} +11 -1
  25. package/dist/web-components/{chunk-YLVTEY23.js → chunk-BTCMJQKB.js} +53 -21
  26. package/dist/web-components/chunk-CUKQ2L5B.js +245 -0
  27. package/dist/web-components/chunk-DIZDCL7Z.js +14 -0
  28. package/dist/web-components/{chunk-PC3IOQAH.js → chunk-DV5T43ZC.js} +27 -7
  29. package/dist/web-components/{chunk-2QA62WCQ.js → chunk-EF7IVYTY.js} +10 -0
  30. package/dist/web-components/{chunk-L3XHOMN6.js → chunk-F3ZDNHZZ.js} +26 -1
  31. package/dist/web-components/chunk-FEDABOR5.js +248 -0
  32. package/dist/web-components/{chunk-6I2DBFQ7.js → chunk-HGFCJXOQ.js} +36 -5
  33. package/dist/web-components/{chunk-SOQQ7RVJ.js → chunk-IPGHUCJT.js} +23 -8
  34. package/dist/web-components/chunk-ITD2MV5K.js +392 -0
  35. package/dist/web-components/{chunk-CM3KYZ6Z.js → chunk-JH6LQKLK.js} +78 -6
  36. package/dist/web-components/chunk-JMCXAAFK.js +120 -0
  37. package/dist/web-components/{chunk-KUI55GFB.js → chunk-JUPCZVVW.js} +50 -18
  38. package/dist/web-components/chunk-JV5QT2OU.js +146 -0
  39. package/dist/web-components/{chunk-3GFWLSLS.js → chunk-KA3VPKMY.js} +29 -6
  40. package/dist/web-components/{chunk-CR5PGUHV.js → chunk-KJGRFDFI.js} +26 -11
  41. package/dist/web-components/{chunk-PY62FTRW.js → chunk-KZPRFY5P.js} +68 -33
  42. package/dist/web-components/{chunk-X46KL5BK.js → chunk-LDIPCTRN.js} +18 -1
  43. package/dist/web-components/chunk-M3ECULRH.js +174 -0
  44. package/dist/web-components/chunk-NBN2ENWJ.js +90 -0
  45. package/dist/web-components/chunk-NIN3JO6Q.js +189 -0
  46. package/dist/web-components/{chunk-2FGATTGT.js → chunk-NTQ4TCUT.js} +76 -55
  47. package/dist/web-components/chunk-OY74D4X6.js +302 -0
  48. package/dist/web-components/{chunk-3S42DZ7W.js → chunk-P24JYC5V.js} +7 -0
  49. package/dist/web-components/{chunk-BKIHQYJR.js → chunk-PXPPKCLT.js} +17 -2
  50. package/dist/web-components/{chunk-TQ7IQ4GZ.js → chunk-QEF2IRID.js} +71 -29
  51. package/dist/web-components/chunk-QZAIRJ75.js +111 -0
  52. package/dist/web-components/{chunk-6I3CFC26.js → chunk-RSZ3BWBK.js} +20 -5
  53. package/dist/web-components/chunk-RVF7W653.js +101 -0
  54. package/dist/web-components/chunk-SEO2F5KJ.js +234 -0
  55. package/dist/web-components/{chunk-VMMVWLXL.js → chunk-SFZ6Q4BP.js} +10 -0
  56. package/dist/web-components/{chunk-LY62SAVN.js → chunk-SYNLFKD3.js} +12 -2
  57. package/dist/web-components/chunk-TMWXRHGF.js +192 -0
  58. package/dist/web-components/{chunk-65JXQOBA.js → chunk-U3YZE6R6.js} +51 -7
  59. package/dist/web-components/chunk-UO7ROSHD.js +424 -0
  60. package/dist/web-components/chunk-VANAKB6D.js +152 -0
  61. package/dist/web-components/{chunk-PXZWYO6Z.js → chunk-VED2DFJ6.js} +108 -29
  62. package/dist/web-components/chunk-W6H5ID2U.js +161 -0
  63. package/dist/web-components/{chunk-57TRNCWQ.js → chunk-WFVKU4GX.js} +22 -11
  64. package/dist/web-components/{chunk-OQTRTMAB.js → chunk-WIBAN7KP.js} +10 -0
  65. package/dist/web-components/chunk-XI7KRAH7.js +54 -0
  66. package/dist/web-components/{chunk-F5E3IT7G.js → chunk-XIZWAWMJ.js} +50 -11
  67. package/dist/web-components/{chunk-FXURM6D5.js → chunk-YFAXGRPR.js} +10 -0
  68. package/dist/web-components/globals.js +1 -1
  69. package/dist/web-components/index.d.ts +77 -707
  70. package/dist/web-components/index.js +339 -2066
  71. package/dist/web-components/interaction/bds-accordion-item.js +1 -1
  72. package/dist/web-components/interaction/bds-accordion.js +1 -1
  73. package/dist/web-components/interaction/bds-button.d.ts +10 -2
  74. package/dist/web-components/interaction/bds-button.js +1 -1
  75. package/dist/web-components/interaction/bds-collapsible.d.ts +7 -2
  76. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  77. package/dist/web-components/interaction/bds-dialog.d.ts +11 -3
  78. package/dist/web-components/interaction/bds-dialog.js +1 -1
  79. package/dist/web-components/interaction/bds-drawer.d.ts +7 -1
  80. package/dist/web-components/interaction/bds-drawer.js +1 -1
  81. package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +51 -0
  82. package/dist/web-components/interaction/bds-dropdown-menu-item.js +7 -0
  83. package/dist/web-components/interaction/bds-dropdown-menu.d.ts +70 -0
  84. package/dist/web-components/interaction/bds-dropdown-menu.js +7 -0
  85. package/dist/web-components/interaction/bds-popover.d.ts +14 -3
  86. package/dist/web-components/interaction/bds-popover.js +1 -1
  87. package/dist/web-components/interaction/bds-skip-link.js +1 -1
  88. package/dist/web-components/interaction/bds-tab-panel.js +1 -1
  89. package/dist/web-components/interaction/bds-tab.js +1 -1
  90. package/dist/web-components/interaction/bds-tabs.d.ts +1 -0
  91. package/dist/web-components/interaction/bds-tabs.js +1 -1
  92. package/dist/web-components/interaction/bds-toast-provider.js +1 -1
  93. package/dist/web-components/interaction/bds-tooltip.d.ts +19 -3
  94. package/dist/web-components/interaction/bds-tooltip.js +1 -1
  95. package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +83 -0
  96. package/dist/web-components/interaction/form/bds-checkbox-group.js +7 -0
  97. package/dist/web-components/interaction/form/bds-checkbox.d.ts +6 -0
  98. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  99. package/dist/web-components/interaction/form/bds-combobox.d.ts +18 -0
  100. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  101. package/dist/web-components/interaction/form/bds-field-group.d.ts +89 -0
  102. package/dist/web-components/interaction/form/bds-field-group.js +7 -0
  103. package/dist/web-components/interaction/form/bds-file-input.d.ts +1 -1
  104. package/dist/web-components/interaction/form/bds-file-input.js +1 -1
  105. package/dist/web-components/interaction/form/bds-form-input.d.ts +92 -0
  106. package/dist/web-components/interaction/form/bds-form-input.js +7 -0
  107. package/dist/web-components/interaction/form/bds-number-input.d.ts +18 -1
  108. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  109. package/dist/web-components/interaction/form/bds-radio-group.d.ts +81 -0
  110. package/dist/web-components/interaction/form/bds-radio-group.js +7 -0
  111. package/dist/web-components/interaction/form/bds-radio.d.ts +34 -6
  112. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  113. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +4 -4
  114. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  115. package/dist/web-components/interaction/form/bds-select.d.ts +22 -1
  116. package/dist/web-components/interaction/form/bds-select.js +1 -1
  117. package/dist/web-components/interaction/form/bds-slider.d.ts +42 -10
  118. package/dist/web-components/interaction/form/bds-slider.js +1 -1
  119. package/dist/web-components/interaction/form/bds-switch.d.ts +20 -0
  120. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  121. package/dist/web-components/interaction/form/bds-textarea.d.ts +23 -3
  122. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  123. package/dist/web-components/layout/bds-grid-item.d.ts +63 -0
  124. package/dist/web-components/layout/bds-grid-item.js +7 -0
  125. package/dist/web-components/layout/bds-grid.d.ts +108 -0
  126. package/dist/web-components/layout/bds-grid.js +7 -0
  127. package/dist/web-components/ui/bds-alert.d.ts +16 -2
  128. package/dist/web-components/ui/bds-alert.js +1 -1
  129. package/dist/web-components/ui/bds-avatar.d.ts +2 -1
  130. package/dist/web-components/ui/bds-avatar.js +1 -1
  131. package/dist/web-components/ui/bds-badge.js +1 -1
  132. package/dist/web-components/ui/bds-breadcrumb.d.ts +3 -1
  133. package/dist/web-components/ui/bds-breadcrumb.js +1 -1
  134. package/dist/web-components/ui/bds-button-container.d.ts +52 -0
  135. package/dist/web-components/ui/bds-button-container.js +7 -0
  136. package/dist/web-components/ui/bds-button-group.d.ts +21 -0
  137. package/dist/web-components/ui/bds-button-group.js +8 -0
  138. package/dist/web-components/ui/bds-calendar.d.ts +121 -0
  139. package/dist/web-components/ui/bds-calendar.js +7 -0
  140. package/dist/web-components/ui/bds-card.d.ts +12 -3
  141. package/dist/web-components/ui/bds-card.js +1 -1
  142. package/dist/web-components/ui/bds-carousel.d.ts +98 -0
  143. package/dist/web-components/ui/bds-carousel.js +7 -0
  144. package/dist/web-components/ui/bds-description-list.js +1 -1
  145. package/dist/web-components/ui/bds-icon-wrapper.d.ts +0 -9
  146. package/dist/web-components/ui/bds-icon-wrapper.js +1 -1
  147. package/dist/web-components/ui/bds-link.js +1 -1
  148. package/dist/web-components/ui/bds-loading.js +1 -1
  149. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  150. package/dist/web-components/ui/bds-pagination.d.ts +47 -0
  151. package/dist/web-components/ui/bds-pagination.js +7 -0
  152. package/dist/web-components/ui/bds-progress-circle.d.ts +3 -1
  153. package/dist/web-components/ui/bds-progress-circle.js +1 -1
  154. package/dist/web-components/ui/bds-progress.d.ts +3 -1
  155. package/dist/web-components/ui/bds-progress.js +1 -1
  156. package/dist/web-components/ui/bds-rating.js +1 -1
  157. package/dist/web-components/ui/bds-section-header.d.ts +18 -4
  158. package/dist/web-components/ui/bds-section-header.js +1 -1
  159. package/dist/web-components/ui/bds-separator.js +1 -1
  160. package/dist/web-components/ui/bds-skeleton.js +1 -1
  161. package/dist/web-components/ui/bds-table.d.ts +52 -0
  162. package/dist/web-components/ui/bds-table.js +7 -0
  163. package/dist/web-components/ui/bds-typography.d.ts +1 -0
  164. package/dist/web-components/ui/bds-typography.js +1 -1
  165. package/package.json +60 -3
  166. package/src/components/interaction/Button/Button.module.css +23 -14
  167. package/src/components/interaction/Button/Button.spec.tsx +20 -0
  168. package/src/components/interaction/Button/Button.tsx +2 -2
  169. package/src/components/interaction/Command/Command.mdx +5 -2
  170. package/src/components/interaction/Command/Command.module.css +23 -7
  171. package/src/components/interaction/Command/Command.spec.tsx +35 -0
  172. package/src/components/interaction/Command/Command.tsx +15 -4
  173. package/src/components/interaction/Command/index.ts +1 -1
  174. package/src/components/interaction/Dialog/Dialog.mdx +4 -4
  175. package/src/components/interaction/Dialog/Dialog.module.css +20 -0
  176. package/src/components/interaction/Dialog/Dialog.spec.tsx +20 -0
  177. package/src/components/interaction/Dialog/Dialog.tsx +8 -3
  178. package/src/components/interaction/Dialog/index.ts +1 -0
  179. package/src/components/interaction/Drawer/Drawer.mdx +5 -2
  180. package/src/components/interaction/Drawer/Drawer.module.css +34 -3
  181. package/src/components/interaction/Drawer/Drawer.spec.tsx +67 -1
  182. package/src/components/interaction/Drawer/Drawer.tsx +30 -12
  183. package/src/components/interaction/Drawer/index.ts +1 -0
  184. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +2 -0
  185. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +34 -0
  186. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +15 -5
  187. package/src/components/interaction/DropdownMenu/index.ts +1 -0
  188. package/src/components/interaction/Popover/Popover.mdx +6 -2
  189. package/src/components/interaction/Popover/Popover.module.css +26 -18
  190. package/src/components/interaction/Popover/Popover.spec.tsx +38 -0
  191. package/src/components/interaction/Popover/Popover.tsx +19 -5
  192. package/src/components/interaction/Popover/index.ts +1 -0
  193. package/src/components/interaction/Rating/Rating.mdx +1 -1
  194. package/src/components/interaction/Rating/Rating.module.css +2 -2
  195. package/src/components/interaction/Rating/Rating.tsx +1 -1
  196. package/src/components/interaction/Rating/index.ts +1 -0
  197. package/src/components/interaction/Toast/Toast.mdx +3 -0
  198. package/src/components/interaction/Toast/Toast.module.css +35 -5
  199. package/src/components/interaction/Toast/Toast.spec.tsx +69 -1
  200. package/src/components/interaction/Toast/Toast.tsx +15 -9
  201. package/src/components/interaction/form/Checkbox/Checkbox.mdx +3 -0
  202. package/src/components/interaction/form/Checkbox/Checkbox.module.css +13 -6
  203. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +6 -0
  204. package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
  205. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +16 -1
  206. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +24 -8
  207. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +32 -2
  208. package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +14 -2
  209. package/src/components/interaction/form/Combobox/Combobox.mdx +11 -4
  210. package/src/components/interaction/form/Combobox/Combobox.module.css +6 -6
  211. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +47 -0
  212. package/src/components/interaction/form/Combobox/Combobox.tsx +10 -5
  213. package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +9 -10
  214. package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +57 -23
  215. package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +129 -7
  216. package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +72 -12
  217. package/src/components/interaction/form/FileInput/FileInput.mdx +9 -1
  218. package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
  219. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +89 -1
  220. package/src/components/interaction/form/FileInput/FileInput.tsx +6 -2
  221. package/src/components/interaction/form/FormInput/FormInput.mdx +9 -5
  222. package/src/components/interaction/form/FormInput/FormInput.module.css +17 -8
  223. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +20 -0
  224. package/src/components/interaction/form/FormInput/FormInput.tsx +7 -2
  225. package/src/components/interaction/form/NumberInput/NumberInput.mdx +5 -1
  226. package/src/components/interaction/form/NumberInput/NumberInput.module.css +6 -6
  227. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +51 -0
  228. package/src/components/interaction/form/NumberInput/NumberInput.tsx +36 -8
  229. package/src/components/interaction/form/Radio/Radio.mdx +8 -1
  230. package/src/components/interaction/form/Radio/Radio.module.css +20 -7
  231. package/src/components/interaction/form/Radio/Radio.spec.tsx +15 -0
  232. package/src/components/interaction/form/Radio/Radio.tsx +10 -4
  233. package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +16 -1
  234. package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +24 -8
  235. package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +32 -2
  236. package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +14 -2
  237. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +9 -3
  238. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +25 -12
  239. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +86 -5
  240. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +44 -4
  241. package/src/components/interaction/form/Select/Select.mdx +10 -4
  242. package/src/components/interaction/form/Select/Select.module.css +4 -4
  243. package/src/components/interaction/form/Select/Select.spec.tsx +34 -0
  244. package/src/components/interaction/form/Select/Select.tsx +21 -9
  245. package/src/components/interaction/form/Slider/Slider.mdx +4 -1
  246. package/src/components/interaction/form/Slider/Slider.module.css +32 -8
  247. package/src/components/interaction/form/Slider/Slider.spec.tsx +29 -0
  248. package/src/components/interaction/form/Slider/Slider.tsx +9 -7
  249. package/src/components/interaction/form/Switch/Switch.mdx +2 -2
  250. package/src/components/interaction/form/Switch/Switch.module.css +12 -13
  251. package/src/components/interaction/form/Switch/Switch.spec.tsx +20 -0
  252. package/src/components/interaction/form/Switch/Switch.tsx +2 -1
  253. package/src/components/interaction/form/Textarea/Textarea.mdx +5 -0
  254. package/src/components/interaction/form/Textarea/Textarea.module.css +5 -5
  255. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -0
  256. package/src/components/interaction/form/Textarea/Textarea.tsx +3 -1
  257. package/src/components/interaction/form/atoms/Message.module.css +1 -1
  258. package/src/components/interaction/form/atoms/Message.spec.tsx +29 -0
  259. package/src/components/interaction/form/atoms/Message.tsx +1 -1
  260. package/src/components/layout/ButtonGroup/ButtonGroup.mdx +11 -0
  261. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -5
  262. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +14 -0
  263. package/src/components/layout/Card/Card.mdx +6 -1
  264. package/src/components/layout/Card/Card.module.css +26 -5
  265. package/src/components/layout/Card/Card.spec.tsx +7 -0
  266. package/src/components/layout/Card/Card.tsx +4 -3
  267. package/src/components/layout/Grid/Grid.spec.tsx +44 -0
  268. package/src/components/layout/Grid/Grid.tsx +5 -5
  269. package/src/components/layout/Grid/GridItem.tsx +6 -6
  270. package/src/components/layout/Grid/masonry.ts +31 -5
  271. package/src/components/layout/Grid/useIsomorphicLayoutEffect.ts +11 -0
  272. package/src/components/layout/IconWrapper/IconWrapper.mdx +8 -2
  273. package/src/components/layout/IconWrapper/IconWrapper.module.css +9 -6
  274. package/src/components/layout/SectionHeader/SectionHeader.mdx +4 -1
  275. package/src/components/layout/SectionHeader/SectionHeader.module.css +10 -9
  276. package/src/components/ui/Accordion/Accordion.mdx +4 -1
  277. package/src/components/ui/Accordion/Accordion.module.css +2 -1
  278. package/src/components/ui/Alert/Alert.mdx +21 -4
  279. package/src/components/ui/Alert/Alert.module.css +9 -4
  280. package/src/components/ui/Alert/Alert.spec.tsx +20 -0
  281. package/src/components/ui/Avatar/Avatar.mdx +8 -3
  282. package/src/components/ui/Avatar/Avatar.module.css +13 -8
  283. package/src/components/ui/Badge/Badge.mdx +3 -1
  284. package/src/components/ui/Breadcrumb/Breadcrumb.mdx +3 -0
  285. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +4 -0
  286. package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +12 -0
  287. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +11 -3
  288. package/src/components/ui/Calendar/Calendar.mdx +6 -0
  289. package/src/components/ui/Calendar/Calendar.module.css +68 -19
  290. package/src/components/ui/Calendar/Calendar.spec.tsx +139 -24
  291. package/src/components/ui/Calendar/Calendar.stories.tsx +14 -0
  292. package/src/components/ui/Calendar/Calendar.tsx +143 -154
  293. package/src/components/ui/Calendar/CalendarRange.mdx +39 -0
  294. package/src/components/ui/Calendar/CalendarRange.spec.tsx +119 -0
  295. package/src/components/ui/Calendar/CalendarRange.stories.tsx +51 -0
  296. package/src/components/ui/Calendar/CalendarRange.tsx +213 -0
  297. package/src/components/ui/Calendar/MonthGrid.tsx +141 -0
  298. package/src/components/ui/Calendar/calendar-core.spec.ts +176 -0
  299. package/src/components/ui/Calendar/calendar-core.ts +164 -0
  300. package/src/components/ui/Calendar/index.ts +1 -0
  301. package/src/components/ui/Carousel/Carousel.mdx +8 -3
  302. package/src/components/ui/Carousel/Carousel.module.css +7 -5
  303. package/src/components/ui/Carousel/Carousel.spec.tsx +67 -0
  304. package/src/components/ui/Carousel/Carousel.tsx +19 -7
  305. package/src/components/ui/Collapsible/Collapsible.mdx +17 -6
  306. package/src/components/ui/Collapsible/Collapsible.module.css +7 -2
  307. package/src/components/ui/Collapsible/Collapsible.spec.tsx +40 -0
  308. package/src/components/ui/Collapsible/Collapsible.tsx +19 -2
  309. package/src/components/ui/DescriptionList/DescriptionList.mdx +2 -0
  310. package/src/components/ui/Link/Link.mdx +4 -2
  311. package/src/components/ui/Loading/Loading.mdx +6 -3
  312. package/src/components/ui/Loading/Loading.module.css +7 -7
  313. package/src/components/ui/Loading/Loading.spec.tsx +12 -0
  314. package/src/components/ui/Loading/Loading.tsx +8 -3
  315. package/src/components/ui/NotificationBanner/NotificationBanner.mdx +4 -0
  316. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +5 -0
  317. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +20 -0
  318. package/src/components/ui/Pagination/Pagination.mdx +29 -7
  319. package/src/components/ui/Pagination/Pagination.module.css +15 -14
  320. package/src/components/ui/Progress/Progress.mdx +6 -2
  321. package/src/components/ui/Progress/Progress.module.css +1 -1
  322. package/src/components/ui/Progress/Progress.spec.tsx +17 -0
  323. package/src/components/ui/Progress/Progress.tsx +2 -2
  324. package/src/components/ui/ProgressCircle/ProgressCircle.mdx +8 -4
  325. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +2 -2
  326. package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +11 -1
  327. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +1 -1
  328. package/src/components/ui/Separator/Separator.mdx +7 -0
  329. package/src/components/ui/Separator/Separator.module.css +1 -0
  330. package/src/components/ui/Skeleton/Skeleton.mdx +17 -0
  331. package/src/components/ui/Skeleton/Skeleton.module.css +12 -5
  332. package/src/components/ui/SkipLink/SkipLink.mdx +19 -0
  333. package/src/components/ui/SkipLink/SkipLink.module.css +8 -7
  334. package/src/components/ui/SkipLink/SkipLink.spec.tsx +28 -0
  335. package/src/components/ui/Table/Table.mdx +14 -0
  336. package/src/components/ui/Table/Table.module.css +13 -8
  337. package/src/components/ui/Table/Table.spec.tsx +56 -0
  338. package/src/components/ui/Table/Table.tsx +44 -6
  339. package/src/components/ui/Tabs/Tabs.mdx +16 -0
  340. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  341. package/src/components/ui/Tabs/Tabs.spec.tsx +20 -0
  342. package/src/components/ui/Tabs/Tabs.tsx +5 -1
  343. package/src/components/ui/Tooltip/Tooltip.mdx +9 -0
  344. package/src/components/ui/Tooltip/Tooltip.module.css +32 -12
  345. package/src/components/ui/Tooltip/Tooltip.spec.tsx +56 -0
  346. package/src/components/ui/Tooltip/Tooltip.tsx +26 -7
  347. package/src/components/ui/Typography/Typography.mdx +7 -0
  348. package/src/components/ui/Typography/Typography.module.css +5 -5
  349. package/src/index.ts +7 -3
  350. package/src/stories/DesignSystem/Borders.mdx +14 -9
  351. package/src/stories/DesignSystem/Colors.mdx +43 -41
  352. package/src/stories/DesignSystem/Overview.mdx +4 -3
  353. package/src/stories/Introduction.mdx +5 -5
  354. package/src/web-components/globals.ts +7 -0
  355. package/src/web-components/index.ts +2 -0
  356. package/src/web-components/interaction/BdsAccordion.mdx +10 -1
  357. package/src/web-components/interaction/BdsButton.mdx +15 -4
  358. package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
  359. package/src/web-components/interaction/BdsCollapsible.mdx +3 -3
  360. package/src/web-components/interaction/BdsDialog.mdx +12 -7
  361. package/src/web-components/interaction/BdsDialog.stories.tsx +2 -2
  362. package/src/web-components/interaction/BdsDrawer.mdx +9 -6
  363. package/src/web-components/interaction/BdsDropdownMenu.mdx +20 -1
  364. package/src/web-components/interaction/BdsPopover.mdx +7 -3
  365. package/src/web-components/interaction/BdsSkipLink.mdx +14 -1
  366. package/src/web-components/interaction/BdsTabs.mdx +18 -1
  367. package/src/web-components/interaction/BdsToastProvider.mdx +21 -3
  368. package/src/web-components/interaction/BdsTooltip.mdx +4 -4
  369. package/src/web-components/interaction/bds-accordion-item.ts +10 -0
  370. package/src/web-components/interaction/bds-accordion.spec.ts +41 -0
  371. package/src/web-components/interaction/bds-accordion.ts +28 -2
  372. package/src/web-components/interaction/bds-button.spec.ts +81 -0
  373. package/src/web-components/interaction/bds-button.ts +60 -13
  374. package/src/web-components/interaction/bds-collapsible.spec.ts +93 -2
  375. package/src/web-components/interaction/bds-collapsible.ts +49 -7
  376. package/src/web-components/interaction/bds-dialog.spec.ts +49 -0
  377. package/src/web-components/interaction/bds-dialog.ts +68 -11
  378. package/src/web-components/interaction/bds-drawer.spec.ts +93 -4
  379. package/src/web-components/interaction/bds-drawer.ts +54 -21
  380. package/src/web-components/interaction/bds-dropdown-menu-item.ts +12 -1
  381. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +63 -0
  382. package/src/web-components/interaction/bds-dropdown-menu.ts +45 -7
  383. package/src/web-components/interaction/bds-popover.spec.ts +80 -6
  384. package/src/web-components/interaction/bds-popover.ts +83 -32
  385. package/src/web-components/interaction/bds-skip-link.spec.ts +18 -0
  386. package/src/web-components/interaction/bds-skip-link.ts +29 -8
  387. package/src/web-components/interaction/bds-tab-panel.ts +10 -0
  388. package/src/web-components/interaction/bds-tab.spec.ts +25 -0
  389. package/src/web-components/interaction/bds-tab.ts +20 -5
  390. package/src/web-components/interaction/bds-tabs.spec.ts +92 -5
  391. package/src/web-components/interaction/bds-tabs.ts +42 -1
  392. package/src/web-components/interaction/bds-toast-provider.spec.ts +43 -0
  393. package/src/web-components/interaction/bds-toast-provider.ts +50 -21
  394. package/src/web-components/interaction/bds-tooltip.spec.ts +71 -0
  395. package/src/web-components/interaction/bds-tooltip.ts +100 -5
  396. package/src/web-components/interaction/form/BdsCheckbox.mdx +20 -1
  397. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +8 -0
  398. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +4 -3
  399. package/src/web-components/interaction/form/BdsCombobox.mdx +39 -2
  400. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +31 -0
  401. package/src/web-components/interaction/form/BdsFieldGroup.mdx +8 -3
  402. package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
  403. package/src/web-components/interaction/form/BdsFileInput.mdx +26 -5
  404. package/src/web-components/interaction/form/BdsFormInput.mdx +16 -1
  405. package/src/web-components/interaction/form/BdsNumberInput.mdx +34 -2
  406. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +27 -3
  407. package/src/web-components/interaction/form/BdsRadio.mdx +39 -12
  408. package/src/web-components/interaction/form/BdsRadio.stories.tsx +26 -0
  409. package/src/web-components/interaction/form/BdsRadioGroup.mdx +9 -4
  410. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +37 -11
  411. package/src/web-components/interaction/form/BdsSelect.mdx +22 -4
  412. package/src/web-components/interaction/form/BdsSelect.stories.tsx +33 -0
  413. package/src/web-components/interaction/form/BdsSlider.mdx +36 -19
  414. package/src/web-components/interaction/form/BdsSlider.stories.tsx +24 -0
  415. package/src/web-components/interaction/form/BdsSwitch.mdx +38 -11
  416. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +16 -0
  417. package/src/web-components/interaction/form/BdsTextarea.mdx +42 -16
  418. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +21 -0
  419. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +99 -0
  420. package/src/web-components/interaction/form/bds-checkbox-group.ts +95 -9
  421. package/src/web-components/interaction/form/bds-checkbox.spec.ts +55 -0
  422. package/src/web-components/interaction/form/bds-checkbox.ts +30 -5
  423. package/src/web-components/interaction/form/bds-combobox.spec.ts +79 -0
  424. package/src/web-components/interaction/form/bds-combobox.ts +102 -17
  425. package/src/web-components/interaction/form/bds-field-group.spec.ts +158 -0
  426. package/src/web-components/interaction/form/bds-field-group.ts +167 -23
  427. package/src/web-components/interaction/form/bds-file-input.spec.ts +73 -5
  428. package/src/web-components/interaction/form/bds-file-input.ts +71 -34
  429. package/src/web-components/interaction/form/bds-form-input.spec.ts +22 -0
  430. package/src/web-components/interaction/form/bds-form-input.ts +27 -16
  431. package/src/web-components/interaction/form/bds-number-input.spec.ts +49 -0
  432. package/src/web-components/interaction/form/bds-number-input.ts +118 -29
  433. package/src/web-components/interaction/form/bds-radio-group.spec.ts +57 -0
  434. package/src/web-components/interaction/form/bds-radio-group.ts +60 -12
  435. package/src/web-components/interaction/form/bds-radio.spec.ts +123 -0
  436. package/src/web-components/interaction/form/bds-radio.ts +136 -23
  437. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +55 -0
  438. package/src/web-components/interaction/form/bds-segmented-control.ts +81 -59
  439. package/src/web-components/interaction/form/bds-select.spec.ts +57 -0
  440. package/src/web-components/interaction/form/bds-select.ts +96 -8
  441. package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
  442. package/src/web-components/interaction/form/bds-slider.ts +181 -33
  443. package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
  444. package/src/web-components/interaction/form/bds-switch.ts +142 -41
  445. package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
  446. package/src/web-components/interaction/form/bds-textarea.ts +109 -8
  447. package/src/web-components/layout/BdsGrid.mdx +6 -2
  448. package/src/web-components/layout/BdsGrid.stories.tsx +4 -0
  449. package/src/web-components/layout/bds-grid-item.spec.ts +21 -0
  450. package/src/web-components/layout/bds-grid-item.ts +17 -3
  451. package/src/web-components/layout/bds-grid.spec.ts +98 -0
  452. package/src/web-components/layout/bds-grid.ts +139 -19
  453. package/src/web-components/ui/BdsAlert.mdx +6 -5
  454. package/src/web-components/ui/BdsAlert.stories.tsx +6 -6
  455. package/src/web-components/ui/BdsAvatar.mdx +4 -2
  456. package/src/web-components/ui/BdsBreadcrumb.mdx +3 -2
  457. package/src/web-components/ui/BdsButtonContainer.mdx +64 -0
  458. package/src/web-components/ui/BdsButtonContainer.stories.tsx +84 -0
  459. package/src/web-components/ui/BdsCalendar.mdx +29 -4
  460. package/src/web-components/ui/BdsCalendar.stories.tsx +16 -18
  461. package/src/web-components/ui/BdsCalendarRange.mdx +71 -0
  462. package/src/web-components/ui/BdsCalendarRange.stories.tsx +31 -0
  463. package/src/web-components/ui/BdsCard.mdx +7 -3
  464. package/src/web-components/ui/BdsCard.stories.tsx +17 -1
  465. package/src/web-components/ui/BdsCarousel.mdx +40 -12
  466. package/src/web-components/ui/BdsDescriptionList.mdx +6 -1
  467. package/src/web-components/ui/BdsIconWrapper.mdx +3 -2
  468. package/src/web-components/ui/BdsLink.mdx +14 -2
  469. package/src/web-components/ui/BdsNotificationBanner.mdx +18 -0
  470. package/src/web-components/ui/BdsPagination.mdx +40 -1
  471. package/src/web-components/ui/BdsProgress.mdx +8 -2
  472. package/src/web-components/ui/BdsProgressCircle.mdx +5 -2
  473. package/src/web-components/ui/BdsRating.mdx +7 -0
  474. package/src/web-components/ui/BdsSectionHeader.mdx +22 -3
  475. package/src/web-components/ui/BdsSectionHeader.stories.tsx +15 -13
  476. package/src/web-components/ui/BdsTable.mdx +23 -2
  477. package/src/web-components/ui/BdsTable.stories.tsx +8 -2
  478. package/src/web-components/ui/BdsTypography.mdx +17 -1
  479. package/src/web-components/ui/bds-alert.spec.ts +17 -2
  480. package/src/web-components/ui/bds-alert.ts +31 -5
  481. package/src/web-components/ui/bds-avatar.spec.ts +29 -0
  482. package/src/web-components/ui/bds-avatar.ts +30 -12
  483. package/src/web-components/ui/bds-badge.spec.ts +5 -0
  484. package/src/web-components/ui/bds-badge.ts +11 -1
  485. package/src/web-components/ui/bds-breadcrumb.spec.ts +30 -0
  486. package/src/web-components/ui/bds-breadcrumb.ts +26 -4
  487. package/src/web-components/ui/bds-button-container.spec.ts +39 -0
  488. package/src/web-components/ui/bds-button-container.ts +123 -0
  489. package/src/web-components/ui/bds-button-group.spec.ts +14 -20
  490. package/src/web-components/ui/bds-button-group.ts +13 -64
  491. package/src/web-components/ui/bds-calendar-range.spec.ts +123 -0
  492. package/src/web-components/ui/bds-calendar-range.ts +307 -0
  493. package/src/web-components/ui/bds-calendar.spec.ts +176 -0
  494. package/src/web-components/ui/bds-calendar.ts +186 -344
  495. package/src/web-components/ui/bds-card.spec.ts +32 -0
  496. package/src/web-components/ui/bds-card.ts +34 -9
  497. package/src/web-components/ui/bds-carousel.spec.ts +147 -0
  498. package/src/web-components/ui/bds-carousel.ts +113 -36
  499. package/src/web-components/ui/bds-description-list.spec.ts +15 -0
  500. package/src/web-components/ui/bds-description-list.ts +37 -3
  501. package/src/web-components/ui/bds-icon-wrapper.spec.ts +12 -0
  502. package/src/web-components/ui/bds-icon-wrapper.ts +16 -15
  503. package/src/web-components/ui/bds-link.spec.ts +17 -0
  504. package/src/web-components/ui/bds-link.ts +26 -11
  505. package/src/web-components/ui/bds-loading.ts +11 -1
  506. package/src/web-components/ui/bds-notification-banner.spec.ts +13 -0
  507. package/src/web-components/ui/bds-notification-banner.ts +18 -1
  508. package/src/web-components/ui/bds-pagination.spec.ts +44 -0
  509. package/src/web-components/ui/bds-pagination.ts +39 -19
  510. package/src/web-components/ui/bds-progress-circle.spec.ts +21 -0
  511. package/src/web-components/ui/bds-progress-circle.ts +17 -5
  512. package/src/web-components/ui/bds-progress.spec.ts +21 -0
  513. package/src/web-components/ui/bds-progress.ts +19 -6
  514. package/src/web-components/ui/bds-rating.spec.ts +6 -0
  515. package/src/web-components/ui/bds-rating.ts +12 -2
  516. package/src/web-components/ui/bds-section-header.spec.ts +39 -8
  517. package/src/web-components/ui/bds-section-header.ts +47 -13
  518. package/src/web-components/ui/bds-separator.ts +10 -0
  519. package/src/web-components/ui/bds-skeleton.ts +10 -0
  520. package/src/web-components/ui/bds-table.spec.ts +63 -0
  521. package/src/web-components/ui/bds-table.ts +88 -28
  522. package/src/web-components/ui/bds-typography.spec.ts +47 -0
  523. package/src/web-components/ui/bds-typography.ts +28 -10
  524. package/src/web-components/ui/calendar-styles.ts +94 -0
  525. package/dist/web-components/chunk-5TFWOPLH.js +0 -49
  526. package/dist/web-components/chunk-7FMR53BW.js +0 -127
  527. package/dist/web-components/chunk-AGKJTMJI.js +0 -56
  528. package/dist/web-components/chunk-BQLZ7I4G.js +0 -56
  529. package/dist/web-components/chunk-IYGF6CD5.js +0 -118
  530. package/dist/web-components/chunk-QBB6ZX4R.js +0 -107
  531. package/dist/web-components/chunk-Y3POAKGS.js +0 -133
  532. package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
  533. package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
  534. package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
  535. package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
@@ -37,12 +37,29 @@ describe('bds-collapsible', () => {
37
37
  const el = await fixture('<bds-collapsible><span slot="summary">Title</span><p>Content</p></bds-collapsible>') as BdsCollapsible;
38
38
  const handler = vi.fn();
39
39
  el.addEventListener('bds-toggle', handler);
40
- // jsdom does not fire the native toggle event on summary click;
41
- // dispatch it directly on the <details> element instead.
40
+ // jsdom does not simulate a real click-driven state change, so emulate it:
41
+ // flip the native <details> element's own `open` property first (what the
42
+ // browser does before dispatching), then fire the "toggle" event it would emit.
42
43
  const details = el.shadowRoot!.querySelector('details')!;
44
+ details.open = true;
43
45
  details.dispatchEvent(new Event('toggle', { bubbles: false }));
44
46
  await el.updateComplete;
45
47
  expect(handler).toHaveBeenCalledOnce();
48
+ expect(handler.mock.calls[0][0].detail).toEqual({ open: true });
49
+ expect(el.open).toBe(true);
50
+ cleanup(el);
51
+ });
52
+
53
+ it('does not dispatch a redundant bds-toggle when the toggle event fires without a real state change', async () => {
54
+ const el = await fixture('<bds-collapsible><span slot="summary">Title</span><p>Content</p></bds-collapsible>') as BdsCollapsible;
55
+ const handler = vi.fn();
56
+ el.addEventListener('bds-toggle', handler);
57
+ // Native <details> "toggle" event coalesces and only fires on real state changes;
58
+ // a spurious dispatch with no actual open-state change must be a no-op.
59
+ const details = el.shadowRoot!.querySelector('details')!;
60
+ details.dispatchEvent(new Event('toggle', { bubbles: false }));
61
+ await el.updateComplete;
62
+ expect(handler).not.toHaveBeenCalled();
46
63
  cleanup(el);
47
64
  });
48
65
 
@@ -53,4 +70,78 @@ describe('bds-collapsible', () => {
53
70
  expect(el.getAttribute('open')).not.toBeNull();
54
71
  cleanup(el);
55
72
  });
73
+
74
+ describe('name grouping (exclusive open)', () => {
75
+ it('closes other open members of the same group when one opens', async () => {
76
+ const a = await fixture(
77
+ '<bds-collapsible name="faq" open><span slot="summary">A</span><p>Content A</p></bds-collapsible>',
78
+ ) as BdsCollapsible;
79
+ const b = await fixture(
80
+ '<bds-collapsible name="faq"><span slot="summary">B</span><p>Content B</p></bds-collapsible>',
81
+ ) as BdsCollapsible;
82
+ expect(a.open).toBe(true);
83
+
84
+ b.open = true;
85
+ await b.updateComplete;
86
+ await a.updateComplete;
87
+
88
+ expect(b.open).toBe(true);
89
+ expect(a.open).toBe(false);
90
+ expect(a.hasAttribute('open')).toBe(false);
91
+ cleanup(a);
92
+ cleanup(b);
93
+ });
94
+
95
+ it('fires bds-toggle on the sibling that gets force-closed', async () => {
96
+ const a = await fixture(
97
+ '<bds-collapsible name="faq" open><span slot="summary">A</span><p>Content A</p></bds-collapsible>',
98
+ ) as BdsCollapsible;
99
+ const b = await fixture(
100
+ '<bds-collapsible name="faq"><span slot="summary">B</span><p>Content B</p></bds-collapsible>',
101
+ ) as BdsCollapsible;
102
+ const aHandler = vi.fn();
103
+ a.addEventListener('bds-toggle', aHandler);
104
+
105
+ b.open = true;
106
+ await b.updateComplete;
107
+ await a.updateComplete;
108
+
109
+ expect(aHandler).toHaveBeenCalledOnce();
110
+ expect(aHandler.mock.calls[0][0].detail).toEqual({ open: false });
111
+ cleanup(a);
112
+ cleanup(b);
113
+ });
114
+
115
+ it('does not affect open collapsibles in a different group', async () => {
116
+ const a = await fixture(
117
+ '<bds-collapsible name="faq" open><span slot="summary">A</span><p>Content A</p></bds-collapsible>',
118
+ ) as BdsCollapsible;
119
+ const b = await fixture(
120
+ '<bds-collapsible name="other"><span slot="summary">B</span><p>Content B</p></bds-collapsible>',
121
+ ) as BdsCollapsible;
122
+
123
+ b.open = true;
124
+ await b.updateComplete;
125
+ await a.updateComplete;
126
+
127
+ expect(a.open).toBe(true);
128
+ expect(b.open).toBe(true);
129
+ cleanup(a);
130
+ cleanup(b);
131
+ });
132
+
133
+ it('does not group collapsibles with no name attribute', async () => {
134
+ const a = await fixture('<bds-collapsible open><span slot="summary">A</span><p>Content A</p></bds-collapsible>') as BdsCollapsible;
135
+ const b = await fixture('<bds-collapsible><span slot="summary">B</span><p>Content B</p></bds-collapsible>') as BdsCollapsible;
136
+
137
+ b.open = true;
138
+ await b.updateComplete;
139
+ await a.updateComplete;
140
+
141
+ expect(a.open).toBe(true);
142
+ expect(b.open).toBe(true);
143
+ cleanup(a);
144
+ cleanup(b);
145
+ });
146
+ });
56
147
  });
@@ -7,8 +7,11 @@ import { LitElement, css, html } from 'lit';
7
7
  *
8
8
  * Attributes:
9
9
  * open — boolean; controlled open state
10
- * name — groups multiple collapsibles so only one is open at a time
11
- * (native <details name="…"> behaviour)
10
+ * name — groups multiple collapsibles so only one is open at a time.
11
+ * Each instance renders its own <details> in its own shadow root, so
12
+ * native <details name> grouping (tree-scoped) can't see across
13
+ * instances; the component emulates it by closing same-name siblings
14
+ * found via document.querySelectorAll() whenever one opens.
12
15
  *
13
16
  * Slots:
14
17
  * summary — the always-visible trigger text
@@ -26,6 +29,16 @@ import { LitElement, css, html } from 'lit';
26
29
  */
27
30
  export class BdsCollapsible extends LitElement {
28
31
  static styles = css`
32
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
33
+ here; otherwise elements with an explicit size + padding size as
34
+ content-box and layouts drift from the React (light-DOM) versions. */
35
+ :host,
36
+ *,
37
+ *::before,
38
+ *::after {
39
+ box-sizing: border-box;
40
+ }
41
+
29
42
  :host {
30
43
  display: block;
31
44
  }
@@ -91,7 +104,9 @@ export class BdsCollapsible extends LitElement {
91
104
 
92
105
  .content {
93
106
  padding: var(--bds-space_m);
94
- background-color: var(--collapsible_content-bg, var(--bds-color_bg));
107
+ /* React counterpart leaves the content panel transparent; keep the override
108
+ hook for consumers but default it to match, not to --bds-color_bg. */
109
+ background-color: var(--collapsible_content-bg, transparent);
95
110
  color: var(--collapsible_on-color, var(--bds-color_on-bg));
96
111
  font-size: var(--bds-font_size--body);
97
112
  line-height: var(--bds-font_line-height--body);
@@ -131,20 +146,47 @@ export class BdsCollapsible extends LitElement {
131
146
  }
132
147
  }
133
148
  }
149
+
150
+ // Emulate native <details name="…"> exclusive-open grouping ourselves: each
151
+ // bds-collapsible renders its own <details> inside its own shadow root, so the
152
+ // browser's built-in name-based grouping is tree-scoped per shadow root and never
153
+ // sees siblings. Whenever this instance opens, close any other open member of the
154
+ // same group.
155
+ if (changed.has('open') && this.open && this.name) {
156
+ this._closeOtherGroupMembers();
157
+ }
134
158
  }
135
159
 
136
- private _onToggle(e: Event) {
137
- const details = e.currentTarget as HTMLDetailsElement;
138
- this.open = details.open;
160
+ private _closeOtherGroupMembers() {
161
+ const groupName = this.name;
162
+ document.querySelectorAll('bds-collapsible').forEach((el) => {
163
+ if (el !== this && el.name === groupName && el.open) {
164
+ el._setOpen(false);
165
+ }
166
+ });
167
+ }
168
+
169
+ // State-guarded setter: returns early once already in the target state so a
170
+ // native <details> "toggle" event re-fired by the browser after a script-driven
171
+ // attribute change (see the toggle event spec's coalescing note) never dispatches
172
+ // a redundant bds-toggle.
173
+ private _setOpen(newOpen: boolean) {
174
+ if (this.open === newOpen) return;
175
+ this.open = newOpen;
139
176
  this.dispatchEvent(
140
177
  new CustomEvent('bds-toggle', {
141
178
  bubbles: true,
142
179
  composed: true,
143
- detail: { open: this.open },
180
+ detail: { open: newOpen },
144
181
  }),
145
182
  );
146
183
  }
147
184
 
185
+ private _onToggle(e: Event) {
186
+ const details = e.currentTarget as HTMLDetailsElement;
187
+ this._setOpen(details.open);
188
+ }
189
+
148
190
  render() {
149
191
  return html`
150
192
  <details
@@ -116,4 +116,53 @@ describe('bds-dialog', () => {
116
116
  expect(el.querySelector('p')!.textContent).toBe('Hello world');
117
117
  cleanup(el);
118
118
  });
119
+
120
+ it('responds to the --show-modal custom command', async () => {
121
+ const el = await fixture('<bds-dialog><p>Content</p></bds-dialog>') as BdsDialog;
122
+ expect(el.open).toBeFalsy();
123
+ el.dispatchEvent(makeCommandEvent('--show-modal'));
124
+ await el.updateComplete;
125
+ expect(el.open).toBe(true);
126
+ expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalled();
127
+ cleanup(el);
128
+ });
129
+
130
+ it('responds to the --close custom command', async () => {
131
+ const el = await fixture('<bds-dialog open><p>Content</p></bds-dialog>') as BdsDialog;
132
+ el.dispatchEvent(makeCommandEvent('--close'));
133
+ await el.updateComplete;
134
+ expect(el.open).toBe(false);
135
+ expect(HTMLDialogElement.prototype.close).toHaveBeenCalled();
136
+ cleanup(el);
137
+ });
138
+
139
+ it('releases the body scroll lock when disconnected while open', async () => {
140
+ const el = await fixture('<bds-dialog open><p>Content</p></bds-dialog>') as BdsDialog;
141
+ expect(document.body.style.overflow).toBe('hidden');
142
+ el.remove();
143
+ expect(document.body.style.overflow).toBe('');
144
+ });
145
+
146
+ it('forwards host aria-label to the shadow dialog', async () => {
147
+ const el = await fixture('<bds-dialog aria-label="Confirm deletion"><p>Content</p></bds-dialog>');
148
+ const dialog = el.shadowRoot!.querySelector('dialog')!;
149
+ expect(dialog.getAttribute('aria-label')).toBe('Confirm deletion');
150
+ cleanup(el);
151
+ });
152
+
153
+ it('omits aria-label on the shadow dialog when unset', async () => {
154
+ const el = await fixture('<bds-dialog><p>Content</p></bds-dialog>');
155
+ const dialog = el.shadowRoot!.querySelector('dialog')!;
156
+ expect(dialog.hasAttribute('aria-label')).toBe(false);
157
+ cleanup(el);
158
+ });
159
+
160
+ it('lays out the dialog as a flex column so dialog-content can scroll internally', () => {
161
+ expect(BdsDialog.styles.cssText).toMatch(/\.dialog\s*{[^}]*display:\s*flex/);
162
+ expect(BdsDialog.styles.cssText).toMatch(/\.dialog\s*{[^}]*flex-direction:\s*column/);
163
+ });
164
+
165
+ it('close button has a :focus-visible outline rule', () => {
166
+ expect(BdsDialog.styles.cssText).toMatch(/\.close-button:focus-visible\s*{[^}]*outline:/);
167
+ });
119
168
  });
@@ -1,4 +1,4 @@
1
- import { LitElement, css, html } from 'lit';
1
+ import { LitElement, css, html, nothing } from 'lit';
2
2
 
3
3
  const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
4
4
 
@@ -7,10 +7,13 @@ const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabi
7
7
  *
8
8
  * Wraps a native `<dialog>` element with `showModal()` / `close()` lifecycle,
9
9
  * backdrop-click to dismiss, Escape key handling, Tab focus trapping, and
10
- * the Invoker Commands API (`command="show-modal"` / `command="close"`).
10
+ * the Invoker Commands API (custom commands `--show-modal` / `--close`; the
11
+ * unprefixed `show-modal` / `close` values are still accepted for
12
+ * compatibility with polyfilled/legacy environments).
11
13
  *
12
14
  * Attributes:
13
15
  * open — boolean; controlled open state (syncs to native showModal/close)
16
+ * aria-label — accessible name forwarded to the inner shadow <dialog>
14
17
  *
15
18
  * Slots:
16
19
  * (default) — dialog content
@@ -20,13 +23,23 @@ const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabi
20
23
  * bds-close — fired (bubbles, composed) when the dialog closes
21
24
  *
22
25
  * @example
23
- * <bds-dialog id="my-dialog">
26
+ * <bds-dialog id="my-dialog" aria-label="Confirm deletion">
24
27
  * <p>Dialog content</p>
25
28
  * </bds-dialog>
26
- * <button commandfor="my-dialog" command="show-modal">Open</button>
29
+ * <button commandfor="my-dialog" command="--show-modal">Open</button>
27
30
  */
28
31
  export class BdsDialog extends LitElement {
29
32
  static styles = css`
33
+ /* Shadow DOM does not inherit the global box-sizing reset, so set it
34
+ here; otherwise elements with an explicit size + padding size as
35
+ content-box and layouts drift from the React (light-DOM) versions. */
36
+ :host,
37
+ *,
38
+ *::before,
39
+ *::after {
40
+ box-sizing: border-box;
41
+ }
42
+
30
43
  :host {
31
44
  display: contents;
32
45
  }
@@ -34,9 +47,11 @@ export class BdsDialog extends LitElement {
34
47
  .dialog {
35
48
  font-size: var(--bds-font_size--body--s);
36
49
  border: var(--dialog_border, none);
37
- --bdc_color: var(--dialog_border-color, currentcolor);
50
+ --bdc_color: currentcolor;
38
51
  --bdc-outline_radius: var(--bds-border_radius--m);
39
52
 
53
+ display: flex;
54
+ flex-direction: column;
40
55
  border-radius: var(--dialog_radius, var(--bdc-outline_radius));
41
56
  box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--dialog_shadow, var(--bdc-outline_shadow));
42
57
  background-color: var(--dialog_color, var(--bds-color_grey--subtle));
@@ -44,10 +59,19 @@ export class BdsDialog extends LitElement {
44
59
  padding: var(--bds-space_l);
45
60
  max-inline-size: 90svw;
46
61
  max-block-size: 90svh;
62
+
63
+ /* visible (not hidden) so the close button, offset outside the box via
64
+ inset-*: -1em, is never clipped; .dialog-content scrolls internally. */
47
65
  overflow: visible;
48
66
  position: relative;
49
67
  }
50
68
 
69
+ /* A native <dialog> is display:none until opened; the flex layout above
70
+ would otherwise override the UA rule and render the dialog permanently. */
71
+ .dialog:not([open]) {
72
+ display: none;
73
+ }
74
+
51
75
  .dialog-content {
52
76
  display: grid;
53
77
  gap: var(--bds-grid_gap);
@@ -70,12 +94,26 @@ export class BdsDialog extends LitElement {
70
94
  background-color: var(--bds-color_bg);
71
95
  color: currentcolor;
72
96
  border: var(--dialog_close-border, none);
73
- --bdc_color: var(--dialog_close-border-color, currentcolor);
97
+ --bdc_color: currentcolor;
74
98
 
75
99
  box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
76
100
  cursor: pointer;
77
101
  }
78
102
 
103
+ /* Extend the hit area to the 44x44px minimum touch target (WCAG 2.5.5)
104
+ without growing the visual 2em disc. */
105
+ .close-button::after {
106
+ content: '';
107
+ position: absolute;
108
+ inset-block: -0.5rem;
109
+ inset-inline: -0.5rem;
110
+ }
111
+
112
+ .close-button:focus-visible {
113
+ outline: var(--bds-outline_default);
114
+ outline-offset: var(--bds-outline_offset);
115
+ }
116
+
79
117
  .close-button svg {
80
118
  inline-size: 1.25em;
81
119
  block-size: 1.25em;
@@ -88,16 +126,19 @@ export class BdsDialog extends LitElement {
88
126
  `;
89
127
 
90
128
  static properties = {
91
- open: { type: Boolean, reflect: true },
129
+ open: { type: Boolean, reflect: true },
130
+ ariaLabel: { type: String, attribute: 'aria-label' },
92
131
  };
93
132
 
94
133
  declare open: boolean;
134
+ declare ariaLabel: string | null;
95
135
 
96
136
  private _triggerEl: Element | null = null;
97
137
 
98
138
  constructor() {
99
139
  super();
100
140
  this.open = false;
141
+ this.ariaLabel = null;
101
142
  }
102
143
 
103
144
  connectedCallback() {
@@ -108,6 +149,13 @@ export class BdsDialog extends LitElement {
108
149
  disconnectedCallback() {
109
150
  super.disconnectedCallback();
110
151
  this.removeEventListener('command', this._onCommand);
152
+ // Release the body scroll-lock if the dialog is removed from the DOM
153
+ // while still open — otherwise the lock leaks and the page stays
154
+ // non-scrollable forever.
155
+ if (this.open) {
156
+ document.body.style.overflow = '';
157
+ this._triggerEl = null;
158
+ }
111
159
  }
112
160
 
113
161
  updated(changed: Map<string, unknown>) {
@@ -134,8 +182,12 @@ export class BdsDialog extends LitElement {
134
182
 
135
183
  private _onCommand = (e: Event) => {
136
184
  const command = (e as CommandEvent).command;
137
- if (command === 'show-modal') this._openDialog();
138
- else if (command === 'close') this._closeDialog();
185
+ // Custom (`--`-prefixed) commands are what the Invoker Commands API
186
+ // actually dispatches on a non-<dialog> custom-element target — the
187
+ // unprefixed "show-modal"/"close" keywords are still accepted so
188
+ // programmatic dispatches and polyfilled environments keep working.
189
+ if (command === '--show-modal' || command === 'show-modal') this._openDialog();
190
+ else if (command === '--close' || command === 'close') this._closeDialog();
139
191
  };
140
192
 
141
193
  private _openDialog() {
@@ -178,10 +230,14 @@ export class BdsDialog extends LitElement {
178
230
  if (all.length === 0) return;
179
231
  const first = all[0];
180
232
  const last = all[all.length - 1];
233
+ // Focus inside the shadow tree (e.g. the close button) resolves via
234
+ // shadowRoot.activeElement — document.activeElement only ever reports
235
+ // the host element for shadow-internal focus.
236
+ const active = this.shadowRoot?.activeElement ?? document.activeElement;
181
237
  if (e.shiftKey) {
182
- if (document.activeElement === first) { e.preventDefault(); last.focus(); }
238
+ if (active === first) { e.preventDefault(); last.focus(); }
183
239
  } else {
184
- if (document.activeElement === last) { e.preventDefault(); first.focus(); }
240
+ if (active === last) { e.preventDefault(); first.focus(); }
185
241
  }
186
242
  }
187
243
 
@@ -190,6 +246,7 @@ export class BdsDialog extends LitElement {
190
246
  <dialog
191
247
  class="dialog"
192
248
  aria-modal="true"
249
+ aria-label=${this.ariaLabel ?? nothing}
193
250
  @click=${this._handleBackdropClick}
194
251
  @cancel=${this._handleCancel}
195
252
  @keydown=${this._handleKeydown}
@@ -71,24 +71,33 @@ describe('bds-drawer', () => {
71
71
  cleanup(el);
72
72
  });
73
73
 
74
- it('fires bds-open on show-modal command', async () => {
74
+ it('fires bds-open on --show-modal command', async () => {
75
75
  const el = await fixture('<bds-drawer><p>Content</p></bds-drawer>');
76
76
  const handler = vi.fn();
77
77
  el.addEventListener('bds-open', handler);
78
- el.dispatchEvent(makeCommandEvent('show-modal'));
78
+ el.dispatchEvent(makeCommandEvent('--show-modal'));
79
79
  expect(handler).toHaveBeenCalledOnce();
80
80
  cleanup(el);
81
81
  });
82
82
 
83
- it('fires bds-close on close command', async () => {
83
+ it('fires bds-close on --close command', async () => {
84
84
  const el = await fixture('<bds-drawer open><p>Content</p></bds-drawer>');
85
85
  const handler = vi.fn();
86
86
  el.addEventListener('bds-close', handler);
87
- el.dispatchEvent(makeCommandEvent('close'));
87
+ el.dispatchEvent(makeCommandEvent('--close'));
88
88
  expect(handler).toHaveBeenCalledOnce();
89
89
  cleanup(el);
90
90
  });
91
91
 
92
+ it('ignores the reserved built-in "show-modal"/"close" command values (never fire on custom elements)', async () => {
93
+ const el = await fixture('<bds-drawer><p>Content</p></bds-drawer>');
94
+ const openHandler = vi.fn();
95
+ el.addEventListener('bds-open', openHandler);
96
+ el.dispatchEvent(makeCommandEvent('show-modal'));
97
+ expect(openHandler).not.toHaveBeenCalled();
98
+ cleanup(el);
99
+ });
100
+
92
101
  it('close button click fires bds-close', async () => {
93
102
  const el = await fixture('<bds-drawer open><p>Content</p></bds-drawer>');
94
103
  const handler = vi.fn();
@@ -110,4 +119,84 @@ describe('bds-drawer', () => {
110
119
  expect(el.querySelector('p')!.textContent).toBe('Body content');
111
120
  cleanup(el);
112
121
  });
122
+
123
+ it('releases body scroll lock when disconnected while open', async () => {
124
+ const el = await fixture('<bds-drawer open><p>Content</p></bds-drawer>');
125
+ expect(document.body.style.overflow).toBe('hidden');
126
+ cleanup(el);
127
+ expect(document.body.style.overflow).toBe('');
128
+ });
129
+
130
+ it('does not touch body scroll when disconnected while closed', async () => {
131
+ const el = await fixture('<bds-drawer><p>Content</p></bds-drawer>');
132
+ document.body.style.overflow = 'hidden';
133
+ cleanup(el);
134
+ expect(document.body.style.overflow).toBe('hidden');
135
+ document.body.style.overflow = '';
136
+ });
137
+
138
+ it('wires aria-labelledby to the title element by default', async () => {
139
+ const el = await fixture('<bds-drawer><span slot="title">Settings</span></bds-drawer>');
140
+ const dialog = el.shadowRoot!.querySelector('dialog')!;
141
+ expect(dialog.getAttribute('aria-labelledby')).toBe('title');
142
+ expect(dialog.hasAttribute('aria-label')).toBe(false);
143
+ const titleEl = el.shadowRoot!.querySelector('#title');
144
+ expect(titleEl).not.toBeNull();
145
+ cleanup(el);
146
+ });
147
+
148
+ it('forwards a host aria-label as an override, dropping aria-labelledby', async () => {
149
+ const el = await fixture('<bds-drawer aria-label="Custom name"><span slot="title">Settings</span></bds-drawer>');
150
+ const dialog = el.shadowRoot!.querySelector('dialog')!;
151
+ expect(dialog.getAttribute('aria-label')).toBe('Custom name');
152
+ expect(dialog.hasAttribute('aria-labelledby')).toBe(false);
153
+ cleanup(el);
154
+ });
155
+
156
+ it('closes when the backdrop (dialog element itself) is clicked', async () => {
157
+ const el = await fixture('<bds-drawer open><p>Content</p></bds-drawer>');
158
+ const handler = vi.fn();
159
+ el.addEventListener('bds-close', handler);
160
+ const dialog = el.shadowRoot!.querySelector('dialog')!;
161
+ dialog.dispatchEvent(new MouseEvent('click', { bubbles: true }));
162
+ expect(handler).toHaveBeenCalledOnce();
163
+ cleanup(el);
164
+ });
165
+
166
+ it('does not close when an inner control is clicked, including a synthetic keyboard-activation click at 0,0 (target !== dialog)', async () => {
167
+ // Regression test: the previous implementation compared e.clientX/clientY
168
+ // against the panel's bounding rect, so a keyboard-activated synthetic
169
+ // click (dispatched at 0,0) on an inner control was misclassified as an
170
+ // "outside" backdrop click and closed the drawer. Coordinates must no
171
+ // longer matter — only event.target identity.
172
+ const el = await fixture('<bds-drawer open><p>Content</p></bds-drawer>');
173
+ const handler = vi.fn();
174
+ el.addEventListener('bds-close', handler);
175
+ const header = el.shadowRoot!.querySelector('.header')!;
176
+ header.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: 0, clientY: 0 }));
177
+ expect(handler).not.toHaveBeenCalled();
178
+ cleanup(el);
179
+ });
180
+
181
+ it('Tab focus trap treats slotted body elements as part of the trap boundary', async () => {
182
+ // Regression test: previously the trap only queried the shadow tree, so
183
+ // `all` was just [close-button] and slotted light-DOM buttons were never
184
+ // reachable as the wrap boundary.
185
+ const el = await fixture(
186
+ '<bds-drawer open><button id="a">A</button><button id="b">B</button></bds-drawer>',
187
+ ) as BdsDrawer;
188
+ const dialog = el.shadowRoot!.querySelector('dialog')!;
189
+ const closeBtn = el.shadowRoot!.querySelector<HTMLButtonElement>('button.close-button')!;
190
+ const btnB = el.querySelector<HTMLButtonElement>('#b')!;
191
+ const focusSpy = vi.spyOn(btnB, 'focus');
192
+ // Stub shadowRoot.activeElement (jsdom doesn't implement shadow-DOM focus
193
+ // retargeting) to simulate focus currently sitting on the close button —
194
+ // the "first" stop in the merged [close-button, ...slotted] boundary list.
195
+ Object.defineProperty(el.shadowRoot, 'activeElement', { value: closeBtn, configurable: true });
196
+ const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true, cancelable: true });
197
+ dialog.dispatchEvent(event);
198
+ // Shift+Tab from "first" must wrap to "last" — the last slotted button (#b).
199
+ expect(focusSpy).toHaveBeenCalled();
200
+ cleanup(el);
201
+ });
113
202
  });