@boostdev/design-system-components 1.1.1 → 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 (206) hide show
  1. package/dist/client.cjs +50 -50
  2. package/dist/client.css +456 -456
  3. package/dist/client.js +50 -50
  4. package/dist/index.cjs +50 -50
  5. package/dist/index.css +456 -456
  6. package/dist/index.js +50 -50
  7. package/dist/web-components/chunk-2QA62WCQ.js +48 -0
  8. package/dist/web-components/chunk-4BX6AWXX.js +146 -0
  9. package/dist/web-components/chunk-57TRNCWQ.js +93 -0
  10. package/dist/web-components/chunk-5IPHEONG.js +139 -0
  11. package/dist/web-components/chunk-5R6IFELV.js +63 -0
  12. package/dist/web-components/chunk-5TFWOPLH.js +49 -0
  13. package/dist/web-components/chunk-6I3CFC26.js +89 -0
  14. package/dist/web-components/chunk-6T3RKUKG.js +65 -0
  15. package/dist/web-components/chunk-7FMR53BW.js +127 -0
  16. package/dist/web-components/chunk-A67LCT7C.js +134 -0
  17. package/dist/web-components/chunk-AGKJTMJI.js +56 -0
  18. package/dist/web-components/chunk-BQLZ7I4G.js +56 -0
  19. package/dist/web-components/chunk-CAPI25CU.js +53 -0
  20. package/dist/web-components/chunk-CJ5ZOO42.js +229 -0
  21. package/dist/web-components/chunk-CR5PGUHV.js +109 -0
  22. package/dist/web-components/chunk-ECW3HHWA.js +185 -0
  23. package/dist/web-components/chunk-HGBNDR22.js +98 -0
  24. package/dist/web-components/chunk-IK3PTFZ4.js +133 -0
  25. package/dist/web-components/chunk-IQGLIQSO.js +188 -0
  26. package/dist/web-components/chunk-ITHLKR7Q.js +105 -0
  27. package/dist/web-components/chunk-K5CE6HSB.js +169 -0
  28. package/dist/web-components/chunk-L3XHOMN6.js +111 -0
  29. package/dist/{web-components.js → web-components/chunk-LGEZYFUU.js} +62 -844
  30. package/dist/web-components/chunk-LSC4N56Q.js +168 -0
  31. package/dist/web-components/chunk-LY62SAVN.js +64 -0
  32. package/dist/web-components/chunk-O4UFI2RX.js +244 -0
  33. package/dist/web-components/chunk-O7I63SQX.js +124 -0
  34. package/dist/web-components/chunk-OHUTLSIQ.js +69 -0
  35. package/dist/web-components/chunk-OQTRTMAB.js +27 -0
  36. package/dist/web-components/chunk-OTX4COSV.js +187 -0
  37. package/dist/web-components/chunk-PY62FTRW.js +179 -0
  38. package/dist/web-components/chunk-Q4T5BJT6.js +94 -0
  39. package/dist/web-components/chunk-QDRH46TF.js +281 -0
  40. package/dist/web-components/chunk-QDST57JC.js +119 -0
  41. package/dist/web-components/chunk-QPOKHLMQ.js +45 -0
  42. package/dist/web-components/chunk-SOQQ7RVJ.js +88 -0
  43. package/dist/web-components/chunk-UFFCKVZE.js +149 -0
  44. package/dist/web-components/chunk-VM3YHGQ2.js +152 -0
  45. package/dist/web-components/chunk-VMMVWLXL.js +53 -0
  46. package/dist/web-components/chunk-VSS5ITNE.js +109 -0
  47. package/dist/web-components/chunk-XIENXR7H.js +118 -0
  48. package/dist/web-components/chunk-Y4MAXTMA.js +80 -0
  49. package/dist/web-components/chunk-YLVTEY23.js +165 -0
  50. package/dist/web-components/globals.d.ts +2 -0
  51. package/dist/web-components/globals.js +1 -0
  52. package/dist/web-components/index.d.ts +41 -0
  53. package/dist/web-components/index.js +165 -0
  54. package/dist/web-components/interaction/bds-accordion-item.d.ts +54 -0
  55. package/dist/web-components/interaction/bds-accordion-item.js +7 -0
  56. package/dist/web-components/interaction/bds-accordion.d.ts +50 -0
  57. package/dist/web-components/interaction/bds-accordion.js +7 -0
  58. package/dist/web-components/interaction/bds-button.d.ts +100 -0
  59. package/dist/web-components/interaction/bds-button.js +7 -0
  60. package/dist/web-components/interaction/bds-collapsible.d.ts +52 -0
  61. package/dist/web-components/interaction/bds-collapsible.js +7 -0
  62. package/dist/web-components/interaction/bds-dialog.d.ts +56 -0
  63. package/dist/web-components/interaction/bds-dialog.js +7 -0
  64. package/dist/web-components/interaction/bds-drawer.d.ts +64 -0
  65. package/dist/web-components/interaction/bds-drawer.js +7 -0
  66. package/dist/web-components/interaction/bds-popover.d.ts +62 -0
  67. package/dist/web-components/interaction/bds-popover.js +7 -0
  68. package/dist/web-components/interaction/bds-skip-link.d.ts +36 -0
  69. package/dist/web-components/interaction/bds-skip-link.js +7 -0
  70. package/dist/web-components/interaction/bds-tab-panel.d.ts +26 -0
  71. package/dist/web-components/interaction/bds-tab-panel.js +7 -0
  72. package/dist/web-components/interaction/bds-tab.d.ts +45 -0
  73. package/dist/web-components/interaction/bds-tab.js +7 -0
  74. package/dist/web-components/interaction/bds-tabs.d.ts +46 -0
  75. package/dist/web-components/interaction/bds-tabs.js +7 -0
  76. package/dist/web-components/interaction/bds-toast-provider.d.ts +57 -0
  77. package/dist/web-components/interaction/bds-toast-provider.js +7 -0
  78. package/dist/web-components/interaction/bds-tooltip.d.ts +50 -0
  79. package/dist/web-components/interaction/bds-tooltip.js +7 -0
  80. package/dist/web-components/interaction/form/bds-checkbox.d.ts +70 -0
  81. package/dist/web-components/interaction/form/bds-checkbox.js +7 -0
  82. package/dist/web-components/interaction/form/bds-combobox.d.ts +84 -0
  83. package/dist/web-components/interaction/form/bds-combobox.js +7 -0
  84. package/dist/web-components/interaction/form/bds-file-input.d.ts +62 -0
  85. package/dist/web-components/interaction/form/bds-file-input.js +7 -0
  86. package/dist/web-components/interaction/form/bds-number-input.d.ts +68 -0
  87. package/dist/web-components/interaction/form/bds-number-input.js +7 -0
  88. package/dist/web-components/interaction/form/bds-radio.d.ts +68 -0
  89. package/dist/web-components/interaction/form/bds-radio.js +7 -0
  90. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +71 -0
  91. package/dist/web-components/interaction/form/bds-segmented-control.js +7 -0
  92. package/dist/web-components/interaction/form/bds-select.d.ts +79 -0
  93. package/dist/web-components/interaction/form/bds-select.js +7 -0
  94. package/dist/web-components/interaction/form/bds-slider.d.ts +70 -0
  95. package/dist/web-components/interaction/form/bds-slider.js +7 -0
  96. package/dist/web-components/interaction/form/bds-switch.d.ts +67 -0
  97. package/dist/web-components/interaction/form/bds-switch.js +7 -0
  98. package/dist/web-components/interaction/form/bds-textarea.d.ts +70 -0
  99. package/dist/web-components/interaction/form/bds-textarea.js +7 -0
  100. package/dist/web-components/ui/bds-alert.d.ts +66 -0
  101. package/dist/web-components/ui/bds-alert.js +7 -0
  102. package/dist/web-components/ui/bds-avatar.d.ts +50 -0
  103. package/dist/web-components/ui/bds-avatar.js +7 -0
  104. package/dist/web-components/ui/bds-badge.d.ts +35 -0
  105. package/dist/web-components/ui/bds-badge.js +7 -0
  106. package/dist/web-components/ui/bds-breadcrumb.d.ts +35 -0
  107. package/dist/web-components/ui/bds-breadcrumb.js +7 -0
  108. package/dist/web-components/ui/bds-card.d.ts +72 -0
  109. package/dist/web-components/ui/bds-card.js +7 -0
  110. package/dist/web-components/ui/bds-description-list.d.ts +42 -0
  111. package/dist/web-components/ui/bds-description-list.js +7 -0
  112. package/dist/web-components/ui/bds-icon-wrapper.d.ts +39 -0
  113. package/dist/web-components/ui/bds-icon-wrapper.js +7 -0
  114. package/dist/web-components/ui/bds-link.d.ts +59 -0
  115. package/dist/web-components/ui/bds-link.js +7 -0
  116. package/dist/web-components/ui/bds-loading.d.ts +32 -0
  117. package/dist/web-components/ui/bds-loading.js +7 -0
  118. package/dist/web-components/ui/bds-notification-banner.d.ts +61 -0
  119. package/dist/web-components/ui/bds-notification-banner.js +7 -0
  120. package/dist/web-components/ui/bds-progress-circle.d.ts +53 -0
  121. package/dist/web-components/ui/bds-progress-circle.js +7 -0
  122. package/dist/web-components/ui/bds-progress.d.ts +54 -0
  123. package/dist/web-components/ui/bds-progress.js +7 -0
  124. package/dist/web-components/ui/bds-rating.d.ts +36 -0
  125. package/dist/web-components/ui/bds-rating.js +7 -0
  126. package/dist/web-components/ui/bds-section-header.d.ts +53 -0
  127. package/dist/web-components/ui/bds-section-header.js +8 -0
  128. package/dist/web-components/ui/bds-separator.d.ts +32 -0
  129. package/dist/web-components/ui/bds-separator.js +7 -0
  130. package/dist/web-components/ui/bds-skeleton.d.ts +22 -0
  131. package/dist/web-components/ui/bds-skeleton.js +7 -0
  132. package/dist/web-components/ui/bds-typography.d.ts +44 -0
  133. package/dist/web-components/ui/bds-typography.js +8 -0
  134. package/package.json +169 -4
  135. package/src/web-components/index.ts +47 -0
  136. package/src/web-components/interaction/bds-accordion-item.spec.ts +75 -0
  137. package/src/web-components/interaction/bds-accordion-item.ts +181 -0
  138. package/src/web-components/interaction/bds-accordion.spec.ts +73 -0
  139. package/src/web-components/interaction/bds-accordion.ts +99 -0
  140. package/src/web-components/interaction/bds-collapsible.spec.ts +56 -0
  141. package/src/web-components/interaction/bds-collapsible.ts +170 -0
  142. package/src/web-components/interaction/bds-dialog.spec.ts +119 -0
  143. package/src/web-components/interaction/bds-dialog.ts +224 -0
  144. package/src/web-components/interaction/bds-drawer.spec.ts +113 -0
  145. package/src/web-components/interaction/bds-drawer.ts +266 -0
  146. package/src/web-components/interaction/bds-skip-link.spec.ts +42 -0
  147. package/src/web-components/interaction/bds-skip-link.ts +74 -0
  148. package/src/web-components/interaction/bds-tab-panel.spec.ts +32 -0
  149. package/src/web-components/interaction/bds-tab-panel.ts +38 -0
  150. package/src/web-components/interaction/bds-tab.spec.ts +71 -0
  151. package/src/web-components/interaction/bds-tab.ts +110 -0
  152. package/src/web-components/interaction/bds-tabs.spec.ts +120 -0
  153. package/src/web-components/interaction/bds-tabs.ts +152 -0
  154. package/src/web-components/interaction/bds-tooltip.spec.ts +49 -0
  155. package/src/web-components/interaction/bds-tooltip.ts +127 -0
  156. package/src/web-components/interaction/form/bds-checkbox.spec.ts +63 -0
  157. package/src/web-components/interaction/form/bds-checkbox.ts +168 -0
  158. package/src/web-components/interaction/form/bds-combobox.spec.ts +117 -0
  159. package/src/web-components/interaction/form/bds-combobox.ts +336 -0
  160. package/src/web-components/interaction/form/bds-file-input.spec.ts +58 -0
  161. package/src/web-components/interaction/form/bds-file-input.ts +209 -0
  162. package/src/web-components/interaction/form/bds-number-input.spec.ts +87 -0
  163. package/src/web-components/interaction/form/bds-number-input.ts +221 -0
  164. package/src/web-components/interaction/form/bds-radio.spec.ts +70 -0
  165. package/src/web-components/interaction/form/bds-radio.ts +157 -0
  166. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +74 -0
  167. package/src/web-components/interaction/form/bds-segmented-control.ts +211 -0
  168. package/src/web-components/interaction/form/bds-select.spec.ts +74 -0
  169. package/src/web-components/interaction/form/bds-select.ts +190 -0
  170. package/src/web-components/interaction/form/bds-slider.spec.ts +41 -0
  171. package/src/web-components/interaction/form/bds-slider.ts +162 -0
  172. package/src/web-components/interaction/form/bds-switch.spec.ts +62 -0
  173. package/src/web-components/interaction/form/bds-switch.ts +166 -0
  174. package/src/web-components/interaction/form/bds-textarea.spec.ts +55 -0
  175. package/src/web-components/interaction/form/bds-textarea.ts +143 -0
  176. package/src/web-components/ui/bds-avatar.spec.ts +75 -0
  177. package/src/web-components/ui/bds-avatar.ts +122 -0
  178. package/src/web-components/ui/bds-breadcrumb.spec.ts +55 -0
  179. package/src/web-components/ui/bds-breadcrumb.ts +114 -0
  180. package/src/web-components/ui/bds-card.spec.ts +70 -0
  181. package/src/web-components/ui/bds-card.ts +156 -0
  182. package/src/web-components/ui/bds-description-list.spec.ts +36 -0
  183. package/src/web-components/ui/bds-description-list.ts +79 -0
  184. package/src/web-components/ui/bds-icon-wrapper.spec.ts +21 -0
  185. package/src/web-components/ui/bds-icon-wrapper.ts +68 -0
  186. package/src/web-components/ui/bds-link.spec.ts +72 -0
  187. package/src/web-components/ui/bds-link.ts +138 -0
  188. package/src/web-components/ui/bds-loading.spec.ts +38 -0
  189. package/src/web-components/ui/bds-loading.ts +76 -0
  190. package/src/web-components/ui/bds-notification-banner.spec.ts +82 -0
  191. package/src/web-components/ui/bds-notification-banner.ts +185 -0
  192. package/src/web-components/ui/bds-progress-circle.spec.ts +58 -0
  193. package/src/web-components/ui/bds-progress-circle.ts +142 -0
  194. package/src/web-components/ui/bds-progress.spec.ts +66 -0
  195. package/src/web-components/ui/bds-progress.ts +127 -0
  196. package/src/web-components/ui/bds-rating.spec.ts +70 -0
  197. package/src/web-components/ui/bds-rating.ts +80 -0
  198. package/src/web-components/ui/bds-section-header.spec.ts +57 -0
  199. package/src/web-components/ui/bds-section-header.ts +96 -0
  200. package/src/web-components/ui/bds-separator.spec.ts +50 -0
  201. package/src/web-components/ui/bds-separator.ts +66 -0
  202. package/src/web-components/ui/bds-skeleton.spec.ts +22 -0
  203. package/src/web-components/ui/bds-skeleton.ts +55 -0
  204. package/src/web-components/ui/bds-typography.spec.ts +76 -0
  205. package/src/web-components/ui/bds-typography.ts +112 -0
  206. package/dist/web-components.d.ts +0 -304
@@ -0,0 +1,127 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
4
+
5
+ /**
6
+ * `<bds-tooltip>` — framework-agnostic Tooltip custom element.
7
+ *
8
+ * Shows a tooltip panel on hover and keyboard focus using CSS-only visibility.
9
+ * The tooltip position is controlled via the `placement` attribute.
10
+ *
11
+ * Attributes:
12
+ * content — text to display in the tooltip
13
+ * placement — "top" (default) | "bottom" | "left" | "right"
14
+ *
15
+ * Slots:
16
+ * (default) — the trigger element (button, icon, etc.)
17
+ *
18
+ * Accessibility:
19
+ * The `aria-describedby` relationship between trigger and tooltip panel
20
+ * cannot cross shadow DOM boundaries; screen readers will announce the tooltip
21
+ * via role="tooltip" proximity when it becomes visible.
22
+ *
23
+ * @example
24
+ * <bds-tooltip content="More information" placement="bottom">
25
+ * <button type="button">?</button>
26
+ * </bds-tooltip>
27
+ */
28
+ export class BdsTooltip extends LitElement {
29
+ static styles = css`
30
+ :host {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ position: relative;
34
+ }
35
+
36
+ .tooltip {
37
+ position: absolute;
38
+ z-index: var(--bds-z-index_popover, 200);
39
+ padding: var(--bds-space_xxs) var(--bds-space_xs);
40
+ border-radius: var(--bds-border_radius--xs);
41
+ background-color: var(--tooltip_bg, var(--bds-color_on-bg));
42
+ color: var(--tooltip_text, var(--bds-color_bg));
43
+ font-size: var(--bds-font_size--body--s);
44
+ line-height: var(--bds-font_line-height--body);
45
+ white-space: nowrap;
46
+ pointer-events: none;
47
+
48
+ /* Hidden by default */
49
+ visibility: hidden;
50
+ opacity: 0;
51
+ transition: opacity var(--bds-animation_transition-duration) var(--bds-animation_easing);
52
+ }
53
+
54
+ /* Show on hover and keyboard focus */
55
+ :host(:hover) .tooltip,
56
+ :host(:focus-within) .tooltip {
57
+ visibility: visible;
58
+ opacity: 1;
59
+ }
60
+
61
+ /* Placement: top (default) */
62
+ .tooltip.--placement_top {
63
+ inset-block-end: calc(100% + var(--bds-space_xs));
64
+ inset-inline-start: 50%;
65
+ transform: translateX(-50%);
66
+ }
67
+
68
+ /* Placement: bottom */
69
+ .tooltip.--placement_bottom {
70
+ inset-block-start: calc(100% + var(--bds-space_xs));
71
+ inset-inline-start: 50%;
72
+ transform: translateX(-50%);
73
+ }
74
+
75
+ /* Placement: left */
76
+ .tooltip.--placement_left {
77
+ inset-inline-end: calc(100% + var(--bds-space_xs));
78
+ inset-block-start: 50%;
79
+ transform: translateY(-50%);
80
+ }
81
+
82
+ /* Placement: right */
83
+ .tooltip.--placement_right {
84
+ inset-inline-start: calc(100% + var(--bds-space_xs));
85
+ inset-block-start: 50%;
86
+ transform: translateY(-50%);
87
+ }
88
+
89
+ @media (prefers-reduced-motion: reduce) {
90
+ .tooltip {
91
+ transition: none;
92
+ }
93
+ }
94
+ `;
95
+
96
+ static properties = {
97
+ content: { type: String },
98
+ placement: { type: String, reflect: true },
99
+ };
100
+
101
+ declare content: string;
102
+ declare placement: TooltipPlacement;
103
+
104
+ constructor() {
105
+ super();
106
+ this.content = '';
107
+ this.placement = 'top';
108
+ }
109
+
110
+ render() {
111
+ return html`
112
+ <slot></slot>
113
+ <span
114
+ class="tooltip --placement_${this.placement}"
115
+ role="tooltip"
116
+ >${this.content}</span>
117
+ `;
118
+ }
119
+ }
120
+
121
+ customElements.define('bds-tooltip', BdsTooltip);
122
+
123
+ declare global {
124
+ interface HTMLElementTagNameMap {
125
+ 'bds-tooltip': BdsTooltip;
126
+ }
127
+ }
@@ -0,0 +1,63 @@
1
+ import { fixture, cleanup } from '../../test/helpers';
2
+ import { BdsCheckbox } from './bds-checkbox';
3
+
4
+ describe('bds-checkbox', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-checkbox')).toBe(BdsCheckbox);
7
+ });
8
+
9
+ it('renders a checkbox input in shadow DOM', async () => {
10
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>');
11
+ expect(el.shadowRoot!.querySelector('input[type="checkbox"]')).not.toBeNull();
12
+ cleanup(el);
13
+ });
14
+
15
+ it('is not checked by default', async () => {
16
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>');
17
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
18
+ expect(input.checked).toBe(false);
19
+ cleanup(el);
20
+ });
21
+
22
+ it('is checked when checked attribute is set', async () => {
23
+ const el = await fixture('<bds-checkbox name="agree" checked>Accept</bds-checkbox>');
24
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
25
+ expect(input.checked).toBe(true);
26
+ cleanup(el);
27
+ });
28
+
29
+ it('input is disabled when disabled attribute is set', async () => {
30
+ const el = await fixture('<bds-checkbox name="agree" disabled>Accept</bds-checkbox>');
31
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
32
+ expect(input.disabled).toBe(true);
33
+ cleanup(el);
34
+ });
35
+
36
+ it('renders a label linked to the input', async () => {
37
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>');
38
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
39
+ const label = el.shadowRoot!.querySelector('label')!;
40
+ expect(input.id).toBeTruthy();
41
+ expect(label.getAttribute('for')).toBe(input.id);
42
+ cleanup(el);
43
+ });
44
+
45
+ it('renders slotted label text', async () => {
46
+ const el = await fixture('<bds-checkbox name="agree">Accept terms</bds-checkbox>');
47
+ expect(el.textContent?.trim()).toBe('Accept terms');
48
+ cleanup(el);
49
+ });
50
+
51
+ it('updates checked property on change', async () => {
52
+ const el = await fixture('<bds-checkbox name="agree">Accept</bds-checkbox>') as BdsCheckbox;
53
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
54
+ input.checked = true;
55
+ input.dispatchEvent(new Event('change', { bubbles: true }));
56
+ expect(el.checked).toBe(true);
57
+ cleanup(el);
58
+ });
59
+
60
+ it('is form-associated', () => {
61
+ expect(BdsCheckbox.formAssociated).toBe(true);
62
+ });
63
+ });
@@ -0,0 +1,168 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-checkbox>` — form-associated Checkbox custom element.
5
+ *
6
+ * Participates in HTML forms via the Form-Associated Custom Elements API
7
+ * (`ElementInternals`). Native browser validation (required, etc.) works
8
+ * without any wrapper.
9
+ *
10
+ * Attributes:
11
+ * name — form field name
12
+ * value — submitted value when checked (default: "on")
13
+ * checked — boolean; whether the checkbox is checked
14
+ * required — boolean
15
+ * disabled — boolean
16
+ *
17
+ * Slots:
18
+ * (default) — label text
19
+ *
20
+ * Events:
21
+ * change — native change event (forwarded from inner input)
22
+ *
23
+ * @example
24
+ * <bds-checkbox name="agree" value="yes" required>I agree</bds-checkbox>
25
+ */
26
+ export class BdsCheckbox extends LitElement {
27
+ static formAssociated = true;
28
+
29
+ static styles = css`
30
+ :host {
31
+ display: inline-flex;
32
+ align-items: flex-start;
33
+ gap: var(--bds-space_xs);
34
+ cursor: pointer;
35
+ font-size: var(--bds-font_size--body);
36
+ font-family: var(--bds-font_family--body);
37
+ color: var(--bds-color_on-bg);
38
+ }
39
+
40
+ :host([disabled]) {
41
+ opacity: 0.5;
42
+ cursor: not-allowed;
43
+ pointer-events: none;
44
+ }
45
+
46
+ .input {
47
+ --inputSize: calc(var(--bds-space_s) * 2);
48
+ margin-block-start: 0.25em;
49
+ inline-size: var(--inputSize);
50
+ block-size: var(--inputSize);
51
+ flex-shrink: 0;
52
+ --bdc_color: currentcolor;
53
+ --bdc-outline_radius: var(--bds-border_radius--xs);
54
+
55
+ outline: var(--bdc-outline_width) solid var(--bdc_color);
56
+ outline-offset: var(--bdc-outline_offset);
57
+ border-radius: var(--checkbox_radius, var(--bdc-outline_radius));
58
+ box-shadow: var(--checkbox_shadow, var(--bdc-outline_shadow));
59
+ border: none;
60
+ appearance: none;
61
+ background-color: var(--checkbox_color_bg, var(--bds-color_bg));
62
+ cursor: pointer;
63
+ position: relative;
64
+ transition: --bdc_color 0.3s ease, background-color 0.3s ease;
65
+ }
66
+
67
+ .input:checked {
68
+ background-color: var(--checkbox_color-active, var(--bdc-color_active));
69
+ --bdc_color: var(--checkbox_color-active, var(--bdc-color_active));
70
+ }
71
+
72
+ .input:checked::after {
73
+ content: '';
74
+ position: absolute;
75
+ inset-inline-start: 0.6em;
76
+ inset-block-start: 0.2em;
77
+ inline-size: 0.5em;
78
+ block-size: 1em;
79
+ border: solid var(--checkbox_color-on-active, var(--bdc-color_on-active));
80
+ border-width: 0 2px 2px 0;
81
+ transform: rotate(45deg);
82
+ }
83
+
84
+ .input:focus-visible {
85
+ outline: var(--bds-outline_default);
86
+ outline-offset: var(--bds-outline_offset);
87
+ }
88
+
89
+ label {
90
+ cursor: pointer;
91
+ flex: 1;
92
+ }
93
+ `;
94
+
95
+ static properties = {
96
+ name: { type: String, reflect: true },
97
+ value: { type: String },
98
+ checked: { type: Boolean, reflect: true },
99
+ required: { type: Boolean, reflect: true },
100
+ disabled: { type: Boolean, reflect: true },
101
+ };
102
+
103
+ declare name: string;
104
+ declare value: string;
105
+ declare checked: boolean;
106
+ declare required: boolean;
107
+ declare disabled: boolean;
108
+
109
+ private _internals!: ElementInternals;
110
+ private readonly _uid = Math.random().toString(36).slice(2, 8);
111
+
112
+ constructor() {
113
+ super();
114
+ this._internals = this.attachInternals();
115
+ this.name = '';
116
+ this.value = 'on';
117
+ this.checked = false;
118
+ this.required = false;
119
+ this.disabled = false;
120
+ }
121
+
122
+ updated(changed: Map<string, unknown>) {
123
+ super.updated(changed);
124
+ if (changed.has('checked') || changed.has('value')) {
125
+ this._internals.setFormValue?.(this.checked ? this.value : null);
126
+ }
127
+ if (changed.has('required')) {
128
+ if (this.required && !this.checked) {
129
+ this._internals.setValidity?.({ valueMissing: true }, 'Required');
130
+ } else {
131
+ this._internals.setValidity?.({});
132
+ }
133
+ }
134
+ }
135
+
136
+ formResetCallback() {
137
+ this.checked = false;
138
+ this._internals.setFormValue?.(null);
139
+ }
140
+
141
+ private _handleChange(e: Event) {
142
+ this.checked = (e.target as HTMLInputElement).checked;
143
+ }
144
+
145
+ render() {
146
+ const id = `bds-checkbox-${this._uid}`;
147
+ return html`
148
+ <input
149
+ id=${id}
150
+ type="checkbox"
151
+ class="input"
152
+ .checked=${this.checked}
153
+ ?required=${this.required}
154
+ ?disabled=${this.disabled}
155
+ @change=${this._handleChange}
156
+ />
157
+ <label for=${id}><slot></slot></label>
158
+ `;
159
+ }
160
+ }
161
+
162
+ customElements.define('bds-checkbox', BdsCheckbox);
163
+
164
+ declare global {
165
+ interface HTMLElementTagNameMap {
166
+ 'bds-checkbox': BdsCheckbox;
167
+ }
168
+ }
@@ -0,0 +1,117 @@
1
+ import { fixture, cleanup } from '../../test/helpers';
2
+ import { BdsCombobox } from './bds-combobox';
3
+
4
+ const OPTIONS = JSON.stringify([
5
+ { value: 'nl', label: 'Netherlands' },
6
+ { value: 'be', label: 'Belgium' },
7
+ { value: 'de', label: 'Germany', disabled: true },
8
+ ]);
9
+
10
+ describe('bds-combobox', () => {
11
+ it('is registered as a custom element', () => {
12
+ expect(customElements.get('bds-combobox')).toBe(BdsCombobox);
13
+ });
14
+
15
+ it('renders a text input with role="combobox"', async () => {
16
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
17
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
18
+ expect(input).not.toBeNull();
19
+ expect(input.getAttribute('role')).toBe('combobox');
20
+ cleanup(el);
21
+ });
22
+
23
+ it('forwards placeholder to the input', async () => {
24
+ const el = await fixture(`<bds-combobox name="country" placeholder="Search…" options='${OPTIONS}'></bds-combobox>`);
25
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
26
+ expect(input.placeholder).toBe('Search…');
27
+ cleanup(el);
28
+ });
29
+
30
+ it('input is disabled when disabled attribute is set', async () => {
31
+ const el = await fixture(`<bds-combobox name="country" disabled options='${OPTIONS}'></bds-combobox>`);
32
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
33
+ expect(input.disabled).toBe(true);
34
+ cleanup(el);
35
+ });
36
+
37
+ it('shows listbox when input is focused', async () => {
38
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
39
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
40
+ input.dispatchEvent(new Event('focus'));
41
+ await el.updateComplete;
42
+ expect(el.shadowRoot!.querySelector('[role="listbox"]')).not.toBeNull();
43
+ cleanup(el);
44
+ });
45
+
46
+ it('renders options in the listbox', async () => {
47
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
48
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
49
+ input.dispatchEvent(new Event('focus'));
50
+ await el.updateComplete;
51
+ const listItems = el.shadowRoot!.querySelectorAll('[role="option"]');
52
+ expect(listItems.length).toBe(3);
53
+ cleanup(el);
54
+ });
55
+
56
+ it('selects an option on mousedown', async () => {
57
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`) as BdsCombobox;
58
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
59
+ input.dispatchEvent(new Event('focus'));
60
+ await el.updateComplete;
61
+ const option = el.shadowRoot!.querySelector<HTMLElement>('[role="option"]')!;
62
+ option.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
63
+ expect(el.value).toBe('nl');
64
+ cleanup(el);
65
+ });
66
+
67
+ it('fires bds-change when an option is selected', async () => {
68
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
69
+ const handler = vi.fn();
70
+ el.addEventListener('bds-change', handler);
71
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
72
+ input.dispatchEvent(new Event('focus'));
73
+ await el.updateComplete;
74
+ const option = el.shadowRoot!.querySelector<HTMLElement>('[role="option"]')!;
75
+ option.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
76
+ expect(handler).toHaveBeenCalledOnce();
77
+ expect(handler.mock.calls[0][0].detail).toMatchObject({ value: 'nl' });
78
+ cleanup(el);
79
+ });
80
+
81
+ it('filters options based on input text', async () => {
82
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
83
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
84
+ input.value = 'Neth';
85
+ input.dispatchEvent(new Event('input', { bubbles: true }));
86
+ await el.updateComplete;
87
+ const options = el.shadowRoot!.querySelectorAll('[role="option"]');
88
+ expect(options.length).toBe(1);
89
+ expect(options[0].textContent?.trim()).toBe('Netherlands');
90
+ cleanup(el);
91
+ });
92
+
93
+ it('shows no results message when filter matches nothing', async () => {
94
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
95
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
96
+ input.value = 'zzz';
97
+ input.dispatchEvent(new Event('input', { bubbles: true }));
98
+ await el.updateComplete;
99
+ expect(el.shadowRoot!.querySelector('.no-results')).not.toBeNull();
100
+ cleanup(el);
101
+ });
102
+
103
+ it('closes listbox on Escape key', async () => {
104
+ const el = await fixture(`<bds-combobox name="country" options='${OPTIONS}'></bds-combobox>`);
105
+ const input = el.shadowRoot!.querySelector<HTMLInputElement>('input')!;
106
+ input.dispatchEvent(new Event('focus'));
107
+ await el.updateComplete;
108
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
109
+ await el.updateComplete;
110
+ expect(el.shadowRoot!.querySelector('[role="listbox"]')).toBeNull();
111
+ cleanup(el);
112
+ });
113
+
114
+ it('is form-associated', () => {
115
+ expect(BdsCombobox.formAssociated).toBe(true);
116
+ });
117
+ });