@crowdstrike/glide-core 0.9.5 → 0.10.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 (207) 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.focus.js +4 -2
  38. package/dist/checkbox.test.interactions.js +11 -11
  39. package/dist/drawer.d.ts +2 -5
  40. package/dist/drawer.js +1 -1
  41. package/dist/drawer.test.accessibility.js +8 -8
  42. package/dist/drawer.test.basics.js +16 -16
  43. package/dist/drawer.test.closing.js +18 -16
  44. package/dist/drawer.test.events.js +13 -24
  45. package/dist/drawer.test.methods.js +22 -22
  46. package/dist/dropdown.d.ts +7 -5
  47. package/dist/dropdown.js +1 -1
  48. package/dist/dropdown.option.d.ts +1 -3
  49. package/dist/dropdown.option.js +1 -1
  50. package/dist/dropdown.option.styles.js +31 -19
  51. package/dist/dropdown.option.test.basics.js +4 -4
  52. package/dist/dropdown.styles.js +39 -3
  53. package/dist/dropdown.test.basics.js +8 -13
  54. package/dist/dropdown.test.basics.multiple.js +63 -31
  55. package/dist/dropdown.test.basics.single.js +49 -0
  56. package/dist/dropdown.test.focus.filterable.js +12 -3
  57. package/dist/dropdown.test.focus.js +18 -2
  58. package/dist/dropdown.test.interactions.filterable.js +121 -45
  59. package/dist/dropdown.test.interactions.multiple.js +71 -30
  60. package/dist/dropdown.test.interactions.single.js +26 -4
  61. package/dist/form-controls-layout.d.ts +0 -2
  62. package/dist/icon-button.d.ts +2 -4
  63. package/dist/icon-button.js +1 -1
  64. package/dist/icon-button.test.basics.js +14 -82
  65. package/dist/icon-button.test.focus.d.ts +1 -0
  66. package/dist/icon-button.test.focus.js +13 -0
  67. package/dist/input.d.ts +4 -5
  68. package/dist/input.js +1 -1
  69. package/dist/input.styles.js +4 -4
  70. package/dist/input.test.basics.js +0 -52
  71. package/dist/input.test.events.js +27 -27
  72. package/dist/input.test.focus.js +27 -26
  73. package/dist/input.test.form.js +6 -6
  74. package/dist/input.test.validity.js +130 -130
  75. package/dist/label.d.ts +1 -3
  76. package/dist/label.js +1 -1
  77. package/dist/label.styles.js +5 -6
  78. package/dist/label.test.basics.js +4 -4
  79. package/dist/library/ow.js +1 -1
  80. package/dist/menu.button.d.ts +0 -2
  81. package/dist/menu.button.test.basics.js +3 -3
  82. package/dist/menu.d.ts +1 -4
  83. package/dist/menu.js +1 -1
  84. package/dist/menu.link.d.ts +1 -2
  85. package/dist/menu.link.js +1 -1
  86. package/dist/menu.options.d.ts +0 -2
  87. package/dist/menu.test.events.js +6 -6
  88. package/dist/menu.test.focus.js +5 -18
  89. package/dist/menu.test.interactions.js +48 -24
  90. package/dist/modal.d.ts +6 -17
  91. package/dist/modal.icon-button.d.ts +0 -2
  92. package/dist/modal.icon-button.test.basics.js +3 -3
  93. package/dist/modal.js +1 -1
  94. package/dist/modal.styles.js +13 -19
  95. package/dist/modal.tertiary-icon.d.ts +0 -3
  96. package/dist/modal.tertiary-icon.test.basics.js +3 -3
  97. package/dist/modal.test.basics.js +9 -5
  98. package/dist/modal.test.close.js +2 -2
  99. package/dist/modal.test.events.js +2 -2
  100. package/dist/radio-group.d.ts +0 -3
  101. package/dist/radio-group.js +1 -1
  102. package/dist/radio-group.test.basics.js +61 -61
  103. package/dist/radio-group.test.events.js +13 -13
  104. package/dist/radio-group.test.focus.js +1 -1
  105. package/dist/radio-group.test.form.js +2 -2
  106. package/dist/radio-group.test.validity.js +12 -12
  107. package/dist/radio.d.ts +0 -3
  108. package/dist/radio.styles.js +4 -12
  109. package/dist/split-button.d.ts +8 -11
  110. package/dist/split-button.js +1 -1
  111. package/dist/split-button.primary-button.d.ts +21 -0
  112. package/dist/split-button.primary-button.js +1 -0
  113. package/dist/split-button.primary-button.styles.js +96 -0
  114. package/dist/split-button.primary-button.test.basics.d.ts +1 -0
  115. package/dist/split-button.primary-button.test.basics.js +31 -0
  116. package/dist/split-button.primary-button.test.focus.d.ts +1 -0
  117. package/dist/split-button.primary-button.test.focus.js +14 -0
  118. package/dist/split-button.primary-link.d.ts +19 -0
  119. package/dist/split-button.primary-link.js +1 -0
  120. package/dist/split-button.primary-link.test.basics.d.ts +1 -0
  121. package/dist/split-button.primary-link.test.basics.js +30 -0
  122. package/dist/split-button.primary-link.test.focus.d.ts +1 -0
  123. package/dist/split-button.primary-link.test.focus.js +15 -0
  124. package/dist/split-button.secondary-button.d.ts +25 -0
  125. package/dist/split-button.secondary-button.js +1 -0
  126. package/dist/split-button.secondary-button.styles.js +103 -0
  127. package/dist/split-button.secondary-button.test.basics.d.ts +1 -0
  128. package/dist/split-button.secondary-button.test.basics.js +58 -0
  129. package/dist/split-button.secondary-button.test.focus.d.ts +1 -0
  130. package/dist/split-button.secondary-button.test.focus.js +14 -0
  131. package/dist/split-button.secondary-button.test.interactions.d.ts +2 -0
  132. package/dist/split-button.secondary-button.test.interactions.js +30 -0
  133. package/dist/split-button.stories.d.ts +4 -3
  134. package/dist/split-button.styles.js +1 -94
  135. package/dist/split-button.test.basics.d.ts +2 -1
  136. package/dist/split-button.test.basics.js +67 -80
  137. package/dist/split-button.test.interactions.d.ts +4 -0
  138. package/dist/split-button.test.interactions.js +51 -0
  139. package/dist/styles/opacity-and-scale-animation.js +2 -6
  140. package/dist/styles/variables.css +1 -1
  141. package/dist/tab.d.ts +2 -11
  142. package/dist/tab.group.d.ts +2 -5
  143. package/dist/tab.group.styles.js +12 -15
  144. package/dist/tab.group.test.basics.js +49 -34
  145. package/dist/tab.group.test.interactions.js +17 -14
  146. package/dist/tab.panel.d.ts +0 -3
  147. package/dist/tab.test.basics.js +6 -5
  148. package/dist/tag.d.ts +6 -4
  149. package/dist/tag.js +1 -1
  150. package/dist/tag.styles.js +36 -36
  151. package/dist/tag.test.basics.js +16 -109
  152. package/dist/tag.test.events.js +12 -8
  153. package/dist/tag.test.focus.js +2 -3
  154. package/dist/tag.test.interactions.d.ts +1 -0
  155. package/dist/tag.test.interactions.js +34 -0
  156. package/dist/textarea.d.ts +2 -3
  157. package/dist/textarea.js +2 -2
  158. package/dist/textarea.test.basics.js +8 -8
  159. package/dist/textarea.test.events.js +55 -55
  160. package/dist/textarea.test.form.js +9 -9
  161. package/dist/textarea.test.validity.js +167 -135
  162. package/dist/toasts.d.ts +1 -5
  163. package/dist/toasts.test.basics.js +2 -1
  164. package/dist/toasts.toast.d.ts +1 -4
  165. package/dist/toasts.toast.js +1 -1
  166. package/dist/toasts.toast.styles.js +12 -0
  167. package/dist/toggle.d.ts +0 -2
  168. package/dist/toggle.styles.js +1 -5
  169. package/dist/toggle.test.basics.js +2 -2
  170. package/dist/toggle.test.interactions.js +7 -7
  171. package/dist/tooltip.d.ts +2 -1
  172. package/dist/tooltip.js +1 -1
  173. package/dist/tooltip.styles.js +37 -14
  174. package/dist/tooltip.test.basics.d.ts +1 -1
  175. package/dist/tooltip.test.basics.js +19 -19
  176. package/dist/tree.d.ts +0 -2
  177. package/dist/tree.item.d.ts +5 -7
  178. package/dist/tree.item.icon-button.d.ts +1 -4
  179. package/dist/tree.item.js +1 -1
  180. package/dist/tree.item.menu.d.ts +0 -2
  181. package/dist/tree.item.menu.test.basics.js +9 -9
  182. package/dist/tree.item.styles.js +4 -3
  183. package/dist/tree.item.test.basics.js +43 -31
  184. package/dist/tree.test.basics.js +29 -29
  185. package/dist/tree.test.focus.js +77 -74
  186. package/package.json +12 -14
  187. package/dist/split-container.d.ts +0 -31
  188. package/dist/split-container.js +0 -1
  189. package/dist/split-container.styles.js +0 -132
  190. package/dist/split-container.test.basics.d.ts +0 -3
  191. package/dist/split-container.test.basics.js +0 -445
  192. package/dist/split-container.test.interactions.d.ts +0 -1
  193. package/dist/split-container.test.interactions.js +0 -20
  194. package/dist/split-link.d.ts +0 -25
  195. package/dist/split-link.js +0 -1
  196. package/dist/split-link.test.basics.d.ts +0 -1
  197. package/dist/split-link.test.basics.js +0 -93
  198. package/dist/split-link.test.interactions.d.ts +0 -1
  199. package/dist/split-link.test.interactions.js +0 -20
  200. package/dist/status-indicator.d.ts +0 -30
  201. package/dist/status-indicator.js +0 -1
  202. package/dist/status-indicator.stories.d.ts +0 -5
  203. package/dist/status-indicator.styles.js +0 -58
  204. package/dist/status-indicator.test.basics.d.ts +0 -1
  205. package/dist/status-indicator.test.basics.js +0 -102
  206. /package/dist/{split-container.styles.d.ts → split-button.primary-button.styles.d.ts} +0 -0
  207. /package/dist/{status-indicator.styles.d.ts → split-button.secondary-button.styles.d.ts} +0 -0
@@ -75,37 +75,6 @@ it('has a tag when an option is initially selected', async () => {
75
75
  expect(tag?.checkVisibility()).to.be.true;
76
76
  expect(tag?.textContent?.trim()).to.equal('One');
77
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);
108
- });
109
78
  it('shows Select All', async () => {
110
79
  const component = await fixture(html `<glide-core-dropdown
111
80
  label="Label"
@@ -271,3 +240,66 @@ it('has no internal label when an option is initially selected', async () => {
271
240
  const label = component.shadowRoot?.querySelector('[data-test="internal-label"]');
272
241
  expect(label).to.not.exist;
273
242
  });
243
+ it('has a "multiselect" icon for each selected option with a value', async () => {
244
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
245
+ <div slot="icon:one">✓</div>
246
+ <div slot="icon:two">✓</div>
247
+ <div slot="icon:three">✓</div>
248
+
249
+ <glide-core-dropdown-option
250
+ label="One"
251
+ value="one"
252
+ selected
253
+ ></glide-core-dropdown-option>
254
+
255
+ <glide-core-dropdown-option
256
+ label="Two"
257
+ value="two"
258
+ selected
259
+ ></glide-core-dropdown-option>
260
+
261
+ <glide-core-dropdown-option
262
+ label="Three"
263
+ selected
264
+ ></glide-core-dropdown-option>
265
+ </glide-core-dropdown>`);
266
+ const icons = component.shadowRoot?.querySelectorAll('[data-test="multiselect-icon-slot"]');
267
+ expect(icons?.length).to.equal(2);
268
+ });
269
+ it('has no "multiselect" icons', async () => {
270
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
271
+ <div slot="icon:one">✓</div>
272
+ <div slot="icon:two">✓</div>
273
+
274
+ <glide-core-dropdown-option
275
+ label="One"
276
+ value="one"
277
+ ></glide-core-dropdown-option>
278
+
279
+ <glide-core-dropdown-option
280
+ label="Two"
281
+ value="two"
282
+ ></glide-core-dropdown-option>
283
+ </glide-core-dropdown>`);
284
+ const icons = component.shadowRoot?.querySelectorAll('[data-test="multiselect-icon-slot"]');
285
+ expect(icons?.length).to.equal(0);
286
+ });
287
+ it('has no "single-select" icon', async () => {
288
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
289
+ <div slot="icon:one">✓</div>
290
+ <div slot="icon:two">✓</div>
291
+
292
+ <glide-core-dropdown-option
293
+ label="One"
294
+ value="one"
295
+ selected
296
+ ></glide-core-dropdown-option>
297
+
298
+ <glide-core-dropdown-option
299
+ label="Two"
300
+ value="two"
301
+ ></glide-core-dropdown-option>
302
+ </glide-core-dropdown>`);
303
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
304
+ expect(iconSlot).to.be.null;
305
+ });
@@ -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
+ });
@@ -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
  });
@@ -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' });
133
154
  const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
134
155
  expect(icon?.checkVisibility()).to.be.not.ok;
135
156
  });
136
- it('hides the magnifying glass icon when an option is selected', async () => {
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' });
167
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
168
+ expect(icon?.checkVisibility()).to.be.not.ok;
169
+ });
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 () => {
@@ -231,6 +284,26 @@ it('updates `value` when an option `value` is changed programmatically', async (
231
284
  option.value = 'two';
232
285
  expect(component.value).to.deep.equal(['two']);
233
286
  });
287
+ it('sets the `value` of its `<input>` when made filterable', async () => {
288
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
289
+ <glide-core-dropdown-option
290
+ label="One"
291
+ value="one"
292
+ selected
293
+ ></glide-core-dropdown-option>
294
+
295
+ <glide-core-dropdown-option
296
+ label="Two"
297
+ value="two"
298
+ ></glide-core-dropdown-option>
299
+ </glide-core-dropdown>`);
300
+ component.filterable = true;
301
+ await elementUpdated(component);
302
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
303
+ // Wait for the `filterable` setter to do its thing.
304
+ await aTimeout(0);
305
+ expect(input?.value).to.equal('One');
306
+ });
234
307
  it('does not select options on Space', async () => {
235
308
  const component = await fixture(html `<glide-core-dropdown
236
309
  label="Label"
@@ -282,7 +355,7 @@ it('deselects all options on Meta + Backspace', async () => {
282
355
  expect(options[1].selected).to.be.false;
283
356
  expect(options[0].selected).to.be.false;
284
357
  });
285
- it('set the `value` of the `<input>` to the label of the selected option when not `multiple`', async () => {
358
+ it('sets the `value` of its `<input>` to the label of the selected option when not `multiple`', async () => {
286
359
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
287
360
  ${defaultSlot}
288
361
  </glide-core-dropdown>`);
@@ -291,7 +364,18 @@ it('set the `value` of the `<input>` to the label of the selected option when no
291
364
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
292
365
  expect(input?.value).to.equal(option?.label);
293
366
  });
294
- it('uses `placeholder` as a placeholder when `multiple` and no option is selected', async () => {
367
+ it('clears the `value` of its `<input>` when multiselect and an option is selected', async () => {
368
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
369
+ ${defaultSlot}
370
+ </glide-core-dropdown>`);
371
+ component.focus();
372
+ await sendKeys({ type: 'one' });
373
+ const option = component?.querySelector('glide-core-dropdown-option');
374
+ option?.click();
375
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
376
+ expect(input?.value).to.be.empty.string;
377
+ });
378
+ it('uses `placeholder` as a placeholder when multiselect and no option is selected', async () => {
295
379
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
296
380
  ${defaultSlot}
297
381
  </glide-core-dropdown>`);
@@ -538,7 +622,7 @@ it('cannot be tabbed to when `disabled`', async () => {
538
622
  await sendKeys({ press: 'Tab' });
539
623
  expect(document.activeElement).to.equal(document.body);
540
624
  });
541
- it('sets the `value` of the `<input>` back to the label of selected option when something else is clicked', async () => {
625
+ it('sets the `value` of its `<input>` back to the label of selected option when something other than it is clicked', async () => {
542
626
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
543
627
  ${defaultSlot}
544
628
  </glide-core-dropdown>`);
@@ -564,39 +648,6 @@ it('selects the filter text when `click()` is called', async () => {
564
648
  component.click();
565
649
  expect(window.getSelection()?.toString()).to.equal('one');
566
650
  });
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
651
  it('clicks the `<input>` when `click()` is called', async () => {
601
652
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
602
653
  ${defaultSlot}
@@ -609,3 +660,28 @@ it('clicks the `<input>` when `click()` is called', async () => {
609
660
  const event = await oneEvent(button, 'click');
610
661
  expect(event instanceof PointerEvent).to.be.true;
611
662
  });
663
+ it('has no icon when filtering and an option is selected', async () => {
664
+ const component = await fixture(html `<glide-core-dropdown
665
+ label="Label"
666
+ placeholder="Placeholder"
667
+ filterable
668
+ >
669
+ <div slot="icon:one">✓</div>
670
+ <div slot="icon:two">✓</div>
671
+
672
+ <glide-core-dropdown-option
673
+ label="One"
674
+ value="one"
675
+ selected
676
+ ></glide-core-dropdown-option>
677
+
678
+ <glide-core-dropdown-option
679
+ label="Two"
680
+ value="two"
681
+ ></glide-core-dropdown-option>
682
+ </glide-core-dropdown>`);
683
+ component.focus();
684
+ await sendKeys({ type: 'one' });
685
+ const iconSlot = component.shadowRoot?.querySelector('[data-test="single-select-icon-slot"]');
686
+ expect(iconSlot).to.be.null;
687
+ });
@@ -1,11 +1,45 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ import { LitElement } from 'lit';
2
9
  import { aTimeout, assert, elementUpdated, expect, fixture, html, oneEvent, } from '@open-wc/testing';
10
+ import { customElement } from 'lit/decorators.js';
3
11
  import { sendKeys } from '@web/test-runner-commands';
4
12
  import { sendMouse } from '@web/test-runner-commands';
5
13
  import GlideCoreDropdown from './dropdown.js';
6
14
  import GlideCoreDropdownOption from './dropdown.option.js';
15
+ let GlideCoreDropdownInAnotherComponent = class GlideCoreDropdownInAnotherComponent extends LitElement {
16
+ static { this.shadowRootOptions = {
17
+ ...LitElement.shadowRootOptions,
18
+ mode: 'closed',
19
+ }; }
20
+ render() {
21
+ return html `<glide-core-dropdown
22
+ label="Label"
23
+ placeholder="Placeholder"
24
+ multiple
25
+ open
26
+ >
27
+ <glide-core-dropdown-option
28
+ label="One"
29
+ value="one"
30
+ ></glide-core-dropdown-option>
31
+
32
+ <glide-core-dropdown-option label="Two"></glide-core-dropdown-option>
33
+ <glide-core-dropdown-option label="Three"></glide-core-dropdown-option>
34
+ </glide-core-dropdown>`;
35
+ }
36
+ };
37
+ GlideCoreDropdownInAnotherComponent = __decorate([
38
+ customElement('glide-core-dropdown-in-another-component')
39
+ ], GlideCoreDropdownInAnotherComponent);
7
40
  GlideCoreDropdown.shadowRootOptions.mode = 'open';
8
41
  GlideCoreDropdownOption.shadowRootOptions.mode = 'open';
42
+ GlideCoreDropdownInAnotherComponent.shadowRootOptions.mode = 'open';
9
43
  it('opens on click', async () => {
10
44
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
11
45
  <glide-core-dropdown-option
@@ -221,25 +255,20 @@ it('deactivates all other options on "mouseover"', async () => {
221
255
  expect(options[2]?.privateActive).to.be.false;
222
256
  });
223
257
  it('remains open when an option is selected via click', async () => {
224
- const component = await fixture(html `<glide-core-dropdown
225
- label="Label"
226
- placeholder="Placeholder"
227
- open
228
- multiple
229
- >
230
- <glide-core-dropdown-option
231
- label="One"
232
- value="one"
233
- selected
234
- ></glide-core-dropdown-option>
235
-
236
- <glide-core-dropdown-option
237
- label="Two"
238
- value="two"
239
- ></glide-core-dropdown-option>
240
- </glide-core-dropdown>`);
241
- component.querySelector('glide-core-dropdown-option')?.click();
242
- expect(component.open).to.be.true;
258
+ const component = await fixture(html `<glide-core-dropdown-in-another-component></glide-core-dropdown-in-another-component>`);
259
+ // Wait for it to open.
260
+ await aTimeout(0);
261
+ const option = component.shadowRoot?.querySelector('glide-core-dropdown-option');
262
+ assert(option);
263
+ const { x, y } = option.getBoundingClientRect();
264
+ // Calling `click()` won't do because Dropdown relies on a "mouseup" event to
265
+ // decide if it should close.
266
+ await sendMouse({
267
+ type: 'click',
268
+ position: [Math.ceil(x), Math.ceil(y)],
269
+ });
270
+ const dropdown = component.shadowRoot?.querySelector('glide-core-dropdown');
271
+ expect(dropdown?.open).to.be.true;
243
272
  });
244
273
  it('remains open when an option is selected via Enter', async () => {
245
274
  const component = await fixture(html `<glide-core-dropdown
@@ -454,7 +483,7 @@ it('updates `value` when an option is selected or deselected via Space', async (
454
483
  await sendKeys({ press: ' ' });
455
484
  expect(component.value).to.deep.equal(['one']);
456
485
  });
457
- it('updates `value` when `multiple` is changed to `true` programmatically', async () => {
486
+ it('updates `value` when multiselect is changed to `true` programmatically', async () => {
458
487
  const component = await fixture(html `<glide-core-dropdown open>
459
488
  <glide-core-dropdown-option
460
489
  label="One"
@@ -493,9 +522,9 @@ it('updates `value` when the `value` of a selected option is changed programmati
493
522
  const option = component.querySelector('glide-core-dropdown-option');
494
523
  assert(option);
495
524
  option.value = 'three';
496
- expect(component.value).to.deep.equal(['two', 'three']);
525
+ expect(component.value).to.deep.equal(['three', 'two']);
497
526
  });
498
- it('updates `value` when the `value` of a selected option is removed programmatically', async () => {
527
+ it('updates `value` when the `value` of a selected option is emptied programmatically', async () => {
499
528
  const component = await fixture(html `<glide-core-dropdown open multiple>
500
529
  <glide-core-dropdown-option
501
530
  label="One"
@@ -538,8 +567,13 @@ it('has no internal label when an option is newly selected', async () => {
538
567
  const label = component.shadowRoot?.querySelector('[data-test="internal-label"]');
539
568
  expect(label).to.not.exist;
540
569
  });
541
- it('only has so many tags when many options are selected', async () => {
542
- const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
570
+ it('hides tags to prevent overflow', async () => {
571
+ const component = await fixture(html `<glide-core-dropdown
572
+ label="Label"
573
+ placeholder="Placeholder"
574
+ multiple
575
+ style="display: block; max-width: 20rem;"
576
+ >
543
577
  <glide-core-dropdown-option
544
578
  label="One"
545
579
  value="one"
@@ -565,14 +599,20 @@ it('only has so many tags when many options are selected', async () => {
565
599
  options[1].selected = true;
566
600
  options[2].selected = true;
567
601
  options[3].selected = true;
568
- await elementUpdated(component);
602
+ // Wait for the Resize Observer to do its thing.
603
+ await aTimeout(0);
569
604
  const tagContainers = [
570
605
  ...(component.shadowRoot?.querySelectorAll('[data-test="tag-container"]') ?? []),
571
606
  ].filter((element) => element.checkVisibility());
572
- expect(tagContainers?.length).to.equal(3);
607
+ expect(tagContainers?.length).to.equal(2);
573
608
  });
574
- it('has overflow text when many options are selected', async () => {
575
- const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
609
+ it('has overflow text when its tags are overflowing', async () => {
610
+ const component = await fixture(html `<glide-core-dropdown
611
+ label="Label"
612
+ placeholder="Placeholder"
613
+ multiple
614
+ style="display: block; max-width: 20rem;"
615
+ >
576
616
  <glide-core-dropdown-option
577
617
  label="One"
578
618
  value="one"
@@ -598,9 +638,10 @@ it('has overflow text when many options are selected', async () => {
598
638
  options[1].selected = true;
599
639
  options[2].selected = true;
600
640
  options[3].selected = true;
601
- await elementUpdated(component);
641
+ // Wait for the Resize Observer to do its thing.
642
+ await aTimeout(0);
602
643
  const tagOverflow = component.shadowRoot?.querySelector('[data-test="tag-overflow-count"]');
603
- expect(tagOverflow?.textContent?.trim()).to.equal('1');
644
+ expect(tagOverflow?.textContent?.trim()).to.equal('2');
604
645
  });
605
646
  it('deselects the option when its tag is removed', async () => {
606
647
  const component = await fixture(html `<glide-core-dropdown