@crowdstrike/glide-core 0.9.6 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +17 -53
  2. package/dist/accordion.d.ts +10 -10
  3. package/dist/accordion.js +1 -1
  4. package/dist/accordion.stories.d.ts +0 -1
  5. package/dist/accordion.styles.js +36 -38
  6. package/dist/accordion.test.basics.js +13 -95
  7. package/dist/accordion.test.events.js +21 -33
  8. package/dist/accordion.test.focus.d.ts +1 -0
  9. package/dist/accordion.test.focus.js +11 -0
  10. package/dist/accordion.test.interactions.d.ts +1 -0
  11. package/dist/accordion.test.interactions.js +75 -0
  12. package/dist/button-group.button.d.ts +2 -4
  13. package/dist/button-group.button.js +1 -1
  14. package/dist/button-group.button.styles.js +6 -14
  15. package/dist/button-group.button.test.basics.js +8 -17
  16. package/dist/button-group.button.test.interactions.js +4 -4
  17. package/dist/button-group.d.ts +0 -2
  18. package/dist/button-group.test.basics.js +10 -10
  19. package/dist/button-group.test.events.js +2 -2
  20. package/dist/button-group.test.interactions.js +1 -1
  21. package/dist/button.d.ts +7 -10
  22. package/dist/button.js +1 -1
  23. package/dist/button.styles.js +4 -7
  24. package/dist/button.test.basics.js +10 -26
  25. package/dist/button.test.events.js +9 -9
  26. package/dist/checkbox-group.d.ts +3 -4
  27. package/dist/checkbox-group.js +1 -1
  28. package/dist/checkbox-group.styles.js +13 -1
  29. package/dist/checkbox-group.test.basics.js +8 -12
  30. package/dist/checkbox-group.test.focus.js +7 -7
  31. package/dist/checkbox-group.test.interactions.d.ts +1 -0
  32. package/dist/checkbox-group.test.interactions.js +82 -0
  33. package/dist/checkbox.d.ts +5 -4
  34. package/dist/checkbox.js +1 -1
  35. package/dist/checkbox.styles.js +35 -15
  36. package/dist/checkbox.test.basics.js +6 -15
  37. package/dist/checkbox.test.events.js +9 -0
  38. package/dist/checkbox.test.focus.js +4 -2
  39. package/dist/checkbox.test.interactions.js +11 -11
  40. package/dist/drawer.d.ts +2 -5
  41. package/dist/drawer.js +1 -1
  42. package/dist/drawer.test.accessibility.js +8 -8
  43. package/dist/drawer.test.basics.js +16 -16
  44. package/dist/drawer.test.closing.js +18 -16
  45. package/dist/drawer.test.events.js +13 -24
  46. package/dist/drawer.test.methods.js +22 -22
  47. package/dist/dropdown.d.ts +7 -5
  48. package/dist/dropdown.js +1 -1
  49. package/dist/dropdown.option.d.ts +2 -3
  50. package/dist/dropdown.option.js +1 -1
  51. package/dist/dropdown.option.styles.js +31 -19
  52. package/dist/dropdown.option.test.basics.js +4 -4
  53. package/dist/dropdown.option.test.events.js +9 -1
  54. package/dist/dropdown.option.test.interactions.single.js +2 -2
  55. package/dist/dropdown.styles.js +39 -3
  56. package/dist/dropdown.test.basics.d.ts +1 -1
  57. package/dist/dropdown.test.basics.js +27 -14
  58. package/dist/dropdown.test.basics.multiple.js +65 -32
  59. package/dist/dropdown.test.basics.single.js +49 -0
  60. package/dist/dropdown.test.events.filterable.js +13 -2
  61. package/dist/dropdown.test.focus.filterable.js +12 -3
  62. package/dist/dropdown.test.focus.js +18 -2
  63. package/dist/dropdown.test.form.multiple.js +3 -2
  64. package/dist/dropdown.test.interactions.filterable.js +141 -45
  65. package/dist/dropdown.test.interactions.js +24 -0
  66. package/dist/dropdown.test.interactions.multiple.js +87 -30
  67. package/dist/dropdown.test.interactions.single.js +40 -4
  68. package/dist/form-controls-layout.d.ts +0 -2
  69. package/dist/icon-button.d.ts +2 -4
  70. package/dist/icon-button.js +1 -1
  71. package/dist/icon-button.test.basics.js +14 -82
  72. package/dist/icon-button.test.focus.d.ts +1 -0
  73. package/dist/icon-button.test.focus.js +13 -0
  74. package/dist/input.d.ts +4 -5
  75. package/dist/input.js +1 -1
  76. package/dist/input.styles.js +4 -4
  77. package/dist/input.test.basics.js +0 -52
  78. package/dist/input.test.events.js +27 -27
  79. package/dist/input.test.focus.js +27 -26
  80. package/dist/input.test.form.js +6 -6
  81. package/dist/input.test.validity.js +130 -130
  82. package/dist/label.d.ts +1 -3
  83. package/dist/label.js +1 -1
  84. package/dist/label.styles.js +5 -6
  85. package/dist/label.test.basics.js +4 -4
  86. package/dist/library/ow.js +1 -1
  87. package/dist/menu.button.d.ts +0 -2
  88. package/dist/menu.button.test.basics.js +3 -3
  89. package/dist/menu.d.ts +1 -4
  90. package/dist/menu.js +1 -1
  91. package/dist/menu.link.d.ts +1 -2
  92. package/dist/menu.link.js +1 -1
  93. package/dist/menu.options.d.ts +0 -2
  94. package/dist/menu.test.events.js +6 -6
  95. package/dist/menu.test.focus.js +5 -18
  96. package/dist/menu.test.interactions.js +48 -24
  97. package/dist/modal.d.ts +6 -17
  98. package/dist/modal.icon-button.d.ts +0 -2
  99. package/dist/modal.icon-button.test.basics.js +3 -3
  100. package/dist/modal.js +1 -1
  101. package/dist/modal.styles.js +13 -19
  102. package/dist/modal.tertiary-icon.d.ts +0 -3
  103. package/dist/modal.tertiary-icon.test.basics.js +3 -3
  104. package/dist/modal.test.basics.js +9 -5
  105. package/dist/modal.test.close.js +2 -2
  106. package/dist/modal.test.events.js +2 -2
  107. package/dist/radio-group.d.ts +0 -3
  108. package/dist/radio-group.js +1 -1
  109. package/dist/radio-group.test.basics.js +61 -61
  110. package/dist/radio-group.test.events.js +13 -13
  111. package/dist/radio-group.test.focus.js +1 -1
  112. package/dist/radio-group.test.form.js +2 -2
  113. package/dist/radio-group.test.validity.js +12 -12
  114. package/dist/radio.d.ts +0 -3
  115. package/dist/radio.styles.js +4 -12
  116. package/dist/split-button.d.ts +8 -11
  117. package/dist/split-button.js +1 -1
  118. package/dist/split-button.primary-button.d.ts +21 -0
  119. package/dist/split-button.primary-button.js +1 -0
  120. package/dist/split-button.primary-button.styles.js +96 -0
  121. package/dist/split-button.primary-button.test.basics.d.ts +1 -0
  122. package/dist/split-button.primary-button.test.basics.js +31 -0
  123. package/dist/split-button.primary-button.test.focus.d.ts +1 -0
  124. package/dist/split-button.primary-button.test.focus.js +14 -0
  125. package/dist/split-button.primary-link.d.ts +19 -0
  126. package/dist/split-button.primary-link.js +1 -0
  127. package/dist/split-button.primary-link.test.basics.d.ts +1 -0
  128. package/dist/split-button.primary-link.test.basics.js +30 -0
  129. package/dist/split-button.primary-link.test.focus.d.ts +1 -0
  130. package/dist/split-button.primary-link.test.focus.js +15 -0
  131. package/dist/split-button.secondary-button.d.ts +25 -0
  132. package/dist/split-button.secondary-button.js +1 -0
  133. package/dist/split-button.secondary-button.styles.js +103 -0
  134. package/dist/split-button.secondary-button.test.basics.d.ts +1 -0
  135. package/dist/split-button.secondary-button.test.basics.js +58 -0
  136. package/dist/split-button.secondary-button.test.focus.d.ts +1 -0
  137. package/dist/split-button.secondary-button.test.focus.js +14 -0
  138. package/dist/split-button.secondary-button.test.interactions.d.ts +2 -0
  139. package/dist/split-button.secondary-button.test.interactions.js +30 -0
  140. package/dist/split-button.stories.d.ts +4 -3
  141. package/dist/split-button.styles.js +1 -94
  142. package/dist/split-button.test.basics.d.ts +2 -1
  143. package/dist/split-button.test.basics.js +67 -80
  144. package/dist/split-button.test.interactions.d.ts +4 -0
  145. package/dist/split-button.test.interactions.js +51 -0
  146. package/dist/styles/opacity-and-scale-animation.js +2 -6
  147. package/dist/styles/variables.css +1 -1
  148. package/dist/tab.d.ts +2 -11
  149. package/dist/tab.group.d.ts +3 -5
  150. package/dist/tab.group.js +1 -1
  151. package/dist/tab.group.styles.js +18 -15
  152. package/dist/tab.group.test.basics.js +49 -34
  153. package/dist/tab.group.test.interactions.js +17 -14
  154. package/dist/tab.panel.d.ts +0 -3
  155. package/dist/tab.test.basics.js +6 -5
  156. package/dist/tag.d.ts +5 -4
  157. package/dist/tag.js +1 -1
  158. package/dist/tag.styles.js +47 -38
  159. package/dist/tag.test.basics.js +18 -110
  160. package/dist/tag.test.events.js +12 -8
  161. package/dist/tag.test.focus.js +2 -3
  162. package/dist/tag.test.interactions.d.ts +1 -0
  163. package/dist/tag.test.interactions.js +36 -0
  164. package/dist/textarea.d.ts +2 -3
  165. package/dist/textarea.js +2 -2
  166. package/dist/textarea.test.basics.js +8 -8
  167. package/dist/textarea.test.events.js +55 -55
  168. package/dist/textarea.test.form.js +9 -9
  169. package/dist/textarea.test.validity.js +167 -135
  170. package/dist/toasts.d.ts +1 -5
  171. package/dist/toasts.test.basics.js +2 -1
  172. package/dist/toasts.toast.d.ts +1 -4
  173. package/dist/toasts.toast.js +1 -1
  174. package/dist/toasts.toast.styles.js +12 -0
  175. package/dist/toggle.d.ts +0 -2
  176. package/dist/toggle.styles.js +1 -5
  177. package/dist/toggle.test.basics.js +2 -2
  178. package/dist/toggle.test.interactions.js +7 -7
  179. package/dist/tooltip.d.ts +2 -1
  180. package/dist/tooltip.js +1 -1
  181. package/dist/tooltip.styles.js +37 -13
  182. package/dist/tooltip.test.basics.d.ts +1 -1
  183. package/dist/tooltip.test.basics.js +19 -19
  184. package/dist/tree.d.ts +0 -2
  185. package/dist/tree.item.d.ts +5 -7
  186. package/dist/tree.item.icon-button.d.ts +1 -4
  187. package/dist/tree.item.js +1 -1
  188. package/dist/tree.item.menu.d.ts +1 -2
  189. package/dist/tree.item.menu.js +1 -1
  190. package/dist/tree.item.menu.test.basics.js +31 -10
  191. package/dist/tree.item.styles.js +7 -9
  192. package/dist/tree.item.test.basics.js +43 -31
  193. package/dist/tree.test.basics.js +29 -29
  194. package/dist/tree.test.focus.js +77 -74
  195. package/package.json +12 -14
  196. package/dist/split-container.d.ts +0 -31
  197. package/dist/split-container.js +0 -1
  198. package/dist/split-container.styles.js +0 -132
  199. package/dist/split-container.test.basics.d.ts +0 -3
  200. package/dist/split-container.test.basics.js +0 -445
  201. package/dist/split-container.test.interactions.d.ts +0 -1
  202. package/dist/split-container.test.interactions.js +0 -20
  203. package/dist/split-link.d.ts +0 -25
  204. package/dist/split-link.js +0 -1
  205. package/dist/split-link.test.basics.d.ts +0 -1
  206. package/dist/split-link.test.basics.js +0 -93
  207. package/dist/split-link.test.interactions.d.ts +0 -1
  208. package/dist/split-link.test.interactions.js +0 -20
  209. package/dist/status-indicator.d.ts +0 -30
  210. package/dist/status-indicator.js +0 -1
  211. package/dist/status-indicator.stories.d.ts +0 -5
  212. package/dist/status-indicator.styles.js +0 -58
  213. package/dist/status-indicator.test.basics.d.ts +0 -1
  214. package/dist/status-indicator.test.basics.js +0 -102
  215. /package/dist/{split-container.styles.d.ts → split-button.primary-button.styles.d.ts} +0 -0
  216. /package/dist/{status-indicator.styles.d.ts → split-button.secondary-button.styles.d.ts} +0 -0
@@ -1,9 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import './dropdown.option.js';
3
2
  import { ArgumentError } from 'ow';
4
3
  import { aTimeout, assert, expect, fixture, html } from '@open-wc/testing';
5
4
  import { repeat } from 'lit/directives/repeat.js';
6
5
  import GlideCoreDropdown from './dropdown.js';
6
+ import GlideCoreDropdownOption from './dropdown.option.js';
7
7
  import expectArgumentError from './library/expect-argument-error.js';
8
8
  import sinon from 'sinon';
9
9
  // You'll notice quite a few duplicated tests among the "*.single.ts", "*.multiple.ts",
@@ -18,6 +18,7 @@ import sinon from 'sinon';
18
18
  // duplicating them in both `dropdown.test.interactions.single.ts` and
19
19
  // `dropdown.test.interactions.multiple.ts` would add a ton of test weight.
20
20
  GlideCoreDropdown.shadowRootOptions.mode = 'open';
21
+ GlideCoreDropdownOption.shadowRootOptions.mode = 'open';
21
22
  it('registers', async () => {
22
23
  expect(window.customElements.get('glide-core-dropdown')).to.equal(GlideCoreDropdown);
23
24
  });
@@ -31,15 +32,10 @@ it('has defaults', async () => {
31
32
  value="value"
32
33
  ></glide-core-dropdown-option>
33
34
  </glide-core-dropdown>`);
34
- expect(component.hasAttribute('disabled')).to.be.false;
35
- expect(component.disabled).to.equal(false);
36
- expect(component.hasAttribute('filterable')).to.be.false;
37
- expect(component.filterable).to.equal(false);
38
- expect(component.getAttribute('name')).to.be.null;
39
- expect(component.name).to.equal(undefined);
40
- expect(component.hasAttribute('required')).to.be.false;
41
- expect(component.required).to.equal(false);
42
- expect(component.getAttribute('size')).to.equal('large');
35
+ expect(component.disabled).to.be.false;
36
+ expect(component.filterable).to.be.false;
37
+ expect(component.name).to.be.empty.string;
38
+ expect(component.required).to.be.false;
43
39
  expect(component.size).to.equal('large');
44
40
  // Not reflected, so no attribute assertion is necessary.
45
41
  expect(component.value).to.deep.equal([]);
@@ -169,7 +165,7 @@ it('can be `disabled`', async () => {
169
165
  ></glide-core-dropdown-option>
170
166
  </glide-core-dropdown>`);
171
167
  expect(component.hasAttribute('disabled')).to.be.true;
172
- expect(component.disabled).to.equal(true);
168
+ expect(component.disabled).to.be.true;
173
169
  });
174
170
  it('can be `required`', async () => {
175
171
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" required>
@@ -179,7 +175,7 @@ it('can be `required`', async () => {
179
175
  ></glide-core-dropdown-option>
180
176
  </glide-core-dropdown>`);
181
177
  expect(component.hasAttribute('required')).to.be.true;
182
- expect(component.required).to.equal(true);
178
+ expect(component.required).to.be.true;
183
179
  });
184
180
  it('can be `multiple`', async () => {
185
181
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
@@ -194,7 +190,7 @@ it('can be `multiple`', async () => {
194
190
  ></glide-core-dropdown-option>
195
191
  </glide-core-dropdown>`);
196
192
  expect(component.hasAttribute('multiple')).to.be.true;
197
- expect(component.multiple).to.equal(true);
193
+ expect(component.multiple).to.be.true;
198
194
  });
199
195
  it('can be `select-all`', async () => {
200
196
  const component = await fixture(html `<glide-core-dropdown
@@ -209,7 +205,7 @@ it('can be `select-all`', async () => {
209
205
  ></glide-core-dropdown-option>
210
206
  </glide-core-dropdown>`);
211
207
  expect(component.hasAttribute('select-all')).to.be.true;
212
- expect(component.selectAll).to.equal(true);
208
+ expect(component.selectAll).to.be.true;
213
209
  });
214
210
  it('activates the first option when no options are initially selected', async () => {
215
211
  const component = await fixture(html `<glide-core-dropdown open>
@@ -386,3 +382,20 @@ it('does not throw if the default slot only contains whitespace', async () => {
386
382
  }
387
383
  expect(spy.callCount).to.equal(0);
388
384
  });
385
+ it('hides the tooltip of the active option when open', async () => {
386
+ // The period is arbitrary. 500 of them ensures we exceed the maximum
387
+ // width even if it's increased.
388
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
389
+ <glide-core-dropdown-option
390
+ label=${'.'.repeat(500)}
391
+ ></glide-core-dropdown-option>
392
+
393
+ <glide-core-dropdown-option label="Two"></glide-core-dropdown-option>
394
+ </glide-core-dropdown>`);
395
+ // Wait for it to open.
396
+ await aTimeout(0);
397
+ const tooltip = component
398
+ .querySelector('glide-core-dropdown-option')
399
+ ?.shadowRoot?.querySelector('[data-test="tooltip"]');
400
+ expect(tooltip?.checkVisibility()).to.be.false;
401
+ });
@@ -2,6 +2,7 @@
2
2
  import './dropdown.option.js';
3
3
  import { aTimeout, expect, fixture, html } from '@open-wc/testing';
4
4
  import GlideCoreDropdown from './dropdown.js';
5
+ import GlideCoreTag from './tag.js';
5
6
  GlideCoreDropdown.shadowRootOptions.mode = 'open';
6
7
  it('is accessible', async () => {
7
8
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
@@ -73,38 +74,7 @@ it('has a tag when an option is initially selected', async () => {
73
74
  </glide-core-dropdown>`);
74
75
  const tag = component.shadowRoot?.querySelector('[data-test="tag"]');
75
76
  expect(tag?.checkVisibility()).to.be.true;
76
- expect(tag?.textContent?.trim()).to.equal('One');
77
- });
78
- it('only has so many tags when many options are initially selected', async () => {
79
- const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
80
- <glide-core-dropdown-option
81
- label="One"
82
- value="one"
83
- selected
84
- ></glide-core-dropdown-option>
85
-
86
- <glide-core-dropdown-option
87
- label="Two"
88
- value="two"
89
- selected
90
- ></glide-core-dropdown-option>
91
-
92
- <glide-core-dropdown-option
93
- label="Three"
94
- value="three"
95
- selected
96
- ></glide-core-dropdown-option>
97
-
98
- <glide-core-dropdown-option
99
- label="Four"
100
- value="four"
101
- selected
102
- ></glide-core-dropdown-option>
103
- </glide-core-dropdown>`);
104
- const tagContainers = [
105
- ...(component.shadowRoot?.querySelectorAll('[data-test="tag-container"]') ?? []),
106
- ].filter((element) => element.checkVisibility());
107
- expect(tagContainers?.length).to.equal(3);
77
+ expect(tag?.label).to.equal('One');
108
78
  });
109
79
  it('shows Select All', async () => {
110
80
  const component = await fixture(html `<glide-core-dropdown
@@ -271,3 +241,66 @@ it('has no internal label when an option is initially selected', async () => {
271
241
  const label = component.shadowRoot?.querySelector('[data-test="internal-label"]');
272
242
  expect(label).to.not.exist;
273
243
  });
244
+ it('has a "multiselect" icon for each selected option with a value', async () => {
245
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
246
+ <div slot="icon:one">✓</div>
247
+ <div slot="icon:two">✓</div>
248
+ <div slot="icon:three">✓</div>
249
+
250
+ <glide-core-dropdown-option
251
+ label="One"
252
+ value="one"
253
+ selected
254
+ ></glide-core-dropdown-option>
255
+
256
+ <glide-core-dropdown-option
257
+ label="Two"
258
+ value="two"
259
+ selected
260
+ ></glide-core-dropdown-option>
261
+
262
+ <glide-core-dropdown-option
263
+ label="Three"
264
+ selected
265
+ ></glide-core-dropdown-option>
266
+ </glide-core-dropdown>`);
267
+ const icons = component.shadowRoot?.querySelectorAll('[data-test="multiselect-icon-slot"]');
268
+ expect(icons?.length).to.equal(2);
269
+ });
270
+ it('has no "multiselect" icons', async () => {
271
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
272
+ <div slot="icon:one">✓</div>
273
+ <div slot="icon:two">✓</div>
274
+
275
+ <glide-core-dropdown-option
276
+ label="One"
277
+ value="one"
278
+ ></glide-core-dropdown-option>
279
+
280
+ <glide-core-dropdown-option
281
+ label="Two"
282
+ value="two"
283
+ ></glide-core-dropdown-option>
284
+ </glide-core-dropdown>`);
285
+ const icons = component.shadowRoot?.querySelectorAll('[data-test="multiselect-icon-slot"]');
286
+ expect(icons?.length).to.equal(0);
287
+ });
288
+ it('has no "single-select" icon', async () => {
289
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
290
+ <div slot="icon:one">✓</div>
291
+ <div slot="icon:two">✓</div>
292
+
293
+ <glide-core-dropdown-option
294
+ label="One"
295
+ value="one"
296
+ selected
297
+ ></glide-core-dropdown-option>
298
+
299
+ <glide-core-dropdown-option
300
+ label="Two"
301
+ value="two"
302
+ ></glide-core-dropdown-option>
303
+ </glide-core-dropdown>`);
304
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
305
+ expect(iconSlot).to.be.null;
306
+ });
@@ -77,3 +77,52 @@ it('hides Select All', async () => {
77
77
  const selectAll = component.shadowRoot?.querySelector('[data-test="select-all"]');
78
78
  expect(selectAll?.checkVisibility()).to.not.be.ok;
79
79
  });
80
+ it('has an icon when an option with a value is selected', async () => {
81
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
82
+ <div slot="icon:one">✓</div>
83
+ <div slot="icon:two">✓</div>
84
+
85
+ <glide-core-dropdown-option label="One" value="one" selected>
86
+ <div slot="icon">✓</div>
87
+ </glide-core-dropdown-option>
88
+
89
+ <glide-core-dropdown-option label="Two" value="two">
90
+ <div slot="icon">✓</div>
91
+ </glide-core-dropdown-option>
92
+ </glide-core-dropdown>`);
93
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
94
+ expect(iconSlot instanceof HTMLSlotElement).to.be.true;
95
+ expect(iconSlot?.assignedElements().at(0)?.slot).to.equal('icon:one');
96
+ });
97
+ it('has no icon when an option without a value is selected', async () => {
98
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
99
+ <div slot="icon:one">✓</div>
100
+ <div slot="icon:two">✓</div>
101
+
102
+ <glide-core-dropdown-option label="One" selected>
103
+ <div slot="icon">✓</div>
104
+ </glide-core-dropdown-option>
105
+
106
+ <glide-core-dropdown-option label="Two" value="two">
107
+ <div slot="icon">✓</div>
108
+ </glide-core-dropdown-option>
109
+ </glide-core-dropdown>`);
110
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
111
+ expect(iconSlot).to.be.null;
112
+ });
113
+ it('has no icon when no option is selected', async () => {
114
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
115
+ <div slot="icon:one">✓</div>
116
+ <div slot="icon:two">✓</div>
117
+
118
+ <glide-core-dropdown-option label="One" value="one">
119
+ <div slot="icon">✓</div>
120
+ </glide-core-dropdown-option>
121
+
122
+ <glide-core-dropdown-option label="Two" value="two">
123
+ <div slot="icon">✓</div>
124
+ </glide-core-dropdown-option>
125
+ </glide-core-dropdown>`);
126
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
127
+ expect(iconSlot).to.be.null;
128
+ });
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
2
  import * as sinon from 'sinon';
3
- import { expect, fixture, html } from '@open-wc/testing';
3
+ import { expect, fixture, html, oneEvent } from '@open-wc/testing';
4
4
  import { sendKeys } from '@web/test-runner-commands';
5
5
  import GlideCoreDropdown from './dropdown.js';
6
6
  import GlideCoreDropdownOption from './dropdown.option.js';
@@ -62,6 +62,17 @@ const defaultSlot = html `
62
62
  value="eleven"
63
63
  ></glide-core-dropdown-option>
64
64
  `;
65
+ it('dispatches a "filter" event on "input"', async () => {
66
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
67
+ ${defaultSlot}
68
+ </glide-core-dropdown>`);
69
+ component.focus();
70
+ sendKeys({ type: 'o' });
71
+ const event = await oneEvent(component, 'filter');
72
+ expect(event instanceof CustomEvent).to.be.true;
73
+ expect(event.bubbles).to.be.true;
74
+ expect(event.detail).to.equal('o');
75
+ });
65
76
  it('does not dispatch "input" events on input', async () => {
66
77
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
67
78
  ${defaultSlot}
@@ -69,6 +80,6 @@ it('does not dispatch "input" events on input', async () => {
69
80
  const spy = sinon.spy();
70
81
  component.addEventListener('input', spy);
71
82
  component.focus();
72
- await sendKeys({ type: ' one ' });
83
+ await sendKeys({ type: 'one' });
73
84
  expect(spy.callCount).to.equal(0);
74
85
  });
@@ -158,8 +158,7 @@ it('does not focus the input when `checkValidity` is called', async () => {
158
158
  component.checkValidity();
159
159
  expect(component.shadowRoot?.activeElement).to.equal(null);
160
160
  });
161
- it('sets the `value` of the `<input>` to the selected option when focus is lost', async () => {
162
- document.body.tabIndex = -1;
161
+ it('sets the `value` of its `<input>` to the selected option when focus is lost', async () => {
163
162
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
164
163
  ${defaultSlot}
165
164
  </glide-core-dropdown>`);
@@ -172,7 +171,17 @@ it('sets the `value` of the `<input>` to the selected option when focus is lost'
172
171
  // back to "One" when focus is lost.
173
172
  component.focus();
174
173
  await sendKeys({ type: 'o' });
175
- document.body.focus();
174
+ component.blur();
176
175
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
177
176
  expect(input?.value).to.equal('One');
178
177
  });
178
+ it('selects the filter text on focus', async () => {
179
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
180
+ ${defaultSlot}
181
+ </glide-core-dropdown>`);
182
+ component.focus();
183
+ await sendKeys({ type: 'one' });
184
+ component.blur();
185
+ component.focus();
186
+ expect(window.getSelection()?.toString()).to.equal('one');
187
+ });
@@ -33,6 +33,22 @@ it('closes and reports validity when it loses focus', async () => {
33
33
  await sendKeys({ up: 'Shift' });
34
34
  expect(component.open).to.be.false;
35
35
  expect(component.shadowRoot?.activeElement).to.equal(null);
36
- expect(component.validity.valid).to.equal(false);
37
- expect(component.shadowRoot?.querySelector('glide-core-private-label')?.error).to.equal(true);
36
+ expect(component.validity.valid).to.be.false;
37
+ expect(component.shadowRoot?.querySelector('glide-core-private-label')?.error)
38
+ .to.be.true;
39
+ });
40
+ it('is focused when clicked', async () => {
41
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
42
+ <glide-core-dropdown-option
43
+ label="Label"
44
+ value="value"
45
+ ></glide-core-dropdown-option>
46
+ </glide-core-dropdown>`);
47
+ // Calling `click()` would be sweet. The problem is it sets `event.detail` to `0`,
48
+ // which puts us in a guard in the event handler. `Event` has no `detail` property
49
+ // and would work. `CustomEvent` is used for completeness and to get us as close as
50
+ // possible to a real click. See the comment in the handler for more information.
51
+ const button = component.shadowRoot?.querySelector('[data-test="button"]');
52
+ button?.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: 1 }));
53
+ expect(component.shadowRoot?.activeElement).to.equal(button);
38
54
  });
@@ -3,6 +3,7 @@ import './dropdown.option.js';
3
3
  import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
4
4
  import GlideCoreDropdown from './dropdown.js';
5
5
  import GlideCoreDropdownOption from './dropdown.option.js';
6
+ import GlideCoreTag from './tag.js';
6
7
  GlideCoreDropdown.shadowRootOptions.mode = 'open';
7
8
  GlideCoreDropdownOption.shadowRootOptions.mode = 'open';
8
9
  it('can be reset', async () => {
@@ -62,8 +63,8 @@ it('can be reset to the initially selected options', async () => {
62
63
  form.reset();
63
64
  const tags = component.shadowRoot?.querySelectorAll('[data-test="tag"]');
64
65
  expect(tags?.length).to.equal(2);
65
- expect(tags?.[0].textContent?.trim()).to.equal('Two');
66
- expect(tags?.[1].textContent?.trim()).to.equal('Three');
66
+ expect(tags?.[0].label).to.equal('Two');
67
+ expect(tags?.[1].label).to.equal('Three');
67
68
  expect(component.value).to.deep.equal(['two', 'three']);
68
69
  });
69
70
  it('has `formData` value when options are selected', async () => {
@@ -83,7 +83,7 @@ it('filters', async () => {
83
83
  ${defaultSlot}
84
84
  </glide-core-dropdown>`);
85
85
  component.focus();
86
- await sendKeys({ type: ' one ' });
86
+ await sendKeys({ type: 'one' });
87
87
  const options = [
88
88
  ...component.querySelectorAll('glide-core-dropdown-option'),
89
89
  ].filter(({ hidden }) => !hidden);
@@ -96,7 +96,7 @@ it('unfilters when an option is selected via click', async () => {
96
96
  // Wait for it to open.
97
97
  await aTimeout(0);
98
98
  component.focus();
99
- await sendKeys({ type: ' one ' });
99
+ await sendKeys({ type: 'one' });
100
100
  [...component.querySelectorAll('glide-core-dropdown-option')]
101
101
  .find(({ hidden }) => !hidden)
102
102
  ?.click();
@@ -110,37 +110,71 @@ it('unfilters when an option is selected via Enter', async () => {
110
110
  ${defaultSlot}
111
111
  </glide-core-dropdown>`);
112
112
  component.focus();
113
- await sendKeys({ type: ' one ' });
113
+ await sendKeys({ type: 'one' });
114
114
  await sendKeys({ press: 'Enter' });
115
115
  const options = [
116
116
  ...component.querySelectorAll('glide-core-dropdown-option'),
117
117
  ].filter(({ hidden }) => !hidden);
118
118
  expect(options.length).to.equal(11);
119
119
  });
120
- it('shows the magnifying glass icon when there is a filter term', async () => {
120
+ it('does nothing on Enter when every option is filtered out', async () => {
121
121
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
122
122
  ${defaultSlot}
123
123
  </glide-core-dropdown>`);
124
124
  component.focus();
125
- await sendKeys({ type: ' one ' });
125
+ await sendKeys({ type: 'blah' });
126
+ await sendKeys({ press: 'Enter' });
127
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
128
+ const hiddenOptions = [
129
+ ...component.querySelectorAll('glide-core-dropdown-option'),
130
+ ].filter(({ hidden }) => hidden);
131
+ const selectedOptions = [
132
+ ...component.querySelectorAll('glide-core-dropdown-option'),
133
+ ].filter(({ selected }) => selected);
134
+ expect(input?.value).to.equal('blah');
135
+ expect(hiddenOptions.length).to.equal(11);
136
+ expect(selectedOptions.length).to.equal(0);
137
+ });
138
+ it('shows its magnifying glass icon when single-select and filtering', async () => {
139
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
140
+ ${defaultSlot}
141
+ </glide-core-dropdown>`);
142
+ component.focus();
143
+ await sendKeys({ type: 'one' });
126
144
  const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
127
145
  expect(icon?.checkVisibility()).to.be.true;
128
146
  });
129
- it('hides the magnifying glass icon when there is no filter term', async () => {
147
+ it('hides its magnifying glass icon when single-select and not filtering', async () => {
130
148
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
131
149
  ${defaultSlot}
132
150
  </glide-core-dropdown>`);
151
+ component.focus();
152
+ await sendKeys({ type: 'o' });
153
+ await sendKeys({ press: 'Backspace' });
154
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
155
+ expect(icon?.checkVisibility()).to.be.not.ok;
156
+ });
157
+ it('hides its magnifying glass icon when single-select and the filter is label of the selected option', async () => {
158
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
159
+ ${defaultSlot}
160
+ </glide-core-dropdown>`);
161
+ const option = [
162
+ ...component.querySelectorAll('glide-core-dropdown-option'),
163
+ ].find(({ hidden }) => !hidden);
164
+ option?.click();
165
+ component.focus();
166
+ await sendKeys({ type: 'One' });
133
167
  const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
134
168
  expect(icon?.checkVisibility()).to.be.not.ok;
135
169
  });
136
- it('hides the magnifying glass icon when an option is selected', async () => {
170
+ it('hides its magnifying glass icon when single-select and an option is selected', async () => {
137
171
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
138
172
  ${defaultSlot}
139
173
  </glide-core-dropdown>`);
140
174
  // Wait for it to open.
141
175
  await aTimeout(0);
142
176
  component.focus();
143
- await sendKeys({ type: ' one ' });
177
+ await sendKeys({ type: 'one' });
144
178
  const option = [
145
179
  ...component.querySelectorAll('glide-core-dropdown-option'),
146
180
  ].find(({ hidden }) => !hidden);
@@ -149,7 +183,7 @@ it('hides the magnifying glass icon when an option is selected', async () => {
149
183
  await elementUpdated(component);
150
184
  expect(icon?.checkVisibility()).to.be.not.ok;
151
185
  });
152
- it('hides the magnifying glass icon when closed programmatically and an option is selected', async () => {
186
+ it('hides its magnifying glass icon when single-select and closed programmatically and an option is selected', async () => {
153
187
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
154
188
  ${defaultSlot}
155
189
  </glide-core-dropdown>`);
@@ -163,6 +197,25 @@ it('hides the magnifying glass icon when closed programmatically and an option i
163
197
  const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
164
198
  component.open = false;
165
199
  await elementUpdated(component);
200
+ expect(icon?.checkVisibility()).to.not.be.ok;
201
+ });
202
+ it('shows its magnifying glass icon when multiselect and filtering', async () => {
203
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
204
+ ${defaultSlot}
205
+ </glide-core-dropdown>`);
206
+ component.focus();
207
+ await sendKeys({ type: 'one' });
208
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
209
+ expect(icon?.checkVisibility()).to.be.true;
210
+ });
211
+ it('hides its magnifying glass icon when multiselect and not filtering', async () => {
212
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
213
+ ${defaultSlot}
214
+ </glide-core-dropdown>`);
215
+ component.focus();
216
+ await sendKeys({ type: 'o' });
217
+ await sendKeys({ press: 'Backspace' });
218
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
166
219
  expect(icon?.checkVisibility()).to.be.not.ok;
167
220
  });
168
221
  it('does not filter on only whitespace', async () => {
@@ -222,6 +275,26 @@ it('sets the first unfiltered option as active when the previously active option
222
275
  expect(option?.privateActive).to.be.true;
223
276
  expect(input?.getAttribute('aria-activedescendant')).to.equal(option?.id);
224
277
  });
278
+ it('updates the `value` of its `<input>` when the `label` of a selected option is changed programmatically', async () => {
279
+ const component = await fixture(html `<glide-core-dropdown
280
+ label="Label"
281
+ placeholder="Placeholder"
282
+ filterable
283
+ >
284
+ <glide-core-dropdown-option
285
+ label="One"
286
+ selected
287
+ ></glide-core-dropdown-option>
288
+
289
+ <glide-core-dropdown-option label="Two"></glide-core-dropdown-option>
290
+ </glide-core-dropdown>`);
291
+ const option = component.querySelector('glide-core-dropdown-option');
292
+ assert(option);
293
+ option.label = 'Three';
294
+ await elementUpdated(component);
295
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
296
+ expect(input?.value).to.equal('Three');
297
+ });
225
298
  it('updates `value` when an option `value` is changed programmatically', async () => {
226
299
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
227
300
  ${defaultSlot}
@@ -231,6 +304,26 @@ it('updates `value` when an option `value` is changed programmatically', async (
231
304
  option.value = 'two';
232
305
  expect(component.value).to.deep.equal(['two']);
233
306
  });
307
+ it('sets the `value` of its `<input>` when made filterable', async () => {
308
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
309
+ <glide-core-dropdown-option
310
+ label="One"
311
+ value="one"
312
+ selected
313
+ ></glide-core-dropdown-option>
314
+
315
+ <glide-core-dropdown-option
316
+ label="Two"
317
+ value="two"
318
+ ></glide-core-dropdown-option>
319
+ </glide-core-dropdown>`);
320
+ component.filterable = true;
321
+ await elementUpdated(component);
322
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
323
+ // Wait for the `filterable` setter to do its thing.
324
+ await aTimeout(0);
325
+ expect(input?.value).to.equal('One');
326
+ });
234
327
  it('does not select options on Space', async () => {
235
328
  const component = await fixture(html `<glide-core-dropdown
236
329
  label="Label"
@@ -282,7 +375,7 @@ it('deselects all options on Meta + Backspace', async () => {
282
375
  expect(options[1].selected).to.be.false;
283
376
  expect(options[0].selected).to.be.false;
284
377
  });
285
- it('set the `value` of the `<input>` to the label of the selected option when not `multiple`', async () => {
378
+ it('sets the `value` of its `<input>` to the label of the selected option when not `multiple`', async () => {
286
379
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
287
380
  ${defaultSlot}
288
381
  </glide-core-dropdown>`);
@@ -291,7 +384,18 @@ it('set the `value` of the `<input>` to the label of the selected option when no
291
384
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
292
385
  expect(input?.value).to.equal(option?.label);
293
386
  });
294
- it('uses `placeholder` as a placeholder when `multiple` and no option is selected', async () => {
387
+ it('clears the `value` of its `<input>` when multiselect and an option is selected', async () => {
388
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
389
+ ${defaultSlot}
390
+ </glide-core-dropdown>`);
391
+ component.focus();
392
+ await sendKeys({ type: 'one' });
393
+ const option = component?.querySelector('glide-core-dropdown-option');
394
+ option?.click();
395
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
396
+ expect(input?.value).to.be.empty.string;
397
+ });
398
+ it('uses `placeholder` as a placeholder when multiselect and no option is selected', async () => {
295
399
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
296
400
  ${defaultSlot}
297
401
  </glide-core-dropdown>`);
@@ -538,7 +642,7 @@ it('cannot be tabbed to when `disabled`', async () => {
538
642
  await sendKeys({ press: 'Tab' });
539
643
  expect(document.activeElement).to.equal(document.body);
540
644
  });
541
- it('sets the `value` of the `<input>` back to the label of selected option when something else is clicked', async () => {
645
+ it('sets the `value` of its `<input>` back to the label of selected option when something other than it is clicked', async () => {
542
646
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
543
647
  ${defaultSlot}
544
648
  </glide-core-dropdown>`);
@@ -564,39 +668,6 @@ it('selects the filter text when `click()` is called', async () => {
564
668
  component.click();
565
669
  expect(window.getSelection()?.toString()).to.equal('one');
566
670
  });
567
- it('selects the filter text when the `<input>` is clicked', async () => {
568
- const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
569
- ${defaultSlot}
570
- </glide-core-dropdown>`);
571
- component.focus();
572
- await sendKeys({ type: 'one' });
573
- // Calling `click()` would be sweet. The problem is it sets `event.detail` to `0`,
574
- // which puts us in a guard in the event handler. `Event` has no `detail` property
575
- // and would work. `CustomEvent` is used for completeness and to get us as close as
576
- // possible to a real click. See the comment in the handler for more information.
577
- component.shadowRoot
578
- ?.querySelector('[data-test="input"]')
579
- ?.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: 1 }));
580
- expect(window.getSelection()?.toString()).to.equal('one');
581
- });
582
- it('selects the filter text when closed and the button is clicked', async () => {
583
- const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
584
- ${defaultSlot}
585
- </glide-core-dropdown>`);
586
- component.focus();
587
- await sendKeys({ type: 'one' });
588
- component.open = false;
589
- await elementUpdated(component);
590
- // Calling `click()` would be sweet. The problem is it sets `event.detail` to `0`,
591
- // which puts us in a guard in the event handler. `Event` has no `detail` property
592
- // and would work. `CustomEvent` is used for completeness and to get us as close as
593
- // possible to a real click. See the comment in the handler for more information.
594
- component.shadowRoot
595
- ?.querySelector('[data-test="button"]')
596
- ?.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: 1 }));
597
- await elementUpdated(component);
598
- expect(window.getSelection()?.toString()).to.equal('one');
599
- });
600
671
  it('clicks the `<input>` when `click()` is called', async () => {
601
672
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
602
673
  ${defaultSlot}
@@ -609,3 +680,28 @@ it('clicks the `<input>` when `click()` is called', async () => {
609
680
  const event = await oneEvent(button, 'click');
610
681
  expect(event instanceof PointerEvent).to.be.true;
611
682
  });
683
+ it('has no icon when filtering and an option is selected', async () => {
684
+ const component = await fixture(html `<glide-core-dropdown
685
+ label="Label"
686
+ placeholder="Placeholder"
687
+ filterable
688
+ >
689
+ <div slot="icon:one">✓</div>
690
+ <div slot="icon:two">✓</div>
691
+
692
+ <glide-core-dropdown-option
693
+ label="One"
694
+ value="one"
695
+ selected
696
+ ></glide-core-dropdown-option>
697
+
698
+ <glide-core-dropdown-option
699
+ label="Two"
700
+ value="two"
701
+ ></glide-core-dropdown-option>
702
+ </glide-core-dropdown>`);
703
+ component.focus();
704
+ await sendKeys({ type: 'one' });
705
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
706
+ expect(iconSlot).to.be.null;
707
+ });