@crowdstrike/glide-core 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/accordion.styles.js +2 -4
  2. package/dist/button-group.button.styles.js +3 -8
  3. package/dist/button-group.styles.js +2 -2
  4. package/dist/button.d.ts +1 -0
  5. package/dist/button.js +1 -1
  6. package/dist/button.styles.js +2 -4
  7. package/dist/button.test.events.js +86 -10
  8. package/dist/checkbox.js +1 -1
  9. package/dist/checkbox.styles.js +43 -6
  10. package/dist/checkbox.test.form.js +16 -0
  11. package/dist/checkbox.test.interactions.js +8 -0
  12. package/dist/drawer.js +1 -1
  13. package/dist/dropdown.d.ts +4 -2
  14. package/dist/dropdown.js +1 -1
  15. package/dist/dropdown.option.js +1 -1
  16. package/dist/dropdown.option.styles.js +1 -0
  17. package/dist/dropdown.styles.js +47 -26
  18. package/dist/dropdown.test.focus.filterable.js +20 -0
  19. package/dist/dropdown.test.focus.js +1 -0
  20. package/dist/dropdown.test.form.js +23 -112
  21. package/dist/dropdown.test.interactions.filterable.js +121 -17
  22. package/dist/dropdown.test.interactions.multiple.js +15 -22
  23. package/dist/dropdown.test.interactions.single.js +44 -22
  24. package/dist/icon-button.styles.js +2 -4
  25. package/dist/icons/checked.d.ts +5 -0
  26. package/dist/icons/checked.js +1 -1
  27. package/dist/input.d.ts +1 -1
  28. package/dist/input.js +1 -1
  29. package/dist/input.stories.d.ts +0 -4
  30. package/dist/input.styles.d.ts +1 -1
  31. package/dist/input.styles.js +93 -93
  32. package/dist/input.test.basics.js +45 -45
  33. package/dist/input.test.form.js +17 -0
  34. package/dist/label.styles.js +6 -11
  35. package/dist/library/localize.test.js +45 -0
  36. package/dist/menu.button.styles.js +1 -0
  37. package/dist/menu.js +1 -1
  38. package/dist/menu.link.styles.js +1 -0
  39. package/dist/menu.styles.js +3 -1
  40. package/dist/menu.test.events.js +1 -97
  41. package/dist/menu.test.focus.js +26 -3
  42. package/dist/menu.test.interactions.js +3 -0
  43. package/dist/modal.d.ts +0 -7
  44. package/dist/modal.icon-button.test.basics.js +9 -9
  45. package/dist/modal.styles.js +2 -4
  46. package/dist/modal.tertiary-icon.test.basics.js +14 -14
  47. package/dist/modal.test.accessibility.js +16 -27
  48. package/dist/modal.test.basics.js +64 -68
  49. package/dist/modal.test.close.js +12 -16
  50. package/dist/modal.test.events.js +32 -44
  51. package/dist/modal.test.lock-scroll.js +15 -25
  52. package/dist/modal.test.methods.js +8 -12
  53. package/dist/modal.test.scrollbars.js +2 -4
  54. package/dist/radio-group.js +1 -1
  55. package/dist/radio-group.test.basics.js +3 -3
  56. package/dist/radio-group.test.events.js +6 -6
  57. package/dist/radio-group.test.form.js +19 -0
  58. package/dist/radio.styles.js +2 -6
  59. package/dist/split-button.styles.js +2 -4
  60. package/dist/split-container.styles.js +2 -4
  61. package/dist/styles/focus-outline.d.ts +1 -1
  62. package/dist/styles/focus-outline.js +7 -1
  63. package/dist/styles/menu-opening-animation.d.ts +2 -0
  64. package/dist/styles/menu-opening-animation.js +26 -0
  65. package/dist/styles/variables.css +1 -1
  66. package/dist/styles/visually-hidden.d.ts +1 -1
  67. package/dist/styles/visually-hidden.js +14 -1
  68. package/dist/tab.group.d.ts +6 -6
  69. package/dist/tab.group.js +1 -1
  70. package/dist/tab.group.styles.js +46 -5
  71. package/dist/tab.group.test.basics.js +9 -2
  72. package/dist/tab.group.test.interactions.js +70 -93
  73. package/dist/tab.js +1 -1
  74. package/dist/tab.panel.styles.js +3 -9
  75. package/dist/tab.styles.js +6 -13
  76. package/dist/tab.test.basics.js +15 -17
  77. package/dist/tag.js +1 -1
  78. package/dist/tag.styles.js +2 -4
  79. package/dist/tag.test.basics.js +28 -27
  80. package/dist/tag.test.events.js +3 -3
  81. package/dist/tag.test.focus.js +4 -4
  82. package/dist/textarea.d.ts +1 -1
  83. package/dist/textarea.stories.d.ts +0 -4
  84. package/dist/textarea.styles.d.ts +1 -1
  85. package/dist/textarea.styles.js +63 -67
  86. package/dist/textarea.test.basics.js +52 -52
  87. package/dist/toasts.d.ts +5 -0
  88. package/dist/toasts.styles.js +1 -1
  89. package/dist/toggle.styles.js +2 -1
  90. package/dist/toggle.test.interactions.js +11 -0
  91. package/dist/tooltip.js +1 -1
  92. package/dist/tooltip.styles.js +22 -18
  93. package/dist/tooltip.test.interactions.js +6 -6
  94. package/dist/translations/en.js +1 -1
  95. package/dist/translations/fr.d.ts +3 -1
  96. package/dist/translations/fr.js +1 -1
  97. package/dist/translations/ja.d.ts +3 -1
  98. package/dist/translations/ja.js +1 -1
  99. package/dist/tree.item.styles.js +11 -3
  100. package/dist/tree.item.test.basics.js +0 -30
  101. package/package.json +1 -1
  102. package/dist/button.test.form.d.ts +0 -1
  103. package/dist/button.test.form.js +0 -50
  104. package/dist/input.test.translations.js +0 -38
  105. package/dist/tag.test.translations.d.ts +0 -1
  106. package/dist/tag.test.translations.js +0 -25
  107. package/dist/textarea.test.translations.d.ts +0 -1
  108. package/dist/textarea.test.translations.js +0 -34
  109. /package/dist/{input.test.translations.d.ts → library/localize.test.d.ts} +0 -0
@@ -1,14 +1,13 @@
1
- import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import visuallyHidden from"./styles/visually-hidden.js";export default[css`
1
+ import{css}from"lit";import menuOpeningAnimation from"./styles/menu-opening-animation.js";import visuallyHidden from"./styles/visually-hidden.js";export default[css`
2
+ ${menuOpeningAnimation(".options:popover-open")}
3
+ ${visuallyHidden(".selected-option-labels")}
4
+ `,css`
2
5
  .component {
3
6
  --min-inline-size: 9.375rem;
4
7
 
5
8
  font-family: var(--glide-core-font-sans);
6
9
  }
7
10
 
8
- .selected-option-labels {
9
- ${visuallyHidden};
10
- }
11
-
12
11
  .dropdown-and-options {
13
12
  display: flex;
14
13
  position: relative;
@@ -20,7 +19,7 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
20
19
  align-items: center;
21
20
  background-color: var(--glide-core-surface-base-lighter);
22
21
  block-size: 2.125rem;
23
- border: 1px solid var(--glide-core-border-base-lighter);
22
+ border: 1px solid var(--glide-core-border-base);
24
23
  border-radius: var(--glide-core-spacing-xs);
25
24
  box-sizing: border-box;
26
25
  color: var(--glide-core-text-body-1);
@@ -34,12 +33,17 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
34
33
  min-inline-size: var(--min-inline-size);
35
34
  padding-inline: var(--glide-core-spacing-sm);
36
35
  text-align: start;
36
+ transition:
37
+ background-color 200ms ease-in-out,
38
+ border-color 200ms ease-in-out;
37
39
  user-select: none;
38
40
  white-space: nowrap;
39
41
 
40
42
  &.quiet:not(.multiple) {
41
43
  background-color: transparent;
42
- border: unset;
44
+ block-size: 1.5rem;
45
+ border-color: transparent;
46
+ border-radius: var(--glide-core-border-radius-round);
43
47
  font-size: var(--glide-core-heading-xxxs-font-size);
44
48
  font-style: var(--glide-core-heading-xxxs-font-style);
45
49
  font-weight: var(--glide-core-heading-xxxs-font-weight);
@@ -51,6 +55,7 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
51
55
 
52
56
  &.disabled {
53
57
  background: var(--glide-core-surface-disabled);
58
+ border-color: var(--glide-core-border-base-light);
54
59
  color: var(--glide-core-text-tertiary-disabled);
55
60
  }
56
61
 
@@ -64,16 +69,31 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
64
69
  padding-inline-start: 0;
65
70
  }
66
71
 
67
- &:has(.button:focus-visible, .input:focus-visible) {
68
- ${focusOutline};
72
+ &.quiet {
73
+ &:is(:hover, :has(.button:focus-visible, .input:focus-visible)):not(
74
+ &.error,
75
+ &.disabled,
76
+ &.multiple,
77
+ &.readonly
78
+ ) {
79
+ background-color: var(--glide-core-surface-hover);
80
+ color: var(--glide-core-text-body-1);
81
+ }
69
82
  }
70
83
 
71
- &:hover:not(&.error, &.disabled, &.readonly) {
72
- border-color: var(--glide-core-border-base);
84
+ &:is(:hover, :has(.button:focus-visible, .input:focus-visible)):not(
85
+ &.disabled,
86
+ &.error,
87
+ &.quiet,
88
+ &.readonly
89
+ ) {
90
+ border-color: var(--glide-core-border-focus);
73
91
  }
74
92
 
75
- &.quiet:hover:not(&.error, &.disabled, &.multiple, &.readonly) {
76
- background-color: var(--glide-core-surface-hover);
93
+ &:has(.button:focus-visible, .input:focus-visible) {
94
+ &.quiet {
95
+ border-color: var(--glide-core-border-focus);
96
+ }
77
97
  }
78
98
  }
79
99
 
@@ -97,6 +117,10 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
97
117
  padding: var(--padding);
98
118
  position: absolute;
99
119
  scroll-behavior: smooth;
120
+
121
+ &.hidden {
122
+ display: none;
123
+ }
100
124
  }
101
125
 
102
126
  .select-all {
@@ -111,6 +135,16 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
111
135
 
112
136
  .placeholder {
113
137
  color: var(--glide-core-text-placeholder);
138
+
139
+ &.quiet {
140
+ &:not(.disabled) {
141
+ color: var(--glide-core-text-body-1);
142
+ }
143
+ }
144
+
145
+ &.disabled {
146
+ color: var(--glide-core-text-tertiary-disabled);
147
+ }
114
148
  }
115
149
 
116
150
  .tags {
@@ -167,21 +201,8 @@ import{css}from"lit";import focusOutline from"./styles/focus-outline.js";import
167
201
  outline: none;
168
202
  }
169
203
 
170
- &.single.selection:not(:focus) {
171
- &::placeholder {
172
- color: inherit;
173
- }
174
- }
175
-
176
204
  &::placeholder {
177
205
  font-family: var(--glide-core-font-sans);
178
206
  }
179
207
  }
180
-
181
- .caret-icon {
182
- &.disabled,
183
- &.readonly {
184
- color: var(--glide-core-surface-selected-disabled);
185
- }
186
- }
187
208
  `];
@@ -1,5 +1,6 @@
1
1
  import './dropdown.option.js';
2
2
  import { aTimeout, assert, expect, fixture, html } from '@open-wc/testing';
3
+ import { sendKeys } from '@web/test-runner-commands';
3
4
  import { sendMouse } from '@web/test-runner-commands';
4
5
  import GlideCoreDropdown from './dropdown.js';
5
6
  import GlideCoreDropdownOption from './dropdown.option.js';
@@ -91,6 +92,7 @@ it('retains focus on the input when an option is selected via click', async () =
91
92
  position: [Math.ceil(x), Math.ceil(y)],
92
93
  });
93
94
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
95
+ assert(component.shadowRoot?.activeElement);
94
96
  expect(component.shadowRoot?.activeElement).to.equal(input);
95
97
  });
96
98
  it('retains focus on the the input when an option is selected via Enter', async () => {
@@ -156,3 +158,21 @@ it('does not focus the input when `checkValidity` is called', async () => {
156
158
  component.checkValidity();
157
159
  expect(component.shadowRoot?.activeElement).to.equal(null);
158
160
  });
161
+ it('sets the `value` of the `<input>` to the selected option when focus is lost', async () => {
162
+ document.body.tabIndex = -1;
163
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
164
+ ${defaultSlot}
165
+ </glide-core-dropdown>`);
166
+ // Wait for it to open.
167
+ await aTimeout(0);
168
+ const option = component.querySelector('glide-core-dropdown-option');
169
+ assert(option);
170
+ option.selected = true;
171
+ // Now type something other than "One" so we can check that it's reverted
172
+ // back to "One" when focus is lost.
173
+ component.focus();
174
+ await sendKeys({ type: 'o' });
175
+ document.body.focus();
176
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
177
+ expect(input?.value).to.equal('One');
178
+ });
@@ -30,6 +30,7 @@ it('closes and reports validity when it loses focus', async () => {
30
30
  // Move focus to the button.
31
31
  await sendKeys({ down: 'Shift' });
32
32
  await sendKeys({ press: 'Tab' });
33
+ await sendKeys({ up: 'Shift' });
33
34
  expect(component.open).to.be.false;
34
35
  expect(component.shadowRoot?.activeElement).to.equal(null);
35
36
  expect(component.validity.valid).to.equal(false);
@@ -1,17 +1,16 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
2
  import './dropdown.option.js';
3
- import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
3
+ import { expect, fixture, html } from '@open-wc/testing';
4
+ import { sendKeys } from '@web/test-runner-commands';
4
5
  import GlideCoreDropdown from './dropdown.js';
5
6
  import GlideCoreDropdownOption from './dropdown.option.js';
7
+ import sinon from 'sinon';
6
8
  GlideCoreDropdown.shadowRootOptions.mode = 'open';
7
9
  GlideCoreDropdownOption.shadowRootOptions.mode = 'open';
8
10
  it('exposes standard form control properties and methods', async () => {
9
11
  const form = document.createElement('form');
10
12
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
11
- <glide-core-dropdown-option
12
- label="Label"
13
- value="value"
14
- ></glide-core-dropdown-option>
13
+ <glide-core-dropdown-option label="Label"></glide-core-dropdown-option>
15
14
  </glide-core-dropdown>`, { parentNode: form });
16
15
  expect(component.form).to.equal(form);
17
16
  expect(component.validity instanceof ValidityState).to.be.true;
@@ -19,123 +18,35 @@ it('exposes standard form control properties and methods', async () => {
19
18
  expect(component.checkValidity).to.be.a('function');
20
19
  expect(component.reportValidity).to.be.a('function');
21
20
  });
22
- it('can be reset', async () => {
21
+ it('submits its form on Enter when closed', async () => {
23
22
  const form = document.createElement('form');
24
23
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
25
- <glide-core-dropdown-option
26
- label="One"
27
- value="one"
28
- ></glide-core-dropdown-option>
29
-
30
- <glide-core-dropdown-option
31
- label="Two"
32
- value="two"
33
- ></glide-core-dropdown-option>
24
+ <glide-core-dropdown-option label="Label"></glide-core-dropdown-option>
34
25
  </glide-core-dropdown>`, {
35
26
  parentNode: form,
36
27
  });
37
- component
38
- .querySelector('glide-core-dropdown-option')
39
- ?.shadowRoot?.querySelector('[data-test="component"]')
40
- ?.dispatchEvent(new Event('click'));
41
- form.reset();
42
- await elementUpdated(component);
43
- const label = component.shadowRoot?.querySelector('[data-test="internal-label"]');
44
- expect(label?.textContent?.trim()).to.equal('Placeholder');
45
- expect(component.value).to.deep.equal([]);
46
- });
47
- it('can be reset to the initially selected option', async () => {
48
- const form = document.createElement('form');
49
- const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
50
- <glide-core-dropdown-option
51
- label="One"
52
- value="one"
53
- ></glide-core-dropdown-option>
54
-
55
- <glide-core-dropdown-option
56
- label="Two"
57
- value="two"
58
- selected
59
- ></glide-core-dropdown-option>
60
- </glide-core-dropdown>`, {
61
- parentNode: form,
62
- });
63
- component
64
- .querySelector('glide-core-dropdown-option')
65
- ?.shadowRoot?.querySelector('[data-test="component"]')
66
- ?.dispatchEvent(new Event('click'));
67
- form.reset();
68
- expect(component.value).to.deep.equal(['two']);
69
- });
70
- it('has `formData` value when an option is selected', async () => {
71
- const form = document.createElement('form');
72
- await fixture(html `<glide-core-dropdown
73
- label="Label"
74
- placeholder="Placeholder"
75
- name="name"
76
- >
77
- <glide-core-dropdown-option
78
- label="Label"
79
- value="value"
80
- selected
81
- ></glide-core-dropdown-option>
82
- </glide-core-dropdown>
83
- >`, {
84
- parentNode: form,
28
+ const spy = sinon.spy();
29
+ form.addEventListener('submit', (event) => {
30
+ event.preventDefault();
31
+ spy();
85
32
  });
86
- const formData = new FormData(form);
87
- expect(formData.get('name')).to.be.equal('["value"]');
33
+ component.focus();
34
+ await sendKeys({ press: 'Enter' });
35
+ expect(spy.callCount).to.equal(1);
88
36
  });
89
- it('has no `formData` value when no option is selected', async () => {
37
+ it('does not submit its form on Enter when open', async () => {
90
38
  const form = document.createElement('form');
91
- await fixture(html `<glide-core-dropdown
92
- label="Label"
93
- placeholder="Placeholder"
94
- name="name"
95
- >
96
- <glide-core-dropdown-option
97
- label=""
98
- value="value"
99
- ></glide-core-dropdown-option>
39
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
40
+ <glide-core-dropdown-option label="Label"></glide-core-dropdown-option>
100
41
  </glide-core-dropdown>`, {
101
42
  parentNode: form,
102
43
  });
103
- const formData = new FormData(form);
104
- expect(formData.get('name')).to.be.null;
105
- });
106
- it('has no `formData` value when disabled and an option is selected', async () => {
107
- const form = document.createElement('form');
108
- await fixture(html `<glide-core-dropdown
109
- label="Label"
110
- placeholder="Placeholder"
111
- name="name"
112
- disabled
113
- >
114
- <glide-core-dropdown-option
115
- label="Label"
116
- value="value"
117
- selected
118
- ></glide-core-dropdown-option>
119
- </glide-core-dropdown>`, {
120
- parentNode: form,
121
- });
122
- const formData = new FormData(form);
123
- expect(formData.get('name')).to.be.null;
124
- });
125
- it('has no `formData` value when an option is selected that has no `value`', async () => {
126
- const form = document.createElement('form');
127
- await fixture(html `<glide-core-dropdown
128
- label="Label"
129
- placeholder="Placeholder"
130
- name="name"
131
- >
132
- <glide-core-dropdown-option
133
- label="Label"
134
- selected
135
- ></glide-core-dropdown-option>
136
- </glide-core-dropdown>`, {
137
- parentNode: form,
44
+ const spy = sinon.spy();
45
+ form.addEventListener('submit', (event) => {
46
+ event.preventDefault();
47
+ spy();
138
48
  });
139
- const formData = new FormData(form);
140
- expect(formData.get('name')).to.be.null;
49
+ component.focus();
50
+ await sendKeys({ press: 'Enter' });
51
+ expect(spy.callCount).to.equal(0);
141
52
  });
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import { aTimeout, assert, elementUpdated, expect, fixture, html, } from '@open-wc/testing';
2
+ import { aTimeout, assert, elementUpdated, expect, fixture, html, oneEvent, } from '@open-wc/testing';
3
3
  import { sendKeys } from '@web/test-runner-commands';
4
4
  import GlideCoreDropdown from './dropdown.js';
5
5
  import GlideCoreDropdownOption from './dropdown.option.js';
@@ -97,15 +97,12 @@ it('unfilters when an option is selected via click', async () => {
97
97
  await aTimeout(0);
98
98
  component.focus();
99
99
  await sendKeys({ type: ' one ' });
100
- const option = [
101
- ...component.querySelectorAll('glide-core-dropdown-option'),
102
- ].find(({ hidden }) => !hidden);
103
- option?.click();
104
- const input = component.shadowRoot?.querySelector('[data-test="input"]');
100
+ [...component.querySelectorAll('glide-core-dropdown-option')]
101
+ .find(({ hidden }) => !hidden)
102
+ ?.click();
105
103
  const options = [
106
104
  ...component.querySelectorAll('glide-core-dropdown-option'),
107
105
  ].filter(({ hidden }) => !hidden);
108
- expect(input?.value).to.equal('');
109
106
  expect(options.length).to.equal(11);
110
107
  });
111
108
  it('unfilters when an option is selected via Enter', async () => {
@@ -115,11 +112,9 @@ it('unfilters when an option is selected via Enter', async () => {
115
112
  component.focus();
116
113
  await sendKeys({ type: ' one ' });
117
114
  await sendKeys({ press: 'Enter' });
118
- const input = component.shadowRoot?.querySelector('[data-test="input"]');
119
115
  const options = [
120
116
  ...component.querySelectorAll('glide-core-dropdown-option'),
121
117
  ].filter(({ hidden }) => !hidden);
122
- expect(input?.value).to.equal('');
123
118
  expect(options.length).to.equal(11);
124
119
  });
125
120
  it('shows the magnifying glass icon when there is a filter term', async () => {
@@ -154,6 +149,22 @@ it('hides the magnifying glass icon when an option is selected', async () => {
154
149
  await elementUpdated(component);
155
150
  expect(icon?.checkVisibility()).to.be.not.ok;
156
151
  });
152
+ it('hides the magnifying glass icon when closed programmatically and an option is selected', async () => {
153
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
154
+ ${defaultSlot}
155
+ </glide-core-dropdown>`);
156
+ // Wait for it to open.
157
+ await aTimeout(0);
158
+ const option = component.querySelector('glide-core-dropdown-option');
159
+ assert(option);
160
+ option.selected = true;
161
+ component.focus();
162
+ await sendKeys({ type: 'two' });
163
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
164
+ component.open = false;
165
+ await elementUpdated(component);
166
+ expect(icon?.checkVisibility()).to.be.not.ok;
167
+ });
157
168
  it('does not filter on only whitespace', async () => {
158
169
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
159
170
  ${defaultSlot}
@@ -234,31 +245,53 @@ it('does not select options on Space', async () => {
234
245
  await sendKeys({ press: ' ' });
235
246
  expect(options[0]?.selected).to.be.false;
236
247
  });
237
- it('deselects options on Backspace', async () => {
248
+ it('deselects the last selected option on Backspace', async () => {
238
249
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
239
250
  ${defaultSlot}
240
251
  </glide-core-dropdown>`);
241
252
  const options = component.querySelectorAll('glide-core-dropdown-option');
242
253
  options[0].selected = true;
243
254
  options[1].selected = true;
255
+ await elementUpdated(component);
244
256
  component.focus();
257
+ component.shadowRoot
258
+ ?.querySelector('[data-test="input"]')
259
+ ?.setSelectionRange(0, 0);
260
+ await aTimeout(0);
245
261
  await sendKeys({ press: 'Backspace' });
246
262
  expect(options[1].selected).to.be.false;
247
263
  expect(options[0].selected).to.be.true;
248
264
  await sendKeys({ press: 'Backspace' });
249
265
  expect(options[0].selected).to.be.false;
250
266
  });
251
- it('uses the label of the selected option as a placeholder when not `multiple`', async () => {
267
+ it('deselects all options on Meta + Backspace', async () => {
268
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
269
+ ${defaultSlot}
270
+ </glide-core-dropdown>`);
271
+ const options = component.querySelectorAll('glide-core-dropdown-option');
272
+ options[0].selected = true;
273
+ options[1].selected = true;
274
+ await elementUpdated(component);
275
+ component.focus();
276
+ component.shadowRoot
277
+ ?.querySelector('[data-test="input"]')
278
+ ?.setSelectionRange(0, 0);
279
+ await sendKeys({ down: 'Meta' });
280
+ await sendKeys({ press: 'Backspace' });
281
+ await sendKeys({ up: 'Meta' });
282
+ expect(options[1].selected).to.be.false;
283
+ expect(options[0].selected).to.be.false;
284
+ });
285
+ it('set the `value` of the `<input>` to the label of the selected option when not `multiple`', async () => {
252
286
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
253
287
  ${defaultSlot}
254
288
  </glide-core-dropdown>`);
255
289
  const option = component?.querySelector('glide-core-dropdown-option');
256
290
  option?.click();
257
- await elementUpdated(component);
258
291
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
259
- expect(input?.placeholder).to.equal(option?.label);
292
+ expect(input?.value).to.equal(option?.label);
260
293
  });
261
- it('uses `placeholder` as a placeholder when `multiple` and an option is selected', async () => {
294
+ it('uses `placeholder` as a placeholder when `multiple` and no option is selected', async () => {
262
295
  const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
263
296
  ${defaultSlot}
264
297
  </glide-core-dropdown>`);
@@ -286,10 +319,10 @@ it('sets `aria-activedescendant` on ArrowDown', async () => {
286
319
  </glide-core-dropdown>`);
287
320
  // Wait for it to open.
288
321
  await aTimeout(0);
289
- const options = component.querySelectorAll('glide-core-dropdown-option');
290
- options[0]?.focus();
322
+ component.focus();
291
323
  await sendKeys({ press: 'ArrowDown' });
292
324
  const input = component.shadowRoot?.querySelector('[data-test="input"]');
325
+ const options = component.querySelectorAll('glide-core-dropdown-option');
293
326
  expect(input?.getAttribute('aria-activedescendant')).to.equal(options[1].id);
294
327
  });
295
328
  it('sets `aria-activedescendant` on ArrowUp', async () => {
@@ -502,6 +535,77 @@ it('cannot be tabbed to when `disabled`', async () => {
502
535
  >
503
536
  ${defaultSlot}
504
537
  </glide-core-dropdown>`);
505
- await sendKeys({ down: 'Tab' });
538
+ await sendKeys({ press: 'Tab' });
506
539
  expect(document.activeElement).to.equal(document.body);
507
540
  });
541
+ it('sets the `value` of the `<input>` back to the label of selected option when something else is clicked', async () => {
542
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
543
+ ${defaultSlot}
544
+ </glide-core-dropdown>`);
545
+ // Wait for it to open.
546
+ await aTimeout(0);
547
+ const option = component.querySelector('glide-core-dropdown-option');
548
+ assert(option);
549
+ option.selected = true;
550
+ // Now type something other than "One" so we can check that it's reverted
551
+ // back to "One" when something else is clicked.
552
+ component.focus();
553
+ await sendKeys({ type: 'o' });
554
+ document.body.click();
555
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
556
+ expect(input?.value).to.equal('One');
557
+ });
558
+ it('selects the filter text when `click()` is called', async () => {
559
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
560
+ ${defaultSlot}
561
+ </glide-core-dropdown>`);
562
+ component.focus();
563
+ await sendKeys({ type: 'one' });
564
+ component.click();
565
+ expect(window.getSelection()?.toString()).to.equal('one');
566
+ });
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
+ it('clicks the `<input>` when `click()` is called', async () => {
601
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
602
+ ${defaultSlot}
603
+ </glide-core-dropdown>`);
604
+ const button = component.shadowRoot?.querySelector('[data-test="input"]');
605
+ assert(button);
606
+ setTimeout(() => {
607
+ component.click();
608
+ });
609
+ const event = await oneEvent(button, 'click');
610
+ expect(event instanceof PointerEvent).to.be.true;
611
+ });
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import { aTimeout, assert, elementUpdated, expect, fixture, html, } from '@open-wc/testing';
2
+ import { aTimeout, assert, elementUpdated, expect, fixture, html, oneEvent, } from '@open-wc/testing';
3
3
  import { sendKeys } from '@web/test-runner-commands';
4
4
  import { sendMouse } from '@web/test-runner-commands';
5
5
  import GlideCoreDropdown from './dropdown.js';
@@ -1056,11 +1056,11 @@ it('remains open when a tag is clicked', async () => {
1056
1056
  await elementUpdated(component);
1057
1057
  expect(component.open).to.be.true;
1058
1058
  });
1059
- it('does not select an option on Enter when the option is not focused', async () => {
1060
- const component = await fixture(html `<glide-core-dropdown
1059
+ it('cannot be tabbed to when `disabled`', async () => {
1060
+ await fixture(html `<glide-core-dropdown
1061
1061
  label="Label"
1062
1062
  placeholder="Placeholder"
1063
- open
1063
+ disabled
1064
1064
  multiple
1065
1065
  >
1066
1066
  <glide-core-dropdown-option
@@ -1073,23 +1073,11 @@ it('does not select an option on Enter when the option is not focused', async ()
1073
1073
  value="two"
1074
1074
  ></glide-core-dropdown-option>
1075
1075
  </glide-core-dropdown>`);
1076
- // Wait for it to open.
1077
- await aTimeout(0);
1078
- const option = component.querySelector('glide-core-dropdown-option');
1079
- option?.focus();
1080
- await sendKeys({ down: 'Tab' });
1081
- await sendKeys({ down: 'Shift' });
1082
- await sendKeys({ up: 'Tab' });
1083
- await sendKeys({ press: 'Enter' });
1084
- expect(option?.selected).to.be.false;
1076
+ await sendKeys({ press: 'Tab' });
1077
+ expect(document.activeElement).to.equal(document.body);
1085
1078
  });
1086
- it('cannot be tabbed to when `disabled`', async () => {
1087
- await fixture(html `<glide-core-dropdown
1088
- label="Label"
1089
- placeholder="Placeholder"
1090
- disabled
1091
- multiple
1092
- >
1079
+ it('clicks the button when `click()` is called', async () => {
1080
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
1093
1081
  <glide-core-dropdown-option
1094
1082
  label="One"
1095
1083
  value="one"
@@ -1100,6 +1088,11 @@ it('cannot be tabbed to when `disabled`', async () => {
1100
1088
  value="two"
1101
1089
  ></glide-core-dropdown-option>
1102
1090
  </glide-core-dropdown>`);
1103
- await sendKeys({ down: 'Tab' });
1104
- expect(document.activeElement).to.equal(document.body);
1091
+ const button = component.shadowRoot?.querySelector('[data-test="button"]');
1092
+ assert(button);
1093
+ setTimeout(() => {
1094
+ component.click();
1095
+ });
1096
+ const event = await oneEvent(button, 'click');
1097
+ expect(event instanceof PointerEvent).to.be.true;
1105
1098
  });