@crowdstrike/glide-core 0.29.2 → 0.30.1

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 (122) hide show
  1. package/dist/accordion.js +240 -1
  2. package/dist/accordion.styles.js +13 -7
  3. package/dist/button-group.button.js +143 -1
  4. package/dist/button-group.button.styles.js +43 -15
  5. package/dist/button-group.js +249 -1
  6. package/dist/button-group.styles.js +10 -5
  7. package/dist/button.js +206 -1
  8. package/dist/button.styles.js +12 -7
  9. package/dist/checkbox-group.js +479 -14
  10. package/dist/checkbox-group.styles.js +5 -2
  11. package/dist/checkbox.js +519 -32
  12. package/dist/checkbox.styles.js +10 -5
  13. package/dist/drawer.js +168 -1
  14. package/dist/drawer.styles.js +5 -2
  15. package/dist/dropdown.js +2423 -123
  16. package/dist/dropdown.option.js +536 -1
  17. package/dist/dropdown.option.styles.js +5 -2
  18. package/dist/dropdown.styles.js +15 -8
  19. package/dist/form-controls-layout.js +102 -1
  20. package/dist/form-controls-layout.styles.js +5 -2
  21. package/dist/icon-button.js +139 -1
  22. package/dist/icon-button.styles.js +19 -7
  23. package/dist/icons/checked.js +28 -1
  24. package/dist/icons/chevron.js +21 -1
  25. package/dist/icons/magnifying-glass.js +23 -1
  26. package/dist/icons/pencil.js +21 -1
  27. package/dist/icons/severity-critical.js +20 -1
  28. package/dist/icons/severity-informational.js +20 -1
  29. package/dist/icons/severity-medium.js +20 -1
  30. package/dist/icons/x.js +21 -1
  31. package/dist/inline-alert.js +118 -1
  32. package/dist/inline-alert.styles.js +5 -2
  33. package/dist/input.d.ts +8 -2
  34. package/dist/input.js +505 -41
  35. package/dist/input.styles.js +25 -4
  36. package/dist/label.js +303 -1
  37. package/dist/label.styles.js +11 -5
  38. package/dist/library/assert-slot.js +136 -1
  39. package/dist/library/expect-unhandled-rejection.js +14 -1
  40. package/dist/library/expect-window-error.js +26 -1
  41. package/dist/library/final.js +18 -1
  42. package/dist/library/form-control.js +1 -1
  43. package/dist/library/localize.js +10 -1
  44. package/dist/library/mouse.js +35 -1
  45. package/dist/library/on-resize.js +24 -1
  46. package/dist/library/required.js +35 -1
  47. package/dist/library/shadow-root-mode.js +4 -1
  48. package/dist/library/unique-id.js +3 -1
  49. package/dist/link.js +92 -1
  50. package/dist/link.styles.js +10 -5
  51. package/dist/menu.d.ts +3 -2
  52. package/dist/menu.js +1259 -1
  53. package/dist/menu.styles.js +35 -19
  54. package/dist/modal.d.ts +4 -0
  55. package/dist/modal.icon-button.js +60 -1
  56. package/dist/modal.icon-button.styles.js +5 -2
  57. package/dist/modal.js +473 -1
  58. package/dist/modal.styles.js +71 -22
  59. package/dist/option.d.ts +74 -0
  60. package/dist/option.js +498 -0
  61. package/dist/option.styles.js +140 -0
  62. package/dist/{menu.options.d.ts → options.d.ts} +5 -6
  63. package/dist/options.js +130 -0
  64. package/dist/options.styles.js +21 -0
  65. package/dist/popover.js +620 -1
  66. package/dist/popover.styles.js +11 -5
  67. package/dist/radio-group.js +624 -17
  68. package/dist/radio-group.radio.js +211 -1
  69. package/dist/radio-group.radio.styles.js +9 -4
  70. package/dist/radio-group.styles.js +5 -2
  71. package/dist/slider.js +1040 -61
  72. package/dist/slider.styles.js +9 -4
  73. package/dist/spinner.js +60 -1
  74. package/dist/spinner.styles.js +5 -2
  75. package/dist/split-button.js +116 -1
  76. package/dist/split-button.primary-button.js +100 -1
  77. package/dist/split-button.primary-button.styles.js +13 -6
  78. package/dist/split-button.primary-link.js +102 -1
  79. package/dist/split-button.secondary-button.d.ts +2 -3
  80. package/dist/split-button.secondary-button.js +121 -1
  81. package/dist/split-button.secondary-button.styles.js +12 -7
  82. package/dist/split-button.styles.js +9 -4
  83. package/dist/styles/focus-outline.js +9 -3
  84. package/dist/styles/fonts.css +6 -1
  85. package/dist/styles/opacity-and-scale-animation.js +6 -3
  86. package/dist/styles/skeleton.js +6 -3
  87. package/dist/styles/variables.css +410 -1
  88. package/dist/styles/visually-hidden.js +6 -3
  89. package/dist/tab.group.js +386 -1
  90. package/dist/tab.group.styles.js +5 -2
  91. package/dist/tab.js +133 -1
  92. package/dist/tab.panel.js +93 -1
  93. package/dist/tab.panel.styles.js +11 -5
  94. package/dist/tab.styles.js +9 -4
  95. package/dist/tag.js +207 -1
  96. package/dist/tag.styles.js +10 -5
  97. package/dist/textarea.js +353 -19
  98. package/dist/textarea.styles.js +23 -4
  99. package/dist/toast.js +130 -1
  100. package/dist/toast.toasts.js +248 -25
  101. package/dist/toast.toasts.styles.js +9 -4
  102. package/dist/toggle.js +178 -1
  103. package/dist/toggle.styles.js +25 -5
  104. package/dist/tooltip.container.js +130 -1
  105. package/dist/tooltip.container.styles.js +5 -2
  106. package/dist/tooltip.js +484 -1
  107. package/dist/tooltip.styles.js +21 -5
  108. package/dist/translations/en.js +36 -1
  109. package/dist/translations/fr.js +37 -1
  110. package/dist/translations/ja.js +37 -1
  111. package/package.json +8 -12
  112. package/dist/menu.button.d.ts +0 -42
  113. package/dist/menu.button.js +0 -1
  114. package/dist/menu.button.styles.js +0 -32
  115. package/dist/menu.link.d.ts +0 -44
  116. package/dist/menu.link.js +0 -1
  117. package/dist/menu.link.styles.js +0 -35
  118. package/dist/menu.options.js +0 -1
  119. package/dist/menu.options.styles.d.ts +0 -2
  120. package/dist/menu.options.styles.js +0 -20
  121. /package/dist/{menu.button.styles.d.ts → option.styles.d.ts} +0 -0
  122. /package/dist/{menu.link.styles.d.ts → options.styles.d.ts} +0 -0
package/dist/dropdown.js CHANGED
@@ -1,16 +1,512 @@
1
- var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length,o=l<3?e:null===s?s=Object.getOwnPropertyDescriptor(e,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(t,e,i,s);else for(var a=t.length-1;a>=0;a--)(n=t[a])&&(o=(l<3?n(o):l>3?n(e,i,o):n(e,i))||o);return l>3&&o&&Object.defineProperty(e,i,o),o};import"./icon-button.js";import"./label.js";import"./tooltip.js";import{html,LitElement}from"lit";import{autoUpdate,computePosition,flip,offset}from"@floating-ui/dom";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import{repeat}from"lit/directives/repeat.js";import{range}from"lit/directives/range.js";import{map}from"lit/directives/map.js";import{unsafeHTML}from"lit/directives/unsafe-html.js";import{when}from"lit/directives/when.js";import packageJson from"../package.json"with{type:"json"};import onResize from"./library/on-resize.js";import DropdownOption from"./dropdown.option.js";import{LocalizeController}from"./library/localize.js";import Tag from"./tag.js";import chevronIcon from"./icons/chevron.js";import magnifyingGlassIcon from"./icons/magnifying-glass.js";import pencilIcon from"./icons/pencil.js";import styles from"./dropdown.styles.js";import assertSlot from"./library/assert-slot.js";import shadowRootMode from"./library/shadow-root-mode.js";import final from"./library/final.js";import required from"./library/required.js";import uniqueId from"./library/unique-id.js";let Dropdown=class Dropdown extends LitElement{static{this.formAssociated=!0}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:shadowRootMode}}static{this.styles=styles}get disabled(){return this.#t}set disabled(t){this.#t=t,this.open&&t?this.#e():this.open&&this.#i()}get filterable(){return this.#s}set filterable(t){this.#s!==t&&t&&!this.multiple?this.#n.value&&this.lastSelectedAndEnabledOption?.label&&(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label,this.isInputOverflowing=this.#n.value.scrollWidth>this.#n.value.clientWidth):this.#s!==t&&this.#l(),this.#s=t}get open(){return this.#o}set open(t){const e=t!==this.#o;if(this.#o=t,t&&e&&!this.disabled)return this.#i(),void this.dispatchEvent(new Event("toggle",{bubbles:!0,composed:!0}));!this.open&&e&&(!this.multiple&&this.#n.value&&this.lastSelectedAndEnabledOption?.label?(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label):(!this.multiple&&this.#n.value&&0===this.selectedAndEnabledOptions.length||this.multiple&&this.#n.value)&&(this.#n.value.value="",this.inputValue=""),this.#l(),this.#e(),this.dispatchEvent(new Event("toggle",{bubbles:!0,composed:!0})))}get multiple(){return this.#a}set multiple(t){const e=this.#a&&!t,i=!this.#a&&t;this.#a=t;for(const i of this.#d)i.privateMultiple=t,e&&i!==this.lastSelectedAndEnabledOption&&(i.selected=!1);e&&this.lastSelectedAndEnabledOption?(this.#r=this.lastSelectedAndEnabledOption?.value?[this.lastSelectedAndEnabledOption.value]:[],this.selectedAndEnabledOptions=[this.lastSelectedAndEnabledOption],this.isShowSingleSelectIcon=Boolean(this.lastSelectedAndEnabledOption?.value),this.#n.value&&this.lastSelectedAndEnabledOption?.label&&(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label)):i&&this.lastSelectedAndEnabledOption&&(this.#n.value&&(this.#n.value.value="",this.inputValue=""),this.lastSelectedAndEnabledOption.privateUpdateCheckbox(),this.isShowSingleSelectIcon=!1)}get value(){return this.#r}set value(t){if(!this.multiple&&t.length>1)throw new Error("Only one value is allowed when not `multiple`.");this.#r=t;for(const t of this.selectedAndEnabledOptions)this.#p=!0,t.selected=!1,this.#p=!1;for(const e of t){const t=this.#d.find((t=>t.value===e&&!t.selected));t&&(this.#p=!0,t.selected=!0,this.#p=!1)}!this.multiple&&0===this.value.length&&this.#n.value?(this.#n.value.value="",this.inputValue=""):!this.multiple&&this.lastSelectedAndEnabledOption?.label&&this.#n.value&&(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label)}get activeOption(){return this.#h?.find((({privateActive:t})=>t))}get areAllOptionsSelected(){return this.#d.length>0&&this.#d.filter((({selected:t})=>t)).length===this.#d.filter((({disabled:t})=>!t)).length}get areSomeOptionsSelected(){return this.#d.some((({selected:t})=>t))}checkValidity(){this.isCheckingValidity=!0;const t=this.#c.checkValidity();return this.isCheckingValidity=!1,t}click(){this.filterable||this.isFilterable?(this.#n.value?.click(),this.#n.value?.select()):this.#u.value?.click()}get lastSelectedAndEnabledOption(){return this.selectedAndEnabledOptions.at(-1)}get internalLabel(){const t=this.filterable||this.isFilterable;return t||this.lastSelectedAndEnabledOption?this.multiple||t||!this.lastSelectedAndEnabledOption?"":this.lastSelectedAndEnabledOption.label:this.placeholder}connectedCallback(){super.connectedCallback(),document.addEventListener("click",this.#v,{capture:!0})}createRenderRoot(){return this.#m=super.createRenderRoot(),this.#m}disconnectedCallback(){super.disconnectedCallback(),this.form?.removeEventListener("formdata",this.#f),document.removeEventListener("click",this.#v,{capture:!0})}async filter(t){return this.#d.filter((({label:e})=>e?.toLowerCase().includes(t.toLowerCase().trim())))}firstUpdated(){this.#b.value&&(this.#b.value.popover="manual"),this.open&&!this.disabled&&this.#i(),this.selectedAndEnabledOptions=this.#d.filter((({selected:t,disabled:e})=>t&&!e)),!this.multiple&&this.lastSelectedAndEnabledOption&&this.#n.value&&this.lastSelectedAndEnabledOption.label&&(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label);if(this.#d.every((({selected:t})=>!t)))for(const t of this.value){const e=this.#d.find((e=>e.value===t&&!e.selected));e&&(e.selected=!0)}}focus(t){this.filterable||this.isFilterable?this.#n.value?.focus(t):this.#u.value?.focus(t)}get form(){return this.#c.form}get validity(){return this.required&&0===this.selectedAndEnabledOptions.length?(this.#c.setValidity({customError:Boolean(this.validityMessage),valueMissing:!0}," ",this.filterable||this.isFilterable?this.#n.value:this.#u.value),this.#c.validity):this.required&&this.#c.validity.valueMissing&&this.selectedAndEnabledOptions.length>0?(this.#c.setValidity({}),this.#c.validity):this.#c.validity}formAssociatedCallback(){this.form?.addEventListener("formdata",this.#f)}formResetCallback(){for(const t of this.#d){t.selected=!1;t.hasAttribute("selected")&&!t.disabled&&(t.selected=!0)}}render(){return html`<div
2
- class=${classMap({component:!0,horizontal:"horizontal"===this.orientation,vertical:"vertical"===this.orientation})}
3
- @mouseup=${this.#E}
4
- ${onResize(this.#g.bind(this))}
5
- ${ref(this.#O)}
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import './icon-button.js';
8
+ import './label.js';
9
+ import './tooltip.js';
10
+ import { html, LitElement } from 'lit';
11
+ import { autoUpdate, computePosition, flip, offset } from '@floating-ui/dom';
12
+ import { classMap } from 'lit/directives/class-map.js';
13
+ import { createRef, ref } from 'lit/directives/ref.js';
14
+ import { customElement, property, state } from 'lit/decorators.js';
15
+ import { ifDefined } from 'lit/directives/if-defined.js';
16
+ import { repeat } from 'lit/directives/repeat.js';
17
+ import { range } from 'lit/directives/range.js';
18
+ import { map } from 'lit/directives/map.js';
19
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
20
+ import { when } from 'lit/directives/when.js';
21
+ import packageJson from '../package.json' with { type: 'json' };
22
+ import onResize from './library/on-resize.js';
23
+ import DropdownOption from './dropdown.option.js';
24
+ import { LocalizeController } from './library/localize.js';
25
+ import Tag from './tag.js';
26
+ import chevronIcon from './icons/chevron.js';
27
+ import magnifyingGlassIcon from './icons/magnifying-glass.js';
28
+ import pencilIcon from './icons/pencil.js';
29
+ import styles from './dropdown.styles.js';
30
+ import assertSlot from './library/assert-slot.js';
31
+ import shadowRootMode from './library/shadow-root-mode.js';
32
+ import final from './library/final.js';
33
+ import required from './library/required.js';
34
+ import uniqueId from './library/unique-id.js';
35
+ /**
36
+ * @attr {string} label
37
+ * @attr {boolean} [add-button=false]
38
+ * @attr {boolean} [disabled=false]
39
+ * @attr {boolean} [filterable=false]
40
+ * @attr {boolean} [hide-label=false]
41
+ * @attr {boolean} [loading=false]
42
+ * @attr {boolean} [multiple=false]
43
+ * @attr {string} [name='']
44
+ * @attr {boolean} [open=false]
45
+ * @attr {'horizontal'|'vertical'} [orientation='horizontal']
46
+ * @attr {string} [placeholder]
47
+ * @attr {boolean} [readonly=false]
48
+ * @attr {boolean} [required=false]
49
+ * @attr {boolean} [select-all=false]
50
+ * @attr {string} [tooltip]
51
+ * @attr {string[]} [value=[]]
52
+ * @attr {'quiet'} [variant]
53
+ *
54
+ * @readonly
55
+ * @attr {string} [version]
56
+ *
57
+ * @slot {DropdownOption}
58
+ * @slot {Element | string} [description] - Additional information or context
59
+ * @slot {Element} [icon:value] - Icons for the selected Dropdown Option(s). Slot one icon per Dropdown Option. `<value>` should be equal to the `value` of each Dropdown Option.
60
+ *
61
+ * @fires {CustomEvent} add
62
+ * @fires {Event} change
63
+ * @fires {Event} input
64
+ * @fires {Event} invalid
65
+ * @fires {Event} toggle
66
+ *
67
+ * @readonly
68
+ * @prop {HTMLFormElement | null} form
69
+ *
70
+ * @readonly
71
+ * @prop {ValidityState} validity
72
+ *
73
+ * @method checkValidity
74
+ * @returns boolean
75
+ *
76
+ * @method filter
77
+ * @param {string} query
78
+ * @returns Promise<DropdownOption[] | undefined | void>
79
+ *
80
+ * @method formAssociatedCallback
81
+ * @method formResetCallback
82
+ *
83
+ * @method reportValidity
84
+ * @returns boolean
85
+ *
86
+ * @method resetValidityFeedback
87
+ *
88
+ * @method setCustomValidity
89
+ * @param {string} message
90
+ *
91
+ * @method setValidity
92
+ * @param {ValidityStateFlags} [flags]
93
+ * @param {string} [message]
94
+ */
95
+ let Dropdown = class Dropdown extends LitElement {
96
+ static { this.formAssociated = true; }
97
+ static { this.shadowRootOptions = {
98
+ ...LitElement.shadowRootOptions,
99
+ mode: shadowRootMode,
100
+ }; }
101
+ static { this.styles = styles; }
102
+ /**
103
+ * @default false
104
+ */
105
+ get disabled() {
106
+ return this.#isDisabled;
107
+ }
108
+ set disabled(isDisabled) {
109
+ this.#isDisabled = isDisabled;
110
+ if (this.open && isDisabled) {
111
+ this.#hide();
112
+ }
113
+ else if (this.open) {
114
+ this.#show();
115
+ }
116
+ }
117
+ /**
118
+ * @default false
119
+ */
120
+ get filterable() {
121
+ return this.#isFilterable;
122
+ }
123
+ set filterable(isFilterable) {
124
+ if (this.#isFilterable !== isFilterable && isFilterable && !this.multiple) {
125
+ if (this.#inputElementRef.value &&
126
+ this.lastSelectedAndEnabledOption?.label) {
127
+ this.#inputElementRef.value.value =
128
+ this.lastSelectedAndEnabledOption.label;
129
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
130
+ this.isInputOverflowing =
131
+ this.#inputElementRef.value.scrollWidth >
132
+ this.#inputElementRef.value.clientWidth;
133
+ }
134
+ }
135
+ else if (this.#isFilterable !== isFilterable) {
136
+ this.#unfilter();
137
+ }
138
+ this.#isFilterable = isFilterable;
139
+ }
140
+ /**
141
+ * @default false
142
+ */
143
+ get open() {
144
+ return this.#isOpen;
145
+ }
146
+ set open(isOpen) {
147
+ const hasChanged = isOpen !== this.#isOpen;
148
+ this.#isOpen = isOpen;
149
+ if (isOpen && hasChanged && !this.disabled) {
150
+ this.#show();
151
+ this.dispatchEvent(new Event('toggle', { bubbles: true, composed: true }));
152
+ return;
153
+ }
154
+ if (!this.open && hasChanged) {
155
+ if (!this.multiple &&
156
+ this.#inputElementRef.value &&
157
+ this.lastSelectedAndEnabledOption?.label) {
158
+ this.#inputElementRef.value.value =
159
+ this.lastSelectedAndEnabledOption.label;
160
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
161
+ }
162
+ else if (!this.multiple &&
163
+ this.#inputElementRef.value &&
164
+ this.selectedAndEnabledOptions.length === 0) {
165
+ this.#inputElementRef.value.value = '';
166
+ this.inputValue = '';
167
+ }
168
+ else if (this.multiple && this.#inputElementRef.value) {
169
+ this.#inputElementRef.value.value = '';
170
+ this.inputValue = '';
171
+ }
172
+ this.#unfilter();
173
+ this.#hide();
174
+ this.dispatchEvent(new Event('toggle', { bubbles: true, composed: true }));
175
+ }
176
+ }
177
+ /**
178
+ * @default false
179
+ */
180
+ get multiple() {
181
+ return this.#isMultiple;
182
+ }
183
+ set multiple(isMultiple) {
184
+ const wasMultiple = this.#isMultiple && !isMultiple;
185
+ const wasSingle = !this.#isMultiple && isMultiple;
186
+ this.#isMultiple = isMultiple;
187
+ for (const option of this.#optionElements) {
188
+ option.privateMultiple = isMultiple;
189
+ // A single-select Dropdown can only have one option selected. So all but the
190
+ // last selected and enabled option is deselected.
191
+ if (wasMultiple && option !== this.lastSelectedAndEnabledOption) {
192
+ option.selected = false;
193
+ }
194
+ }
195
+ if (wasMultiple && this.lastSelectedAndEnabledOption) {
196
+ this.#value = this.lastSelectedAndEnabledOption?.value
197
+ ? [this.lastSelectedAndEnabledOption.value]
198
+ : [];
199
+ this.selectedAndEnabledOptions = [this.lastSelectedAndEnabledOption];
200
+ this.isShowSingleSelectIcon = Boolean(this.lastSelectedAndEnabledOption?.value);
201
+ if (this.#inputElementRef.value &&
202
+ this.lastSelectedAndEnabledOption?.label) {
203
+ this.#inputElementRef.value.value =
204
+ this.lastSelectedAndEnabledOption.label;
205
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
206
+ }
207
+ }
208
+ else if (wasSingle && this.lastSelectedAndEnabledOption) {
209
+ // If Dropdown was single-select and filterable and an option is selected,
210
+ // then the value of its `<input>` is set to the label of the selected option.
211
+ // That behavior doesn't apply to multiselect Dropdown because its selected
212
+ // option or options are represented by tags. So we clear input field.
213
+ if (this.#inputElementRef.value) {
214
+ this.#inputElementRef.value.value = '';
215
+ this.inputValue = '';
216
+ }
217
+ this.lastSelectedAndEnabledOption.privateUpdateCheckbox();
218
+ this.isShowSingleSelectIcon = false;
219
+ }
220
+ }
221
+ // Intentionally not reflected to match native.
222
+ /**
223
+ * @default []
224
+ */
225
+ get value() {
226
+ return this.#value;
227
+ }
228
+ set value(value) {
229
+ if (!this.multiple && value.length > 1) {
230
+ throw new Error('Only one value is allowed when not `multiple`.');
231
+ }
232
+ this.#value = value;
233
+ // `#onOptionsSelectedChange()` is called when an option is selected. It updates
234
+ // `this.selectedAndEnabledOptions`. Deselecting every option before reselecting
235
+ // those in `value` ensures tags appear in the same order as in `value`.
236
+ for (const option of this.selectedAndEnabledOptions) {
237
+ this.#isSelectionFromValueSetter = true;
238
+ option.selected = false;
239
+ this.#isSelectionFromValueSetter = false;
240
+ }
241
+ for (const optionValue of value) {
242
+ // We select only the first option with a given value so what the user sees as
243
+ // selected matches what is submitted with the form. If we were to select every
244
+ // matching option here, then the user would be under the impression that multiple
245
+ // instances of that value will be submitted with the form.
246
+ //
247
+ // Imagine a case where there are two options whose value is "one" and `value`
248
+ // is `['one']`.
249
+ const option = this.#optionElements.find((option) => option.value === optionValue &&
250
+ // There may be more than one of the same value in `value`. If there is, we want
251
+ // more than one option to be selected. Without this condition, only the first
252
+ // option with the value would be selected.
253
+ !option.selected);
254
+ if (option) {
255
+ this.#isSelectionFromValueSetter = true;
256
+ option.selected = true;
257
+ this.#isSelectionFromValueSetter = false;
258
+ }
259
+ }
260
+ // When an option is selected, `#inputElementRef.value.value` is set the `label`
261
+ // of the selected option. If Dropdown's `value` has been emptied, it means an
262
+ // option is no longer selected. So the `value` of `#inputElementRef.value` should
263
+ // be emptied too.
264
+ if (!this.multiple &&
265
+ this.value.length === 0 &&
266
+ this.#inputElementRef.value) {
267
+ this.#inputElementRef.value.value = '';
268
+ this.inputValue = '';
269
+ }
270
+ else if (!this.multiple &&
271
+ this.lastSelectedAndEnabledOption?.label &&
272
+ this.#inputElementRef.value) {
273
+ this.#inputElementRef.value.value =
274
+ this.lastSelectedAndEnabledOption.label;
275
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
276
+ }
277
+ }
278
+ get activeOption() {
279
+ return this.#optionElementsIncludingSelectAll?.find(({ privateActive }) => privateActive);
280
+ }
281
+ get areAllOptionsSelected() {
282
+ return (this.#optionElements.length > 0 &&
283
+ this.#optionElements.filter(({ selected }) => selected).length ===
284
+ this.#optionElements.filter(({ disabled }) => !disabled).length);
285
+ }
286
+ get areSomeOptionsSelected() {
287
+ return this.#optionElements.some(({ selected }) => selected);
288
+ }
289
+ checkValidity() {
290
+ this.isCheckingValidity = true;
291
+ const isValid = this.#internals.checkValidity();
292
+ this.isCheckingValidity = false;
293
+ return isValid;
294
+ }
295
+ click() {
296
+ if (this.filterable || this.isFilterable) {
297
+ this.#inputElementRef.value?.click();
298
+ this.#inputElementRef.value?.select();
299
+ }
300
+ else {
301
+ this.#primaryButtonElementRef.value?.click();
302
+ }
303
+ }
304
+ get lastSelectedAndEnabledOption() {
305
+ return this.selectedAndEnabledOptions.at(-1);
306
+ }
307
+ get internalLabel() {
308
+ const isFilterable = this.filterable || this.isFilterable;
309
+ return !isFilterable && !this.lastSelectedAndEnabledOption
310
+ ? this.placeholder
311
+ : !this.multiple && !isFilterable && this.lastSelectedAndEnabledOption
312
+ ? this.lastSelectedAndEnabledOption.label
313
+ : '';
314
+ }
315
+ connectedCallback() {
316
+ super.connectedCallback();
317
+ document.addEventListener('click', this.#onDocumentClick, {
318
+ // 1. The consumer has a click handler on a button.
319
+ // 2. The user clicks the button.
320
+ // 3. The button's click handler is called and it sets `this.open` to `true`.
321
+ // 4. The "click" event bubbles up and is handled by `#onDocumentClick`.
322
+ // 5. That handler sets `open` to `false` because the click came from outside
323
+ // Dropdown.
324
+ // 6. Dropdown is opened then closed in the same frame and so never opens.
325
+ //
326
+ // `capture` ensures `#onDocumentClick` is called before #3, so the button click
327
+ // handler setting `open` to `true` isn't overwritten by this handler setting
328
+ // `open` to `false`.
329
+ capture: true,
330
+ });
331
+ }
332
+ createRenderRoot() {
333
+ this.#shadowRoot = super.createRenderRoot();
334
+ return this.#shadowRoot;
335
+ }
336
+ disconnectedCallback() {
337
+ super.disconnectedCallback();
338
+ this.form?.removeEventListener('formdata', this.#onFormdata);
339
+ document.removeEventListener('click', this.#onDocumentClick, {
340
+ capture: true,
341
+ });
342
+ }
343
+ // `async` so consumers can fetch and return a promise when they override.
344
+ //
345
+ // eslint-disable-next-line @typescript-eslint/require-await
346
+ async filter(query) {
347
+ return this.#optionElements.filter(({ label }) => {
348
+ return label?.toLowerCase().includes(query.toLowerCase().trim());
349
+ });
350
+ }
351
+ firstUpdated() {
352
+ if (this.#optionsAndFeedbackElementRef.value) {
353
+ // `popover` is used so the options can break out of Modal or another container
354
+ // that has `overflow: hidden`. Elements with `popover` are positioned relative
355
+ // to the viewport. Thus Floating UI in addition to `popover`.
356
+ //
357
+ // Set here instead of in the template to escape Lit Analyzer, which isn't aware
358
+ // of `popover` and doesn't have a way to disable its "no-unknown-attribute" rule.
359
+ //
360
+ // "auto" means only one popover can be open at a time. Consumers, however, may
361
+ // have popovers in their own components that need to be open while this one is
362
+ // open.
363
+ //
364
+ // "auto" also automatically opens the popover when its target is clicked. We want
365
+ // it to remain closed when clicked when there are no options. We also want it to
366
+ // close when every option has been filtered out.
367
+ this.#optionsAndFeedbackElementRef.value.popover = 'manual';
368
+ }
369
+ if (this.open && !this.disabled) {
370
+ this.#show();
371
+ }
372
+ this.selectedAndEnabledOptions = this.#optionElements.filter(({ selected, disabled }) => selected && !disabled);
373
+ if (!this.multiple &&
374
+ this.lastSelectedAndEnabledOption &&
375
+ this.#inputElementRef.value &&
376
+ this.lastSelectedAndEnabledOption.label) {
377
+ this.#inputElementRef.value.value =
378
+ this.lastSelectedAndEnabledOption.label;
379
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
380
+ }
381
+ const hasNoSelectedOptions = this.#optionElements.every(({ selected }) => !selected);
382
+ // When `value` is set on initial render, its setter is called before
383
+ // `connectedCallback()` and thus before the default slot has any assigned
384
+ // elements. So we select options here after the initial render is complete
385
+ // and `this.#optionElements` isn't empty.
386
+ //
387
+ // Additionally, `#onDefaultSlotChange()` is called after `firstUpdated()`
388
+ // and sets `value` based on which options are selected. And the initial `value`
389
+ // may conflict with the one derived from which options are selected.
390
+ //
391
+ // So we have a decision to make. On first render, do we defer to the initial
392
+ // `value` and select and deselect options below? Or do we defer to
393
+ // `#onDefaultSlotChange()` and let that method change `value` from its initial
394
+ // value based on which options are selected?
395
+ //
396
+ // It's largely a toss-up. But the latter seems like the logical choice given
397
+ // `#onDefaultSlotChange()` is called after `firstUpdated()`. In other words, we
398
+ // defer to the lifecycle. `#onDefaultSlotChange()` is called second. So it gets to
399
+ // override what `value` was initially.
400
+ //
401
+ // If no options are selected, then it's obvious that the consumer's intention is
402
+ // to select options based on the initial `value` and that the initial `value` is
403
+ // the intended one. So we proceed.
404
+ if (hasNoSelectedOptions) {
405
+ for (const optionValue of this.value) {
406
+ const option = this.#optionElements.find((option) => option.value === optionValue &&
407
+ // There may be more than one of the same value in `value`. If there is, we want
408
+ // more than one option to be selected. Without this condition, only the first
409
+ // option with the value would be selected.
410
+ !option.selected);
411
+ if (option) {
412
+ option.selected = true;
413
+ }
414
+ }
415
+ }
416
+ }
417
+ // The button doesn't receive focus when `shadowRoot.delegatesFocus` is set,
418
+ // and the inherited `this.focus` is called. It's not clear why. Thus the override.
419
+ focus(options) {
420
+ if (this.filterable || this.isFilterable) {
421
+ this.#inputElementRef.value?.focus(options);
422
+ }
423
+ else {
424
+ this.#primaryButtonElementRef.value?.focus(options);
425
+ }
426
+ }
427
+ get form() {
428
+ return this.#internals.form;
429
+ }
430
+ get validity() {
431
+ if (this.required && this.selectedAndEnabledOptions.length === 0) {
432
+ // A validation message is required but unused because we disable native validation
433
+ // feedback. And an empty string isn't allowed. Thus a single space.
434
+ this.#internals.setValidity({ customError: Boolean(this.validityMessage), valueMissing: true }, ' ', this.filterable || this.isFilterable
435
+ ? this.#inputElementRef.value
436
+ : this.#primaryButtonElementRef.value);
437
+ return this.#internals.validity;
438
+ }
439
+ if (this.required &&
440
+ this.#internals.validity.valueMissing &&
441
+ this.selectedAndEnabledOptions.length > 0) {
442
+ this.#internals.setValidity({});
443
+ return this.#internals.validity;
444
+ }
445
+ return this.#internals.validity;
446
+ }
447
+ formAssociatedCallback() {
448
+ this.form?.addEventListener('formdata', this.#onFormdata);
449
+ }
450
+ formResetCallback() {
451
+ for (const option of this.#optionElements) {
452
+ // `#onOptionsSelectedChange()` is called when an option is selected. It updates
453
+ // `this.selectedAndEnabledOptions`. Deselecting every option before reselecting
454
+ // those in `value` ensures tags appear in the same order they were initially.
455
+ option.selected = false;
456
+ const isInitiallySelected = option.hasAttribute('selected') && !option.disabled;
457
+ if (isInitiallySelected) {
458
+ option.selected = true;
459
+ }
460
+ }
461
+ }
462
+ render() {
463
+ // `hidden` is frowned upon because it adds a second source of truth for styling.
464
+ // However, it's the simplest way to hide slotted options and to later check if
465
+ // they're hidden. Select All is different because we can conditionally render it
466
+ // and check if it exists. `hidden` is used with it nonetheless for consistency and
467
+ // to simplify the logic that checks an option's visibility, such as in
468
+ // `#optionElementsNotHiddenIncludingSelectAll`.
469
+ // ".tag-overflow-text" is hidden from screen readers because it's redundant. The
470
+ // selected options are announced when Dropdown receives focus.
471
+ // The linter checks that all ULs have LIs as children. It doesn't account for
472
+ // slots, which can contain LIs.
473
+ // The linter wants a "focus" handler on the slot, but there's nothing to be done
474
+ // with one in this case.
475
+ // The linter wants a "keydown" handler on '.dropdown'. Instead, there's one on
476
+ // `.dropdown-and-options` because much of the logic in the handler also applies
477
+ // to options, which can receive focus, and "keydown" events won't be emitted on
478
+ // ".dropdown" when it doesn't have focus.
479
+ // 'aria-selected="false"' on the Add button because every `"role="option"` must
480
+ // have that attribute. The attribute's value is hardcoded to "false" because the
481
+ // Add button can't be selected in the usual, persistent sense.
482
+ //
483
+ // Admittedly, it's a hack with respect to accessibility. But screenreader users
484
+ // should be able to understand what's going on.
485
+ //
486
+ // The alternative, an Add button that's not an option but instead tabbed to, would
487
+ // certainly be a worse user experience in general because users would have to tab
488
+ // out of the input field while filtering to add a new option. Then they would have
489
+ // to tab back to the input field to continue filtering. This would also likely be
490
+ // worse for screenreader users because they wouldn't discover the Add button until
491
+ // they move focus off the input field.
492
+ /* eslint-disable lit-a11y/mouse-events-have-key-events, lit-a11y/click-events-have-key-events */
493
+ return html `<div
494
+ class=${classMap({
495
+ component: true,
496
+ horizontal: this.orientation === 'horizontal',
497
+ vertical: this.orientation === 'vertical',
498
+ })}
499
+ @mouseup=${this.#onComponentMouseup}
500
+ ${onResize(this.#setTagOverflowLimit.bind(this))}
501
+ ${ref(this.#componentElementRef)}
6
502
  >
7
503
  <glide-core-private-label
8
504
  label=${ifDefined(this.label)}
9
505
  orientation=${this.orientation}
10
- split=${ifDefined(this.privateSplit??void 0)}
506
+ split=${ifDefined(this.privateSplit ?? undefined)}
11
507
  tooltip=${ifDefined(this.tooltip)}
12
508
  ?disabled=${this.disabled}
13
- ?error=${this.#A}
509
+ ?error=${this.#isShowValidationFeedback}
14
510
  ?hide=${this.hideLabel}
15
511
  ?required=${this.required}
16
512
  >
@@ -19,45 +515,66 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
19
515
  <div
20
516
  class="dropdown-and-options"
21
517
  slot="control"
22
- @focusout=${this.#y}
23
- @keydown=${this.#w}
518
+ @focusout=${this.#onDropdownAndOptionsFocusout}
519
+ @keydown=${this.#onDropdownAndOptionsKeydown}
24
520
  >
25
521
  <div
26
- class=${classMap({dropdown:!0,quiet:"quiet"===this.variant,disabled:this.disabled,error:this.#A,readonly:this.readonly,multiple:this.multiple})}
27
- @click=${this.#R}
28
- @mousedown=${this.#S}
29
- ${ref(this.#$)}
522
+ class=${classMap({
523
+ dropdown: true,
524
+ quiet: this.variant === 'quiet',
525
+ disabled: this.disabled,
526
+ error: this.#isShowValidationFeedback,
527
+ readonly: this.readonly,
528
+ multiple: this.multiple,
529
+ })}
530
+ @click=${this.#onDropdownClick}
531
+ @mousedown=${this.#onDropdownMousedown}
532
+ ${ref(this.#dropdownElementRef)}
30
533
  >
31
534
  <span class="selected-option-labels" id="selected-option-labels">
32
- ${this.selectedAndEnabledOptions.filter((t=>!!this.multiple||t===this.selectedAndEnabledOptions.at(-1))).map((({label:t})=>html`<span data-test="selected-option-label">
33
- ${t},
34
- </span>`))}
535
+ ${this.selectedAndEnabledOptions
536
+ .filter((option) => {
537
+ return this.multiple
538
+ ? true
539
+ : option === this.selectedAndEnabledOptions.at(-1);
540
+ })
541
+ .map(({ label }) => {
542
+ return html `<span data-test="selected-option-label">
543
+ ${label},
544
+ </span>`;
545
+ })}
35
546
  </span>
36
547
 
37
- ${when(this.multiple&&this.selectedAndEnabledOptions.length>0,(()=>html`<ul
548
+ ${when(this.multiple && this.selectedAndEnabledOptions.length > 0, () => {
549
+ return html `<ul
38
550
  aria-describedby="tag-overflow-text"
39
551
  class="tags"
40
- ${ref(this.#I)}
552
+ ${ref(this.#tagsElementRef)}
41
553
  >
42
- ${repeat(this.selectedAndEnabledOptions,(({id:t})=>t),((t,e)=>html`<li
43
- class=${classMap({"tag-container":!0,hidden:e>this.tagOverflowLimit-1})}
554
+ ${repeat(this.selectedAndEnabledOptions, ({ id }) => id, (option, index) => {
555
+ return html `<li
556
+ class=${classMap({
557
+ 'tag-container': true,
558
+ hidden: index > this.tagOverflowLimit - 1,
559
+ })}
44
560
  data-test="tag-container"
45
- data-test-hidden=${e>this.tagOverflowLimit-1}
561
+ data-test-hidden=${index > this.tagOverflowLimit - 1}
46
562
  >
47
563
  <glide-core-tag
48
564
  data-test="tag"
49
- data-id=${t.id}
50
- label=${ifDefined(t.label)}
565
+ data-id=${option.id}
566
+ label=${ifDefined(option.label)}
51
567
  removable
52
- ?disabled=${this.disabled||this.readonly}
53
- ?private-editable=${t.editable}
54
- @edit=${this.#D}
55
- @remove=${this.#B.bind(this,t)}
568
+ ?disabled=${this.disabled || this.readonly}
569
+ ?private-editable=${option.editable}
570
+ @edit=${this.#onTagEdit}
571
+ @remove=${this.#onTagRemove.bind(this, option)}
56
572
  >
57
- ${when(t.value,(()=>html`
573
+ ${when(option.value, () => {
574
+ return html `
58
575
  <slot
59
576
  data-test="multiselect-icon-slot"
60
- name="icon:${t.value}"
577
+ name="icon:${option.value}"
61
578
  slot="icon"
62
579
  >
63
580
  <!--
@@ -69,24 +586,36 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
69
586
  @type {Element}
70
587
  -->
71
588
  </slot>
72
- `))}
589
+ `;
590
+ })}
73
591
  </glide-core-tag>
74
- </li>`))}
75
- </ul>`))}
76
- ${when(this.multiple&&this.selectedAndEnabledOptions.length>this.tagOverflowLimit,(()=>html`<div
592
+ </li>`;
593
+ })}
594
+ </ul>`;
595
+ })}
596
+ ${when(this.multiple &&
597
+ this.selectedAndEnabledOptions.length > this.tagOverflowLimit, () => {
598
+ return html `<div
77
599
  aria-hidden="true"
78
600
  class="tag-overflow-text"
79
601
  id="tag-overflow-text"
80
602
  >
81
603
  +
82
604
  <span data-test="tag-overflow-count">
83
- ${this.selectedAndEnabledOptions.length-this.tagOverflowLimit}
605
+ ${this.selectedAndEnabledOptions.length -
606
+ this.tagOverflowLimit}
84
607
  </span>
85
608
 
86
609
  more
87
- </div>`))}
88
- ${when(this.isShowSingleSelectIcon&&this.lastSelectedAndEnabledOption?.value,(()=>html`<slot
89
- class=${classMap({"single-select-icon-slot":!0,quiet:"quiet"===this.variant})}
610
+ </div>`;
611
+ })}
612
+ ${when(this.isShowSingleSelectIcon &&
613
+ this.lastSelectedAndEnabledOption?.value, () => {
614
+ return html `<slot
615
+ class=${classMap({
616
+ 'single-select-icon-slot': true,
617
+ quiet: this.variant === 'quiet',
618
+ })}
90
619
  data-test="single-select-icon-slot"
91
620
  name="icon:${this.lastSelectedAndEnabledOption?.value}"
92
621
  >
@@ -94,16 +623,20 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
94
623
  @type {Element}
95
624
  @ignore
96
625
  -->
97
- </slot>`))}
626
+ </slot>`;
627
+ })}
98
628
 
99
629
  <glide-core-tooltip
100
- class=${classMap({"input-tooltip":!0,visible:this.filterable||this.isFilterable})}
630
+ class=${classMap({
631
+ 'input-tooltip': true,
632
+ visible: this.filterable || this.isFilterable,
633
+ })}
101
634
  data-test="input-tooltip"
102
635
  label=${this.inputValue}
103
636
  offset=${8}
104
- ?disabled=${this.open||!this.isInputOverflowing}
105
- ?open=${!this.open&&this.isInputTooltipOpen}
106
- @toggle=${this.#k}
637
+ ?disabled=${this.open || !this.isInputOverflowing}
638
+ ?open=${!this.open && this.isInputTooltipOpen}
639
+ @toggle=${this.#onTooltipToggle}
107
640
  screenreader-hidden
108
641
  >
109
642
  <div class="input-container" slot="target">
@@ -111,33 +644,47 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
111
644
  aria-activedescendant=${this.ariaActivedescendant}
112
645
  aria-controls="options"
113
646
  aria-describedby="description"
114
- aria-expanded=${this.open&&!this.disabled}
115
- aria-labelledby="selected-option-labels label loading-feedback ${this.isCommunicateItemCountToScreenreaders?"item-count":""}"
647
+ aria-expanded=${this.open && !this.disabled}
648
+ aria-labelledby="selected-option-labels label loading-feedback ${this
649
+ .isCommunicateItemCountToScreenreaders
650
+ ? 'item-count'
651
+ : ''}"
116
652
  autocapitalize="off"
117
653
  autocomplete="off"
118
- class=${classMap({input:!0,quiet:"quiet"===this.variant})}
654
+ class=${classMap({
655
+ input: true,
656
+ quiet: this.variant === 'quiet',
657
+ })}
119
658
  data-test="input"
120
659
  id="input"
121
- placeholder=${this.multiple||!this.lastSelectedAndEnabledOption?.label?this.placeholder??"":""}
660
+ placeholder=${this.multiple ||
661
+ !this.lastSelectedAndEnabledOption?.label
662
+ ? (this.placeholder ?? '')
663
+ : ''}
122
664
  role="combobox"
123
665
  spellcheck="false"
124
- tabindex=${this.disabled?"-1":"0"}
666
+ tabindex=${this.disabled ? '-1' : '0'}
125
667
  ?disabled=${this.disabled}
126
668
  ?readonly=${this.readonly}
127
- @blur=${this.#F}
128
- @focus=${this.#V}
129
- @input=${this.#_}
130
- @keydown=${this.#C}
131
- ${onResize(this.#T.bind(this))}
132
- ${ref(this.#n)}
669
+ @blur=${this.#onInputBlur}
670
+ @focus=${this.#onInputFocus}
671
+ @input=${this.#onInputInput}
672
+ @keydown=${this.#onInputKeydown}
673
+ ${onResize(this.#onInputResize.bind(this))}
674
+ ${ref(this.#inputElementRef)}
133
675
  />
134
676
 
135
- ${when(!this.multiple&&this.isInputOverflowing&&this.inputValue===this.lastSelectedAndEnabledOption?.label,(()=>html`<span aria-hidden="true" data-test="ellipsis">
677
+ ${when(!this.multiple &&
678
+ this.isInputOverflowing &&
679
+ this.inputValue ===
680
+ this.lastSelectedAndEnabledOption?.label, () => {
681
+ return html `<span aria-hidden="true" data-test="ellipsis">
136
682
 
137
- </span>`))}
683
+ </span>`;
684
+ })}
138
685
 
139
686
  <span
140
- aria-label=${this.#L.term("itemCount",this.itemCount.toString())}
687
+ aria-label=${this.#localize.term('itemCount', this.itemCount.toString())}
141
688
  aria-live="assertive"
142
689
  class="item-count"
143
690
  data-test="item-count"
@@ -148,105 +695,148 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
148
695
  </glide-core-tooltip>
149
696
 
150
697
  <glide-core-tooltip
151
- class=${classMap({"internal-label-tooltip":!0,visible:Boolean(this.internalLabel)})}
698
+ class=${classMap({
699
+ 'internal-label-tooltip': true,
700
+ visible: Boolean(this.internalLabel),
701
+ })}
152
702
  data-test="internal-label-tooltip"
153
- label=${this.internalLabel??""}
703
+ label=${this.internalLabel ?? ''}
154
704
  offset=${8}
155
- ?disabled=${this.open||this.multiple||this.filterable||this.isFilterable||!this.isInternalLabelOverflowing}
156
- ?open=${!this.open&&this.isInternalLabelTooltipOpen}
157
- @toggle=${this.#k}
705
+ ?disabled=${this.open ||
706
+ this.multiple ||
707
+ this.filterable ||
708
+ this.isFilterable ||
709
+ !this.isInternalLabelOverflowing}
710
+ ?open=${!this.open && this.isInternalLabelTooltipOpen}
711
+ @toggle=${this.#onTooltipToggle}
158
712
  screenreader-hidden
159
713
  >
160
714
  <div
161
715
  class="internal-label"
162
716
  data-test="internal-label"
163
717
  slot="target"
164
- ${onResize(this.#M.bind(this))}
165
- ${ref(this.#N)}
718
+ ${onResize(this.#onInternalLabelResize.bind(this))}
719
+ ${ref(this.#internalLabelElementRef)}
166
720
  >
167
- ${when(this.internalLabel===this.placeholder,(()=>html`<span
168
- class=${classMap({placeholder:!0,quiet:"quiet"===this.variant})}
721
+ ${when(this.internalLabel === this.placeholder, () => {
722
+ return html `<span
723
+ class=${classMap({
724
+ placeholder: true,
725
+ quiet: this.variant === 'quiet',
726
+ })}
169
727
  >
170
728
  ${this.internalLabel}
171
- </span>`),(()=>this.internalLabel))}
729
+ </span>`;
730
+ }, () => this.internalLabel)}
172
731
  </div>
173
732
  </glide-core-tooltip>
174
733
 
175
734
  <div class="buttons">
176
- ${when(!this.multiple&&this.lastSelectedAndEnabledOption?.editable&&!this.isFiltering,(()=>html`<glide-core-icon-button
735
+ ${when(!this.multiple &&
736
+ this.lastSelectedAndEnabledOption?.editable &&
737
+ !this.isFiltering, () => {
738
+ return html `<glide-core-icon-button
177
739
  class="edit-button"
178
740
  data-test="edit-button"
179
- label=${this.#L.term("editOption",this.lastSelectedAndEnabledOption.label)}
180
- tabindex=${this.disabled||this.readonly?"-1":"0"}
741
+ label=${this.#localize.term('editOption',
742
+ // `this.lastSelectedAndEnabledOption` is guaranteed to be defined by the
743
+ // `when()` above. And its `label` property is always defined because it's
744
+ // required.
745
+ //
746
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
747
+ this.lastSelectedAndEnabledOption.label)}
748
+ tabindex=${this.disabled || this.readonly ? '-1' : '0'}
181
749
  variant="tertiary"
182
- ?disabled=${this.disabled||this.readonly}
183
- @click=${this.#q}
184
- ${ref(this.#j)}
750
+ ?disabled=${this.disabled || this.readonly}
751
+ @click=${this.#onEditButtonClick}
752
+ ${ref(this.#editButtonElementRef)}
185
753
  >
186
754
  ${pencilIcon}
187
- </glide-core-icon-button>`))}
755
+ </glide-core-icon-button>`;
756
+ })}
188
757
 
189
758
  <button
190
759
  aria-controls="options"
191
760
  aria-describedby="description"
192
- aria-expanded=${this.open&&!this.disabled}
761
+ aria-expanded=${this.open && !this.disabled}
193
762
  aria-haspopup="listbox"
194
- aria-hidden=${this.filterable||this.isFilterable}
763
+ aria-hidden=${this.filterable || this.isFilterable}
195
764
  aria-labelledby="selected-option-labels label loading-feedback"
196
765
  class="primary-button"
197
766
  data-test="primary-button"
198
767
  id="primary-button"
199
- tabindex=${this.filterable||this.isFilterable||this.disabled?"-1":"0"}
768
+ tabindex=${this.filterable || this.isFilterable || this.disabled
769
+ ? '-1'
770
+ : '0'}
200
771
  type="button"
201
772
  ?disabled=${this.disabled}
202
- @focusin=${this.#H}
203
- @focusout=${this.#x}
204
- ${ref(this.#u)}
773
+ @focusin=${this.#onPrimaryButtonFocusin}
774
+ @focusout=${this.#onPrimaryButtonFocusout}
775
+ ${ref(this.#primaryButtonElementRef)}
205
776
  >
206
- ${when(this.isFiltering,(()=>html`<div data-test="magnifying-glass-icon">
777
+ ${when(this.isFiltering, () => {
778
+ return html `<div data-test="magnifying-glass-icon">
207
779
  ${magnifyingGlassIcon}
208
- </div>`),(()=>chevronIcon))}
780
+ </div>`;
781
+ }, () => chevronIcon)}
209
782
  </button>
210
783
  </div>
211
784
  </div>
212
785
 
213
786
  <div
214
- aria-labelledby=${this.filterable||this.isFilterable?"input":"primary-button"}
215
- class=${classMap({"options-and-feedback":!0,optionless:(this.hasNoAvailableOptions||this.hasNoMatchingOptions)&&!this.loading&&!this.isAddButtonVisible})}
787
+ aria-labelledby=${this.filterable || this.isFilterable
788
+ ? 'input'
789
+ : 'primary-button'}
790
+ class=${classMap({
791
+ 'options-and-feedback': true,
792
+ optionless: (this.hasNoAvailableOptions || this.hasNoMatchingOptions) &&
793
+ !this.loading &&
794
+ !this.isAddButtonVisible,
795
+ })}
216
796
  role="listbox"
217
797
  tabindex="-1"
218
- ${ref(this.#b)}
798
+ ${ref(this.#optionsAndFeedbackElementRef)}
219
799
  >
220
800
  <div
221
- class=${classMap({options:!0,hidden:!this.isAddButtonVisible&&(this.hasNoAvailableOptions||this.hasNoMatchingOptions||this.loading)})}
801
+ class=${classMap({
802
+ options: true,
803
+ hidden: !this.isAddButtonVisible &&
804
+ (this.hasNoAvailableOptions ||
805
+ this.hasNoMatchingOptions ||
806
+ this.loading),
807
+ })}
222
808
  data-test="options"
223
809
  id="options"
224
- @change=${this.#z}
225
- @click=${this.#U}
226
- @focusin=${this.#W}
227
- @mousedown=${this.#P}
228
- @mouseover=${this.#K}
229
- @private-disabled-change=${this.#J}
230
- @private-editable-change=${this.#G}
231
- @private-label-change=${this.#Q}
232
- @private-value-change=${this.#X}
810
+ @change=${this.#onOptionsChange}
811
+ @click=${this.#onOptionsClick}
812
+ @focusin=${this.#onOptionsFocusin}
813
+ @mousedown=${this.#onOptionsMousedown}
814
+ @mouseover=${this.#onOptionsMouseover}
815
+ @private-disabled-change=${this.#onOptionsDisabledChange}
816
+ @private-editable-change=${this.#onOptionsEditableChange}
817
+ @private-label-change=${this.#onOptionsLabelChange}
818
+ @private-value-change=${this.#onOptionsValueChange}
233
819
  >
234
820
  <glide-core-dropdown-option
235
821
  class="select-all"
236
822
  data-test="select-all"
237
- label=${this.#L.term("selectAll")}
823
+ label=${this.#localize.term('selectAll')}
238
824
  private-multiple
239
- ?hidden=${!this.selectAll||!this.multiple||this.isFiltering}
240
- ?private-indeterminate=${this.areSomeOptionsSelected&&!this.areAllOptionsSelected}
241
- ${ref(this.#Y)}
825
+ ?hidden=${!this.selectAll || !this.multiple || this.isFiltering}
826
+ ?private-indeterminate=${this.areSomeOptionsSelected &&
827
+ !this.areAllOptionsSelected}
828
+ ${ref(this.#selectAllElementRef)}
242
829
  ></glide-core-dropdown-option>
243
830
 
244
831
  <slot
245
- class=${classMap({"default-slot":!0,optionless:this.hasNoMatchingOptions})}
246
- @private-selected-change=${this.#Z}
247
- @slotchange=${this.#tt}
248
- ${assertSlot([DropdownOption,Text],!0)}
249
- ${ref(this.#et)}
832
+ class=${classMap({
833
+ 'default-slot': true,
834
+ optionless: this.hasNoMatchingOptions,
835
+ })}
836
+ @private-selected-change=${this.#onOptionsSelectedChange}
837
+ @slotchange=${this.#onDefaultSlotChange}
838
+ ${assertSlot([DropdownOption, Text], true)}
839
+ ${ref(this.#defaultSlotElementRef)}
250
840
  >
251
841
  <!--
252
842
  @required
@@ -255,22 +845,29 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
255
845
  </slot>
256
846
  </div>
257
847
 
258
- ${when(this.isAddButtonVisible,(()=>html`
848
+ ${when(this.isAddButtonVisible, () => {
849
+ return html `
259
850
  <div
260
- class=${classMap({"add-button-container":!0,bordered:!this.hasNoAvailableOptions&&!this.hasNoMatchingOptions})}
851
+ class=${classMap({
852
+ 'add-button-container': true,
853
+ bordered: !this.hasNoAvailableOptions && !this.hasNoMatchingOptions,
854
+ })}
261
855
  >
262
856
  <button
263
857
  aria-selected="false"
264
- class=${classMap({"add-button":!0,active:this.isAddButtonActive})}
858
+ class=${classMap({
859
+ 'add-button': true,
860
+ active: this.isAddButtonActive,
861
+ })}
265
862
  data-test="add-button"
266
863
  data-test-active=${this.isAddButtonActive}
267
- id=${this.#it}
864
+ id=${this.#addButtonId}
268
865
  role="option"
269
866
  tabindex="-1"
270
867
  type="button"
271
- @click=${this.#st}
272
- @mouseover=${this.#nt}
273
- ${ref(this.#lt)}
868
+ @click=${this.#selectAddButton}
869
+ @mouseover=${this.#onAddButtonMouseover}
870
+ ${ref(this.#addButtonElementRef)}
274
871
  >
275
872
  <div class="add-button-label">
276
873
  ${this.inputValue.trim()}
@@ -279,28 +876,40 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
279
876
  &nbsp;
280
877
 
281
878
  <div class="add-button-description">
282
- (${this.#L.term("add")})
879
+ (${this.#localize.term('add')})
283
880
  </div>
284
881
  </button>
285
882
  </div>
286
- `))}
287
- ${when(this.loading,(()=>html`<div
288
- aria-label=${this.#L.term("loading")}
883
+ `;
884
+ })}
885
+ ${when(this.loading, () => {
886
+ return html `<div
887
+ aria-label=${this.#localize.term('loading')}
289
888
  class="loading-feedback"
290
889
  data-test="loading-feedback"
291
890
  id="loading-feedback"
292
891
  >
293
- ${map(range(7),(()=>html`<div></div>`))}
294
- </div>`))}
295
- ${when((this.hasNoAvailableOptions||this.hasNoMatchingOptions)&&!this.loading&&!this.isAddButtonVisible,(()=>html`<div data-test="optionless-feedback">
296
- ${this.hasNoAvailableOptions?this.#L.term("noAvailableOptions"):this.#L.term("noMatchingOptions")}
297
- </div>`))}
892
+ ${map(range(7), () => html `<div></div>`)}
893
+ </div>`;
894
+ })}
895
+ ${when((this.hasNoAvailableOptions || this.hasNoMatchingOptions) &&
896
+ !this.loading &&
897
+ !this.isAddButtonVisible, () => {
898
+ return html `<div data-test="optionless-feedback">
899
+ ${this.hasNoAvailableOptions
900
+ ? this.#localize.term('noAvailableOptions')
901
+ : this.#localize.term('noMatchingOptions')}
902
+ </div>`;
903
+ })}
298
904
  </div>
299
905
  </div>
300
906
 
301
907
  <div id="description" slot="description">
302
908
  <slot
303
- class=${classMap({description:!0,hidden:Boolean(this.#A&&this.validityMessage)})}
909
+ class=${classMap({
910
+ description: true,
911
+ hidden: Boolean(this.#isShowValidationFeedback && this.validityMessage),
912
+ })}
304
913
  name="description"
305
914
  >
306
915
  <!--
@@ -309,9 +918,1700 @@ var __decorate=this&&this.__decorate||function(t,e,i,s){var n,l=arguments.length
309
918
  -->
310
919
  </slot>
311
920
 
312
- ${when(this.#A&&this.validityMessage,(()=>html`<span class="validity-message" data-test="validity-message"
921
+ ${when(this.#isShowValidationFeedback && this.validityMessage, () => html `<span class="validity-message" data-test="validity-message"
313
922
  >${unsafeHTML(this.validityMessage)}</span
314
- >`))}
923
+ >`)}
315
924
  </div>
316
925
  </glide-core-private-label>
317
- </div>`}reportValidity(){this.isReportValidityOrSubmit=!0;const t=this.#c.reportValidity();return this.requestUpdate(),t}resetValidityFeedback(){this.isReportValidityOrSubmit=!1}setCustomValidity(t){this.validityMessage=t,""===t?this.#c.setValidity({customError:!1},"",this.filterable||this.isFilterable?this.#n.value:this.#u.value):this.#c.setValidity({customError:!0,valueMissing:this.#c.validity.valueMissing}," ",this.filterable||this.isFilterable?this.#n.value:this.#u.value)}setValidity(t,e){this.validityMessage=e,this.#c.setValidity(t," ",this.filterable||this.isFilterable?this.#n.value:this.#u.value)}constructor(){super(),this.addButton=!1,this.hideLabel=!1,this.loading=!1,this.name="",this.orientation="horizontal",this.readonly=!1,this.selectAll=!1,this.required=!1,this.version=packageJson.version,this.ariaActivedescendant="",this.hasNoAvailableOptions=!1,this.hasNoMatchingOptions=!1,this.inputValue="",this.isAddButtonActive=!1,this.isAddButtonVisible=!1,this.isBlurring=!1,this.isCheckingValidity=!1,this.isCommunicateItemCountToScreenreaders=!1,this.isFilterable=!1,this.isFiltering=!1,this.isInputOverflowing=!1,this.isInputTooltipOpen=!1,this.isInternalLabelOverflowing=!1,this.isInternalLabelTooltipOpen=!1,this.isReportValidityOrSubmit=!1,this.isShowSingleSelectIcon=!1,this.itemCount=0,this.selectedAndEnabledOptions=[],this.tagOverflowLimit=0,this.#lt=createRef(),this.#it=uniqueId(),this.#O=createRef(),this.#et=createRef(),this.#$=createRef(),this.#j=createRef(),this.#n=createRef(),this.#N=createRef(),this.#ot=!1,this.#t=!1,this.#at=!1,this.#s=!1,this.#dt=!0,this.#a=!1,this.#o=!1,this.#rt=!1,this.#pt=!1,this.#p=!1,this.#ht=!1,this.#L=new LocalizeController(this),this.#b=createRef(),this.#u=createRef(),this.#Y=createRef(),this.#I=createRef(),this.#r=[],this.#v=()=>{this.#ot?setTimeout((()=>{this.#ot=!1})):this.open=!1},this.#f=({formData:t})=>{this.name&&this.value.length>0&&!this.disabled&&t.append(this.name,JSON.stringify(this.value))},this.#c=this.attachInternals(),this.addEventListener("invalid",(t=>{if(t.preventDefault(),this.isCheckingValidity||this.isBlurring)return;this.isReportValidityOrSubmit=!0;this.form?.querySelector(":invalid")===this&&this.focus()}))}#lt;#it;#ct;#O;#et;#$;#j;#n;#N;#c;#ot;#t;#at;#s;#dt;#a;#o;#rt;#pt;#p;#ht;#L;#b;#ut;#u;#Y;#m;#I;#r;#v;#f;#e(){this.#ct?.(),this.#b.value?.hidePopover(),this.ariaActivedescendant="",this.activeOption&&(this.#ut=this.activeOption,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1)}get#A(){return!this.disabled&&!this.validity.valid&&this.isReportValidityOrSubmit}#nt(){this.isAddButtonActive=!0,this.#lt.value&&(this.ariaActivedescendant=this.#lt.value.id),this.activeOption&&(this.#ut=this.activeOption,this.activeOption.privateActive=!1)}#E(){this.#ot=!0}async#tt(){if(this.open){const t=this.#vt?.find((t=>!t.disabled));this.activeOption&&!this.activeOption?.disabled||!t||(this.#ut=t,this.ariaActivedescendant=t.id,t.privateActive=!0)}this.#Y.value&&(this.#Y.value.selected=this.areAllOptionsSelected),this.hasNoAvailableOptions=0===this.#d.length,this.selectedAndEnabledOptions=this.#d.filter((t=>t.selected&&!t.disabled)),this.#dt&&(this.isFilterable=this.#d.length>10,this.#dt=!1),this.multiple?(this.#r=this.selectedAndEnabledOptions.filter((({value:t})=>Boolean(t))).map((({value:t})=>t)),this.tagOverflowLimit=this.selectedAndEnabledOptions.length,this.#g()):(this.lastSelectedAndEnabledOption?.value&&(this.#r=[this.lastSelectedAndEnabledOption.value]),this.isShowSingleSelectIcon=Boolean(this.lastSelectedAndEnabledOption?.value),await this.updateComplete,this.#n.value&&this.lastSelectedAndEnabledOption?.label?(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label,this.isInputOverflowing=this.#n.value.scrollWidth>this.#n.value.clientWidth):this.#n.value&&!this.isFiltering&&(this.#n.value.value="",this.inputValue="",this.isAddButtonVisible=!1,this.isInputOverflowing=this.#n.value.scrollWidth>this.#n.value.clientWidth));for(const t of this.#d)t.privateMultiple=this.multiple,!this.multiple&&t.selected&&t.requestUpdate()}#y(t){(null===t.relatedTarget||t.relatedTarget instanceof Node&&!this.#m?.contains(t.relatedTarget)&&!this.contains(t.relatedTarget))&&!this.#at&&(this.open=!1,this.isBlurring=!0,this.reportValidity(),this.isBlurring=!1)}#w(t){if(this.disabled||this.readonly)return;if(("Enter"===t.key||" "===t.key)&&t.target===this.#j.value)return void(this.#ot=!0);if(!this.open&&"Enter"===t.key&&!this.#at)return void this.form?.requestSubmit();if("Escape"===t.key)return t.preventDefault(),void(this.open=!1);const e=t.target===this.#u.value||t.target===this.#n.value||t.target instanceof DropdownOption;if(!this.multiple||e){if(!this.open&&[" ","ArrowUp","ArrowDown"].includes(t.key))return t.preventDefault(),this.open=!0,void(this.activeOption&&(this.activeOption.privateIsTooltipOpen=!this.activeOption.privateIsEditActive));if(this.isAddButtonActive&&this.open){if("ArrowUp"===t.key&&t.metaKey||["Home","PageUp"].includes(t.key)){t.preventDefault();const e=this.#mt?.at(0);e&&(e.privateActive=!0,e.privateIsTooltipOpen=!e.editable,this.isAddButtonActive=!1,this.ariaActivedescendant=e.id,e.scrollIntoView())}if("ArrowUp"===t.key){t.preventDefault();const e=this.#ut&&!this.#ut.hidden?this.#ut:this.#mt?.at(-1);e&&(e.privateActive=!0,e.privateIsEditActive=e.editable,e.privateIsTooltipOpen=!e.editable,this.isAddButtonActive=!1,this.ariaActivedescendant=e.id)}"Enter"===t.key&&this.#st()}else if(this.activeOption&&this.open){if("Enter"===t.key||" "===t.key){if(this.activeOption.privateIsEditActive)return this.activeOption.privateEdit(),void(this.open=!1);if("Enter"===t.key&&this.#mt&&this.#mt.length>0||" "===t.key&&!this.filterable&&!this.isFilterable)return this.#ht=!0,t.preventDefault(),this.activeOption.selected=!this.multiple||!this.activeOption.selected,this.activeOption===this.#Y.value&&this.#ft(),this.#ht=!1,this.#l(),this.multiple?(this.#n.value&&(this.#n.value.value=""),this.inputValue=""):(this.#n.value&&void 0!==this.activeOption.label&&(this.#n.value.value=this.activeOption.label,this.inputValue=this.activeOption.label,this.isInputOverflowing=this.#n.value.scrollWidth-1>this.#n.value.clientWidth),this.open=!1,this.isInputTooltipOpen=!1),this.dispatchEvent(new Event("input",{bubbles:!0,composed:!0})),void this.dispatchEvent(new Event("change",{bubbles:!0,composed:!0}))}const e=this.#vt?.indexOf(this.activeOption);if("ArrowUp"===t.key&&!t.metaKey&&this.#vt&&"number"==typeof e){t.preventDefault();const i=this.#vt.findLast(((t,i)=>!t.disabled&&i<e));return void(this.activeOption?.privateIsEditActive?(this.activeOption.privateIsEditActive=!1,this.activeOption.privateIsTooltipOpen=!0):i&&(this.#ut=this.activeOption,this.activeOption.privateIsEditActive=!1,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1,this.ariaActivedescendant=i.id,i.privateActive=!0,i.privateIsEditActive=i.editable,i.privateIsTooltipOpen=!i.editable,i.scrollIntoView({block:"center"})))}if("ArrowDown"===t.key&&!t.metaKey&&this.#vt&&"number"==typeof e){t.preventDefault();const i=this.#vt.find(((t,i)=>!t.disabled&&i>e));return void(this.activeOption.editable&&!this.activeOption.privateIsEditActive?(this.activeOption.privateIsEditActive=!0,this.activeOption.privateIsTooltipOpen=!1):i?(this.#ut=this.activeOption,this.activeOption.privateIsEditActive=!1,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1,this.ariaActivedescendant=i.id,i.privateActive=!0,i.privateIsTooltipOpen=!0,i.scrollIntoView({block:"center"})):this.isAddButtonVisible&&this.#lt.value&&(this.#ut=this.activeOption,this.activeOption.privateIsEditActive=!1,this.activeOption.privateActive=!1,this.isAddButtonActive=!0,this.ariaActivedescendant=this.#lt.value.id))}if(("ArrowUp"===t.key&&t.metaKey||"Home"===t.key||"PageUp"===t.key)&&this.#vt){t.preventDefault();const e=[...this.#vt].reverse().findLast((t=>!t.disabled));return void(e&&(this.#ut=this.activeOption,this.activeOption.privateIsEditActive=!1,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1,this.ariaActivedescendant=e.id,e.privateActive=!0,e.privateIsTooltipOpen=!0,e.scrollIntoView()))}if(("ArrowDown"===t.key&&t.metaKey||"End"===t.key||"PageDown"===t.key)&&this.#vt){t.preventDefault();const e=[...this.#vt].findLast((t=>!t.disabled));return void(this.isAddButtonVisible&&this.#lt.value?(this.#ut=this.activeOption,this.activeOption.privateIsEditActive=!1,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1,this.isAddButtonActive=!0,this.ariaActivedescendant=this.#lt.value.id):e&&this.activeOption&&(this.#ut=this.activeOption,this.activeOption.privateIsEditActive=!1,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1,this.ariaActivedescendant=e.id,e.privateActive=!0,e.privateIsTooltipOpen=!0,e.scrollIntoView()))}}}}#R(t){if(this.disabled||this.readonly)return;if(this.#at)return void(this.#at=!1);if(t.target instanceof Node&&this.#j.value?.contains(t.target))return void this.lastSelectedAndEnabledOption?.privateEdit();const e=this.filterable||this.isFilterable;if(this.#ht||!this.open||e&&!(t.target instanceof Element&&this.#u.value?.contains(t.target)))return 0!==t.detail?(this.open=!0,void this.focus()):void 0;this.open=!1}#S(t){const e=this.filterable||this.isFilterable,i=t.target instanceof Tag;e&&!i?t.target!==this.#n.value&&(t.preventDefault(),this.focus()):i||t.preventDefault()}#q(){this.open=!1}#F(){this.isCommunicateItemCountToScreenreaders=!1,this.isInputTooltipOpen=!1}#V(){this.#n.value?.select(),this.isInputTooltipOpen=!0}async#_(t){let e;if(t.stopPropagation(),this.open=!0,this.#n.value&&(this.inputValue=this.#n.value.value),this.multiple&&""!==this.#n.value?.value?this.isFiltering=!0:this.multiple?this.isFiltering=!1:""!==this.#n.value?.value&&this.#n.value?.value!==this.lastSelectedAndEnabledOption?.label?(this.isFiltering=!0,this.isShowSingleSelectIcon=!1):(this.isFiltering=!1,this.isShowSingleSelectIcon=!1),this.#n.value){this.isAddButtonVisible=this.addButton&&this.#n.value?.value.trim().length>0&&!this.#d.some((({label:t})=>this.#n.value&&t?.toLowerCase()===this.#n.value.value.toLowerCase().trim()));try{e=await this.filter(this.#n.value.value)}catch{}}if(e)for(const t of this.#d)t.hidden=!e.includes(t);if(this.isCommunicateItemCountToScreenreaders=!0,this.#mt&&(this.itemCount=this.isAddButtonVisible?this.#mt.length+1:this.#mt.length),this.hasNoMatchingOptions=0===this.#mt?.length,this.hasNoMatchingOptions)return void(this.addButton?(this.isAddButtonActive=!0,this.activeOption&&this.#lt.value&&(this.#ut=this.activeOption,this.activeOption.privateActive=!1,this.ariaActivedescendant=this.#lt.value.id)):(this.activeOption&&(this.#ut=this.activeOption,this.activeOption.privateActive=!1),this.ariaActivedescendant=""));const i=this.#mt?.find((({disabled:t})=>!t));if(this.isAddButtonActive&&!this.isAddButtonVisible&&this.#ut&&!this.#ut.hidden&&!this.#ut.disabled)return this.isAddButtonActive=!1,this.#ut.privateActive=!0,void(this.ariaActivedescendant=this.#ut.id);if(this.isAddButtonActive&&!this.isAddButtonVisible&&i)return i.privateActive=!0,this.isAddButtonActive=!1,void(this.ariaActivedescendant=i.id);if((!this.activeOption||this.activeOption?.hidden||this.activeOption?.disabled)&&this.#ut&&!this.#ut.hidden&&!this.#ut.disabled){const t=this.#ut;return this.activeOption&&(this.#ut=this.activeOption,this.#ut.privateActive=!1),t.privateActive=!0,void(this.ariaActivedescendant=t.id)}return this.activeOption?.hidden&&i?(this.#ut=this.activeOption,this.activeOption.privateActive=!1,this.ariaActivedescendant=i.id,void(i.privateActive=!0)):void 0}#C(t){const e=this.selectedAndEnabledOptions.findLast(((t,e)=>e<=this.tagOverflowLimit-1));if(e&&"Backspace"===t.key&&!t.metaKey&&this.multiple&&this.#n.value&&0===this.#n.value.selectionStart)return this.#at=!0,e.selected=!1,void(this.#at=!1);const i=this.selectedAndEnabledOptions.filter(((t,e)=>e<=this.tagOverflowLimit-1));if(e&&"Backspace"===t.key&&t.metaKey&&this.multiple&&this.#n.value&&0===this.#n.value.selectionStart){this.#at=!0;for(const t of i)t.selected=!1;this.#at=!1}else;}#T(){this.#n.value&&(this.isInputOverflowing=this.#n.value.scrollWidth>this.#n.value.clientWidth)}#M(){this.#N.value&&(this.isInternalLabelOverflowing=this.#N.value.scrollWidth>this.#N.value.clientWidth)}get#d(){return this.#et.value?.assignedElements().filter((t=>t instanceof DropdownOption))??[]}get#h(){const t=this.#d;return this.#Y.value&&t.unshift(this.#Y.value),t}get#mt(){return this.#et.value?.assignedElements().filter((t=>t instanceof DropdownOption&&!t.hidden))}get#vt(){const t=this.#mt;return this.#Y.value&&!this.#Y.value.hidden&&t?.unshift(this.#Y.value),t}#z(t){t.target instanceof DropdownOption&&(t.target.selected=!t.target.selected),t.target===this.#Y.value&&this.#ft(),this.#n.value&&(this.#n.value.value=""),this.inputValue="",this.#l()}#U(t){if(t.target instanceof Element){const e=t.target.closest("glide-core-dropdown-option");if(e instanceof DropdownOption&&e.disabled)return;if(e instanceof DropdownOption&&e.privateIsEditActive)return e.privateEdit(),void(this.open=!1);if(e&&!e.selected)return e.selected=!0,this.#l(),this.open=!1,this.isInputTooltipOpen=!1,this.#n.value&&void 0!==e.label&&(this.#n.value.value=e.label,this.inputValue=e.label,this.isInputOverflowing=this.#n.value.scrollWidth-1>this.#n.value.clientWidth),this.dispatchEvent(new Event("input",{bubbles:!0,composed:!0})),void this.dispatchEvent(new Event("change",{bubbles:!0,composed:!0}));if(e?.selected&&!this.multiple)return void(this.open=!1)}}#J(t){if(this.multiple&&t.target instanceof DropdownOption&&t.target.disabled){if(t.target.selected&&(this.#r=this.#r.filter(((e,i)=>i!==this.selectedAndEnabledOptions.indexOf(t.target))),this.selectedAndEnabledOptions=this.selectedAndEnabledOptions.filter((e=>e!==t.target)),this.#g()),t.target.privateActive){t.target.privateActive=!1;const e=this.#d.find((({disabled:t})=>!t));e&&(e.privateActive=!0,this.#ut=e,this.ariaActivedescendant=e.id)}}else if(t.target instanceof DropdownOption&&t.target.disabled){if(this.selectedAndEnabledOptions=this.selectedAndEnabledOptions.filter((e=>e!==t.target)),this.#r=this.lastSelectedAndEnabledOption?.value?[this.lastSelectedAndEnabledOption.value]:[],t.target.privateActive){t.target.privateActive=!1;const e=this.#d.find((({disabled:t})=>!t));e&&(e.privateActive=!0,this.#ut=e,this.ariaActivedescendant=e.id)}this.#n.value&&(this.#n.value.value=this.lastSelectedAndEnabledOption?.label??"",this.inputValue=this.lastSelectedAndEnabledOption?.label??"",this.isInputOverflowing=this.#n.value.scrollWidth-1>this.#n.value.clientWidth);for(const e of this.#d)e.selected&&e!==t.target&&e.requestUpdate()}else if(this.multiple&&t.target instanceof DropdownOption&&t.target.selected)t.target.value&&this.#r.push(t.target.value),this.selectedAndEnabledOptions=[...this.selectedAndEnabledOptions,t.target],this.#g();else if(t.target instanceof DropdownOption&&t.target.selected&&void 0!==t.target.label){this.selectedAndEnabledOptions=[...this.selectedAndEnabledOptions,t.target],this.#r=t.target===this.lastSelectedAndEnabledOption&&t.target.value?[t.target.value]:[];for(const e of this.#d)e.selected&&e!==t.target&&e.requestUpdate();this.#n.value&&(this.#n.value.value=t.target.label,this.inputValue=t.target.label,this.isInputOverflowing=this.#n.value.scrollWidth-1>this.#n.value.clientWidth)}}#G(){this.requestUpdate()}#W(t){t.target instanceof DropdownOption&&(this.activeOption&&(this.activeOption.privateActive=!1),t.target.privateActive=!0,this.#ut=t.target)}#Q(){this.selectedAndEnabledOptions.length>0&&(this.multiple?this.requestUpdate():(this.filterable||this.isFilterable)&&this.#n.value&&this.lastSelectedAndEnabledOption?.label?(this.#n.value.value=this.lastSelectedAndEnabledOption.label,this.inputValue=this.lastSelectedAndEnabledOption.label,this.isInputOverflowing=this.#n.value.scrollWidth>this.#n.value.clientWidth):this.requestUpdate())}#P(t){(this.filterable||this.isFilterable)&&t.preventDefault()}#K(t){if(t.target instanceof DropdownOption&&this.#vt){if(t.target.disabled)return;this.activeOption&&(this.#ut=this.activeOption,this.activeOption.privateIsTooltipOpen=!1,this.activeOption.privateActive=!1),this.ariaActivedescendant=t.target.id,this.isAddButtonActive=!1,t.target.privateActive=!0,t.target.privateIsEditActive=!1}}#Z(t){if(!this.#pt){if(this.#Y.value&&(this.#Y.value.selected=this.areAllOptionsSelected),t.target instanceof DropdownOption){const e=this.selectedAndEnabledOptions.every((e=>e!==t.target));if(this.multiple)t.target.selected?(t.target.disabled&&(t.target.disabled=!1),e&&(this.selectedAndEnabledOptions=[...this.selectedAndEnabledOptions,t.target],this.#p||(this.#r=[...this.value,t.target.value]))):(this.#p||(this.#r=this.#r.filter(((e,i)=>i!==this.selectedAndEnabledOptions.indexOf(t.target)))),this.selectedAndEnabledOptions=this.selectedAndEnabledOptions.filter((e=>e!==t.target)));else if(!this.multiple)if(t.target.selected){t.target.disabled&&(t.target.disabled=!1),e&&(this.selectedAndEnabledOptions=[...this.selectedAndEnabledOptions,t.target]);for(const e of this.#d)e!==t.target&&(e.selected=!1);this.#p||(this.#r=[t.target.value])}else this.selectedAndEnabledOptions=this.selectedAndEnabledOptions.filter((e=>e!==t.target)),this.#p||(this.#r=this.lastSelectedAndEnabledOption?.value?[this.lastSelectedAndEnabledOption.value]:[])}this.multiple&&this.#g()}}#X(t){t.target instanceof DropdownOption&&this.multiple&&t.target.selected&&t.detail.new?this.#r=this.value.map((e=>e===t.detail.old?t.detail.new:e)):t.target instanceof DropdownOption&&this.multiple?this.#r=this.value.filter((e=>e!==t.detail.old)):t.target instanceof DropdownOption&&(this.#r=t.detail.new?[t.detail.new]:[])}#H(){this.isInternalLabelTooltipOpen=!0}#x(){this.isInternalLabelTooltipOpen=!1}#D(){this.#at=!0,this.open=!1}async#B(t){this.#at=!0,t.selected=!1;const e=this.#I.value?.querySelectorAll("glide-core-tag");if(e&&this.selectedAndEnabledOptions.length>0){const i=[...e].findIndex((e=>e.dataset.id===t.id)),s=e[i<e.length-1?i+1:i-1];await this.updateComplete,setTimeout((()=>{s?.focus(),this.#at=!1}))}else setTimeout((()=>{this.focus(),this.#at=!1}));this.dispatchEvent(new Event("input",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("change",{bubbles:!0,composed:!0}))}#k(t){t.stopPropagation()}#st(){this.#n.value&&(this.dispatchEvent(new CustomEvent("add",{bubbles:!0,composed:!0,detail:this.#n.value.value})),this.#n.value.value="",this.inputValue="");const t=this.#d.at(0);t&&(t.privateActive=!0,t.scrollIntoView(),this.ariaActivedescendant=t.id),this.multiple||(this.open=!1,this.isInputTooltipOpen=!1),this.#l(),this.focus()}#ft(){this.#pt=!0;for(const t of this.#d)!this.#Y.value?.selected||t.selected||t.disabled?!this.#Y.value?.selected&&t.selected&&(t.selected=!1):t.selected=!0;this.#pt=!1,this.selectedAndEnabledOptions=this.#d.filter((t=>t.selected&&!t.disabled)),this.#r=this.selectedAndEnabledOptions.map((({value:t})=>t)),this.#g()}async#g(){if(await this.updateComplete,this.#O.value){const t=this.#O.value.scrollWidth>this.#O.value.clientWidth;t&&this.tagOverflowLimit>1?(this.tagOverflowLimit=this.tagOverflowLimit-1,await this.updateComplete,this.#g()):!t&&!this.#rt&&this.tagOverflowLimit<this.selectedAndEnabledOptions.length?(this.#rt=!0,this.tagOverflowLimit=this.tagOverflowLimit+1,this.#g()):this.#rt=!1}}#i(){this.#ct?.(),this.#mt&&(this.itemCount=this.#mt.length),this.hasNoAvailableOptions=0===this.#d.length;const t=this.#vt?.find((t=>!t.disabled));!this.#ut||this.#ut.hidden||this.#ut.disabled?t&&(t.privateActive=!0,this.ariaActivedescendant=t.id,this.#ut=t):(this.#ut.privateActive=!0,this.ariaActivedescendant=this.#ut.id),this.#$.value&&this.#b.value&&(this.#ct=autoUpdate(this.#$.value,this.#b.value,(()=>{(async()=>{if(this.#$.value&&this.#b.value){const{x:t,y:e,placement:i}=await computePosition(this.#$.value,this.#b.value,{placement:"bottom-start",middleware:[offset({mainAxis:Number.parseFloat(window.getComputedStyle(document.body).getPropertyValue("--glide-core-spacing-base-xxs"))*Number.parseFloat(window.getComputedStyle(document.documentElement).fontSize)}),flip()]});this.#b.value.dataset.placement=i,Object.assign(this.#b.value.style,{left:`${t}px`,top:`${e}px`}),this.#b.value?.showPopover()}})()})))}#l(){for(const t of this.#d)t.hidden=!1;this.isFiltering=!1,this.isAddButtonActive=!1,this.isAddButtonVisible=!1,this.hasNoMatchingOptions=!1,this.isShowSingleSelectIcon=Boolean(this.lastSelectedAndEnabledOption?.value)}};__decorate([property({reflect:!0}),required],Dropdown.prototype,"label",void 0),__decorate([property({attribute:"add-button",reflect:!0,type:Boolean})],Dropdown.prototype,"addButton",void 0),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"disabled",null),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"filterable",null),__decorate([property({attribute:"hide-label",reflect:!0,type:Boolean})],Dropdown.prototype,"hideLabel",void 0),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"loading",void 0),__decorate([property({reflect:!0,useDefault:!0})],Dropdown.prototype,"name",void 0),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"open",null),__decorate([property({reflect:!0,useDefault:!0})],Dropdown.prototype,"orientation",void 0),__decorate([property({reflect:!0})],Dropdown.prototype,"placeholder",void 0),__decorate([property()],Dropdown.prototype,"privateSplit",void 0),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"readonly",void 0),__decorate([property({attribute:"select-all",reflect:!0,type:Boolean})],Dropdown.prototype,"selectAll",void 0),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"required",void 0),__decorate([property({reflect:!0,type:Boolean})],Dropdown.prototype,"multiple",null),__decorate([property({reflect:!0})],Dropdown.prototype,"tooltip",void 0),__decorate([property({type:Array})],Dropdown.prototype,"value",null),__decorate([property({reflect:!0})],Dropdown.prototype,"variant",void 0),__decorate([property({reflect:!0})],Dropdown.prototype,"version",void 0),__decorate([state()],Dropdown.prototype,"ariaActivedescendant",void 0),__decorate([state()],Dropdown.prototype,"hasNoAvailableOptions",void 0),__decorate([state()],Dropdown.prototype,"hasNoMatchingOptions",void 0),__decorate([state()],Dropdown.prototype,"inputValue",void 0),__decorate([state()],Dropdown.prototype,"isAddButtonActive",void 0),__decorate([state()],Dropdown.prototype,"isAddButtonVisible",void 0),__decorate([state()],Dropdown.prototype,"isBlurring",void 0),__decorate([state()],Dropdown.prototype,"isCheckingValidity",void 0),__decorate([state()],Dropdown.prototype,"isCommunicateItemCountToScreenreaders",void 0),__decorate([state()],Dropdown.prototype,"isFilterable",void 0),__decorate([state()],Dropdown.prototype,"isFiltering",void 0),__decorate([state()],Dropdown.prototype,"isInputOverflowing",void 0),__decorate([state()],Dropdown.prototype,"isInputTooltipOpen",void 0),__decorate([state()],Dropdown.prototype,"isInternalLabelOverflowing",void 0),__decorate([state()],Dropdown.prototype,"isInternalLabelTooltipOpen",void 0),__decorate([state()],Dropdown.prototype,"isReportValidityOrSubmit",void 0),__decorate([state()],Dropdown.prototype,"isShowSingleSelectIcon",void 0),__decorate([state()],Dropdown.prototype,"itemCount",void 0),__decorate([state()],Dropdown.prototype,"selectedAndEnabledOptions",void 0),__decorate([state()],Dropdown.prototype,"tagOverflowLimit",void 0),__decorate([state()],Dropdown.prototype,"validityMessage",void 0),Dropdown=__decorate([customElement("glide-core-dropdown"),final],Dropdown);export default Dropdown;
926
+ </div>`;
927
+ }
928
+ reportValidity() {
929
+ this.isReportValidityOrSubmit = true;
930
+ const isValid = this.#internals.reportValidity();
931
+ // Ensures that getters referencing `this.validity.valid` are updated.
932
+ this.requestUpdate();
933
+ return isValid;
934
+ }
935
+ resetValidityFeedback() {
936
+ this.isReportValidityOrSubmit = false;
937
+ }
938
+ setCustomValidity(message) {
939
+ this.validityMessage = message;
940
+ if (message === '') {
941
+ this.#internals.setValidity({ customError: false }, '', this.filterable || this.isFilterable
942
+ ? this.#inputElementRef.value
943
+ : this.#primaryButtonElementRef.value);
944
+ }
945
+ else {
946
+ // A validation message is required but unused because we disable native
947
+ // validation feedback. And an empty string isn't allowed. Thus a single space.
948
+ this.#internals.setValidity({
949
+ customError: true,
950
+ valueMissing: this.#internals.validity.valueMissing,
951
+ }, ' ', this.filterable || this.isFilterable
952
+ ? this.#inputElementRef.value
953
+ : this.#primaryButtonElementRef.value);
954
+ }
955
+ }
956
+ setValidity(flags, message) {
957
+ this.validityMessage = message;
958
+ // A validation message is required but unused because we disable native
959
+ // validation feedback. And an empty string isn't allowed. Thus a single space.
960
+ this.#internals.setValidity(flags, ' ', this.filterable || this.isFilterable
961
+ ? this.#inputElementRef.value
962
+ : this.#primaryButtonElementRef.value);
963
+ }
964
+ constructor() {
965
+ super();
966
+ this.addButton = false;
967
+ this.hideLabel = false;
968
+ this.loading = false;
969
+ this.name = '';
970
+ this.orientation = 'horizontal';
971
+ this.readonly = false;
972
+ this.selectAll = false;
973
+ this.required = false;
974
+ this.version = packageJson.version;
975
+ this.ariaActivedescendant = '';
976
+ // Used to show feedback when there are no slotted options.
977
+ this.hasNoAvailableOptions = false;
978
+ // Used to show feedback when every option has been hidden by having been filtered
979
+ // out. The names for both this and `hasNoAvailableOptions` are not ideal. But both
980
+ // are downstream of the copy provided by Design.
981
+ this.hasNoMatchingOptions = false;
982
+ this.inputValue = '';
983
+ this.isAddButtonActive = false;
984
+ this.isAddButtonVisible = false;
985
+ this.isBlurring = false;
986
+ this.isCheckingValidity = false;
987
+ // `itemCount` isn't immediately communicated to screenreaders so the number of
988
+ // options isn't read twice when Dropdown receives focus. It's already read once
989
+ // without additional effort due to `role="combobox"` and
990
+ // `aria-controls="options"`.
991
+ //
992
+ // However, an updated count isn't read when the user filters, which is where
993
+ // `itemCount` comes in. `isCommunicateItemCountToScreenreaders` is used to toggle
994
+ // `itemCount` so it isn't read on focus.
995
+ this.isCommunicateItemCountToScreenreaders = false;
996
+ this.isFilterable = false;
997
+ this.isFiltering = false;
998
+ this.isInputOverflowing = false;
999
+ this.isInputTooltipOpen = false;
1000
+ this.isInternalLabelOverflowing = false;
1001
+ this.isInternalLabelTooltipOpen = false;
1002
+ this.isReportValidityOrSubmit = false;
1003
+ this.isShowSingleSelectIcon = false;
1004
+ // "optionCount" or similar is arguably more natural. But "item" is how VoiceOver
1005
+ // refers to options when Dropdown is initially focused. So that's what the text
1006
+ // is. And so the variable name follows the text.
1007
+ this.itemCount = 0;
1008
+ this.selectedAndEnabledOptions = [];
1009
+ this.tagOverflowLimit = 0;
1010
+ this.#addButtonElementRef = createRef();
1011
+ // Not dynamically generated in `render()` so it remains constant when referenced
1012
+ // by `this.ariaActiveDescendant`.
1013
+ this.#addButtonId = uniqueId();
1014
+ this.#componentElementRef = createRef();
1015
+ this.#defaultSlotElementRef = createRef();
1016
+ this.#dropdownElementRef = createRef();
1017
+ this.#editButtonElementRef = createRef();
1018
+ this.#inputElementRef = createRef();
1019
+ this.#internalLabelElementRef = createRef();
1020
+ // `#onDocumentClick()` listens for clicks in their capture phase. There's a
1021
+ // comment in that method explaining why. `#isComponentClick` is set in
1022
+ // `#onComponentMouseup()` before `#onDocumentClick()` is called so
1023
+ // `#onDocumentClick()` has the information it needs to decide if it should
1024
+ // close Dropdown.
1025
+ this.#isComponentClick = false;
1026
+ this.#isDisabled = false;
1027
+ // Used to prevent Dropdown from reopening on click immediately after it's
1028
+ // closed when a tag is edited. Also used to prevent form submissions when a
1029
+ // tag is edited via Enter.
1030
+ this.#isEditingOrRemovingTag = false;
1031
+ this.#isFilterable = false;
1032
+ // Used in `#onDefaultSlotChange()` to lock Dropdown into being automatically
1033
+ // filterable or not based on the number of options present on first render.
1034
+ this.#isFirstDefaultSlotChange = true;
1035
+ this.#isMultiple = false;
1036
+ this.#isOpen = false;
1037
+ // See `#setTagOverflowLimit()`.
1038
+ this.#isOverflowTest = false;
1039
+ // Used in `#onOptionsSelectedChange()` to guard against, among other things,
1040
+ // resetting Select All back to its previous value after Select All is selected
1041
+ // or deselected.
1042
+ this.#isSelectionFromSelectAllOrNone = false;
1043
+ // Used in `#onOptionsSelectedChange()` to guard against a loop of duplicate
1044
+ // additions to `this.value` when `#onOptionsSelectedChange()` is called as a
1045
+ // result of `this.value` being set.
1046
+ this.#isSelectionFromValueSetter = false;
1047
+ // Used in `#onDropdownClick()`, which is opens and closes Dropdown. Space and
1048
+ // Enter produce "click" events. This field gives `#onDropdownClick()` the
1049
+ // information it needs to guard against opening or closing when the click comes
1050
+ // from option selection or deselection.
1051
+ this.#isSelectionViaSpaceOrEnter = false;
1052
+ this.#localize = new LocalizeController(this);
1053
+ this.#optionsAndFeedbackElementRef = createRef();
1054
+ this.#primaryButtonElementRef = createRef();
1055
+ this.#selectAllElementRef = createRef();
1056
+ this.#tagsElementRef = createRef();
1057
+ this.#value = [];
1058
+ // An arrow function field instead of a method so `this` is closed over and set
1059
+ // to the component instead of `document`.
1060
+ this.#onDocumentClick = () => {
1061
+ if (this.#isComponentClick) {
1062
+ // If the click came from within Dropdown, Dropdown should stay open. But,
1063
+ // now that the click has happened, we need reset `#isComponentClick` so
1064
+ // a later click from outside of Dropdown results in Dropdown closing.
1065
+ //
1066
+ // Options with a Checkbox emit two "click" events for every "mouseup": one
1067
+ // from the `<label>`, another from input field. It's just how a `<label>`
1068
+ // with a form control works. A timeout is used to ensure both events have
1069
+ // been dispatched before `#isComponentClick` is reset.
1070
+ //
1071
+ // Checking that the click's `event.target` is an instance of `Dropdown`
1072
+ // or `DropdownOption` would be a lot simpler. But, when Dropdown is
1073
+ // inside of another web component, `event.target` will that component instead.
1074
+ setTimeout(() => {
1075
+ this.#isComponentClick = false;
1076
+ });
1077
+ }
1078
+ else {
1079
+ this.open = false;
1080
+ }
1081
+ };
1082
+ // An arrow function field instead of a method so `this` is closed over and
1083
+ // set to the component instead of the form.
1084
+ this.#onFormdata = ({ formData }) => {
1085
+ if (this.name && this.value.length > 0 && !this.disabled) {
1086
+ formData.append(this.name, JSON.stringify(this.value));
1087
+ }
1088
+ };
1089
+ this.#internals = this.attachInternals();
1090
+ // Event handlers on the host aren't great because consumers can remove them.
1091
+ // Unfortunately, the host is the only thing on which this event is dispatched
1092
+ // because it's the host that is form-associated.
1093
+ this.addEventListener('invalid', (event) => {
1094
+ event.preventDefault(); // Canceled so a native validation message isn't shown.
1095
+ // We only want to focus the input if the "invalid" event resulted from either:
1096
+ //
1097
+ // 1. A form submission.
1098
+ // 2. A call of `reportValidity()` that did not result from Checkbox's "blur"
1099
+ // event.
1100
+ if (this.isCheckingValidity || this.isBlurring) {
1101
+ return;
1102
+ }
1103
+ this.isReportValidityOrSubmit = true;
1104
+ const isFirstInvalidFormElement = this.form?.querySelector(':invalid') === this;
1105
+ if (isFirstInvalidFormElement) {
1106
+ // Canceling the event means Dropdown won't get focus, even if we were to use
1107
+ // `this.#internals.delegatesFocus`. So we have to focus manually.
1108
+ this.focus();
1109
+ }
1110
+ });
1111
+ }
1112
+ #addButtonElementRef;
1113
+ // Not dynamically generated in `render()` so it remains constant when referenced
1114
+ // by `this.ariaActiveDescendant`.
1115
+ #addButtonId;
1116
+ #cleanUpFloatingUi;
1117
+ #componentElementRef;
1118
+ #defaultSlotElementRef;
1119
+ #dropdownElementRef;
1120
+ #editButtonElementRef;
1121
+ #inputElementRef;
1122
+ #internalLabelElementRef;
1123
+ #internals;
1124
+ // `#onDocumentClick()` listens for clicks in their capture phase. There's a
1125
+ // comment in that method explaining why. `#isComponentClick` is set in
1126
+ // `#onComponentMouseup()` before `#onDocumentClick()` is called so
1127
+ // `#onDocumentClick()` has the information it needs to decide if it should
1128
+ // close Dropdown.
1129
+ #isComponentClick;
1130
+ #isDisabled;
1131
+ // Used to prevent Dropdown from reopening on click immediately after it's
1132
+ // closed when a tag is edited. Also used to prevent form submissions when a
1133
+ // tag is edited via Enter.
1134
+ #isEditingOrRemovingTag;
1135
+ #isFilterable;
1136
+ // Used in `#onDefaultSlotChange()` to lock Dropdown into being automatically
1137
+ // filterable or not based on the number of options present on first render.
1138
+ #isFirstDefaultSlotChange;
1139
+ #isMultiple;
1140
+ #isOpen;
1141
+ // See `#setTagOverflowLimit()`.
1142
+ #isOverflowTest;
1143
+ // Used in `#onOptionsSelectedChange()` to guard against, among other things,
1144
+ // resetting Select All back to its previous value after Select All is selected
1145
+ // or deselected.
1146
+ #isSelectionFromSelectAllOrNone;
1147
+ // Used in `#onOptionsSelectedChange()` to guard against a loop of duplicate
1148
+ // additions to `this.value` when `#onOptionsSelectedChange()` is called as a
1149
+ // result of `this.value` being set.
1150
+ #isSelectionFromValueSetter;
1151
+ // Used in `#onDropdownClick()`, which is opens and closes Dropdown. Space and
1152
+ // Enter produce "click" events. This field gives `#onDropdownClick()` the
1153
+ // information it needs to guard against opening or closing when the click comes
1154
+ // from option selection or deselection.
1155
+ #isSelectionViaSpaceOrEnter;
1156
+ #localize;
1157
+ #optionsAndFeedbackElementRef;
1158
+ // Used in various situations to reactivate the previously active option.
1159
+ #previouslyActiveOption;
1160
+ #primaryButtonElementRef;
1161
+ #selectAllElementRef;
1162
+ #shadowRoot;
1163
+ #tagsElementRef;
1164
+ #value;
1165
+ // An arrow function field instead of a method so `this` is closed over and set
1166
+ // to the component instead of `document`.
1167
+ #onDocumentClick;
1168
+ // An arrow function field instead of a method so `this` is closed over and
1169
+ // set to the component instead of the form.
1170
+ #onFormdata;
1171
+ #hide() {
1172
+ this.#cleanUpFloatingUi?.();
1173
+ this.#optionsAndFeedbackElementRef.value?.hidePopover();
1174
+ this.ariaActivedescendant = '';
1175
+ if (this.activeOption) {
1176
+ this.#previouslyActiveOption = this.activeOption;
1177
+ this.activeOption.privateIsTooltipOpen = false;
1178
+ this.activeOption.privateActive = false;
1179
+ }
1180
+ }
1181
+ get #isShowValidationFeedback() {
1182
+ return (!this.disabled && !this.validity.valid && this.isReportValidityOrSubmit);
1183
+ }
1184
+ #onAddButtonMouseover() {
1185
+ this.isAddButtonActive = true;
1186
+ if (this.#addButtonElementRef.value) {
1187
+ this.ariaActivedescendant = this.#addButtonElementRef.value.id;
1188
+ }
1189
+ if (this.activeOption) {
1190
+ this.#previouslyActiveOption = this.activeOption;
1191
+ this.activeOption.privateActive = false;
1192
+ }
1193
+ }
1194
+ #onComponentMouseup() {
1195
+ this.#isComponentClick = true;
1196
+ }
1197
+ async #onDefaultSlotChange() {
1198
+ if (this.open) {
1199
+ const firstEnabledOption = this.#optionElementsNotHiddenIncludingSelectAll?.find((option) => !option.disabled);
1200
+ // Setting the active option happens here and elsewhere instead of once in the
1201
+ // `open` setter because Dropdown's options may change when Dropdown is open.
1202
+ // For example, a developer may for whatever reason, may remove the active option
1203
+ // while Dropdown is open, leaving the user without one.
1204
+ if ((!this.activeOption || this.activeOption?.disabled) &&
1205
+ firstEnabledOption) {
1206
+ this.#previouslyActiveOption = firstEnabledOption;
1207
+ this.ariaActivedescendant = firstEnabledOption.id;
1208
+ firstEnabledOption.privateActive = true;
1209
+ }
1210
+ }
1211
+ if (this.#selectAllElementRef.value) {
1212
+ this.#selectAllElementRef.value.selected = this.areAllOptionsSelected;
1213
+ }
1214
+ // Set here in addition to in `#show()` because, every option may be removed by the
1215
+ // consumer while Dropdown is open. Many consumers do this while the user is
1216
+ // filtering.
1217
+ this.hasNoAvailableOptions = this.#optionElements.length === 0;
1218
+ this.selectedAndEnabledOptions = this.#optionElements.filter((option) => {
1219
+ return option.selected && !option.disabled;
1220
+ });
1221
+ if (this.#isFirstDefaultSlotChange) {
1222
+ // It's a requirement of Design for Dropdown to automatically become filterable
1223
+ // when there are more than 10 options. But it's also a bad user experience for
1224
+ // Dropdown to suddenly become unfilterable when a developer using Dropdown reduces
1225
+ // the number of options in the slot in response to the user filtering.
1226
+ //
1227
+ // So we lock in Dropdown as either filterable or unfilterable with the first slot
1228
+ // change. Consumers can still force filterability using the `filterable`
1229
+ // attribute.
1230
+ this.isFilterable = this.#optionElements.length > 10;
1231
+ this.#isFirstDefaultSlotChange = false;
1232
+ }
1233
+ if (this.multiple) {
1234
+ this.#value = this.selectedAndEnabledOptions
1235
+ .filter(({ value }) => Boolean(value))
1236
+ .map(({ value }) => value);
1237
+ // We set the overflow limit to show every tag initially. `#setTagOverflowLimit()`
1238
+ // then pares the limit back if necessary so no tags are overflowing.
1239
+ this.tagOverflowLimit = this.selectedAndEnabledOptions.length;
1240
+ this.#setTagOverflowLimit();
1241
+ }
1242
+ else {
1243
+ // With single-select, there's nothing to stop developers from adding a `selected`
1244
+ // attribute to more than one option. How native handles this when setting `value`
1245
+ // is to choose the last selected option, which seems reasonable.
1246
+ if (this.lastSelectedAndEnabledOption?.value) {
1247
+ this.#value = [this.lastSelectedAndEnabledOption.value];
1248
+ }
1249
+ this.isShowSingleSelectIcon = Boolean(this.lastSelectedAndEnabledOption?.value);
1250
+ // Dropdown becomes filterable if there are more than 10 options. But input field
1251
+ // won't have rendered yet given we just set `this.isFilterable` above. So we wait
1252
+ // for it to render (or not) before setting the value of input field.
1253
+ await this.updateComplete;
1254
+ if (this.#inputElementRef.value &&
1255
+ this.lastSelectedAndEnabledOption?.label) {
1256
+ this.#inputElementRef.value.value =
1257
+ this.lastSelectedAndEnabledOption.label;
1258
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
1259
+ this.isInputOverflowing =
1260
+ this.#inputElementRef.value.scrollWidth >
1261
+ this.#inputElementRef.value.clientWidth;
1262
+ // For the case where the selected option is programmatically removed from the DOM.
1263
+ // Without this, the value of the input field would still be set to the selected
1264
+ // option's `label`. And an ellipsis would still be shown if the `label` was long
1265
+ // enough to be truncated.
1266
+ //
1267
+ // We guard against `this.isFiltering` so we don't clear the user's filter query
1268
+ // when the user is filtering and an option is added or removed. This is
1269
+ // particularly helpful when consumers fetch and render options from the server in
1270
+ // response to their override of `this.filter()` being called.
1271
+ }
1272
+ else if (this.#inputElementRef.value && !this.isFiltering) {
1273
+ this.#inputElementRef.value.value = '';
1274
+ this.inputValue = '';
1275
+ this.isAddButtonVisible = false;
1276
+ this.isInputOverflowing =
1277
+ this.#inputElementRef.value.scrollWidth >
1278
+ this.#inputElementRef.value.clientWidth;
1279
+ }
1280
+ }
1281
+ for (const option of this.#optionElements) {
1282
+ // Both here and in the `this.multiple` setter because the setter isn't called when
1283
+ // an option is added to Dropdown after initial render.
1284
+ option.privateMultiple = this.multiple;
1285
+ if (!this.multiple && option.selected) {
1286
+ // When Dropdown is single-select, a Dropdown Option only appears as selected when
1287
+ // it's the last selected option because only the `value` of the last selected
1288
+ // option will be included in Dropdown's `value`. And what the user sees as
1289
+ // selected should always be the same as what's submitted with the form.
1290
+ //
1291
+ // Dropdown Options determine whether they're the last selected option (and thus
1292
+ // whether to show themselves as selected using a checkmark) via their internal
1293
+ // `lastSelectedAndEnabledOption` getter.
1294
+ //
1295
+ // An additional selected option can be added to Dropdown's default slot at any
1296
+ // time. And it may now be the last selected option. But what was the last selected
1297
+ // option won't know it's no longer the last. So we force selected options to
1298
+ // rerender.
1299
+ option.requestUpdate();
1300
+ }
1301
+ }
1302
+ }
1303
+ #onDropdownAndOptionsFocusout(event) {
1304
+ // If `event.relatedTarget` is `null`, the user has clicked an element outside
1305
+ // Dropdown that cannot receive focus. Otherwise, the user has either clicked
1306
+ // an element outside Dropdown that can receive focus or else has tabbed away
1307
+ // from Dropdown.
1308
+ const isFocusLost = event.relatedTarget === null ||
1309
+ (event.relatedTarget instanceof Node &&
1310
+ !this.#shadowRoot?.contains(event.relatedTarget) &&
1311
+ !this.contains(event.relatedTarget));
1312
+ if (isFocusLost && !this.#isEditingOrRemovingTag) {
1313
+ this.open = false;
1314
+ this.isBlurring = true;
1315
+ this.reportValidity();
1316
+ this.isBlurring = false;
1317
+ }
1318
+ }
1319
+ // This handler is on `.dropdown-and-options` instead of `.dropdown` because
1320
+ // options can receive focus via VoiceOver, and `.dropdown` won't emit a "keydown"
1321
+ // if an option is focused.
1322
+ #onDropdownAndOptionsKeydown(event) {
1323
+ if (this.disabled || this.readonly) {
1324
+ return;
1325
+ }
1326
+ // On Enter and Space, an "edit" event should be dispatched and Dropdown should
1327
+ // close. Both Enter and Space result in a "click" event. So no need to dispatch
1328
+ // "edit" here. It's dispatched in `#onDropdownClick`. And Dropdown is closed via
1329
+ // `#onEditButtonClick`.
1330
+ if ((event.key === 'Enter' || event.key === ' ') &&
1331
+ event.target === this.#editButtonElementRef.value) {
1332
+ this.#isComponentClick = true;
1333
+ return;
1334
+ }
1335
+ if (!this.open && event.key === 'Enter' && !this.#isEditingOrRemovingTag) {
1336
+ this.form?.requestSubmit();
1337
+ return;
1338
+ }
1339
+ if (event.key === 'Escape') {
1340
+ // Prevent Safari from leaving full screen.
1341
+ event.preventDefault();
1342
+ this.open = false;
1343
+ return;
1344
+ }
1345
+ const isFromPrimaryButtonOrInputOrAnOption = event.target === this.#primaryButtonElementRef.value ||
1346
+ event.target === this.#inputElementRef.value ||
1347
+ event.target instanceof DropdownOption;
1348
+ // If multiselect, and `event.target` isn't one of the above, then the event came
1349
+ // from a tag and focus is on the tag. Arrowing up or down then pressing Enter
1350
+ // would both remove the tag and select or deselect the active option or it would
1351
+ // open Dropdown, and neither of which is probably what the user would expect.
1352
+ // Similar situation for when a key is pressed and focus is on single-select's Edit
1353
+ // button.
1354
+ if (this.multiple && !isFromPrimaryButtonOrInputOrAnOption) {
1355
+ return;
1356
+ }
1357
+ if (!this.open && [' ', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
1358
+ // Prevents page scroll. Also prevents the insertion point moving to beginning or
1359
+ // end of the field and a space from being entered in addition to making the
1360
+ // options visible when Dropdown is filterable.
1361
+ event.preventDefault();
1362
+ this.open = true;
1363
+ if (this.activeOption) {
1364
+ this.activeOption.privateIsTooltipOpen =
1365
+ !this.activeOption.privateIsEditActive;
1366
+ }
1367
+ // The user almost certainly wasn't intending to do both open Dropdown and change
1368
+ // the active option in the case of ArrowUp or ArrowDown. Thus return. The user
1369
+ // can press ArrowUp or ArrowDown again to change the active option.
1370
+ return;
1371
+ }
1372
+ // `event.key` is checked in the below conditions instead of this one to trap every
1373
+ // key press in this condition, which returns, so they're not handled elsewhere.
1374
+ // Any other key, like ArrowDown and PageDown, not handled below is meant to have
1375
+ // no effect when the Add button is active.
1376
+ if (this.isAddButtonActive && this.open) {
1377
+ if ((event.key === 'ArrowUp' && event.metaKey) ||
1378
+ ['Home', 'PageUp'].includes(event.key)) {
1379
+ // Prevent page scroll. Also prevent the insertion point from moving to the
1380
+ // beginning of the field.
1381
+ event.preventDefault();
1382
+ const firstOption = this.#optionElementsNotHidden?.at(0);
1383
+ if (firstOption) {
1384
+ firstOption.privateActive = true;
1385
+ firstOption.privateIsTooltipOpen = !firstOption.editable;
1386
+ this.isAddButtonActive = false;
1387
+ this.ariaActivedescendant = firstOption.id;
1388
+ firstOption.scrollIntoView();
1389
+ }
1390
+ }
1391
+ if (event.key === 'ArrowUp') {
1392
+ // Prevent page scroll. Also prevent the insertion point from moving to the
1393
+ // beginning of the field.
1394
+ event.preventDefault();
1395
+ // The user can jump to the Add button from any other option is active by pressing
1396
+ // Meta + ArrowDown, PageDown, or End. So he may not have arrived on the Add button
1397
+ // via the last option. That's why, when `this.#previouslyActiveOption` is
1398
+ // available, we move the user back to it. That way he doesn't have arrow up
1399
+ // through what may be a long list of options to get back to where he was.
1400
+ const option = this.#previouslyActiveOption && !this.#previouslyActiveOption.hidden
1401
+ ? this.#previouslyActiveOption
1402
+ : this.#optionElementsNotHidden?.at(-1);
1403
+ if (option) {
1404
+ option.privateActive = true;
1405
+ option.privateIsEditActive = option.editable;
1406
+ option.privateIsTooltipOpen = !option.editable;
1407
+ this.isAddButtonActive = false;
1408
+ this.ariaActivedescendant = option.id;
1409
+ }
1410
+ }
1411
+ if (event.key === 'Enter') {
1412
+ this.#selectAddButton();
1413
+ }
1414
+ return;
1415
+ }
1416
+ if (this.activeOption && this.open) {
1417
+ if (event.key === 'Enter' || event.key === ' ') {
1418
+ if (this.activeOption.privateIsEditActive) {
1419
+ // Pressing Enter or Space when an option is active won't result in a
1420
+ // "click" event because options don't receive focus except with VoiceOver.
1421
+ // Otherwise, we'd dispatch this event from within Dropdown Option.
1422
+ //
1423
+ // A "click" event, on the other hand, is dispatched when an Edit button is
1424
+ // clicked. So Dropdown Option could dispatch "edit" in that case. But then
1425
+ // two components instead of one would be responsible for dispatching "edit".
1426
+ this.activeOption.privateEdit();
1427
+ this.open = false;
1428
+ return;
1429
+ }
1430
+ // Space is excluded when Dropdown is filterable because the user may want to
1431
+ // include a space in his filter and because he expects pressing Space to result
1432
+ // in a space. So we either cancel Space and let it select and deselect as when
1433
+ // Dropdown isn't filterable, or we let the user type it. Neither is ideal.
1434
+ if ((event.key === 'Enter' &&
1435
+ this.#optionElementsNotHidden &&
1436
+ this.#optionElementsNotHidden.length > 0) ||
1437
+ (event.key === ' ' && !this.filterable && !this.isFilterable)) {
1438
+ this.#isSelectionViaSpaceOrEnter = true;
1439
+ // Prevent the options from scrolling when a focused option is selected via Space
1440
+ // when using VoiceOver.
1441
+ event.preventDefault();
1442
+ this.activeOption.selected = this.multiple
1443
+ ? !this.activeOption.selected
1444
+ : true;
1445
+ if (this.activeOption === this.#selectAllElementRef.value) {
1446
+ this.#selectAllOrNone();
1447
+ }
1448
+ this.#isSelectionViaSpaceOrEnter = false;
1449
+ this.#unfilter();
1450
+ if (this.multiple) {
1451
+ if (this.#inputElementRef.value) {
1452
+ this.#inputElementRef.value.value = '';
1453
+ }
1454
+ this.inputValue = '';
1455
+ }
1456
+ else {
1457
+ if (this.#inputElementRef.value &&
1458
+ // `undefined` is guarded against only to satisfy the type system.
1459
+ // `this.activeOption.label` is guaranteed to be defined because it's required.
1460
+ this.activeOption.label !== undefined) {
1461
+ this.#inputElementRef.value.value = this.activeOption.label;
1462
+ this.inputValue = this.activeOption.label;
1463
+ // One is subtracted to account for an apparent Chrome bug where `scrollWidth`
1464
+ // is off by one relative to `clientWidth` when they should be the same. It
1465
+ // happens whenever the input field was overflowing and now isn't.
1466
+ this.isInputOverflowing =
1467
+ this.#inputElementRef.value.scrollWidth - 1 >
1468
+ this.#inputElementRef.value.clientWidth;
1469
+ }
1470
+ this.open = false;
1471
+ // `this.isInputTooltipOpen` is set to `true` when the input field receives
1472
+ // focus and `false` when it loses focus. It's currently `true`. But the
1473
+ // user just selected an option and so knows what the option's label is. So
1474
+ // there's no need to show a tooltip.
1475
+ this.isInputTooltipOpen = false;
1476
+ }
1477
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
1478
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
1479
+ return;
1480
+ }
1481
+ }
1482
+ const activeOptionIndex = this.#optionElementsNotHiddenIncludingSelectAll?.indexOf(this.activeOption);
1483
+ // All the logic below could just as well go in a "keydown" handler on each
1484
+ // Dropdown Option. It's here to mirror the tests, which necessarily test against
1485
+ // Dropdown as a whole because more than one option is required to test these
1486
+ // interactions.
1487
+ if (event.key === 'ArrowUp' &&
1488
+ !event.metaKey &&
1489
+ this.#optionElementsNotHiddenIncludingSelectAll &&
1490
+ typeof activeOptionIndex === 'number') {
1491
+ // Prevent page scroll. When filterable, also prevent the insertion point from
1492
+ // moving to the beginning of the field.
1493
+ event.preventDefault();
1494
+ const previousOption = this.#optionElementsNotHiddenIncludingSelectAll.findLast((option, index) => {
1495
+ return !option.disabled && index < activeOptionIndex;
1496
+ });
1497
+ if (this.activeOption?.privateIsEditActive) {
1498
+ this.activeOption.privateIsEditActive = false;
1499
+ this.activeOption.privateIsTooltipOpen = true;
1500
+ // If `previousOption` isn't defined, we've reached the top.
1501
+ }
1502
+ else if (previousOption) {
1503
+ this.#previouslyActiveOption = this.activeOption;
1504
+ this.activeOption.privateIsEditActive = false;
1505
+ this.activeOption.privateIsTooltipOpen = false;
1506
+ this.activeOption.privateActive = false;
1507
+ this.ariaActivedescendant = previousOption.id;
1508
+ previousOption.privateActive = true;
1509
+ previousOption.privateIsEditActive = previousOption.editable;
1510
+ previousOption.privateIsTooltipOpen = !previousOption.editable;
1511
+ // "center" so options before the current one are in view.
1512
+ previousOption.scrollIntoView({ block: 'center' });
1513
+ }
1514
+ return;
1515
+ }
1516
+ if (event.key === 'ArrowDown' &&
1517
+ !event.metaKey &&
1518
+ this.#optionElementsNotHiddenIncludingSelectAll &&
1519
+ typeof activeOptionIndex === 'number') {
1520
+ // Prevent page scroll. When filterable, also prevent the insertion point from
1521
+ // moving to the end of the field.
1522
+ event.preventDefault();
1523
+ const nextOption = this.#optionElementsNotHiddenIncludingSelectAll.find((option, index) => {
1524
+ return !option.disabled && index > activeOptionIndex;
1525
+ });
1526
+ if (this.activeOption.editable &&
1527
+ !this.activeOption.privateIsEditActive) {
1528
+ this.activeOption.privateIsEditActive = true;
1529
+ this.activeOption.privateIsTooltipOpen = false;
1530
+ // If `nextOption` isn't defined, we've reached the bottom.
1531
+ }
1532
+ else if (nextOption) {
1533
+ this.#previouslyActiveOption = this.activeOption;
1534
+ this.activeOption.privateIsEditActive = false;
1535
+ this.activeOption.privateIsTooltipOpen = false;
1536
+ this.activeOption.privateActive = false;
1537
+ this.ariaActivedescendant = nextOption.id;
1538
+ nextOption.privateActive = true;
1539
+ nextOption.privateIsTooltipOpen = true;
1540
+ // "center" so options after the current one are in view.
1541
+ nextOption.scrollIntoView({ block: 'center' });
1542
+ }
1543
+ else if (this.isAddButtonVisible && this.#addButtonElementRef.value) {
1544
+ this.#previouslyActiveOption = this.activeOption;
1545
+ this.activeOption.privateIsEditActive = false;
1546
+ this.activeOption.privateActive = false;
1547
+ this.isAddButtonActive = true;
1548
+ this.ariaActivedescendant = this.#addButtonElementRef.value.id;
1549
+ }
1550
+ return;
1551
+ }
1552
+ if (((event.key === 'ArrowUp' && event.metaKey) ||
1553
+ event.key === 'Home' ||
1554
+ event.key === 'PageUp') &&
1555
+ this.#optionElementsNotHiddenIncludingSelectAll) {
1556
+ // Prevent page scroll. When filterable, also prevent the insertion point from
1557
+ // moving to the beginning of the field.
1558
+ event.preventDefault();
1559
+ const firstOption = [...this.#optionElementsNotHiddenIncludingSelectAll]
1560
+ .reverse()
1561
+ .findLast((option) => !option.disabled);
1562
+ if (firstOption) {
1563
+ this.#previouslyActiveOption = this.activeOption;
1564
+ this.activeOption.privateIsEditActive = false;
1565
+ this.activeOption.privateIsTooltipOpen = false;
1566
+ this.activeOption.privateActive = false;
1567
+ this.ariaActivedescendant = firstOption.id;
1568
+ firstOption.privateActive = true;
1569
+ firstOption.privateIsTooltipOpen = true;
1570
+ firstOption.scrollIntoView();
1571
+ }
1572
+ return;
1573
+ }
1574
+ if (((event.key === 'ArrowDown' && event.metaKey) ||
1575
+ event.key === 'End' ||
1576
+ event.key === 'PageDown') &&
1577
+ this.#optionElementsNotHiddenIncludingSelectAll) {
1578
+ // Prevent page scroll. When filterable, also prevent the insertion point from
1579
+ // moving to the end of the field.
1580
+ event.preventDefault();
1581
+ const lastOption = [
1582
+ ...this.#optionElementsNotHiddenIncludingSelectAll,
1583
+ ].findLast((option) => !option.disabled);
1584
+ if (this.isAddButtonVisible && this.#addButtonElementRef.value) {
1585
+ this.#previouslyActiveOption = this.activeOption;
1586
+ this.activeOption.privateIsEditActive = false;
1587
+ this.activeOption.privateIsTooltipOpen = false;
1588
+ this.activeOption.privateActive = false;
1589
+ this.isAddButtonActive = true;
1590
+ this.ariaActivedescendant = this.#addButtonElementRef.value.id;
1591
+ // If `lastOption` isn't defined, we've reached the bottom.
1592
+ }
1593
+ else if (lastOption && this.activeOption) {
1594
+ this.#previouslyActiveOption = this.activeOption;
1595
+ this.activeOption.privateIsEditActive = false;
1596
+ this.activeOption.privateIsTooltipOpen = false;
1597
+ this.activeOption.privateActive = false;
1598
+ this.ariaActivedescendant = lastOption.id;
1599
+ lastOption.privateActive = true;
1600
+ lastOption.privateIsTooltipOpen = true;
1601
+ lastOption.scrollIntoView();
1602
+ }
1603
+ return;
1604
+ }
1605
+ }
1606
+ }
1607
+ #onDropdownClick(event) {
1608
+ if (this.disabled || this.readonly) {
1609
+ return;
1610
+ }
1611
+ if (this.#isEditingOrRemovingTag) {
1612
+ this.#isEditingOrRemovingTag = false;
1613
+ return;
1614
+ }
1615
+ if (event.target instanceof Node &&
1616
+ this.#editButtonElementRef.value?.contains(event.target)) {
1617
+ this.lastSelectedAndEnabledOption?.privateEdit();
1618
+ return;
1619
+ }
1620
+ const isFilterable = this.filterable || this.isFilterable;
1621
+ if (!this.#isSelectionViaSpaceOrEnter &&
1622
+ this.open &&
1623
+ (!isFilterable ||
1624
+ (event.target instanceof Element &&
1625
+ this.#primaryButtonElementRef.value?.contains(event.target)))) {
1626
+ this.open = false;
1627
+ return;
1628
+ }
1629
+ // `event.detail` is an integer set to the number of clicks. When it's zero,
1630
+ // the event most likely originated from an Enter press. And, if Dropdown is part
1631
+ // of a form, Enter should submit the form instead of opening Dropdown.
1632
+ if (event.detail !== 0) {
1633
+ this.open = true;
1634
+ // If Dropdown was opened because its primary button or `<input>` were clicked,
1635
+ // then Dropdown will already have focus. But if something else was clicked, like
1636
+ // the padding around Dropdown or a Tag, then it won't. So we focus it manually.
1637
+ this.focus();
1638
+ return;
1639
+ }
1640
+ }
1641
+ #onDropdownMousedown(event) {
1642
+ // Retain focus if anything inside Dropdown is about to be clicked, like the
1643
+ // padding around the component. Clicking the padding will move focus to
1644
+ // `document.body`, which is not what the user expects.
1645
+ //
1646
+ // Having to exclude tags is unfortunate because clicking on the tag's label or
1647
+ // padding shouldn't cause the input to lose focus. The trouble is we don't know
1648
+ // it if is the Tag's removal button that's being clicked. And if it is we have
1649
+ // to allow it to receive focus.
1650
+ const isFilterable = this.filterable || this.isFilterable;
1651
+ const isTag = event.target instanceof Tag;
1652
+ if (isFilterable && !isTag) {
1653
+ // If input field is about to be clicked, canceling the event would prevent the
1654
+ // insertion point from moving inside the input.
1655
+ if (event.target !== this.#inputElementRef.value) {
1656
+ event.preventDefault();
1657
+ this.focus();
1658
+ }
1659
+ }
1660
+ else if (!isTag) {
1661
+ event.preventDefault();
1662
+ }
1663
+ }
1664
+ #onEditButtonClick() {
1665
+ this.open = false;
1666
+ }
1667
+ #onInputBlur() {
1668
+ this.isCommunicateItemCountToScreenreaders = false;
1669
+ this.isInputTooltipOpen = false;
1670
+ }
1671
+ #onInputFocus() {
1672
+ this.#inputElementRef.value?.select();
1673
+ this.isInputTooltipOpen = true;
1674
+ }
1675
+ async #onInputInput(event) {
1676
+ // Allowing the event to propagate would break things for consumers or at least
1677
+ // confuse them. They expect "input" events only when an option is selected or
1678
+ // deselected.
1679
+ event.stopPropagation();
1680
+ this.open = true;
1681
+ if (this.#inputElementRef.value) {
1682
+ this.inputValue = this.#inputElementRef.value.value;
1683
+ }
1684
+ if (this.multiple && this.#inputElementRef.value?.value !== '') {
1685
+ this.isFiltering = true;
1686
+ }
1687
+ else if (this.multiple) {
1688
+ this.isFiltering = false;
1689
+ }
1690
+ else if (this.#inputElementRef.value?.value !== '' &&
1691
+ this.#inputElementRef.value?.value !==
1692
+ this.lastSelectedAndEnabledOption?.label) {
1693
+ this.isFiltering = true;
1694
+ this.isShowSingleSelectIcon = false;
1695
+ }
1696
+ else {
1697
+ this.isFiltering = false;
1698
+ this.isShowSingleSelectIcon = false;
1699
+ }
1700
+ let options;
1701
+ if (this.#inputElementRef.value) {
1702
+ this.isAddButtonVisible =
1703
+ this.addButton &&
1704
+ this.#inputElementRef.value?.value.trim().length > 0 &&
1705
+ !this.#optionElements.some(({ label }) => {
1706
+ // Design doesn't know of such a case. But it's possible that case sensitivity
1707
+ // matters to some consumers. Ignoring case sensitivity at the outset is the
1708
+ // best way to smoke that case out.
1709
+ return (this.#inputElementRef.value &&
1710
+ label?.toLowerCase() ===
1711
+ this.#inputElementRef.value.value.toLowerCase().trim());
1712
+ });
1713
+ try {
1714
+ // It would be convenient for consumers if we passed an array of options
1715
+ // as the second argument. The problem is consumers fetch and render new
1716
+ // options when filtering. So the array will become stale.
1717
+ options = await this.filter(this.#inputElementRef.value.value);
1718
+ // eslint-disable-next-line no-empty
1719
+ }
1720
+ catch { }
1721
+ }
1722
+ if (options) {
1723
+ for (const option of this.#optionElements) {
1724
+ option.hidden = !options.includes(option);
1725
+ }
1726
+ }
1727
+ this.isCommunicateItemCountToScreenreaders = true;
1728
+ if (this.#optionElementsNotHidden) {
1729
+ this.itemCount = this.isAddButtonVisible
1730
+ ? this.#optionElementsNotHidden.length + 1
1731
+ : this.#optionElementsNotHidden.length;
1732
+ }
1733
+ this.hasNoMatchingOptions =
1734
+ this.#optionElementsNotHidden?.length === 0 ? true : false;
1735
+ if (this.hasNoMatchingOptions) {
1736
+ // 1. Options are "One", "Two".
1737
+ // 2. Filter query is "".
1738
+ // 3. "One" is active.
1739
+ // 4. Filter query is now "three".
1740
+ // 5. The Add button is activated.
1741
+ if (this.addButton) {
1742
+ this.isAddButtonActive = true;
1743
+ if (this.activeOption && this.#addButtonElementRef.value) {
1744
+ this.#previouslyActiveOption = this.activeOption;
1745
+ this.activeOption.privateActive = false;
1746
+ this.ariaActivedescendant = this.#addButtonElementRef.value.id;
1747
+ }
1748
+ // 1. Options are "One", "Two".
1749
+ // 2. Filter query is "o".
1750
+ // 3. "One" is active.
1751
+ // 4. Filter query is now "three".
1752
+ // 5. "One" is deactivated.
1753
+ }
1754
+ else {
1755
+ if (this.activeOption) {
1756
+ this.#previouslyActiveOption = this.activeOption;
1757
+ this.activeOption.privateActive = false;
1758
+ }
1759
+ this.ariaActivedescendant = '';
1760
+ }
1761
+ return;
1762
+ }
1763
+ const firstEnabledAndVisibleOption = this.#optionElementsNotHidden?.find(({ disabled }) => !disabled);
1764
+ // 1. Options are "One", "Two".
1765
+ // 2. Filter query is "o".
1766
+ // 3. Previous active option is "Two".
1767
+ // 4. Add button is active.
1768
+ // 5. Filter query is now "".
1769
+ // 6. Add button is filtered out.
1770
+ // 7. "Two" is reactivated.
1771
+ if (this.isAddButtonActive &&
1772
+ !this.isAddButtonVisible &&
1773
+ this.#previouslyActiveOption &&
1774
+ !this.#previouslyActiveOption.hidden &&
1775
+ !this.#previouslyActiveOption.disabled) {
1776
+ this.isAddButtonActive = false;
1777
+ this.#previouslyActiveOption.privateActive = true;
1778
+ this.ariaActivedescendant = this.#previouslyActiveOption.id;
1779
+ return;
1780
+ }
1781
+ // 1. Options are "One", "Two", "Three", "Four".
1782
+ // 2. Filter query is "o".
1783
+ // 2. Previous active option is "Four".
1784
+ // 4. Filter query is now "ne".
1785
+ // 5. The Add button is filtered out.
1786
+ // 6. "Four" is filtered out.
1787
+ // 7. "One" is activated.
1788
+ if (this.isAddButtonActive &&
1789
+ !this.isAddButtonVisible &&
1790
+ firstEnabledAndVisibleOption) {
1791
+ firstEnabledAndVisibleOption.privateActive = true;
1792
+ this.isAddButtonActive = false;
1793
+ this.ariaActivedescendant = firstEnabledAndVisibleOption.id;
1794
+ return;
1795
+ }
1796
+ // 1. Options are "ABC", "AB", "A".
1797
+ // 2. Previous active option is "AB".
1798
+ // 3. Filter query is "abd". No active option.
1799
+ // 4. Filter query is now "ab".
1800
+ // 5. "AB" is activated.
1801
+ //
1802
+ // Or:
1803
+ //
1804
+ // 1. Options are "ABC", "AB", "A".
1805
+ // 2. Previous active option is "AB".
1806
+ // 3. Filter query is "".
1807
+ // 4. Active option is "A"
1808
+ // 5. Filter query is now "ab".
1809
+ // 6. "A" is filtered out.
1810
+ // 7. "AB" is activated.
1811
+ if ((!this.activeOption ||
1812
+ this.activeOption?.hidden ||
1813
+ this.activeOption?.disabled) &&
1814
+ this.#previouslyActiveOption &&
1815
+ !this.#previouslyActiveOption.hidden &&
1816
+ !this.#previouslyActiveOption.disabled) {
1817
+ const previouslyActiveOption = this.#previouslyActiveOption;
1818
+ if (this.activeOption) {
1819
+ this.#previouslyActiveOption = this.activeOption;
1820
+ this.#previouslyActiveOption.privateActive = false;
1821
+ }
1822
+ previouslyActiveOption.privateActive = true;
1823
+ this.ariaActivedescendant = previouslyActiveOption.id;
1824
+ return;
1825
+ }
1826
+ // 1. Options are "ABCD", "ABC", "AB", "A".
1827
+ // 2. "ABCD" is disabled.
1828
+ // 3. Previous active option is "AB".
1829
+ // 4. Active option is "A".
1830
+ // 5. Filter query was "".
1831
+ // 6. Filter query is now "abc".
1832
+ // 7. "A" is now hidden.
1833
+ // 8. "AB" is now hidden.
1834
+ // 9. "ABC" is activated.
1835
+ if (this.activeOption?.hidden && firstEnabledAndVisibleOption) {
1836
+ this.#previouslyActiveOption = this.activeOption;
1837
+ this.activeOption.privateActive = false;
1838
+ this.ariaActivedescendant = firstEnabledAndVisibleOption.id;
1839
+ firstEnabledAndVisibleOption.privateActive = true;
1840
+ return;
1841
+ }
1842
+ }
1843
+ #onInputKeydown(event) {
1844
+ // Deselecting an option the user can't see ain't good. So they're filtered out.
1845
+ // As the user deselects options, ones previously overflowing will be become
1846
+ // visible and thus deselectable using Backspace.
1847
+ const lastSelectedAndNotOverflowingOption = this.selectedAndEnabledOptions.findLast((_, index) => index <= this.tagOverflowLimit - 1);
1848
+ if (lastSelectedAndNotOverflowingOption &&
1849
+ event.key === 'Backspace' &&
1850
+ !event.metaKey &&
1851
+ this.multiple &&
1852
+ this.#inputElementRef.value &&
1853
+ this.#inputElementRef.value.selectionStart === 0) {
1854
+ this.#isEditingOrRemovingTag = true;
1855
+ lastSelectedAndNotOverflowingOption.selected = false;
1856
+ this.#isEditingOrRemovingTag = false;
1857
+ return;
1858
+ }
1859
+ const selectedAndNotOverflowingOptions = this.selectedAndEnabledOptions.filter((_, index) => index <= this.tagOverflowLimit - 1);
1860
+ if (lastSelectedAndNotOverflowingOption &&
1861
+ event.key === 'Backspace' &&
1862
+ event.metaKey &&
1863
+ this.multiple &&
1864
+ this.#inputElementRef.value &&
1865
+ this.#inputElementRef.value.selectionStart === 0) {
1866
+ this.#isEditingOrRemovingTag = true;
1867
+ for (const option of selectedAndNotOverflowingOptions) {
1868
+ option.selected = false;
1869
+ }
1870
+ this.#isEditingOrRemovingTag = false;
1871
+ return;
1872
+ }
1873
+ }
1874
+ #onInputResize() {
1875
+ if (this.#inputElementRef.value) {
1876
+ this.isInputOverflowing =
1877
+ this.#inputElementRef.value.scrollWidth >
1878
+ this.#inputElementRef.value.clientWidth;
1879
+ }
1880
+ }
1881
+ #onInternalLabelResize() {
1882
+ if (this.#internalLabelElementRef.value) {
1883
+ this.isInternalLabelOverflowing =
1884
+ this.#internalLabelElementRef.value.scrollWidth >
1885
+ this.#internalLabelElementRef.value.clientWidth;
1886
+ }
1887
+ }
1888
+ get #optionElements() {
1889
+ return (this.#defaultSlotElementRef.value
1890
+ ?.assignedElements()
1891
+ .filter((element) => element instanceof DropdownOption) ?? []);
1892
+ }
1893
+ get #optionElementsIncludingSelectAll() {
1894
+ const options = this.#optionElements;
1895
+ if (this.#selectAllElementRef.value) {
1896
+ options.unshift(this.#selectAllElementRef.value);
1897
+ }
1898
+ return options;
1899
+ }
1900
+ get #optionElementsNotHidden() {
1901
+ return this.#defaultSlotElementRef.value
1902
+ ?.assignedElements()
1903
+ .filter((element) => element instanceof DropdownOption && !element.hidden);
1904
+ }
1905
+ get #optionElementsNotHiddenIncludingSelectAll() {
1906
+ const options = this.#optionElementsNotHidden;
1907
+ if (this.#selectAllElementRef.value &&
1908
+ !this.#selectAllElementRef.value.hidden) {
1909
+ options?.unshift(this.#selectAllElementRef.value);
1910
+ }
1911
+ return options;
1912
+ }
1913
+ #onOptionsChange(event) {
1914
+ if (event.target instanceof DropdownOption) {
1915
+ event.target.selected = !event.target.selected;
1916
+ }
1917
+ if (event.target === this.#selectAllElementRef.value) {
1918
+ this.#selectAllOrNone();
1919
+ }
1920
+ if (this.#inputElementRef.value) {
1921
+ this.#inputElementRef.value.value = '';
1922
+ }
1923
+ this.inputValue = '';
1924
+ this.#unfilter();
1925
+ }
1926
+ #onOptionsClick(event) {
1927
+ if (event.target instanceof Element) {
1928
+ const option = event.target.closest('glide-core-dropdown-option');
1929
+ if (option instanceof DropdownOption && option.disabled) {
1930
+ return;
1931
+ }
1932
+ if (option instanceof DropdownOption && option.privateIsEditActive) {
1933
+ option.privateEdit();
1934
+ this.open = false;
1935
+ return;
1936
+ }
1937
+ if (option && !option.selected) {
1938
+ option.selected = true;
1939
+ this.#unfilter();
1940
+ this.open = false;
1941
+ // `this.isInputTooltipOpen` is set to `true` when the input field receives
1942
+ // focus and `false` when it loses focus. It's currently `true`. But the
1943
+ // user just selected an option and so knows what the option's label is. So
1944
+ // there's no need to show a tooltip.
1945
+ this.isInputTooltipOpen = false;
1946
+ // `undefined` is guarded against only to satisfy the type system. `option.label`
1947
+ // is guaranteed to be defined because it's required.
1948
+ if (this.#inputElementRef.value && option.label !== undefined) {
1949
+ this.#inputElementRef.value.value = option.label;
1950
+ this.inputValue = option.label;
1951
+ // One is subtracted to account for an apparent Chrome bug where `scrollWidth`
1952
+ // is off by one relative to `clientWidth` when they should be the same. It
1953
+ // happens whenever the input field was overflowing and now isn't.
1954
+ this.isInputOverflowing =
1955
+ this.#inputElementRef.value.scrollWidth - 1 >
1956
+ this.#inputElementRef.value.clientWidth;
1957
+ }
1958
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
1959
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
1960
+ return;
1961
+ }
1962
+ if (option?.selected && !this.multiple) {
1963
+ this.open = false;
1964
+ return;
1965
+ }
1966
+ }
1967
+ }
1968
+ #onOptionsDisabledChange(event) {
1969
+ if (this.multiple &&
1970
+ event.target instanceof DropdownOption &&
1971
+ event.target.disabled) {
1972
+ if (event.target.selected) {
1973
+ this.#value = this.#value.filter((_, index) => {
1974
+ return (index !==
1975
+ this.selectedAndEnabledOptions.indexOf(event.target));
1976
+ });
1977
+ this.selectedAndEnabledOptions = this.selectedAndEnabledOptions.filter((option) => option !== event.target);
1978
+ this.#setTagOverflowLimit();
1979
+ }
1980
+ if (event.target.privateActive) {
1981
+ event.target.privateActive = false;
1982
+ const firstEnabledOption = this.#optionElements.find(({ disabled }) => !disabled);
1983
+ if (firstEnabledOption) {
1984
+ firstEnabledOption.privateActive = true;
1985
+ this.#previouslyActiveOption = firstEnabledOption;
1986
+ this.ariaActivedescendant = firstEnabledOption.id;
1987
+ }
1988
+ }
1989
+ }
1990
+ else if (event.target instanceof DropdownOption &&
1991
+ event.target.disabled) {
1992
+ this.selectedAndEnabledOptions = this.selectedAndEnabledOptions.filter((option) => option !== event.target);
1993
+ this.#value = this.lastSelectedAndEnabledOption?.value
1994
+ ? [this.lastSelectedAndEnabledOption.value]
1995
+ : [];
1996
+ if (event.target.privateActive) {
1997
+ event.target.privateActive = false;
1998
+ const firstEnabledOption = this.#optionElements.find(({ disabled }) => !disabled);
1999
+ if (firstEnabledOption) {
2000
+ firstEnabledOption.privateActive = true;
2001
+ this.#previouslyActiveOption = firstEnabledOption;
2002
+ this.ariaActivedescendant = firstEnabledOption.id;
2003
+ }
2004
+ }
2005
+ if (this.#inputElementRef.value) {
2006
+ this.#inputElementRef.value.value =
2007
+ this.lastSelectedAndEnabledOption?.label ?? '';
2008
+ this.inputValue = this.lastSelectedAndEnabledOption?.label ?? '';
2009
+ // One is subtracted to account for an apparent Chrome bug where `scrollWidth`
2010
+ // is off by one relative to `clientWidth` when they should be the same. It
2011
+ // happens whenever the input field was overflowing and now isn't.
2012
+ this.isInputOverflowing =
2013
+ this.#inputElementRef.value.scrollWidth - 1 >
2014
+ this.#inputElementRef.value.clientWidth;
2015
+ }
2016
+ for (const option of this.#optionElements) {
2017
+ if (option.selected && option !== event.target) {
2018
+ // When Dropdown is single-select, a Dropdown Option should only appear selected
2019
+ // when it's the last selected option because only the `value` of the last selected
2020
+ // option will be included in Dropdown's `value`. And what the user sees as
2021
+ // selected should always be the same as what's submitted with the form.
2022
+ //
2023
+ // Dropdown Options determine whether they're the last selected option (and thus
2024
+ // whether to show themselves as selected using a checkmark) via their internal
2025
+ // `lastSelectedAndEnabledOption` getter.
2026
+ //
2027
+ // The previously last selected option may have been the one that was disabled
2028
+ // and another selected option may be enabled. But the enabled option won't know
2029
+ // it's now the last selected option. So we force selected options to rerender.
2030
+ option.requestUpdate();
2031
+ }
2032
+ }
2033
+ }
2034
+ else if (this.multiple &&
2035
+ event.target instanceof DropdownOption &&
2036
+ event.target.selected) {
2037
+ if (event.target.value) {
2038
+ this.#value.push(event.target.value);
2039
+ }
2040
+ this.selectedAndEnabledOptions = [
2041
+ ...this.selectedAndEnabledOptions,
2042
+ event.target,
2043
+ ];
2044
+ this.#setTagOverflowLimit();
2045
+ }
2046
+ else if (event.target instanceof DropdownOption &&
2047
+ event.target.selected &&
2048
+ // `undefined` is guarded against only to satisfy the type system.
2049
+ // `event.target.label` is guaranteed to be defined because it's required.
2050
+ event.target.label !== undefined) {
2051
+ this.selectedAndEnabledOptions = [
2052
+ ...this.selectedAndEnabledOptions,
2053
+ event.target,
2054
+ ];
2055
+ this.#value =
2056
+ event.target === this.lastSelectedAndEnabledOption && event.target.value
2057
+ ? [event.target.value]
2058
+ : [];
2059
+ for (const option of this.#optionElements) {
2060
+ if (option.selected && option !== event.target) {
2061
+ // When Dropdown is single-select, an option should only appear selected when it's
2062
+ // the last selected one because only the `value` of the last selected option will
2063
+ // be included in Dropdown's `value`. And what the user sees as selected should
2064
+ // always be the same as what's submitted with the form.
2065
+ //
2066
+ // Dropdown Options determine whether they're the last selected option (and thus
2067
+ // whether to show themselves as selected using a checkmark) via their internal
2068
+ // `lastSelectedAndEnabledOption` getter.
2069
+ //
2070
+ // What was previously the last selected and enabled option may no longer be the
2071
+ // last. But it won't know that because it doesn't know another option was enabled.
2072
+ // So we force selected options to rerender.
2073
+ option.requestUpdate();
2074
+ }
2075
+ }
2076
+ if (this.#inputElementRef.value) {
2077
+ this.#inputElementRef.value.value = event.target.label;
2078
+ this.inputValue = event.target.label;
2079
+ // One is subtracted to account for an apparent Chrome bug where `scrollWidth`
2080
+ // is off by one relative to `clientWidth` when they should be the same. It
2081
+ // happens whenever the input field was overflowing and now isn't.
2082
+ this.isInputOverflowing =
2083
+ this.#inputElementRef.value.scrollWidth - 1 >
2084
+ this.#inputElementRef.value.clientWidth;
2085
+ }
2086
+ }
2087
+ }
2088
+ #onOptionsEditableChange() {
2089
+ // Dropdown doesn't know to rerender when an option's `editable` property
2090
+ // has changed. But it needs to rerender to show or hide its edit button
2091
+ // or else to set or unset its Tags as editable in the case of multiselect.
2092
+ // So a rerender is forced.
2093
+ this.requestUpdate();
2094
+ }
2095
+ // Options don't receive focus normally but can receive programmatic focus from
2096
+ // screen readers.
2097
+ #onOptionsFocusin(event) {
2098
+ if (event.target instanceof DropdownOption) {
2099
+ if (this.activeOption) {
2100
+ this.activeOption.privateActive = false;
2101
+ }
2102
+ event.target.privateActive = true;
2103
+ this.#previouslyActiveOption = event.target;
2104
+ }
2105
+ }
2106
+ #onOptionsLabelChange() {
2107
+ if (this.selectedAndEnabledOptions.length > 0) {
2108
+ if (this.multiple) {
2109
+ // The option's label has changed and is reactive. But it's a separate component.
2110
+ // So Dropdown won't know to rerender its tags unless we tell it to.
2111
+ this.requestUpdate();
2112
+ }
2113
+ else if ((this.filterable || this.isFilterable) &&
2114
+ this.#inputElementRef.value &&
2115
+ this.lastSelectedAndEnabledOption?.label) {
2116
+ this.#inputElementRef.value.value =
2117
+ this.lastSelectedAndEnabledOption.label;
2118
+ this.inputValue = this.lastSelectedAndEnabledOption.label;
2119
+ this.isInputOverflowing =
2120
+ this.#inputElementRef.value.scrollWidth >
2121
+ this.#inputElementRef.value.clientWidth;
2122
+ }
2123
+ else {
2124
+ // The option's label has changed and is reactive. But it's a separate component.
2125
+ // So Dropdown won't know to rerun its `internalLabel` getter and rerender unless
2126
+ // we tell it to.
2127
+ this.requestUpdate();
2128
+ }
2129
+ }
2130
+ }
2131
+ #onOptionsMousedown(event) {
2132
+ // Keep focus on the input so the user can continue filtering while selecting
2133
+ // options.
2134
+ if (this.filterable || this.isFilterable) {
2135
+ event.preventDefault();
2136
+ }
2137
+ }
2138
+ #onOptionsMouseover(event) {
2139
+ if (event.target instanceof DropdownOption &&
2140
+ this.#optionElementsNotHiddenIncludingSelectAll) {
2141
+ if (event.target.disabled) {
2142
+ return;
2143
+ }
2144
+ if (this.activeOption) {
2145
+ this.#previouslyActiveOption = this.activeOption;
2146
+ // The currently active option may have been arrowed to and its tooltip forced
2147
+ // open. Normally, its tooltip would be closed when the user arrows again. But
2148
+ // now the user is using a mouse. So we force it closed.
2149
+ this.activeOption.privateIsTooltipOpen = false;
2150
+ this.activeOption.privateActive = false;
2151
+ }
2152
+ this.ariaActivedescendant = event.target.id;
2153
+ this.isAddButtonActive = false;
2154
+ event.target.privateActive = true;
2155
+ event.target.privateIsEditActive = false;
2156
+ }
2157
+ }
2158
+ #onOptionsSelectedChange(event) {
2159
+ if (this.#isSelectionFromSelectAllOrNone) {
2160
+ return;
2161
+ }
2162
+ if (this.#selectAllElementRef.value) {
2163
+ this.#selectAllElementRef.value.selected = this.areAllOptionsSelected;
2164
+ }
2165
+ if (event.target instanceof DropdownOption) {
2166
+ // It's possible the option was already selected but, for whatever reason, set as
2167
+ // selected again. So we use this to guard against adding its value twice to
2168
+ // `this.value`.
2169
+ const isOptionNewlySelected = this.selectedAndEnabledOptions.every((option) => option !== event.target);
2170
+ if (this.multiple) {
2171
+ if (event.target.selected) {
2172
+ if (event.target.disabled) {
2173
+ // We enable programmatically selected options for a couple reasons:
2174
+ //
2175
+ // One is because programmatic selection is a signal of developer intent that an
2176
+ // option is meant to appear to the user as selected. We only show to the user
2177
+ // selected options as selected when they are enabled. We do that because we think
2178
+ // users don't exactly know what it means for an option that appears as disabled to
2179
+ // also appear selected. Because disabled states often appear visually the same or
2180
+ // similar to read-only ones, it's not obvious that a disabled option won't be
2181
+ // submitted with the form.
2182
+ //
2183
+ //
2184
+ // Another is Dropdown's `value` setter, which calls this method indirectly when
2185
+ // it selects options programmatically. We have a few options if `value` is set
2186
+ // programmatically to include the value of a disabled option: we can throw, remove
2187
+ // the value from `this.value`, or enable the option. Throwing can be disruptive if
2188
+ // errors aren't handled downstream, which they often aren't. So we avoid throwing
2189
+ // unless we have to. And removing the value would, in a small way, be a breach of
2190
+ // Dropdown's contract with consumers. So we enable it.
2191
+ event.target.disabled = false;
2192
+ }
2193
+ if (isOptionNewlySelected) {
2194
+ this.selectedAndEnabledOptions = [
2195
+ ...this.selectedAndEnabledOptions,
2196
+ event.target,
2197
+ ];
2198
+ if (!this.#isSelectionFromValueSetter) {
2199
+ this.#value = [...this.value, event.target.value];
2200
+ }
2201
+ }
2202
+ }
2203
+ else {
2204
+ if (!this.#isSelectionFromValueSetter) {
2205
+ this.#value = this.#value.filter((_, index) => {
2206
+ return (index !==
2207
+ this.selectedAndEnabledOptions.indexOf(event.target));
2208
+ });
2209
+ }
2210
+ this.selectedAndEnabledOptions =
2211
+ this.selectedAndEnabledOptions.filter((option) => option !== event.target);
2212
+ }
2213
+ }
2214
+ else if (!this.multiple) {
2215
+ if (event.target.selected) {
2216
+ if (event.target.disabled) {
2217
+ event.target.disabled = false;
2218
+ }
2219
+ if (isOptionNewlySelected) {
2220
+ this.selectedAndEnabledOptions = [
2221
+ ...this.selectedAndEnabledOptions,
2222
+ event.target,
2223
+ ];
2224
+ }
2225
+ for (const option of this.#optionElements) {
2226
+ if (option !== event.target) {
2227
+ option.selected = false;
2228
+ }
2229
+ }
2230
+ if (!this.#isSelectionFromValueSetter) {
2231
+ this.#value = [event.target.value];
2232
+ }
2233
+ }
2234
+ else {
2235
+ this.selectedAndEnabledOptions =
2236
+ this.selectedAndEnabledOptions.filter((option) => option !== event.target);
2237
+ if (!this.#isSelectionFromValueSetter) {
2238
+ this.#value = this.lastSelectedAndEnabledOption?.value
2239
+ ? [this.lastSelectedAndEnabledOption.value]
2240
+ : [];
2241
+ }
2242
+ }
2243
+ }
2244
+ }
2245
+ if (this.multiple) {
2246
+ this.#setTagOverflowLimit();
2247
+ }
2248
+ }
2249
+ #onOptionsValueChange(event) {
2250
+ // A cleaner approach would be to return early if `event.target` isn't an instance
2251
+ // of DropdownOption. But doing so would create an untestable branch and thus
2252
+ // force less than full code coverage because `event.target` will always be an
2253
+ // instance of `DropdownOption`.
2254
+ //
2255
+ // This is also why `#optionElementsIncludingSelectAll` and
2256
+ // `#optionElementsNotHiddenIncludingSelectAll` return `undefined` instead of
2257
+ // always returning an empty array. The empty array branch would only exist to
2258
+ // appease the typesystem and so would never actually get hit, making full test
2259
+ // coverage impossible. Maybe there's a better way?
2260
+ if (event.target instanceof DropdownOption &&
2261
+ this.multiple &&
2262
+ event.target.selected &&
2263
+ event.detail.new) {
2264
+ // There shouldn't be duplicate values. But this will fall short if there are.
2265
+ // Both instances of the value will be removed from `this.#value` when, strictly
2266
+ // speaking, only one of them should. Knowing which to remove would require storing
2267
+ // some state in a map and probably isn't worth the trouble.
2268
+ this.#value = this.value.map((value) => {
2269
+ return value === event.detail.old ? event.detail.new : value;
2270
+ });
2271
+ }
2272
+ else if (event.target instanceof DropdownOption && this.multiple) {
2273
+ this.#value = this.value.filter((value) => {
2274
+ return value !== event.detail.old;
2275
+ });
2276
+ }
2277
+ else if (event.target instanceof DropdownOption) {
2278
+ this.#value = event.detail.new ? [event.detail.new] : [];
2279
+ }
2280
+ }
2281
+ #onPrimaryButtonFocusin() {
2282
+ this.isInternalLabelTooltipOpen = true;
2283
+ }
2284
+ #onPrimaryButtonFocusout() {
2285
+ this.isInternalLabelTooltipOpen = false;
2286
+ }
2287
+ #onTagEdit() {
2288
+ this.#isEditingOrRemovingTag = true;
2289
+ this.open = false;
2290
+ }
2291
+ async #onTagRemove(option) {
2292
+ this.#isEditingOrRemovingTag = true;
2293
+ option.selected = false;
2294
+ const tags = this.#tagsElementRef.value?.querySelectorAll('glide-core-tag');
2295
+ if (tags && this.selectedAndEnabledOptions.length > 0) {
2296
+ const removedTagIndex = [...tags].findIndex((tag) => tag.dataset.id === option.id);
2297
+ // The tag to the right of the one removed unless it was the rightmost tag.
2298
+ // Otherwise the tag to the left.
2299
+ const tagToFocus = tags[removedTagIndex < tags.length - 1
2300
+ ? removedTagIndex + 1
2301
+ : removedTagIndex - 1];
2302
+ // The tag to be focused may be one that's currently hidden. So we wait for it
2303
+ // to become visible before trying to focus it.
2304
+ await this.updateComplete;
2305
+ // Browsers have a quirk where, if focus is moved during a "keydown" event from
2306
+ // an Enter press, the "click" event that follows will be transferred to the now-
2307
+ // focused element. Thus we wait a tick, until the "click" event has passed,
2308
+ // before moving focus.
2309
+ //
2310
+ // Without a timeout, two tags will be removed by a single Enter press: the
2311
+ // previously focused Tag and the now-focused one. The previous because of the
2312
+ // "edit" event dispatched by Tag on "keydown". And the now-focused one because
2313
+ // of the "edit" event dispatched by Tag on "click".
2314
+ setTimeout(() => {
2315
+ tagToFocus?.focus();
2316
+ // Because the tag has been removed via `option.selected = false` above and focus
2317
+ // isn't moved until after the click, a "click" event will never be dispatched.
2318
+ // Otherwise, we could reset `#isEditingOrRemovingTag` in `#onDropdownClick()`.
2319
+ this.#isEditingOrRemovingTag = false;
2320
+ });
2321
+ }
2322
+ else {
2323
+ setTimeout(() => {
2324
+ this.focus();
2325
+ this.#isEditingOrRemovingTag = false;
2326
+ });
2327
+ }
2328
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
2329
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
2330
+ }
2331
+ #onTooltipToggle(event) {
2332
+ // Dropdown dispatches its own "toggle" event. Letting Tooltip's "toggle"
2333
+ // propagate would mean that consumers listening for the event on Dropdown
2334
+ // would have to filter it out when it comes from Tooltip. But first they'll
2335
+ // probably file a bug. It's likely no consumer will be interested in knowing
2336
+ // when this component's Tooltips are open. So it's probably best to stop the
2337
+ // events from propagating.
2338
+ event.stopPropagation();
2339
+ }
2340
+ #selectAddButton() {
2341
+ if (this.#inputElementRef.value) {
2342
+ this.dispatchEvent(new CustomEvent('add', {
2343
+ bubbles: true,
2344
+ composed: true,
2345
+ detail: this.#inputElementRef.value.value,
2346
+ }));
2347
+ this.#inputElementRef.value.value = '';
2348
+ this.inputValue = '';
2349
+ }
2350
+ const firstOption = this.#optionElements.at(0);
2351
+ if (firstOption) {
2352
+ firstOption.privateActive = true;
2353
+ firstOption.scrollIntoView();
2354
+ this.ariaActivedescendant = firstOption.id;
2355
+ }
2356
+ if (!this.multiple) {
2357
+ this.open = false;
2358
+ // `this.isInputTooltipOpen` is set to `true` when the input field receives
2359
+ // focus and `false` when it loses focus. It's currently `true`. But the
2360
+ // user just selected an option and so knows what the option's label is. So
2361
+ // there's no need to show a tooltip.
2362
+ this.isInputTooltipOpen = false;
2363
+ }
2364
+ this.#unfilter();
2365
+ this.focus();
2366
+ }
2367
+ #selectAllOrNone() {
2368
+ this.#isSelectionFromSelectAllOrNone = true;
2369
+ for (const option of this.#optionElements) {
2370
+ if (this.#selectAllElementRef.value?.selected &&
2371
+ !option.selected &&
2372
+ !option.disabled) {
2373
+ option.selected = true;
2374
+ }
2375
+ else if (!this.#selectAllElementRef.value?.selected &&
2376
+ option.selected) {
2377
+ option.selected = false;
2378
+ }
2379
+ }
2380
+ this.#isSelectionFromSelectAllOrNone = false;
2381
+ this.selectedAndEnabledOptions = this.#optionElements.filter((option) => {
2382
+ return option.selected && !option.disabled;
2383
+ });
2384
+ this.#value = this.selectedAndEnabledOptions.map(({ value }) => value);
2385
+ this.#setTagOverflowLimit();
2386
+ }
2387
+ async #setTagOverflowLimit() {
2388
+ // Flush pending state changes before checking for overflow.
2389
+ await this.updateComplete;
2390
+ if (this.#componentElementRef.value) {
2391
+ const isOverflowing = this.#componentElementRef.value.scrollWidth >
2392
+ this.#componentElementRef.value.clientWidth;
2393
+ if (isOverflowing && this.tagOverflowLimit > 1) {
2394
+ this.tagOverflowLimit = this.tagOverflowLimit - 1;
2395
+ // Wait for the update to complete. Then run through this logic again to see
2396
+ // if Dropdown is still overflowing. Rinse and repeat until there's no overflow.
2397
+ await this.updateComplete;
2398
+ this.#setTagOverflowLimit();
2399
+ }
2400
+ else if (!isOverflowing &&
2401
+ !this.#isOverflowTest &&
2402
+ this.tagOverflowLimit < this.selectedAndEnabledOptions.length) {
2403
+ // The limit increase may cause an overflow. But we won't know until we rerun this
2404
+ // method. If it does cause an overflow, the branch above will correct the overflow
2405
+ // when it calls this function again.
2406
+ //
2407
+ // `#isOverflowTest` is set so we don't wind up back in this branch after returning
2408
+ // to the branch above, creating a loop.
2409
+ this.#isOverflowTest = true;
2410
+ this.tagOverflowLimit = this.tagOverflowLimit + 1;
2411
+ this.#setTagOverflowLimit();
2412
+ }
2413
+ else {
2414
+ this.#isOverflowTest = false;
2415
+ }
2416
+ }
2417
+ }
2418
+ #show() {
2419
+ // `#show()` is called whenever `open` is set. And `open` can be set arbitrarily
2420
+ // by the consumer. Rather than guarding against calling `#show()` everywhere,
2421
+ // Floating UI is simply cleaned up every time `#show()` is called.
2422
+ this.#cleanUpFloatingUi?.();
2423
+ if (this.#optionElementsNotHidden) {
2424
+ this.itemCount = this.#optionElementsNotHidden.length;
2425
+ }
2426
+ // Set here, in addition to in `#onDefaultSlotChange()` because
2427
+ // `#onDefaultSlotChange()` isn't called if there are no options.
2428
+ this.hasNoAvailableOptions = this.#optionElements.length === 0;
2429
+ const firstEnabledOption = this.#optionElementsNotHiddenIncludingSelectAll?.find((option) => !option.disabled);
2430
+ // Done now instead of after Floating UI does its thing because the user may begin
2431
+ // arrowing immediately or may have arrowed to open Dropdown and accidentally
2432
+ // arrowed again.
2433
+ if (this.#previouslyActiveOption &&
2434
+ !this.#previouslyActiveOption.hidden &&
2435
+ !this.#previouslyActiveOption.disabled) {
2436
+ this.#previouslyActiveOption.privateActive = true;
2437
+ this.ariaActivedescendant = this.#previouslyActiveOption.id;
2438
+ // It's possible the consumer disabled the previously active option since Dropdown
2439
+ // was last open. If so, we land in this condition.
2440
+ }
2441
+ else if (firstEnabledOption) {
2442
+ firstEnabledOption.privateActive = true;
2443
+ this.ariaActivedescendant = firstEnabledOption.id;
2444
+ this.#previouslyActiveOption = firstEnabledOption;
2445
+ }
2446
+ if (this.#dropdownElementRef.value &&
2447
+ this.#optionsAndFeedbackElementRef.value) {
2448
+ this.#cleanUpFloatingUi = autoUpdate(this.#dropdownElementRef.value, this.#optionsAndFeedbackElementRef.value, () => {
2449
+ (async () => {
2450
+ if (this.#dropdownElementRef.value &&
2451
+ this.#optionsAndFeedbackElementRef.value) {
2452
+ const { x, y, placement } = await computePosition(this.#dropdownElementRef.value, this.#optionsAndFeedbackElementRef.value, {
2453
+ placement: 'bottom-start',
2454
+ middleware: [
2455
+ offset({
2456
+ mainAxis: Number.parseFloat(window
2457
+ .getComputedStyle(document.body)
2458
+ .getPropertyValue('--glide-core-spacing-base-xxs')) *
2459
+ Number.parseFloat(window.getComputedStyle(document.documentElement)
2460
+ .fontSize),
2461
+ }),
2462
+ flip(),
2463
+ ],
2464
+ });
2465
+ this.#optionsAndFeedbackElementRef.value.dataset.placement =
2466
+ placement;
2467
+ Object.assign(this.#optionsAndFeedbackElementRef.value.style, {
2468
+ left: `${x}px`,
2469
+ top: `${y}px`,
2470
+ });
2471
+ this.#optionsAndFeedbackElementRef.value?.showPopover();
2472
+ }
2473
+ })();
2474
+ });
2475
+ }
2476
+ }
2477
+ #unfilter() {
2478
+ // It would be nice to clear the input field and `this.inputValue` in here
2479
+ // too. It would work for multiselect. But single-select calls this method
2480
+ // after a selection is made and sets the input field and `this.inputValue`
2481
+ // to the label of the selected option instead of clearing them.
2482
+ for (const option of this.#optionElements) {
2483
+ option.hidden = false;
2484
+ }
2485
+ this.isFiltering = false;
2486
+ this.isAddButtonActive = false;
2487
+ this.isAddButtonVisible = false;
2488
+ this.hasNoMatchingOptions = false;
2489
+ this.isShowSingleSelectIcon = Boolean(this.lastSelectedAndEnabledOption?.value);
2490
+ }
2491
+ };
2492
+ __decorate([
2493
+ property({ reflect: true }),
2494
+ required
2495
+ ], Dropdown.prototype, "label", void 0);
2496
+ __decorate([
2497
+ property({ attribute: 'add-button', reflect: true, type: Boolean })
2498
+ ], Dropdown.prototype, "addButton", void 0);
2499
+ __decorate([
2500
+ property({ reflect: true, type: Boolean })
2501
+ ], Dropdown.prototype, "disabled", null);
2502
+ __decorate([
2503
+ property({ reflect: true, type: Boolean })
2504
+ ], Dropdown.prototype, "filterable", null);
2505
+ __decorate([
2506
+ property({ attribute: 'hide-label', reflect: true, type: Boolean })
2507
+ ], Dropdown.prototype, "hideLabel", void 0);
2508
+ __decorate([
2509
+ property({ reflect: true, type: Boolean })
2510
+ ], Dropdown.prototype, "loading", void 0);
2511
+ __decorate([
2512
+ property({ reflect: true, useDefault: true })
2513
+ ], Dropdown.prototype, "name", void 0);
2514
+ __decorate([
2515
+ property({ reflect: true, type: Boolean })
2516
+ ], Dropdown.prototype, "open", null);
2517
+ __decorate([
2518
+ property({ reflect: true, useDefault: true })
2519
+ ], Dropdown.prototype, "orientation", void 0);
2520
+ __decorate([
2521
+ property({ reflect: true })
2522
+ ], Dropdown.prototype, "placeholder", void 0);
2523
+ __decorate([
2524
+ property()
2525
+ ], Dropdown.prototype, "privateSplit", void 0);
2526
+ __decorate([
2527
+ property({ reflect: true, type: Boolean })
2528
+ ], Dropdown.prototype, "readonly", void 0);
2529
+ __decorate([
2530
+ property({ attribute: 'select-all', reflect: true, type: Boolean })
2531
+ ], Dropdown.prototype, "selectAll", void 0);
2532
+ __decorate([
2533
+ property({ reflect: true, type: Boolean })
2534
+ ], Dropdown.prototype, "required", void 0);
2535
+ __decorate([
2536
+ property({ reflect: true, type: Boolean })
2537
+ ], Dropdown.prototype, "multiple", null);
2538
+ __decorate([
2539
+ property({ reflect: true })
2540
+ ], Dropdown.prototype, "tooltip", void 0);
2541
+ __decorate([
2542
+ property({ type: Array })
2543
+ ], Dropdown.prototype, "value", null);
2544
+ __decorate([
2545
+ property({ reflect: true })
2546
+ ], Dropdown.prototype, "variant", void 0);
2547
+ __decorate([
2548
+ property({ reflect: true })
2549
+ ], Dropdown.prototype, "version", void 0);
2550
+ __decorate([
2551
+ state()
2552
+ ], Dropdown.prototype, "ariaActivedescendant", void 0);
2553
+ __decorate([
2554
+ state()
2555
+ ], Dropdown.prototype, "hasNoAvailableOptions", void 0);
2556
+ __decorate([
2557
+ state()
2558
+ ], Dropdown.prototype, "hasNoMatchingOptions", void 0);
2559
+ __decorate([
2560
+ state()
2561
+ ], Dropdown.prototype, "inputValue", void 0);
2562
+ __decorate([
2563
+ state()
2564
+ ], Dropdown.prototype, "isAddButtonActive", void 0);
2565
+ __decorate([
2566
+ state()
2567
+ ], Dropdown.prototype, "isAddButtonVisible", void 0);
2568
+ __decorate([
2569
+ state()
2570
+ ], Dropdown.prototype, "isBlurring", void 0);
2571
+ __decorate([
2572
+ state()
2573
+ ], Dropdown.prototype, "isCheckingValidity", void 0);
2574
+ __decorate([
2575
+ state()
2576
+ ], Dropdown.prototype, "isCommunicateItemCountToScreenreaders", void 0);
2577
+ __decorate([
2578
+ state()
2579
+ ], Dropdown.prototype, "isFilterable", void 0);
2580
+ __decorate([
2581
+ state()
2582
+ ], Dropdown.prototype, "isFiltering", void 0);
2583
+ __decorate([
2584
+ state()
2585
+ ], Dropdown.prototype, "isInputOverflowing", void 0);
2586
+ __decorate([
2587
+ state()
2588
+ ], Dropdown.prototype, "isInputTooltipOpen", void 0);
2589
+ __decorate([
2590
+ state()
2591
+ ], Dropdown.prototype, "isInternalLabelOverflowing", void 0);
2592
+ __decorate([
2593
+ state()
2594
+ ], Dropdown.prototype, "isInternalLabelTooltipOpen", void 0);
2595
+ __decorate([
2596
+ state()
2597
+ ], Dropdown.prototype, "isReportValidityOrSubmit", void 0);
2598
+ __decorate([
2599
+ state()
2600
+ ], Dropdown.prototype, "isShowSingleSelectIcon", void 0);
2601
+ __decorate([
2602
+ state()
2603
+ ], Dropdown.prototype, "itemCount", void 0);
2604
+ __decorate([
2605
+ state()
2606
+ ], Dropdown.prototype, "selectedAndEnabledOptions", void 0);
2607
+ __decorate([
2608
+ state()
2609
+ ], Dropdown.prototype, "tagOverflowLimit", void 0);
2610
+ __decorate([
2611
+ state()
2612
+ ], Dropdown.prototype, "validityMessage", void 0);
2613
+ Dropdown = __decorate([
2614
+ customElement('glide-core-dropdown'),
2615
+ final
2616
+ ], Dropdown);
2617
+ export default Dropdown;