@brandsync/wc 0.0.8 → 0.0.9

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 (193) hide show
  1. package/custom-elements.json +1808 -353
  2. package/dist/brandsync-wc/brandsync-wc.esm.js +1 -1
  3. package/dist/brandsync-wc/p-0d64ea40.entry.js +1 -0
  4. package/dist/brandsync-wc/{p-c63d16e1.entry.js → p-1515498e.entry.js} +1 -1
  5. package/dist/brandsync-wc/p-16a54578.entry.js +1 -0
  6. package/dist/brandsync-wc/p-18af02df.entry.js +1 -0
  7. package/dist/brandsync-wc/p-30a0e168.entry.js +1 -0
  8. package/dist/brandsync-wc/p-3dacae70.entry.js +1 -0
  9. package/dist/brandsync-wc/p-5333bb5a.entry.js +1 -0
  10. package/dist/brandsync-wc/p-575ce905.entry.js +1 -0
  11. package/dist/brandsync-wc/{p-dd0fea0e.entry.js → p-7776b57b.entry.js} +1 -1
  12. package/dist/brandsync-wc/{p-260bd201.entry.js → p-805644e4.entry.js} +1 -1
  13. package/dist/brandsync-wc/{p-f88d9a6e.entry.js → p-8a78d6ea.entry.js} +1 -1
  14. package/dist/brandsync-wc/p-8d12ed96.entry.js +1 -0
  15. package/dist/brandsync-wc/p-94ed898a.entry.js +1 -0
  16. package/dist/brandsync-wc/{p-9e5d2211.entry.js → p-96f374be.entry.js} +1 -1
  17. package/dist/brandsync-wc/{p-ab5cc7af.entry.js → p-a34f460d.entry.js} +1 -1
  18. package/dist/brandsync-wc/p-bc72439a.entry.js +1 -0
  19. package/dist/brandsync-wc/{p-76eca100.entry.js → p-ce0914a7.entry.js} +1 -1
  20. package/dist/brandsync-wc/{p-5a0fe72e.entry.js → p-d682675e.entry.js} +1 -1
  21. package/dist/brandsync-wc/p-dad4a92c.entry.js +1 -0
  22. package/dist/brandsync-wc/{p-cfc3421b.entry.js → p-dd747a12.entry.js} +1 -1
  23. package/dist/brandsync-wc/{p-2742ecef.entry.js → p-eb4d87f0.entry.js} +1 -1
  24. package/dist/cjs/brandsync-wc.cjs.js +1 -1
  25. package/dist/cjs/bs-ai-disclaimer.cjs.entry.js +17 -0
  26. package/dist/cjs/bs-ai-greeting.cjs.entry.js +24 -0
  27. package/dist/cjs/bs-ai-thinking.cjs.entry.js +1 -1
  28. package/dist/cjs/bs-attachment-list.cjs.entry.js +47 -0
  29. package/dist/cjs/bs-attachment.cjs.entry.js +46 -0
  30. package/dist/cjs/bs-badge.cjs.entry.js +1 -1
  31. package/dist/cjs/bs-button-skeleton.cjs.entry.js +1 -1
  32. package/dist/cjs/bs-button.cjs.entry.js +1 -1
  33. package/dist/cjs/bs-card.cjs.entry.js +1 -1
  34. package/dist/cjs/bs-chatbot-feedback.cjs.entry.js +94 -0
  35. package/dist/cjs/bs-chatbot-header.cjs.entry.js +1 -1
  36. package/dist/cjs/bs-chatbot-response-action.cjs.entry.js +1 -1
  37. package/dist/cjs/bs-chatbot-sources-drawer.cjs.entry.js +38 -0
  38. package/dist/cjs/bs-chatbot-suggestion-button.cjs.entry.js +1 -1
  39. package/dist/cjs/bs-composer-status-banner.cjs.entry.js +43 -0
  40. package/dist/cjs/bs-composer.cjs.entry.js +16 -2
  41. package/dist/cjs/bs-data-table.cjs.entry.js +2 -2
  42. package/dist/cjs/bs-input.cjs.entry.js +270 -4
  43. package/dist/cjs/{bs-menu-item.cjs.entry.js → bs-menu_2.cjs.entry.js} +14 -1
  44. package/dist/cjs/bs-source-link.cjs.entry.js +39 -0
  45. package/dist/cjs/bs-tooltip.cjs.entry.js +1 -1
  46. package/dist/cjs/loader.cjs.js +1 -1
  47. package/dist/collection/collection-manifest.json +8 -0
  48. package/dist/collection/components/bs-ai-chatbot/bs-ai-chatbot.stories.js +180 -0
  49. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.cmp.test.js +14 -0
  50. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.css +27 -0
  51. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.js +41 -0
  52. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.stories.js +15 -0
  53. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.cmp.test.js +22 -0
  54. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.css +53 -0
  55. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.js +103 -0
  56. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.stories.js +23 -0
  57. package/dist/collection/components/bs-ai-thinking/bs-ai-thinking.js +1 -1
  58. package/dist/collection/components/bs-attachment/bs-attachment.cmp.test.js +41 -0
  59. package/dist/collection/components/bs-attachment/bs-attachment.css +184 -0
  60. package/dist/collection/components/bs-attachment/bs-attachment.js +230 -0
  61. package/dist/collection/components/bs-attachment/bs-attachment.stories.js +58 -0
  62. package/dist/collection/components/bs-attachment-list/bs-attachment-list.cmp.test.js +28 -0
  63. package/dist/collection/components/bs-attachment-list/bs-attachment-list.css +56 -0
  64. package/dist/collection/components/bs-attachment-list/bs-attachment-list.js +105 -0
  65. package/dist/collection/components/bs-attachment-list/bs-attachment-list.stories.js +47 -0
  66. package/dist/collection/components/bs-badge/bs-badge.js +1 -1
  67. package/dist/collection/components/bs-button/bs-button.js +1 -1
  68. package/dist/collection/components/bs-button-skeleton/bs-button-skeleton.js +1 -1
  69. package/dist/collection/components/bs-card/bs-card.js +1 -1
  70. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.cmp.test.js +73 -0
  71. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.css +253 -0
  72. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.js +324 -0
  73. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.stories.js +54 -0
  74. package/dist/collection/components/bs-chatbot-header/bs-chatbot-header.js +1 -1
  75. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.js +1 -1
  76. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.cmp.test.js +34 -0
  77. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.css +106 -0
  78. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.js +142 -0
  79. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.stories.js +44 -0
  80. package/dist/collection/components/bs-chatbot-suggestion-button/bs-chatbot-suggestion-button.js +1 -1
  81. package/dist/collection/components/bs-composer/bs-composer.cmp.test.js +12 -0
  82. package/dist/collection/components/bs-composer/bs-composer.css +10 -0
  83. package/dist/collection/components/bs-composer/bs-composer.js +25 -1
  84. package/dist/collection/components/bs-composer/bs-composer.stories.js +13 -0
  85. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.cmp.test.js +42 -0
  86. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.css +173 -0
  87. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.js +270 -0
  88. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.stories.js +75 -0
  89. package/dist/collection/components/bs-data-table/bs-data-table.js +2 -2
  90. package/dist/collection/components/bs-input/bs-input.cmp.test.js +296 -0
  91. package/dist/collection/components/bs-input/bs-input.css +333 -23
  92. package/dist/collection/components/bs-input/bs-input.js +647 -11
  93. package/dist/collection/components/bs-input/bs-input.stories.js +128 -1
  94. package/dist/collection/components/bs-menu/bs-menu.js +1 -1
  95. package/dist/collection/components/bs-menu-item/bs-menu-item.js +1 -1
  96. package/dist/collection/components/bs-source-link/bs-source-link.cmp.test.js +42 -0
  97. package/dist/collection/components/bs-source-link/bs-source-link.css +114 -0
  98. package/dist/collection/components/bs-source-link/bs-source-link.js +192 -0
  99. package/dist/collection/components/bs-source-link/bs-source-link.stories.js +33 -0
  100. package/dist/collection/components/bs-tooltip/bs-tooltip.js +1 -1
  101. package/dist/components/bs-ai-disclaimer.d.ts +11 -0
  102. package/dist/components/bs-ai-disclaimer.js +1 -0
  103. package/dist/components/bs-ai-greeting.d.ts +11 -0
  104. package/dist/components/bs-ai-greeting.js +1 -0
  105. package/dist/components/bs-ai-thinking.js +1 -1
  106. package/dist/components/bs-attachment-list.d.ts +11 -0
  107. package/dist/components/bs-attachment-list.js +1 -0
  108. package/dist/components/bs-attachment.d.ts +11 -0
  109. package/dist/components/bs-attachment.js +1 -0
  110. package/dist/components/bs-badge.js +1 -1
  111. package/dist/components/bs-button-skeleton.js +1 -1
  112. package/dist/components/bs-button.js +1 -1
  113. package/dist/components/bs-card.js +1 -1
  114. package/dist/components/bs-chatbot-feedback.d.ts +11 -0
  115. package/dist/components/bs-chatbot-feedback.js +1 -0
  116. package/dist/components/bs-chatbot-header.js +1 -1
  117. package/dist/components/bs-chatbot-response-action.js +1 -1
  118. package/dist/components/bs-chatbot-sources-drawer.d.ts +11 -0
  119. package/dist/components/bs-chatbot-sources-drawer.js +1 -0
  120. package/dist/components/bs-chatbot-suggestion-button.js +1 -1
  121. package/dist/components/bs-composer-status-banner.d.ts +11 -0
  122. package/dist/components/bs-composer-status-banner.js +1 -0
  123. package/dist/components/bs-composer.js +1 -1
  124. package/dist/components/bs-data-table.js +1 -1
  125. package/dist/components/bs-input.js +1 -1
  126. package/dist/components/bs-menu-item.js +1 -1
  127. package/dist/components/bs-menu.js +1 -1
  128. package/dist/components/bs-source-link.d.ts +11 -0
  129. package/dist/components/bs-source-link.js +1 -0
  130. package/dist/components/bs-tooltip.js +1 -1
  131. package/dist/components/{p-CoC4LQCI.js → p-Cz2nfz2j.js} +1 -1
  132. package/dist/components/p-DndQiCMP.js +1 -0
  133. package/dist/components/p-_vXDfB3V.js +1 -0
  134. package/dist/esm/brandsync-wc.js +1 -1
  135. package/dist/esm/bs-ai-disclaimer.entry.js +15 -0
  136. package/dist/esm/bs-ai-greeting.entry.js +22 -0
  137. package/dist/esm/bs-ai-thinking.entry.js +1 -1
  138. package/dist/esm/bs-attachment-list.entry.js +45 -0
  139. package/dist/esm/bs-attachment.entry.js +44 -0
  140. package/dist/esm/bs-badge.entry.js +1 -1
  141. package/dist/esm/bs-button-skeleton.entry.js +1 -1
  142. package/dist/esm/bs-button.entry.js +1 -1
  143. package/dist/esm/bs-card.entry.js +1 -1
  144. package/dist/esm/bs-chatbot-feedback.entry.js +92 -0
  145. package/dist/esm/bs-chatbot-header.entry.js +1 -1
  146. package/dist/esm/bs-chatbot-response-action.entry.js +1 -1
  147. package/dist/esm/bs-chatbot-sources-drawer.entry.js +36 -0
  148. package/dist/esm/bs-chatbot-suggestion-button.entry.js +1 -1
  149. package/dist/esm/bs-composer-status-banner.entry.js +41 -0
  150. package/dist/esm/bs-composer.entry.js +17 -3
  151. package/dist/esm/bs-data-table.entry.js +2 -2
  152. package/dist/esm/bs-input.entry.js +271 -5
  153. package/dist/esm/{bs-menu-item.entry.js → bs-menu_2.entry.js} +15 -3
  154. package/dist/esm/bs-source-link.entry.js +37 -0
  155. package/dist/esm/bs-tooltip.entry.js +1 -1
  156. package/dist/esm/loader.js +1 -1
  157. package/dist/types/components/bs-ai-chatbot/bs-ai-chatbot.stories.d.ts +18 -0
  158. package/dist/types/components/bs-ai-disclaimer/bs-ai-disclaimer.cmp.test.d.ts +1 -0
  159. package/dist/types/components/bs-ai-disclaimer/bs-ai-disclaimer.d.ts +26 -0
  160. package/dist/types/components/bs-ai-disclaimer/bs-ai-disclaimer.stories.d.ts +8 -0
  161. package/dist/types/components/bs-ai-greeting/bs-ai-greeting.cmp.test.d.ts +1 -0
  162. package/dist/types/components/bs-ai-greeting/bs-ai-greeting.d.ts +42 -0
  163. package/dist/types/components/bs-ai-greeting/bs-ai-greeting.stories.d.ts +10 -0
  164. package/dist/types/components/bs-attachment/bs-attachment.cmp.test.d.ts +1 -0
  165. package/dist/types/components/bs-attachment/bs-attachment.d.ts +59 -0
  166. package/dist/types/components/bs-attachment/bs-attachment.stories.d.ts +17 -0
  167. package/dist/types/components/bs-attachment-list/bs-attachment-list.cmp.test.d.ts +1 -0
  168. package/dist/types/components/bs-attachment-list/bs-attachment-list.d.ts +42 -0
  169. package/dist/types/components/bs-attachment-list/bs-attachment-list.stories.d.ts +10 -0
  170. package/dist/types/components/bs-chatbot-feedback/bs-chatbot-feedback.cmp.test.d.ts +1 -0
  171. package/dist/types/components/bs-chatbot-feedback/bs-chatbot-feedback.d.ts +90 -0
  172. package/dist/types/components/bs-chatbot-feedback/bs-chatbot-feedback.stories.d.ts +16 -0
  173. package/dist/types/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.cmp.test.d.ts +1 -0
  174. package/dist/types/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.d.ts +54 -0
  175. package/dist/types/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.stories.d.ts +10 -0
  176. package/dist/types/components/bs-composer/bs-composer.d.ts +14 -0
  177. package/dist/types/components/bs-composer/bs-composer.stories.d.ts +1 -0
  178. package/dist/types/components/bs-composer-status-banner/bs-composer-status-banner.cmp.test.d.ts +1 -0
  179. package/dist/types/components/bs-composer-status-banner/bs-composer-status-banner.d.ts +88 -0
  180. package/dist/types/components/bs-composer-status-banner/bs-composer-status-banner.stories.d.ts +20 -0
  181. package/dist/types/components/bs-input/bs-input.d.ts +139 -6
  182. package/dist/types/components/bs-input/bs-input.stories.d.ts +19 -0
  183. package/dist/types/components/bs-source-link/bs-source-link.cmp.test.d.ts +1 -0
  184. package/dist/types/components/bs-source-link/bs-source-link.d.ts +51 -0
  185. package/dist/types/components/bs-source-link/bs-source-link.stories.d.ts +13 -0
  186. package/dist/types/components.d.ts +1746 -19
  187. package/package.json +1 -1
  188. package/dist/brandsync-wc/p-6abdfaf0.entry.js +0 -1
  189. package/dist/brandsync-wc/p-6c1fa4da.entry.js +0 -1
  190. package/dist/brandsync-wc/p-a12d0a60.entry.js +0 -1
  191. package/dist/brandsync-wc/p-f5f443e2.entry.js +0 -1
  192. package/dist/cjs/bs-menu.cjs.entry.js +0 -17
  193. package/dist/esm/bs-menu.entry.js +0 -15
@@ -62,6 +62,12 @@ describe('bs-input', () => {
62
62
  expect(input).toEqualAttribute('aria-invalid', 'true');
63
63
  expect(input).toEqualAttribute('aria-describedby', 'description-or-error');
64
64
  });
65
+ it('renders a warning icon before the error message, but not before the description', async () => {
66
+ const { root } = await render(h("bs-input", { error: "This field is required" }));
67
+ expect(root.shadowRoot.querySelector('[part="error"] [part="error-icon"]')).not.toBeNull();
68
+ const { root: rootWithDescription } = await render(h("bs-input", { description: "Helper text" }));
69
+ expect(rootWithDescription.shadowRoot.querySelector('[part="error-icon"]')).toBeNull();
70
+ });
65
71
  it('emits bsInput with the current value as the user types', async () => {
66
72
  const { root, spyOnEvent } = await render(h("bs-input", null));
67
73
  const bsInputSpy = spyOnEvent('bsInput');
@@ -91,6 +97,36 @@ describe('bs-input', () => {
91
97
  input.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
92
98
  expect(bsInputSpy).toHaveReceivedEventTimes(1);
93
99
  });
100
+ it('applies bs-input__icon--visible when the icon slot has assigned content', async () => {
101
+ const { root } = await render(h("bs-input", null, h("svg", { slot: "icon", viewBox: "0 0 16 16" })));
102
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
103
+ expect(icon).toHaveClass('bs-input__icon--visible');
104
+ });
105
+ it('does not apply bs-input__icon--visible when the icon slot is empty', async () => {
106
+ const { root } = await render(h("bs-input", null));
107
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
108
+ expect(icon).not.toHaveClass('bs-input__icon--visible');
109
+ });
110
+ it('applies bs-input__end-icon--visible when the end-icon slot has assigned content', async () => {
111
+ const { root } = await render(h("bs-input", null, h("svg", { slot: "end-icon", viewBox: "0 0 16 16" })));
112
+ const endIcon = root.shadowRoot.querySelector('[part="end-icon"]');
113
+ expect(endIcon).toHaveClass('bs-input__end-icon--visible');
114
+ });
115
+ it('does not apply bs-input__end-icon--visible when the end-icon slot is empty', async () => {
116
+ const { root } = await render(h("bs-input", null));
117
+ const endIcon = root.shadowRoot.querySelector('[part="end-icon"]');
118
+ expect(endIcon).not.toHaveClass('bs-input__end-icon--visible');
119
+ });
120
+ it('applies bs-input__field--error to the field wrapper when error is set', async () => {
121
+ const { root } = await render(h("bs-input", { error: "Required" }));
122
+ const field = root.shadowRoot.querySelector('[part="field"]');
123
+ expect(field).toHaveClass('bs-input__field--error');
124
+ });
125
+ it('applies bs-input__field--disabled to the field wrapper when disabled is set', async () => {
126
+ const { root } = await render(h("bs-input", { disabled: true }));
127
+ const field = root.shadowRoot.querySelector('[part="field"]');
128
+ expect(field).toHaveClass('bs-input__field--disabled');
129
+ });
94
130
  it('delegates focus from the host to the native input (delegatesFocus)', async () => {
95
131
  const { root } = await render(h("bs-input", { label: "Name" }));
96
132
  const input = root.shadowRoot.querySelector('input');
@@ -98,4 +134,264 @@ describe('bs-input', () => {
98
134
  expect(root.shadowRoot.activeElement).toBe(input);
99
135
  expect(document.activeElement).toBe(root);
100
136
  });
137
+ describe('required', () => {
138
+ it('renders a red asterisk after the label when required is set', async () => {
139
+ const { root } = await render(h("bs-input", { label: "Name", required: true }));
140
+ const required = root.shadowRoot.querySelector('[part="required"]');
141
+ expect(required).toHaveTextContent('*');
142
+ });
143
+ it('does not render an asterisk when required is not set', async () => {
144
+ const { root } = await render(h("bs-input", { label: "Name" }));
145
+ expect(root.shadowRoot.querySelector('[part="required"]')).toBeNull();
146
+ });
147
+ it('sets aria-required on the control when required is set', async () => {
148
+ const { root } = await render(h("bs-input", { required: true }));
149
+ const input = root.shadowRoot.querySelector('input');
150
+ expect(input).toEqualAttribute('aria-required', 'true');
151
+ });
152
+ });
153
+ ['search', 'date'].forEach(type => {
154
+ it(`applies type="${type}" to the native input`, async () => {
155
+ const { root } = await render(h("bs-input", { type: type }));
156
+ const input = root.shadowRoot.querySelector('input');
157
+ expect(input).toEqualAttribute('type', type);
158
+ });
159
+ });
160
+ describe('type="search"', () => {
161
+ it('renders a default magnifying-glass icon in the leading icon position when nothing is slotted', async () => {
162
+ const { root } = await render(h("bs-input", { type: "search" }));
163
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
164
+ expect(icon).toHaveClass('bs-input__icon--visible');
165
+ expect(root.shadowRoot.querySelector('.bs-input__search-icon')).not.toBeNull();
166
+ });
167
+ it('uses the slotted icon instead of the default when one is provided', async () => {
168
+ const { root } = await render(h("bs-input", { type: "search" }, h("svg", { slot: "icon", viewBox: "0 0 16 16" })));
169
+ expect(root.shadowRoot.querySelector('.bs-input__search-icon')).toBeNull();
170
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
171
+ expect(icon).toHaveClass('bs-input__icon--visible');
172
+ });
173
+ it('does not render the default magnifying-glass icon for other types', async () => {
174
+ const { root } = await render(h("bs-input", { type: "text" }));
175
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
176
+ expect(icon).not.toHaveClass('bs-input__icon--visible');
177
+ expect(root.shadowRoot.querySelector('.bs-input__search-icon')).toBeNull();
178
+ });
179
+ });
180
+ describe('type="number"', () => {
181
+ it('renders increment/decrement stepper buttons instead of an end-icon slot', async () => {
182
+ const { root } = await render(h("bs-input", { type: "number", value: "1" }));
183
+ const buttons = root.shadowRoot.querySelectorAll('.bs-input__stepper-btn');
184
+ expect(buttons.length).toBe(2);
185
+ });
186
+ it('increments the value on the increment button, respecting max', async () => {
187
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "number", value: "9", step: 1, max: 10 }));
188
+ const bsChangeSpy = spyOnEvent('bsChange');
189
+ const [incrementBtn] = Array.from(root.shadowRoot.querySelectorAll('.bs-input__stepper-btn'));
190
+ incrementBtn.click();
191
+ expect(bsChangeSpy).toHaveReceivedEventDetail('10');
192
+ incrementBtn.click();
193
+ expect(bsChangeSpy).toHaveReceivedEventDetail('10');
194
+ });
195
+ it('decrements the value on the decrement button, respecting min', async () => {
196
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "number", value: "0", step: 1, min: 0 }));
197
+ const bsChangeSpy = spyOnEvent('bsChange');
198
+ const [, decrementBtn] = Array.from(root.shadowRoot.querySelectorAll('.bs-input__stepper-btn'));
199
+ decrementBtn.click();
200
+ expect(bsChangeSpy).toHaveReceivedEventDetail('0');
201
+ });
202
+ });
203
+ describe('type="password"', () => {
204
+ it('renders a show-password toggle button in the end-icon position', async () => {
205
+ const { root } = await render(h("bs-input", { type: "password" }));
206
+ const toggle = root.shadowRoot.querySelector('.bs-input__password-toggle');
207
+ expect(toggle).toEqualAttribute('aria-label', 'Show password');
208
+ });
209
+ it('toggles the rendered native input type and aria-label when clicked', async () => {
210
+ const { root, waitForChanges } = await render(h("bs-input", { type: "password" }));
211
+ const toggle = root.shadowRoot.querySelector('.bs-input__password-toggle');
212
+ toggle.click();
213
+ await waitForChanges();
214
+ const input = root.shadowRoot.querySelector('input');
215
+ expect(input).toEqualAttribute('type', 'text');
216
+ const toggleAfter = root.shadowRoot.querySelector('.bs-input__password-toggle');
217
+ expect(toggleAfter).toEqualAttribute('aria-label', 'Hide password');
218
+ });
219
+ it('is a type="button" so it never submits an enclosing form', async () => {
220
+ const { root } = await render(h("bs-input", { type: "password" }));
221
+ const toggle = root.shadowRoot.querySelector('.bs-input__password-toggle');
222
+ expect(toggle).toEqualAttribute('type', 'button');
223
+ });
224
+ });
225
+ describe('type="dropdown"', () => {
226
+ it('renders the control as readonly, not disabled', async () => {
227
+ const { root } = await render(h("bs-input", { type: "dropdown" }));
228
+ const input = root.shadowRoot.querySelector('input');
229
+ expect(input).toHaveAttribute('readonly');
230
+ expect(input).not.toHaveAttribute('disabled');
231
+ });
232
+ it('toggles open and emits bsOpen when the field is clicked', async () => {
233
+ const { root, waitForChanges, spyOnEvent } = await render(h("bs-input", { type: "dropdown" }));
234
+ const bsOpenSpy = spyOnEvent('bsOpen');
235
+ const field = root.shadowRoot.querySelector('[part="field"]');
236
+ field.click();
237
+ await waitForChanges();
238
+ expect(bsOpenSpy).toHaveReceivedEventDetail(true);
239
+ expect(root).toEqualAttribute('open', '');
240
+ field.click();
241
+ await waitForChanges();
242
+ expect(bsOpenSpy).toHaveReceivedEventDetail(false);
243
+ expect(root).not.toHaveAttribute('open');
244
+ });
245
+ it('does not render a bs-menu when there are no options, even if open', async () => {
246
+ const { root } = await render(h("bs-input", { type: "dropdown", open: true }));
247
+ expect(root.shadowRoot.querySelector('bs-menu')).toBeNull();
248
+ });
249
+ it('renders a bs-menu with one bs-menu-item per option when open', async () => {
250
+ const { root } = await render(h("bs-input", { type: "dropdown", open: true, options: [{ label: 'Denmark', value: 'dk' }, { label: 'Sweden', value: 'se' }] }));
251
+ const items = root.shadowRoot.querySelectorAll('bs-menu-item');
252
+ expect(items.length).toBe(2);
253
+ expect(items[0]).toHaveTextContent('Denmark');
254
+ expect(items[1]).toHaveTextContent('Sweden');
255
+ });
256
+ it('does not render the bs-menu when closed', async () => {
257
+ const { root } = await render(h("bs-input", { type: "dropdown", options: [{ label: 'Denmark', value: 'dk' }] }));
258
+ expect(root.shadowRoot.querySelector('bs-menu')).toBeNull();
259
+ });
260
+ it('selecting an option sets value, closes the menu, and emits bsChange', async () => {
261
+ const { root, waitForChanges, spyOnEvent } = await render(h("bs-input", { type: "dropdown", open: true, options: [{ label: 'Denmark', value: 'dk' }, { label: 'Sweden', value: 'se' }] }));
262
+ const bsChangeSpy = spyOnEvent('bsChange');
263
+ const item = root.shadowRoot.querySelectorAll('bs-menu-item')[1];
264
+ item.dispatchEvent(new CustomEvent('bsSelect', { bubbles: true, composed: true }));
265
+ await waitForChanges();
266
+ expect(bsChangeSpy).toHaveReceivedEventDetail('Sweden');
267
+ expect(root).not.toHaveAttribute('open');
268
+ expect(root.shadowRoot.querySelector('bs-menu')).toBeNull();
269
+ const input = root.shadowRoot.querySelector('input');
270
+ expect(input.value).toBe('Sweden');
271
+ });
272
+ it('closes the menu on Escape', async () => {
273
+ const { root, waitForChanges } = await render(h("bs-input", { type: "dropdown", open: true, options: [{ label: 'Denmark', value: 'dk' }] }));
274
+ root.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }));
275
+ await waitForChanges();
276
+ expect(root).not.toHaveAttribute('open');
277
+ });
278
+ });
279
+ describe('type="select"', () => {
280
+ it('renders a native select with an option per entry', async () => {
281
+ const { root } = await render(h("bs-input", { type: "select", options: [
282
+ { label: 'One', value: '1' },
283
+ { label: 'Two', value: '2' },
284
+ ] }));
285
+ const select = root.shadowRoot.querySelector('select');
286
+ const options = select.querySelectorAll('option');
287
+ expect(options.length).toBe(2);
288
+ expect(options[1]).toHaveTextContent('Two');
289
+ });
290
+ it('emits bsChange with the selected value on change', async () => {
291
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "select", options: [
292
+ { label: 'One', value: '1' },
293
+ { label: 'Two', value: '2' },
294
+ ] }));
295
+ const bsChangeSpy = spyOnEvent('bsChange');
296
+ const select = root.shadowRoot.querySelector('select');
297
+ select.value = '2';
298
+ select.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
299
+ expect(bsChangeSpy).toHaveReceivedEventDetail('2');
300
+ });
301
+ });
302
+ describe('type="textarea"', () => {
303
+ it('renders a native textarea with the rows prop applied', async () => {
304
+ const { root } = await render(h("bs-input", { type: "textarea", rows: 5, value: "hello" }));
305
+ const textarea = root.shadowRoot.querySelector('textarea');
306
+ expect(textarea).toEqualAttribute('rows', '5');
307
+ expect(textarea.value).toBe('hello');
308
+ });
309
+ });
310
+ describe('type="chip"', () => {
311
+ it('renders a chip per entry with a remove button', async () => {
312
+ const { root } = await render(h("bs-input", { type: "chip", chips: ['red', 'blue'] }));
313
+ const chips = root.shadowRoot.querySelectorAll('.bs-input__chip');
314
+ expect(chips.length).toBe(2);
315
+ expect(chips[0]).toHaveTextContent('red');
316
+ });
317
+ it('commits the draft input as a new chip on Enter and clears it', async () => {
318
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "chip", chips: [] }));
319
+ const bsChipAddSpy = spyOnEvent('bsChipAdd');
320
+ const input = root.shadowRoot.querySelector('.bs-input__chip-input');
321
+ input.value = 'green';
322
+ input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
323
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true }));
324
+ expect(bsChipAddSpy).toHaveReceivedEventDetail('green');
325
+ });
326
+ it('emits bsChipRemove when a chip remove button is clicked', async () => {
327
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "chip", chips: ['red'] }));
328
+ const bsChipRemoveSpy = spyOnEvent('bsChipRemove');
329
+ const removeBtn = root.shadowRoot.querySelector('.bs-input__chip-remove');
330
+ removeBtn.click();
331
+ expect(bsChipRemoveSpy).toHaveReceivedEventDetail('red');
332
+ });
333
+ });
334
+ describe('type="initials"', () => {
335
+ it('renders a title select followed by a text input, sharing one label', async () => {
336
+ const { root } = await render(h("bs-input", { type: "initials", label: "Name", titleOptions: ['Mrs.', 'Mr.', 'Dr.'] }));
337
+ const select = root.shadowRoot.querySelector('.bs-input__title-select');
338
+ const input = root.shadowRoot.querySelector('input');
339
+ const label = root.shadowRoot.querySelector('label');
340
+ expect(select.querySelectorAll('option').length).toBe(3);
341
+ expect(label).toEqualAttribute('for', 'control');
342
+ expect(input).toEqualAttribute('id', 'control');
343
+ });
344
+ it('emits bsTitleChange when the title select changes', async () => {
345
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "initials", titleOptions: ['Mrs.', 'Mr.'] }));
346
+ const bsTitleChangeSpy = spyOnEvent('bsTitleChange');
347
+ const select = root.shadowRoot.querySelector('.bs-input__title-select');
348
+ select.value = 'Mr.';
349
+ select.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
350
+ expect(bsTitleChangeSpy).toHaveReceivedEventDetail('Mr.');
351
+ });
352
+ });
353
+ describe('type="country"', () => {
354
+ it('renders a country select followed by a text input', async () => {
355
+ const { root } = await render(h("bs-input", { type: "country", countryOptions: [{ code: 'DK', label: 'Denmark' }, { code: 'US', label: 'United States' }] }));
356
+ const select = root.shadowRoot.querySelector('.bs-input__country-select');
357
+ expect(select.querySelectorAll('option').length).toBe(2);
358
+ });
359
+ it('emits bsCountryChange when the country select changes', async () => {
360
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "country", countryOptions: [{ code: 'DK', label: 'Denmark' }, { code: 'US', label: 'United States' }] }));
361
+ const bsCountryChangeSpy = spyOnEvent('bsCountryChange');
362
+ const select = root.shadowRoot.querySelector('.bs-input__country-select');
363
+ select.value = 'US';
364
+ select.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
365
+ expect(bsCountryChangeSpy).toHaveReceivedEventDetail('US');
366
+ });
367
+ });
368
+ describe('type="pin"', () => {
369
+ it('renders one input cell per length', async () => {
370
+ const { root } = await render(h("bs-input", { type: "pin", length: 4 }));
371
+ const cells = root.shadowRoot.querySelectorAll('.bs-input__pin-cell');
372
+ expect(cells.length).toBe(4);
373
+ });
374
+ it('auto-advances focus to the next cell when a digit is typed', async () => {
375
+ const { root } = await render(h("bs-input", { type: "pin", length: 4 }));
376
+ const cells = Array.from(root.shadowRoot.querySelectorAll('.bs-input__pin-cell'));
377
+ cells[0].value = '1';
378
+ cells[0].dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
379
+ expect(root.shadowRoot.activeElement).toBe(cells[1]);
380
+ });
381
+ it('moves focus to the previous cell on backspace in an empty cell', async () => {
382
+ const { root } = await render(h("bs-input", { type: "pin", length: 4 }));
383
+ const cells = Array.from(root.shadowRoot.querySelectorAll('.bs-input__pin-cell'));
384
+ cells[1].focus();
385
+ cells[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace', bubbles: true, composed: true }));
386
+ expect(root.shadowRoot.activeElement).toBe(cells[0]);
387
+ });
388
+ it('emits bsChange with the concatenated pin value as cells are typed', async () => {
389
+ const { root, spyOnEvent } = await render(h("bs-input", { type: "pin", length: 2 }));
390
+ const bsChangeSpy = spyOnEvent('bsChange');
391
+ const cells = Array.from(root.shadowRoot.querySelectorAll('.bs-input__pin-cell'));
392
+ cells[0].value = '4';
393
+ cells[0].dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
394
+ expect(bsChangeSpy).toHaveReceivedEventDetail('4');
395
+ });
396
+ });
101
397
  });