@boostdev/design-system-components 1.1.0 → 1.1.2

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 (269) hide show
  1. package/AGENTS.md +46 -5
  2. package/dist/client.cjs +206 -113
  3. package/dist/client.css +664 -624
  4. package/dist/client.d.cts +9 -3
  5. package/dist/client.d.ts +9 -3
  6. package/dist/client.js +241 -149
  7. package/dist/index.cjs +206 -113
  8. package/dist/index.css +664 -624
  9. package/dist/index.d.cts +9 -3
  10. package/dist/index.d.ts +9 -3
  11. package/dist/index.js +241 -149
  12. package/dist/web-components/chunk-2QA62WCQ.js +48 -0
  13. package/dist/web-components/chunk-4BX6AWXX.js +146 -0
  14. package/dist/web-components/chunk-57TRNCWQ.js +93 -0
  15. package/dist/web-components/chunk-5IPHEONG.js +139 -0
  16. package/dist/web-components/chunk-5R6IFELV.js +63 -0
  17. package/dist/web-components/chunk-5TFWOPLH.js +49 -0
  18. package/dist/web-components/chunk-6I3CFC26.js +89 -0
  19. package/dist/web-components/chunk-6T3RKUKG.js +65 -0
  20. package/dist/web-components/chunk-7FMR53BW.js +127 -0
  21. package/dist/web-components/chunk-A67LCT7C.js +134 -0
  22. package/dist/web-components/chunk-AGKJTMJI.js +56 -0
  23. package/dist/web-components/chunk-BQLZ7I4G.js +56 -0
  24. package/dist/web-components/chunk-CAPI25CU.js +53 -0
  25. package/dist/web-components/chunk-CJ5ZOO42.js +229 -0
  26. package/dist/web-components/chunk-CR5PGUHV.js +109 -0
  27. package/dist/web-components/chunk-ECW3HHWA.js +185 -0
  28. package/dist/web-components/chunk-HGBNDR22.js +98 -0
  29. package/dist/web-components/chunk-IK3PTFZ4.js +133 -0
  30. package/dist/web-components/chunk-IQGLIQSO.js +188 -0
  31. package/dist/web-components/chunk-ITHLKR7Q.js +105 -0
  32. package/dist/web-components/chunk-K5CE6HSB.js +169 -0
  33. package/dist/web-components/chunk-L3XHOMN6.js +111 -0
  34. package/dist/{web-components.js → web-components/chunk-LGEZYFUU.js} +62 -834
  35. package/dist/web-components/chunk-LSC4N56Q.js +168 -0
  36. package/dist/web-components/chunk-LY62SAVN.js +64 -0
  37. package/dist/web-components/chunk-O4UFI2RX.js +244 -0
  38. package/dist/web-components/chunk-O7I63SQX.js +124 -0
  39. package/dist/web-components/chunk-OHUTLSIQ.js +69 -0
  40. package/dist/web-components/chunk-OQTRTMAB.js +27 -0
  41. package/dist/web-components/chunk-OTX4COSV.js +187 -0
  42. package/dist/web-components/chunk-PY62FTRW.js +179 -0
  43. package/dist/web-components/chunk-Q4T5BJT6.js +94 -0
  44. package/dist/web-components/chunk-QDRH46TF.js +281 -0
  45. package/dist/web-components/chunk-QDST57JC.js +119 -0
  46. package/dist/web-components/chunk-QPOKHLMQ.js +45 -0
  47. package/dist/web-components/chunk-SOQQ7RVJ.js +88 -0
  48. package/dist/web-components/chunk-UFFCKVZE.js +149 -0
  49. package/dist/web-components/chunk-VM3YHGQ2.js +152 -0
  50. package/dist/web-components/chunk-VMMVWLXL.js +53 -0
  51. package/dist/web-components/chunk-VSS5ITNE.js +109 -0
  52. package/dist/web-components/chunk-XIENXR7H.js +118 -0
  53. package/dist/web-components/chunk-Y4MAXTMA.js +80 -0
  54. package/dist/web-components/chunk-YLVTEY23.js +165 -0
  55. package/dist/web-components/globals.d.ts +2 -0
  56. package/dist/web-components/globals.js +1 -0
  57. package/dist/web-components/index.d.ts +41 -0
  58. package/dist/web-components/index.js +165 -0
  59. package/dist/web-components/interaction/bds-accordion-item.d.ts +54 -0
  60. package/dist/web-components/interaction/bds-accordion-item.js +7 -0
  61. package/dist/web-components/interaction/bds-accordion.d.ts +50 -0
  62. package/dist/web-components/interaction/bds-accordion.js +7 -0
  63. package/dist/web-components/interaction/bds-button.d.ts +100 -0
  64. package/dist/web-components/interaction/bds-button.js +7 -0
  65. package/dist/web-components/interaction/bds-collapsible.d.ts +52 -0
  66. package/dist/web-components/interaction/bds-collapsible.js +7 -0
  67. package/dist/web-components/interaction/bds-dialog.d.ts +56 -0
  68. package/dist/web-components/interaction/bds-dialog.js +7 -0
  69. package/dist/web-components/interaction/bds-drawer.d.ts +64 -0
  70. package/dist/web-components/interaction/bds-drawer.js +7 -0
  71. package/dist/web-components/interaction/bds-popover.d.ts +62 -0
  72. package/dist/web-components/interaction/bds-popover.js +7 -0
  73. package/dist/web-components/interaction/bds-skip-link.d.ts +36 -0
  74. package/dist/web-components/interaction/bds-skip-link.js +7 -0
  75. package/dist/web-components/interaction/bds-tab-panel.d.ts +26 -0
  76. package/dist/web-components/interaction/bds-tab-panel.js +7 -0
  77. package/dist/web-components/interaction/bds-tab.d.ts +45 -0
  78. package/dist/web-components/interaction/bds-tab.js +7 -0
  79. package/dist/web-components/interaction/bds-tabs.d.ts +46 -0
  80. package/dist/web-components/interaction/bds-tabs.js +7 -0
  81. package/dist/web-components/interaction/bds-toast-provider.d.ts +57 -0
  82. package/dist/web-components/interaction/bds-toast-provider.js +7 -0
  83. package/dist/web-components/interaction/bds-tooltip.d.ts +50 -0
  84. package/dist/web-components/interaction/bds-tooltip.js +7 -0
  85. package/dist/web-components/interaction/form/bds-checkbox.d.ts +70 -0
  86. package/dist/web-components/interaction/form/bds-checkbox.js +7 -0
  87. package/dist/web-components/interaction/form/bds-combobox.d.ts +84 -0
  88. package/dist/web-components/interaction/form/bds-combobox.js +7 -0
  89. package/dist/web-components/interaction/form/bds-file-input.d.ts +62 -0
  90. package/dist/web-components/interaction/form/bds-file-input.js +7 -0
  91. package/dist/web-components/interaction/form/bds-number-input.d.ts +68 -0
  92. package/dist/web-components/interaction/form/bds-number-input.js +7 -0
  93. package/dist/web-components/interaction/form/bds-radio.d.ts +68 -0
  94. package/dist/web-components/interaction/form/bds-radio.js +7 -0
  95. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +71 -0
  96. package/dist/web-components/interaction/form/bds-segmented-control.js +7 -0
  97. package/dist/web-components/interaction/form/bds-select.d.ts +79 -0
  98. package/dist/web-components/interaction/form/bds-select.js +7 -0
  99. package/dist/web-components/interaction/form/bds-slider.d.ts +70 -0
  100. package/dist/web-components/interaction/form/bds-slider.js +7 -0
  101. package/dist/web-components/interaction/form/bds-switch.d.ts +67 -0
  102. package/dist/web-components/interaction/form/bds-switch.js +7 -0
  103. package/dist/web-components/interaction/form/bds-textarea.d.ts +70 -0
  104. package/dist/web-components/interaction/form/bds-textarea.js +7 -0
  105. package/dist/web-components/ui/bds-alert.d.ts +66 -0
  106. package/dist/web-components/ui/bds-alert.js +7 -0
  107. package/dist/web-components/ui/bds-avatar.d.ts +50 -0
  108. package/dist/web-components/ui/bds-avatar.js +7 -0
  109. package/dist/web-components/ui/bds-badge.d.ts +35 -0
  110. package/dist/web-components/ui/bds-badge.js +7 -0
  111. package/dist/web-components/ui/bds-breadcrumb.d.ts +35 -0
  112. package/dist/web-components/ui/bds-breadcrumb.js +7 -0
  113. package/dist/web-components/ui/bds-card.d.ts +72 -0
  114. package/dist/web-components/ui/bds-card.js +7 -0
  115. package/dist/web-components/ui/bds-description-list.d.ts +42 -0
  116. package/dist/web-components/ui/bds-description-list.js +7 -0
  117. package/dist/web-components/ui/bds-icon-wrapper.d.ts +39 -0
  118. package/dist/web-components/ui/bds-icon-wrapper.js +7 -0
  119. package/dist/web-components/ui/bds-link.d.ts +59 -0
  120. package/dist/web-components/ui/bds-link.js +7 -0
  121. package/dist/web-components/ui/bds-loading.d.ts +32 -0
  122. package/dist/web-components/ui/bds-loading.js +7 -0
  123. package/dist/web-components/ui/bds-notification-banner.d.ts +61 -0
  124. package/dist/web-components/ui/bds-notification-banner.js +7 -0
  125. package/dist/web-components/ui/bds-progress-circle.d.ts +53 -0
  126. package/dist/web-components/ui/bds-progress-circle.js +7 -0
  127. package/dist/web-components/ui/bds-progress.d.ts +54 -0
  128. package/dist/web-components/ui/bds-progress.js +7 -0
  129. package/dist/web-components/ui/bds-rating.d.ts +36 -0
  130. package/dist/web-components/ui/bds-rating.js +7 -0
  131. package/dist/web-components/ui/bds-section-header.d.ts +53 -0
  132. package/dist/web-components/ui/bds-section-header.js +8 -0
  133. package/dist/web-components/ui/bds-separator.d.ts +32 -0
  134. package/dist/web-components/ui/bds-separator.js +7 -0
  135. package/dist/web-components/ui/bds-skeleton.d.ts +22 -0
  136. package/dist/web-components/ui/bds-skeleton.js +7 -0
  137. package/dist/web-components/ui/bds-typography.d.ts +44 -0
  138. package/dist/web-components/ui/bds-typography.js +8 -0
  139. package/package.json +169 -4
  140. package/src/components/interaction/Button/Button.module.css +1 -1
  141. package/src/components/interaction/Button/Button.native.mdx +72 -0
  142. package/src/components/interaction/Command/Command.module.css +7 -7
  143. package/src/components/interaction/Command/Command.spec.tsx +37 -0
  144. package/src/components/interaction/Command/Command.tsx +37 -2
  145. package/src/components/interaction/Dialog/Dialog.module.css +10 -13
  146. package/src/components/interaction/Dialog/Dialog.spec.tsx +53 -1
  147. package/src/components/interaction/Dialog/Dialog.stories.tsx +17 -0
  148. package/src/components/interaction/Dialog/Dialog.tsx +50 -16
  149. package/src/components/interaction/Drawer/Drawer.module.css +11 -11
  150. package/src/components/interaction/Drawer/Drawer.spec.tsx +48 -1
  151. package/src/components/interaction/Drawer/Drawer.tsx +38 -4
  152. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +6 -6
  153. package/src/components/interaction/Popover/Popover.module.css +74 -15
  154. package/src/components/interaction/Popover/Popover.spec.tsx +94 -59
  155. package/src/components/interaction/Popover/Popover.tsx +44 -60
  156. package/src/components/interaction/Toast/Toast.module.css +7 -7
  157. package/src/components/interaction/form/Checkbox/Checkbox.module.css +6 -6
  158. package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +74 -0
  159. package/src/components/interaction/form/Combobox/Combobox.module.css +7 -8
  160. package/src/components/interaction/form/FileInput/FileInput.module.css +4 -4
  161. package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
  162. package/src/components/interaction/form/Radio/Radio.module.css +6 -6
  163. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -6
  164. package/src/components/interaction/form/Select/Select.module.css +3 -3
  165. package/src/components/interaction/form/Slider/Slider.module.css +6 -6
  166. package/src/components/interaction/form/Switch/Switch.module.css +8 -8
  167. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
  168. package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
  169. package/src/components/ui/Accordion/Accordion.module.css +5 -5
  170. package/src/components/ui/Alert/Alert.native.mdx +62 -0
  171. package/src/components/ui/Avatar/Avatar.native.mdx +50 -0
  172. package/src/components/ui/Badge/Badge.native.mdx +54 -0
  173. package/src/components/ui/Calendar/Calendar.module.css +7 -7
  174. package/src/components/ui/Carousel/Carousel.module.css +5 -5
  175. package/src/components/ui/Collapsible/Collapsible.module.css +4 -4
  176. package/src/components/ui/Link/Link.module.css +2 -2
  177. package/src/components/ui/Loading/Loading.module.css +7 -7
  178. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -3
  179. package/src/components/ui/Pagination/Pagination.module.css +6 -6
  180. package/src/components/ui/Progress/Progress.module.css +5 -5
  181. package/src/components/ui/Separator/Separator.module.css +3 -3
  182. package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
  183. package/src/components/ui/Table/Table.module.css +9 -9
  184. package/src/components/ui/Tabs/Tabs.module.css +3 -3
  185. package/src/components/ui/Tooltip/Tooltip.module.css +8 -8
  186. package/src/components/ui/Typography/Typography.native.mdx +56 -0
  187. package/src/polyfill-invoker-commands.ts +68 -0
  188. package/src/react-augment.d.ts +19 -0
  189. package/src/stories/ReactNative.mdx +121 -0
  190. package/src/web-components/index.ts +47 -0
  191. package/src/web-components/interaction/BdsButton.mdx +106 -0
  192. package/src/web-components/interaction/BdsPopover.mdx +120 -0
  193. package/src/web-components/interaction/BdsToastProvider.mdx +94 -0
  194. package/src/web-components/interaction/bds-accordion-item.spec.ts +75 -0
  195. package/src/web-components/interaction/bds-accordion-item.ts +181 -0
  196. package/src/web-components/interaction/bds-accordion.spec.ts +73 -0
  197. package/src/web-components/interaction/bds-accordion.ts +99 -0
  198. package/src/web-components/interaction/bds-collapsible.spec.ts +56 -0
  199. package/src/web-components/interaction/bds-collapsible.ts +170 -0
  200. package/src/web-components/interaction/bds-dialog.spec.ts +119 -0
  201. package/src/web-components/interaction/bds-dialog.ts +224 -0
  202. package/src/web-components/interaction/bds-drawer.spec.ts +113 -0
  203. package/src/web-components/interaction/bds-drawer.ts +266 -0
  204. package/src/web-components/interaction/bds-popover.spec.ts +49 -1
  205. package/src/web-components/interaction/bds-popover.ts +12 -0
  206. package/src/web-components/interaction/bds-skip-link.spec.ts +42 -0
  207. package/src/web-components/interaction/bds-skip-link.ts +74 -0
  208. package/src/web-components/interaction/bds-tab-panel.spec.ts +32 -0
  209. package/src/web-components/interaction/bds-tab-panel.ts +38 -0
  210. package/src/web-components/interaction/bds-tab.spec.ts +71 -0
  211. package/src/web-components/interaction/bds-tab.ts +110 -0
  212. package/src/web-components/interaction/bds-tabs.spec.ts +120 -0
  213. package/src/web-components/interaction/bds-tabs.ts +152 -0
  214. package/src/web-components/interaction/bds-tooltip.spec.ts +49 -0
  215. package/src/web-components/interaction/bds-tooltip.ts +127 -0
  216. package/src/web-components/interaction/form/bds-checkbox.spec.ts +63 -0
  217. package/src/web-components/interaction/form/bds-checkbox.ts +168 -0
  218. package/src/web-components/interaction/form/bds-combobox.spec.ts +117 -0
  219. package/src/web-components/interaction/form/bds-combobox.ts +336 -0
  220. package/src/web-components/interaction/form/bds-file-input.spec.ts +58 -0
  221. package/src/web-components/interaction/form/bds-file-input.ts +209 -0
  222. package/src/web-components/interaction/form/bds-number-input.spec.ts +87 -0
  223. package/src/web-components/interaction/form/bds-number-input.ts +221 -0
  224. package/src/web-components/interaction/form/bds-radio.spec.ts +70 -0
  225. package/src/web-components/interaction/form/bds-radio.ts +157 -0
  226. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +74 -0
  227. package/src/web-components/interaction/form/bds-segmented-control.ts +211 -0
  228. package/src/web-components/interaction/form/bds-select.spec.ts +74 -0
  229. package/src/web-components/interaction/form/bds-select.ts +190 -0
  230. package/src/web-components/interaction/form/bds-slider.spec.ts +41 -0
  231. package/src/web-components/interaction/form/bds-slider.ts +162 -0
  232. package/src/web-components/interaction/form/bds-switch.spec.ts +62 -0
  233. package/src/web-components/interaction/form/bds-switch.ts +166 -0
  234. package/src/web-components/interaction/form/bds-textarea.spec.ts +55 -0
  235. package/src/web-components/interaction/form/bds-textarea.ts +143 -0
  236. package/src/web-components/test/helpers.ts +8 -0
  237. package/src/web-components/ui/BdsAlert.mdx +90 -0
  238. package/src/web-components/ui/BdsBadge.mdx +74 -0
  239. package/src/web-components/ui/bds-avatar.spec.ts +75 -0
  240. package/src/web-components/ui/bds-avatar.ts +122 -0
  241. package/src/web-components/ui/bds-breadcrumb.spec.ts +55 -0
  242. package/src/web-components/ui/bds-breadcrumb.ts +114 -0
  243. package/src/web-components/ui/bds-card.spec.ts +70 -0
  244. package/src/web-components/ui/bds-card.ts +156 -0
  245. package/src/web-components/ui/bds-description-list.spec.ts +36 -0
  246. package/src/web-components/ui/bds-description-list.ts +79 -0
  247. package/src/web-components/ui/bds-icon-wrapper.spec.ts +21 -0
  248. package/src/web-components/ui/bds-icon-wrapper.ts +68 -0
  249. package/src/web-components/ui/bds-link.spec.ts +72 -0
  250. package/src/web-components/ui/bds-link.ts +138 -0
  251. package/src/web-components/ui/bds-loading.spec.ts +38 -0
  252. package/src/web-components/ui/bds-loading.ts +76 -0
  253. package/src/web-components/ui/bds-notification-banner.spec.ts +82 -0
  254. package/src/web-components/ui/bds-notification-banner.ts +185 -0
  255. package/src/web-components/ui/bds-progress-circle.spec.ts +58 -0
  256. package/src/web-components/ui/bds-progress-circle.ts +142 -0
  257. package/src/web-components/ui/bds-progress.spec.ts +66 -0
  258. package/src/web-components/ui/bds-progress.ts +127 -0
  259. package/src/web-components/ui/bds-rating.spec.ts +70 -0
  260. package/src/web-components/ui/bds-rating.ts +80 -0
  261. package/src/web-components/ui/bds-section-header.spec.ts +57 -0
  262. package/src/web-components/ui/bds-section-header.ts +96 -0
  263. package/src/web-components/ui/bds-separator.spec.ts +50 -0
  264. package/src/web-components/ui/bds-separator.ts +66 -0
  265. package/src/web-components/ui/bds-skeleton.spec.ts +22 -0
  266. package/src/web-components/ui/bds-skeleton.ts +55 -0
  267. package/src/web-components/ui/bds-typography.spec.ts +76 -0
  268. package/src/web-components/ui/bds-typography.ts +112 -0
  269. package/dist/web-components.d.ts +0 -303
@@ -0,0 +1,266 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ const FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
4
+
5
+ export type DrawerSide = 'left' | 'right';
6
+
7
+ /**
8
+ * `<bds-drawer>` — framework-agnostic Drawer (slide-in panel) custom element.
9
+ *
10
+ * Same modal infrastructure as `<bds-dialog>` but positioned as a side panel.
11
+ * Uses a native `<dialog>` element with a slide-in inner panel.
12
+ *
13
+ * Attributes:
14
+ * open — boolean; controlled open state
15
+ * side — "left" | "right" (default: "right")
16
+ *
17
+ * Slots:
18
+ * title — drawer title text
19
+ * (default) — drawer body content
20
+ *
21
+ * Events:
22
+ * bds-open — fired (bubbles, composed) when the drawer opens
23
+ * bds-close — fired (bubbles, composed) when the drawer closes
24
+ *
25
+ * @example
26
+ * <bds-drawer id="my-drawer" side="right">
27
+ * <span slot="title">Settings</span>
28
+ * <p>Drawer content</p>
29
+ * </bds-drawer>
30
+ * <button commandfor="my-drawer" command="show-modal">Open</button>
31
+ */
32
+ export class BdsDrawer extends LitElement {
33
+ static styles = css`
34
+ :host {
35
+ display: contents;
36
+ }
37
+
38
+ .drawer {
39
+ position: fixed;
40
+ inset: 0;
41
+ margin: 0;
42
+ padding: 0;
43
+ inline-size: 100%;
44
+ max-inline-size: 100%;
45
+ block-size: 100%;
46
+ max-block-size: 100%;
47
+ border: none;
48
+ background: transparent;
49
+ overflow: hidden;
50
+ }
51
+
52
+ .drawer::backdrop {
53
+ background-color: var(--color_backdrop, rgb(0 0 0 / 50%));
54
+ backdrop-filter: blur(3px);
55
+ }
56
+
57
+ .panel {
58
+ position: absolute;
59
+ inset-block: 0;
60
+ display: flex;
61
+ flex-direction: column;
62
+ inline-size: min(28rem, 90vw);
63
+ block-size: 100%;
64
+ background-color: var(--drawer_color, var(--bds-color_bg));
65
+ color: var(--drawer_on-color, var(--bds-color_on-bg));
66
+ box-shadow: var(--bds-shadow_xl);
67
+ overflow: hidden;
68
+ transition: var(--bds-animation_transition);
69
+ }
70
+
71
+ :host([side='right']) .panel {
72
+ inset-inline-end: 0;
73
+ }
74
+
75
+ :host([side='left']) .panel,
76
+ :host(:not([side])) .panel {
77
+ inset-inline-start: 0;
78
+ }
79
+
80
+ .header {
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: space-between;
84
+ padding: var(--bds-space_m) var(--bds-space_l);
85
+ border-block-end: 1px solid currentcolor;
86
+ flex-shrink: 0;
87
+ }
88
+
89
+ .title {
90
+ font-weight: var(--bds-font_weight--semibold);
91
+ font-size: var(--bds-font_size--body);
92
+ }
93
+
94
+ .close-button {
95
+ all: unset;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ inline-size: 2rem;
100
+ block-size: 2rem;
101
+ border-radius: 50%;
102
+ cursor: pointer;
103
+ transition: var(--bds-animation_transition);
104
+ }
105
+
106
+ .close-button svg {
107
+ inline-size: 1.25rem;
108
+ block-size: 1.25rem;
109
+ }
110
+
111
+ .close-button:focus-visible {
112
+ outline: var(--bds-outline_default);
113
+ outline-offset: var(--bds-outline_offset);
114
+ }
115
+
116
+ @media (hover: hover) and (pointer: fine) {
117
+ .close-button:hover {
118
+ background-color: var(--bds-color_bg--subtle);
119
+ }
120
+ }
121
+
122
+ .body {
123
+ flex: 1;
124
+ overflow-block: auto;
125
+ padding: var(--bds-space_l);
126
+ display: flex;
127
+ flex-direction: column;
128
+ }
129
+
130
+ @media (prefers-reduced-motion: reduce) {
131
+ .panel {
132
+ transition: none;
133
+ }
134
+ }
135
+ `;
136
+
137
+ static properties = {
138
+ open: { type: Boolean, reflect: true },
139
+ side: { type: String, reflect: true },
140
+ };
141
+
142
+ declare open: boolean;
143
+ declare side: DrawerSide;
144
+
145
+ private _triggerEl: Element | null = null;
146
+
147
+ constructor() {
148
+ super();
149
+ this.open = false;
150
+ this.side = 'right';
151
+ }
152
+
153
+ connectedCallback() {
154
+ super.connectedCallback();
155
+ this.addEventListener('command', this._onCommand);
156
+ }
157
+
158
+ disconnectedCallback() {
159
+ super.disconnectedCallback();
160
+ this.removeEventListener('command', this._onCommand);
161
+ }
162
+
163
+ updated(changed: Map<string, unknown>) {
164
+ super.updated(changed);
165
+ if (changed.has('open')) {
166
+ const dialog = this._dialog;
167
+ if (!dialog) return;
168
+ if (this.open && !dialog.open) {
169
+ this._triggerEl = document.activeElement;
170
+ dialog.showModal();
171
+ document.body.style.overflow = 'hidden';
172
+ } else if (!this.open && dialog.open) {
173
+ dialog.close();
174
+ document.body.style.overflow = '';
175
+ (this._triggerEl as HTMLElement | null)?.focus();
176
+ this._triggerEl = null;
177
+ }
178
+ }
179
+ }
180
+
181
+ private get _dialog(): HTMLDialogElement | null {
182
+ return this.shadowRoot?.querySelector('dialog') ?? null;
183
+ }
184
+
185
+ private _onCommand = (e: Event) => {
186
+ const command = (e as CommandEvent).command;
187
+ if (command === 'show-modal') this._openDrawer();
188
+ else if (command === 'close') this._closeDrawer();
189
+ };
190
+
191
+ private _openDrawer() {
192
+ if (this.open) return;
193
+ this._triggerEl = document.activeElement;
194
+ this.open = true;
195
+ this.dispatchEvent(new CustomEvent('bds-open', { bubbles: true, composed: true }));
196
+ }
197
+
198
+ private _closeDrawer() {
199
+ if (!this.open) return;
200
+ this.open = false;
201
+ this.dispatchEvent(new CustomEvent('bds-close', { bubbles: true, composed: true }));
202
+ }
203
+
204
+ private _handleBackdropClick(e: MouseEvent) {
205
+ if (e.target === this._dialog) this._closeDrawer();
206
+ }
207
+
208
+ private _handleCancel(e: Event) {
209
+ e.preventDefault();
210
+ this._closeDrawer();
211
+ }
212
+
213
+ private _handleKeydown(e: KeyboardEvent) {
214
+ if (e.key !== 'Tab') return;
215
+ const dialog = this._dialog;
216
+ if (!dialog) return;
217
+ const focusable = Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE));
218
+ if (focusable.length === 0) return;
219
+ const first = focusable[0];
220
+ const last = focusable[focusable.length - 1];
221
+ if (e.shiftKey) {
222
+ if (document.activeElement === first) { e.preventDefault(); last.focus(); }
223
+ } else {
224
+ if (document.activeElement === last) { e.preventDefault(); first.focus(); }
225
+ }
226
+ }
227
+
228
+ render() {
229
+ return html`
230
+ <dialog
231
+ class="drawer"
232
+ aria-modal="true"
233
+ @click=${this._handleBackdropClick}
234
+ @cancel=${this._handleCancel}
235
+ @keydown=${this._handleKeydown}
236
+ >
237
+ <div class="panel">
238
+ <div class="header">
239
+ <span class="title"><slot name="title"></slot></span>
240
+ <button
241
+ type="button"
242
+ class="close-button"
243
+ aria-label="Close drawer"
244
+ @click=${this._closeDrawer}
245
+ >
246
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
247
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
248
+ </svg>
249
+ </button>
250
+ </div>
251
+ <div class="body">
252
+ <slot></slot>
253
+ </div>
254
+ </div>
255
+ </dialog>
256
+ `;
257
+ }
258
+ }
259
+
260
+ customElements.define('bds-drawer', BdsDrawer);
261
+
262
+ declare global {
263
+ interface HTMLElementTagNameMap {
264
+ 'bds-drawer': BdsDrawer;
265
+ }
266
+ }
@@ -1,4 +1,4 @@
1
- import { fixture, cleanup } from '../test/helpers';
1
+ import { fixture, cleanup, makeCommandEvent } from '../test/helpers';
2
2
  import { BdsPopover } from './bds-popover';
3
3
 
4
4
  describe('bds-popover', () => {
@@ -114,6 +114,54 @@ describe('bds-popover', () => {
114
114
  cleanup(el);
115
115
  });
116
116
 
117
+ describe('Invoker Commands API (command event on host)', () => {
118
+ it('opens on --open command', async () => {
119
+ const el = await fixture('<bds-popover id="p1"><button slot="trigger">T</button></bds-popover>') as BdsPopover;
120
+ el.dispatchEvent(makeCommandEvent('--open'));
121
+ await el.updateComplete;
122
+ expect(el.open).toBe(true);
123
+ cleanup(el);
124
+ });
125
+
126
+ it('closes on --close command', async () => {
127
+ const el = await fixture('<bds-popover id="p2" open><button slot="trigger">T</button></bds-popover>') as BdsPopover;
128
+ await el.updateComplete;
129
+ el.dispatchEvent(makeCommandEvent('--close'));
130
+ await el.updateComplete;
131
+ expect(el.open).toBe(false);
132
+ cleanup(el);
133
+ });
134
+
135
+ it('toggles on --toggle command', async () => {
136
+ const el = await fixture('<bds-popover id="p3"><button slot="trigger">T</button></bds-popover>') as BdsPopover;
137
+ el.dispatchEvent(makeCommandEvent('--toggle'));
138
+ await el.updateComplete;
139
+ expect(el.open).toBe(true);
140
+ el.dispatchEvent(makeCommandEvent('--toggle'));
141
+ await el.updateComplete;
142
+ expect(el.open).toBe(false);
143
+ cleanup(el);
144
+ });
145
+
146
+ it('fires bds-open event on --open command', async () => {
147
+ const el = await fixture('<bds-popover id="p4"><button slot="trigger">T</button></bds-popover>') as BdsPopover;
148
+ const handler = vi.fn();
149
+ el.addEventListener('bds-open', handler);
150
+ el.dispatchEvent(makeCommandEvent('--open'));
151
+ await el.updateComplete;
152
+ expect(handler).toHaveBeenCalledOnce();
153
+ cleanup(el);
154
+ });
155
+
156
+ it('ignores unknown commands', async () => {
157
+ const el = await fixture('<bds-popover id="p5"><button slot="trigger">T</button></bds-popover>') as BdsPopover;
158
+ el.dispatchEvent(makeCommandEvent('--unknown'));
159
+ await el.updateComplete;
160
+ expect(el.open).toBe(false);
161
+ cleanup(el);
162
+ });
163
+ });
164
+
117
165
  it('two instances get different anchor names', async () => {
118
166
  const a = await fixture('<bds-popover><button slot="trigger">A</button></bds-popover>');
119
167
  const b = await fixture('<bds-popover><button slot="trigger">B</button></bds-popover>');
@@ -157,18 +157,28 @@ export class BdsPopover extends LitElement {
157
157
  this.style.setProperty('--bds-popover-anchor', this._anchorName);
158
158
  document.addEventListener('keydown', this._onKeydown);
159
159
  document.addEventListener('mousedown', this._onOutsideClick);
160
+ // Invoker Commands API — host is in light DOM so commandfor can reference its id.
161
+ this.addEventListener('command', this._onCommand);
160
162
  }
161
163
 
162
164
  disconnectedCallback() {
163
165
  super.disconnectedCallback();
164
166
  document.removeEventListener('keydown', this._onKeydown);
165
167
  document.removeEventListener('mousedown', this._onOutsideClick);
168
+ this.removeEventListener('command', this._onCommand);
166
169
  }
167
170
 
168
171
  private _onKeydown = (e: KeyboardEvent) => {
169
172
  if (this.open && e.key === 'Escape') this._close();
170
173
  };
171
174
 
175
+ private _onCommand = (e: Event) => {
176
+ const command = (e as CommandEvent).command;
177
+ if (command === '--toggle') this._toggle();
178
+ else if (command === '--open') this._open();
179
+ else if (command === '--close') this._close();
180
+ };
181
+
172
182
  private _onOutsideClick = (e: MouseEvent) => {
173
183
  if (this.open && !this.contains(e.target as Node)) this._close();
174
184
  };
@@ -182,11 +192,13 @@ export class BdsPopover extends LitElement {
182
192
  }
183
193
 
184
194
  private _open() {
195
+ if (this.open) return;
185
196
  this.open = true;
186
197
  this.dispatchEvent(new CustomEvent('bds-open', { bubbles: true, composed: true }));
187
198
  }
188
199
 
189
200
  private _close() {
201
+ if (!this.open) return;
190
202
  this.open = false;
191
203
  this.dispatchEvent(new CustomEvent('bds-close', { bubbles: true, composed: true }));
192
204
  }
@@ -0,0 +1,42 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsSkipLink } from './bds-skip-link';
3
+
4
+ describe('bds-skip-link', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-skip-link')).toBe(BdsSkipLink);
7
+ });
8
+
9
+ it('defaults to href="#main"', async () => {
10
+ const el = await fixture('<bds-skip-link></bds-skip-link>') as BdsSkipLink;
11
+ expect(el.href).toBe('#main');
12
+ cleanup(el);
13
+ });
14
+
15
+ it('renders an <a> element in shadow DOM', async () => {
16
+ const el = await fixture('<bds-skip-link></bds-skip-link>');
17
+ const a = el.shadowRoot!.querySelector('a.skip-link');
18
+ expect(a).not.toBeNull();
19
+ cleanup(el);
20
+ });
21
+
22
+ it('uses the href attribute on the anchor', async () => {
23
+ const el = await fixture('<bds-skip-link href="#content"></bds-skip-link>');
24
+ const a = el.shadowRoot!.querySelector('a');
25
+ expect(a!.getAttribute('href')).toBe('#content');
26
+ cleanup(el);
27
+ });
28
+
29
+ it('renders default slot text "Skip to main content"', async () => {
30
+ const el = await fixture('<bds-skip-link></bds-skip-link>');
31
+ // The slot default content is not visible in textContent from light DOM.
32
+ // Verify the anchor exists and the slot is present.
33
+ expect(el.shadowRoot!.querySelector('slot')).not.toBeNull();
34
+ cleanup(el);
35
+ });
36
+
37
+ it('renders slotted link text', async () => {
38
+ const el = await fixture('<bds-skip-link href="#main">Go to main</bds-skip-link>');
39
+ expect(el.textContent?.trim()).toBe('Go to main');
40
+ cleanup(el);
41
+ });
42
+ });
@@ -0,0 +1,74 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-skip-link>` — framework-agnostic Skip Navigation Link custom element.
5
+ *
6
+ * Visually hidden until focused. Allows keyboard users to skip repetitive
7
+ * navigation and jump directly to main content.
8
+ *
9
+ * Attributes:
10
+ * href — the target anchor (default: "#main")
11
+ *
12
+ * Slots:
13
+ * (default) — link text (default: "Skip to main content")
14
+ *
15
+ * @example
16
+ * <bds-skip-link href="#main-content">Skip to main content</bds-skip-link>
17
+ */
18
+ export class BdsSkipLink extends LitElement {
19
+ static styles = css`
20
+ :host {
21
+ display: block;
22
+ }
23
+
24
+ .skip-link {
25
+ position: absolute;
26
+ inset-block-start: var(--bds-space_s);
27
+ inset-inline-start: var(--bds-space_s);
28
+ z-index: 9999;
29
+ padding: var(--bds-space_xs) var(--bds-space_m);
30
+ background-color: var(--bds-color_bg);
31
+ color: var(--bds-color_on-bg);
32
+ border-radius: var(--bds-border_radius--s);
33
+ font-weight: var(--bds-font_weight--semibold);
34
+ font-size: var(--bds-font_size--body);
35
+ text-decoration: none;
36
+ box-shadow: var(--bds-shadow_m);
37
+ transform: translateY(calc(-100% - var(--bds-space_s)));
38
+ transition: transform var(--bds-animation_transition-duration) var(--bds-animation_easing);
39
+ }
40
+
41
+ .skip-link:focus-visible {
42
+ transform: translateY(0);
43
+ outline: var(--bds-outline_default);
44
+ outline-offset: var(--bds-outline_offset);
45
+ }
46
+ `;
47
+
48
+ static properties = {
49
+ href: { type: String },
50
+ };
51
+
52
+ declare href: string;
53
+
54
+ constructor() {
55
+ super();
56
+ this.href = '#main';
57
+ }
58
+
59
+ render() {
60
+ return html`
61
+ <a class="skip-link" href=${this.href}>
62
+ <slot>Skip to main content</slot>
63
+ </a>
64
+ `;
65
+ }
66
+ }
67
+
68
+ customElements.define('bds-skip-link', BdsSkipLink);
69
+
70
+ declare global {
71
+ interface HTMLElementTagNameMap {
72
+ 'bds-skip-link': BdsSkipLink;
73
+ }
74
+ }
@@ -0,0 +1,32 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsTabPanel } from './bds-tab-panel';
3
+
4
+ describe('bds-tab-panel', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-tab-panel')).toBe(BdsTabPanel);
7
+ });
8
+
9
+ it('renders slotted content', async () => {
10
+ const el = await fixture('<bds-tab-panel>Panel content</bds-tab-panel>');
11
+ expect(el.textContent?.trim()).toBe('Panel content');
12
+ cleanup(el);
13
+ });
14
+
15
+ it('renders a slot in shadow DOM', async () => {
16
+ const el = await fixture('<bds-tab-panel>Content</bds-tab-panel>');
17
+ expect(el.shadowRoot!.querySelector('slot')).not.toBeNull();
18
+ cleanup(el);
19
+ });
20
+
21
+ it('is visible by default (no hidden attribute)', async () => {
22
+ const el = await fixture('<bds-tab-panel>Content</bds-tab-panel>');
23
+ expect(el.hasAttribute('hidden')).toBe(false);
24
+ cleanup(el);
25
+ });
26
+
27
+ it('applies hidden attribute when set externally', async () => {
28
+ const el = await fixture('<bds-tab-panel hidden>Content</bds-tab-panel>');
29
+ expect(el.hasAttribute('hidden')).toBe(true);
30
+ cleanup(el);
31
+ });
32
+ });
@@ -0,0 +1,38 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-tab-panel>` — child element of `<bds-tabs>`.
5
+ *
6
+ * Contains the content displayed for the associated tab. The `hidden`
7
+ * state is managed by the parent `<bds-tabs>` element.
8
+ *
9
+ * Slots:
10
+ * (default) — panel content
11
+ *
12
+ * @example
13
+ * <bds-tab-panel>Panel content here</bds-tab-panel>
14
+ */
15
+ export class BdsTabPanel extends LitElement {
16
+ static styles = css`
17
+ :host {
18
+ display: block;
19
+ padding-block-start: var(--bds-space_m);
20
+ }
21
+
22
+ :host([hidden]) {
23
+ display: none;
24
+ }
25
+ `;
26
+
27
+ render() {
28
+ return html`<slot></slot>`;
29
+ }
30
+ }
31
+
32
+ customElements.define('bds-tab-panel', BdsTabPanel);
33
+
34
+ declare global {
35
+ interface HTMLElementTagNameMap {
36
+ 'bds-tab-panel': BdsTabPanel;
37
+ }
38
+ }
@@ -0,0 +1,71 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsTab } from './bds-tab';
3
+
4
+ describe('bds-tab', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-tab')).toBe(BdsTab);
7
+ });
8
+
9
+ it('renders a button with role="tab"', async () => {
10
+ const el = await fixture('<bds-tab>Label</bds-tab>');
11
+ const btn = el.shadowRoot!.querySelector('button');
12
+ expect(btn).not.toBeNull();
13
+ expect(btn!.getAttribute('role')).toBe('tab');
14
+ cleanup(el);
15
+ });
16
+
17
+ it('is not selected by default', async () => {
18
+ const el = await fixture('<bds-tab>Label</bds-tab>') as BdsTab;
19
+ expect(el.selected).toBe(false);
20
+ expect(el.shadowRoot!.querySelector('button')!.getAttribute('aria-selected')).toBe('false');
21
+ cleanup(el);
22
+ });
23
+
24
+ it('has tabindex="-1" when not selected', async () => {
25
+ const el = await fixture('<bds-tab>Label</bds-tab>');
26
+ const btn = el.shadowRoot!.querySelector('button')!;
27
+ expect(btn.getAttribute('tabindex')).toBe('-1');
28
+ cleanup(el);
29
+ });
30
+
31
+ it('has tabindex="0" when selected', async () => {
32
+ const el = await fixture('<bds-tab selected>Label</bds-tab>');
33
+ const btn = el.shadowRoot!.querySelector('button')!;
34
+ expect(btn.getAttribute('tabindex')).toBe('0');
35
+ cleanup(el);
36
+ });
37
+
38
+ it('sets aria-selected="true" when selected', async () => {
39
+ const el = await fixture('<bds-tab selected>Label</bds-tab>');
40
+ const btn = el.shadowRoot!.querySelector('button')!;
41
+ expect(btn.getAttribute('aria-selected')).toBe('true');
42
+ cleanup(el);
43
+ });
44
+
45
+ it('fires bds-tab-select when clicked', async () => {
46
+ const el = await fixture('<bds-tab>Label</bds-tab>');
47
+ const btn = el.shadowRoot!.querySelector('button')!;
48
+ const handler = vi.fn();
49
+ el.addEventListener('bds-tab-select', handler);
50
+ btn.click();
51
+ expect(handler).toHaveBeenCalledOnce();
52
+ cleanup(el);
53
+ });
54
+
55
+ it('does not fire bds-tab-select when disabled', async () => {
56
+ const el = await fixture('<bds-tab disabled>Label</bds-tab>');
57
+ const btn = el.shadowRoot!.querySelector('button')!;
58
+ const handler = vi.fn();
59
+ el.addEventListener('bds-tab-select', handler);
60
+ btn.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
61
+ expect(handler).not.toHaveBeenCalled();
62
+ cleanup(el);
63
+ });
64
+
65
+ it('button is disabled when disabled attribute is set', async () => {
66
+ const el = await fixture('<bds-tab disabled>Label</bds-tab>');
67
+ const btn = el.shadowRoot!.querySelector('button')!;
68
+ expect(btn.hasAttribute('disabled')).toBe(true);
69
+ cleanup(el);
70
+ });
71
+ });