@visitwonders/assembly 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/declarations/form/combobox-field.d.ts +73 -0
- package/declarations/form/combobox-field.d.ts.map +1 -0
- package/declarations/form/combobox-shared.d.ts +36 -0
- package/declarations/form/combobox-shared.d.ts.map +1 -0
- package/declarations/form/combobox.d.ts +239 -0
- package/declarations/form/combobox.d.ts.map +1 -0
- package/declarations/form/control.d.ts +3 -1
- package/declarations/form/control.d.ts.map +1 -1
- package/declarations/form/country-select-field.d.ts +2 -0
- package/declarations/form/country-select-field.d.ts.map +1 -1
- package/declarations/form/date-picker-field.d.ts +2 -0
- package/declarations/form/date-picker-field.d.ts.map +1 -1
- package/declarations/form/date-range-picker-field.d.ts +2 -0
- package/declarations/form/date-range-picker-field.d.ts.map +1 -1
- package/declarations/form/display-field.d.ts +2 -0
- package/declarations/form/display-field.d.ts.map +1 -1
- package/declarations/form/index.d.ts +4 -0
- package/declarations/form/index.d.ts.map +1 -1
- package/declarations/form/label.d.ts +1 -1
- package/declarations/form/label.d.ts.map +1 -1
- package/declarations/form/money-field.d.ts +2 -0
- package/declarations/form/money-field.d.ts.map +1 -1
- package/declarations/form/multi-combobox-field.d.ts +74 -0
- package/declarations/form/multi-combobox-field.d.ts.map +1 -0
- package/declarations/form/multi-combobox.d.ts +202 -0
- package/declarations/form/multi-combobox.d.ts.map +1 -0
- package/declarations/form/number-field.d.ts +2 -0
- package/declarations/form/number-field.d.ts.map +1 -1
- package/declarations/form/select-field.d.ts +2 -0
- package/declarations/form/select-field.d.ts.map +1 -1
- package/declarations/form/text-field.d.ts +2 -0
- package/declarations/form/text-field.d.ts.map +1 -1
- package/declarations/form/time-picker-field.d.ts +2 -0
- package/declarations/form/time-picker-field.d.ts.map +1 -1
- package/declarations/layout/h-stack.d.ts.map +1 -1
- package/declarations/layout/stack.d.ts.map +1 -1
- package/declarations/layout/v-stack.d.ts.map +1 -1
- package/declarations/overlay/popover.d.ts +20 -1
- package/declarations/overlay/popover.d.ts.map +1 -1
- package/dist/_app_/form/combobox-field.js +1 -0
- package/dist/_app_/form/combobox-shared.js +1 -0
- package/dist/_app_/form/combobox.js +1 -0
- package/dist/_app_/form/multi-combobox-field.js +1 -0
- package/dist/_app_/form/multi-combobox.js +1 -0
- package/dist/data/{sortable-list-css-211fcfeedc08052ccbac7f51549ce0b1.css → sortable-list-css-03e5d237ea377f7d6056e76cc85b2aaa.css} +8 -4
- package/dist/data/sortable-list.js +1 -1
- package/dist/form/combobox-field.js +37 -0
- package/dist/form/combobox-field.js.map +1 -0
- package/dist/form/combobox-shared.js +76 -0
- package/dist/form/combobox-shared.js.map +1 -0
- package/dist/form/combobox.css +345 -0
- package/dist/form/combobox.js +612 -0
- package/dist/form/combobox.js.map +1 -0
- package/dist/form/control.js +1 -1
- package/dist/form/country-select-field.js +1 -1
- package/dist/form/country-select-field.js.map +1 -1
- package/dist/form/date-picker-field.js +1 -1
- package/dist/form/date-picker-field.js.map +1 -1
- package/dist/form/date-range-picker-field.js +1 -1
- package/dist/form/date-range-picker-field.js.map +1 -1
- package/dist/form/{display-field-css-890d9be4b5da61613fd017071f330735.css → display-field-css-502236a2343d47e31e52bdb93a769ca1.css} +2 -2
- package/dist/form/display-field.js +2 -2
- package/dist/form/index.js +4 -0
- package/dist/form/index.js.map +1 -1
- package/dist/form/label.css +2 -1
- package/dist/form/label.js +1 -1
- package/dist/form/money-field.js +1 -1
- package/dist/form/multi-combobox-field.js +36 -0
- package/dist/form/multi-combobox-field.js.map +1 -0
- package/dist/form/multi-combobox.css +422 -0
- package/dist/form/multi-combobox.js +626 -0
- package/dist/form/multi-combobox.js.map +1 -0
- package/dist/form/number-field.js +1 -1
- package/dist/form/select-field.js +1 -1
- package/dist/form/select-field.js.map +1 -1
- package/dist/form/text-field.js +1 -1
- package/dist/form/time-picker-field.js +1 -1
- package/dist/form/time-picker-field.js.map +1 -1
- package/dist/layout/h-stack.js.map +1 -1
- package/dist/layout/v-stack.js.map +1 -1
- package/dist/overlay/popover.js +19 -1
- package/package.json +21 -17
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import "./multi-combobox.css"
|
|
2
|
+
import Component from '@glimmer/component';
|
|
3
|
+
import { tracked, cached } from '@glimmer/tracking';
|
|
4
|
+
import { on } from '@ember/modifier';
|
|
5
|
+
import { fn } from '@ember/helper';
|
|
6
|
+
import { modifier } from 'ember-modifier';
|
|
7
|
+
import { uniqueId } from 'ember-primitives/utils';
|
|
8
|
+
import Popover from '../overlay/popover.js';
|
|
9
|
+
import Icon from '../media/icon.js';
|
|
10
|
+
import { ChevronDown, X } from 'lucide';
|
|
11
|
+
import { isOptionGroup, flattenOptions, splitOnMatch } from './combobox-shared.js';
|
|
12
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
13
|
+
import { setComponentTemplate } from '@ember/component';
|
|
14
|
+
import { g, i, n } from 'decorator-transforms/runtime';
|
|
15
|
+
|
|
16
|
+
;
|
|
17
|
+
|
|
18
|
+
/** Rows skipped per PageUp / PageDown keypress. */const PAGE_STEP = 10;
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Derived types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Main Component
|
|
29
|
+
// ============================================================================
|
|
30
|
+
class MultiComboBox extends Component {
|
|
31
|
+
static {
|
|
32
|
+
g(this.prototype, "isOpen", [tracked], function () {
|
|
33
|
+
return false;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
#isOpen = (i(this, "isOpen"), void 0);
|
|
37
|
+
static {
|
|
38
|
+
g(this.prototype, "query", [tracked], function () {
|
|
39
|
+
return '';
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
#query = (i(this, "query"), void 0);
|
|
43
|
+
static {
|
|
44
|
+
g(this.prototype, "activeId", [tracked], function () {
|
|
45
|
+
return null;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
#activeId = (i(this, "activeId"), void 0);
|
|
49
|
+
static {
|
|
50
|
+
g(this.prototype, "remoteItems", [tracked], function () {
|
|
51
|
+
return null;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
#remoteItems = (i(this, "remoteItems"), void 0);
|
|
55
|
+
static {
|
|
56
|
+
g(this.prototype, "isLoadingInternal", [tracked], function () {
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
#isLoadingInternal = (i(this, "isLoadingInternal"), void 0);
|
|
61
|
+
searchSeq = 0;
|
|
62
|
+
searchTimer = null;
|
|
63
|
+
inputElement = null;
|
|
64
|
+
listboxElement = null;
|
|
65
|
+
componentId = `multi-combobox-${uniqueId()}`;
|
|
66
|
+
listboxId = `${this.componentId}-listbox`;
|
|
67
|
+
createOptionId = `${this.componentId}-create`;
|
|
68
|
+
willDestroy() {
|
|
69
|
+
super.willDestroy();
|
|
70
|
+
if (this.searchTimer) clearTimeout(this.searchTimer);
|
|
71
|
+
}
|
|
72
|
+
// --------------------------------------------------------------------------
|
|
73
|
+
// Items + filtering
|
|
74
|
+
// --------------------------------------------------------------------------
|
|
75
|
+
get isAsync() {
|
|
76
|
+
return this.args.onSearch !== undefined;
|
|
77
|
+
}
|
|
78
|
+
get effectiveItems() {
|
|
79
|
+
if (this.isAsync && this.remoteItems !== null) {
|
|
80
|
+
return this.remoteItems;
|
|
81
|
+
}
|
|
82
|
+
return this.args.items ?? [];
|
|
83
|
+
}
|
|
84
|
+
get values() {
|
|
85
|
+
return this.args.values ?? [];
|
|
86
|
+
}
|
|
87
|
+
get valueSet() {
|
|
88
|
+
return new Set(this.values);
|
|
89
|
+
}
|
|
90
|
+
static {
|
|
91
|
+
n(this.prototype, "valueSet", [cached]);
|
|
92
|
+
}
|
|
93
|
+
get normalizedGroups() {
|
|
94
|
+
const items = this.effectiveItems;
|
|
95
|
+
if (items.length === 0) return [];
|
|
96
|
+
if (isOptionGroup(items[0])) {
|
|
97
|
+
return items.map((g, i) => ({
|
|
98
|
+
label: g.label,
|
|
99
|
+
headingId: `${this.componentId}-group-${i}`,
|
|
100
|
+
options: g.options
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
return [{
|
|
104
|
+
label: null,
|
|
105
|
+
headingId: null,
|
|
106
|
+
options: items
|
|
107
|
+
}];
|
|
108
|
+
}
|
|
109
|
+
get renderedGroups() {
|
|
110
|
+
const trimmed = this.query.trim().toLowerCase();
|
|
111
|
+
const skipLocalFilter = this.isAsync || !trimmed;
|
|
112
|
+
const selected = this.valueSet;
|
|
113
|
+
const result = [];
|
|
114
|
+
for (const group of this.normalizedGroups) {
|
|
115
|
+
const matched = skipLocalFilter ? group.options : group.options.filter(option => {
|
|
116
|
+
const inLabel = option.label.toLowerCase().includes(trimmed);
|
|
117
|
+
const inDescription = option.description ? option.description.toLowerCase().includes(trimmed) : false;
|
|
118
|
+
return inLabel || inDescription;
|
|
119
|
+
});
|
|
120
|
+
if (matched.length === 0) continue;
|
|
121
|
+
const rows = matched.map(option => {
|
|
122
|
+
const optionId = `${this.componentId}-option-${option.value}`;
|
|
123
|
+
return {
|
|
124
|
+
kind: 'option',
|
|
125
|
+
option,
|
|
126
|
+
optionId,
|
|
127
|
+
isActive: this.activeId === optionId,
|
|
128
|
+
isSelected: selected.has(option.value)
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
result.push({
|
|
132
|
+
key: group.headingId ?? 'ungrouped',
|
|
133
|
+
label: group.label,
|
|
134
|
+
headingId: group.headingId,
|
|
135
|
+
rows
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
static {
|
|
141
|
+
n(this.prototype, "renderedGroups", [cached]);
|
|
142
|
+
}
|
|
143
|
+
get navigableRows() {
|
|
144
|
+
const rows = [];
|
|
145
|
+
for (const group of this.renderedGroups) {
|
|
146
|
+
for (const row of group.rows) {
|
|
147
|
+
if (!row.option.isDisabled) rows.push(row);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const create = this.createRow;
|
|
151
|
+
if (create) rows.push(create);
|
|
152
|
+
return rows;
|
|
153
|
+
}
|
|
154
|
+
static {
|
|
155
|
+
n(this.prototype, "navigableRows", [cached]);
|
|
156
|
+
}
|
|
157
|
+
get hasOptionResults() {
|
|
158
|
+
return this.renderedGroups.some(g => g.rows.length > 0);
|
|
159
|
+
}
|
|
160
|
+
// --------------------------------------------------------------------------
|
|
161
|
+
// Chips (from the selected values)
|
|
162
|
+
// --------------------------------------------------------------------------
|
|
163
|
+
get maxVisibleChips() {
|
|
164
|
+
return this.args.maxVisibleChips ?? 3;
|
|
165
|
+
}
|
|
166
|
+
get selectedOptions() {
|
|
167
|
+
// Resolve `values` to their option objects by looking across all
|
|
168
|
+
// items (incl. async payload) AND the current render. If an option
|
|
169
|
+
// isn't found in items, fabricate a minimal one so the chip still
|
|
170
|
+
// shows — common with async-backed data where items lag.
|
|
171
|
+
const flat = flattenOptions(this.effectiveItems);
|
|
172
|
+
const byValue = new Map(flat.map(o => [o.value, o]));
|
|
173
|
+
return this.values.map(v => byValue.get(v) ?? {
|
|
174
|
+
value: v,
|
|
175
|
+
label: v
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
get chips() {
|
|
179
|
+
return this.selectedOptions.map(option => ({
|
|
180
|
+
value: option.value,
|
|
181
|
+
label: option.label,
|
|
182
|
+
option
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
get visibleChips() {
|
|
186
|
+
return this.chips.slice(0, this.maxVisibleChips);
|
|
187
|
+
}
|
|
188
|
+
get overflowCount() {
|
|
189
|
+
return Math.max(0, this.chips.length - this.maxVisibleChips);
|
|
190
|
+
}
|
|
191
|
+
get hasOverflow() {
|
|
192
|
+
return this.overflowCount > 0;
|
|
193
|
+
}
|
|
194
|
+
// --------------------------------------------------------------------------
|
|
195
|
+
// Create row
|
|
196
|
+
// --------------------------------------------------------------------------
|
|
197
|
+
get isCreateVisible() {
|
|
198
|
+
if (!this.args.isCreatable) return false;
|
|
199
|
+
const trimmed = this.query.trim();
|
|
200
|
+
if (!trimmed) return false;
|
|
201
|
+
const flat = flattenOptions(this.effectiveItems);
|
|
202
|
+
const lower = trimmed.toLowerCase();
|
|
203
|
+
return !flat.some(opt => opt.label.toLowerCase() === lower);
|
|
204
|
+
}
|
|
205
|
+
get createRow() {
|
|
206
|
+
if (!this.isCreateVisible) return null;
|
|
207
|
+
const query = this.query.trim();
|
|
208
|
+
const label = this.args.createLabel ? this.args.createLabel(query) : `Create "${query}"`;
|
|
209
|
+
return {
|
|
210
|
+
kind: 'create',
|
|
211
|
+
optionId: this.createOptionId,
|
|
212
|
+
isActive: this.activeId === this.createOptionId,
|
|
213
|
+
query,
|
|
214
|
+
label
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// --------------------------------------------------------------------------
|
|
218
|
+
// Async search
|
|
219
|
+
// --------------------------------------------------------------------------
|
|
220
|
+
get isLoading() {
|
|
221
|
+
if (this.args.isLoading !== undefined) return this.args.isLoading;
|
|
222
|
+
return this.isLoadingInternal;
|
|
223
|
+
}
|
|
224
|
+
get loadingText() {
|
|
225
|
+
return this.args.loadingText ?? 'Loading…';
|
|
226
|
+
}
|
|
227
|
+
get searchDebounceMs() {
|
|
228
|
+
return this.args.searchDebounceMs ?? 200;
|
|
229
|
+
}
|
|
230
|
+
scheduleAsyncSearch(query) {
|
|
231
|
+
if (!this.args.onSearch) return;
|
|
232
|
+
if (this.searchTimer) clearTimeout(this.searchTimer);
|
|
233
|
+
this.isLoadingInternal = true;
|
|
234
|
+
this.searchTimer = setTimeout(() => {
|
|
235
|
+
this.searchTimer = null;
|
|
236
|
+
void this.runAsyncSearch(query);
|
|
237
|
+
}, this.searchDebounceMs);
|
|
238
|
+
}
|
|
239
|
+
async runAsyncSearch(query) {
|
|
240
|
+
if (!this.args.onSearch) return;
|
|
241
|
+
const seq = ++this.searchSeq;
|
|
242
|
+
try {
|
|
243
|
+
const result = await this.args.onSearch(query);
|
|
244
|
+
if (seq !== this.searchSeq) return;
|
|
245
|
+
this.remoteItems = result;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// Only surface the error for the latest request; stale errors
|
|
248
|
+
// are as uninteresting as stale results.
|
|
249
|
+
if (seq === this.searchSeq) {
|
|
250
|
+
this.args.onSearchError?.(error);
|
|
251
|
+
}
|
|
252
|
+
} finally {
|
|
253
|
+
if (seq === this.searchSeq) {
|
|
254
|
+
this.isLoadingInternal = false;
|
|
255
|
+
this.resetActiveToFirstNavigable();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// --------------------------------------------------------------------------
|
|
260
|
+
// Derived UI state
|
|
261
|
+
// --------------------------------------------------------------------------
|
|
262
|
+
get hasAnyValue() {
|
|
263
|
+
return this.values.length > 0;
|
|
264
|
+
}
|
|
265
|
+
get isClearable() {
|
|
266
|
+
return this.args.isClearable ?? true;
|
|
267
|
+
}
|
|
268
|
+
get showClearButton() {
|
|
269
|
+
return this.isClearable && this.hasAnyValue && !this.args.isDisabled;
|
|
270
|
+
}
|
|
271
|
+
get noResultsText() {
|
|
272
|
+
return this.args.noResultsText ?? 'No results';
|
|
273
|
+
}
|
|
274
|
+
/** Text for the aria-live region — see combobox.gts for rationale. */
|
|
275
|
+
get announcement() {
|
|
276
|
+
if (!this.isOpen) return '';
|
|
277
|
+
if (this.isLoading) return this.loadingText;
|
|
278
|
+
const optionCount = this.navigableRows.filter(r => r.kind === 'option').length;
|
|
279
|
+
if (optionCount === 0) {
|
|
280
|
+
if (this.isCreateVisible) return 'Create new option available';
|
|
281
|
+
return this.noResultsText;
|
|
282
|
+
}
|
|
283
|
+
return `${optionCount} ${optionCount === 1 ? 'option' : 'options'} available`;
|
|
284
|
+
}
|
|
285
|
+
get showEmptyState() {
|
|
286
|
+
if (this.isLoading) return false;
|
|
287
|
+
if (this.hasOptionResults) return false;
|
|
288
|
+
if (this.isCreateVisible) return false;
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
get ariaExpanded() {
|
|
292
|
+
return this.isOpen ? 'true' : 'false';
|
|
293
|
+
}
|
|
294
|
+
get ariaRequired() {
|
|
295
|
+
return this.args.isRequired ? 'true' : undefined;
|
|
296
|
+
}
|
|
297
|
+
get ariaInvalid() {
|
|
298
|
+
return this.args.isInvalid ? 'true' : undefined;
|
|
299
|
+
}
|
|
300
|
+
// --------------------------------------------------------------------------
|
|
301
|
+
// Element registration
|
|
302
|
+
// --------------------------------------------------------------------------
|
|
303
|
+
setupInput = modifier(element => {
|
|
304
|
+
this.inputElement = element;
|
|
305
|
+
return () => {
|
|
306
|
+
this.inputElement = null;
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
setupListbox = modifier(element => {
|
|
310
|
+
this.listboxElement = element;
|
|
311
|
+
return () => {
|
|
312
|
+
this.listboxElement = null;
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
// --------------------------------------------------------------------------
|
|
316
|
+
// Event handlers — input (the trigger)
|
|
317
|
+
// --------------------------------------------------------------------------
|
|
318
|
+
handleInputFocus = event => {
|
|
319
|
+
this.args.onFocus?.(event);
|
|
320
|
+
};
|
|
321
|
+
handleInputBlur = event => {
|
|
322
|
+
this.args.onBlur?.(event);
|
|
323
|
+
};
|
|
324
|
+
handleInputClick = () => {
|
|
325
|
+
if (this.args.isDisabled) return;
|
|
326
|
+
if (!this.isOpen) this.openDropdown();
|
|
327
|
+
};
|
|
328
|
+
handleInput = event => {
|
|
329
|
+
const target = event.target;
|
|
330
|
+
this.query = target.value;
|
|
331
|
+
if (!this.isOpen) this.openDropdown({
|
|
332
|
+
resetActive: false
|
|
333
|
+
});
|
|
334
|
+
if (this.isAsync) {
|
|
335
|
+
this.scheduleAsyncSearch(this.query);
|
|
336
|
+
} else {
|
|
337
|
+
this.resetActiveToFirstNavigable();
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
handleInputKeyDown = event => {
|
|
341
|
+
// Backspace on an empty input removes the last chip — available
|
|
342
|
+
// whether open or closed.
|
|
343
|
+
if (event.key === 'Backspace' && this.query === '' && this.values.length > 0) {
|
|
344
|
+
event.preventDefault();
|
|
345
|
+
this.removeLastChip();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!this.isOpen) {
|
|
349
|
+
switch (event.key) {
|
|
350
|
+
case 'ArrowDown':
|
|
351
|
+
case 'ArrowUp':
|
|
352
|
+
event.preventDefault();
|
|
353
|
+
this.openDropdown();
|
|
354
|
+
return;
|
|
355
|
+
case 'Escape':
|
|
356
|
+
if (this.showClearButton) {
|
|
357
|
+
event.preventDefault();
|
|
358
|
+
this.clearAll();
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
default:
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
switch (event.key) {
|
|
366
|
+
case 'ArrowDown':
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
this.moveActive(1);
|
|
369
|
+
break;
|
|
370
|
+
case 'ArrowUp':
|
|
371
|
+
event.preventDefault();
|
|
372
|
+
this.moveActive(-1);
|
|
373
|
+
break;
|
|
374
|
+
case 'PageDown':
|
|
375
|
+
event.preventDefault();
|
|
376
|
+
this.movePage(1);
|
|
377
|
+
break;
|
|
378
|
+
case 'PageUp':
|
|
379
|
+
event.preventDefault();
|
|
380
|
+
this.movePage(-1);
|
|
381
|
+
break;
|
|
382
|
+
case 'Home':
|
|
383
|
+
event.preventDefault();
|
|
384
|
+
this.setActiveToFirst();
|
|
385
|
+
break;
|
|
386
|
+
case 'End':
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
this.setActiveToLast();
|
|
389
|
+
break;
|
|
390
|
+
case 'Enter':
|
|
391
|
+
event.preventDefault();
|
|
392
|
+
this.activateActive();
|
|
393
|
+
break;
|
|
394
|
+
case 'Escape':
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
this.closeDropdown();
|
|
397
|
+
break;
|
|
398
|
+
case 'Tab':
|
|
399
|
+
this.closeDropdown();
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
handleChevronMouseDown = event => {
|
|
404
|
+
event.preventDefault();
|
|
405
|
+
};
|
|
406
|
+
handleChevronClick = event => {
|
|
407
|
+
event.preventDefault();
|
|
408
|
+
if (this.args.isDisabled) return;
|
|
409
|
+
if (this.isOpen) {
|
|
410
|
+
this.closeDropdown();
|
|
411
|
+
} else {
|
|
412
|
+
this.openDropdown();
|
|
413
|
+
}
|
|
414
|
+
this.inputElement?.focus({
|
|
415
|
+
preventScroll: true
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
// --------------------------------------------------------------------------
|
|
419
|
+
// Open / close
|
|
420
|
+
// --------------------------------------------------------------------------
|
|
421
|
+
openDropdown = (opts = {}) => {
|
|
422
|
+
if (this.args.isDisabled || this.isOpen) return;
|
|
423
|
+
this.isOpen = true;
|
|
424
|
+
if (opts.resetActive !== false) {
|
|
425
|
+
this.query = '';
|
|
426
|
+
this.resetActiveToInitial();
|
|
427
|
+
} else {
|
|
428
|
+
this.resetActiveToFirstNavigable();
|
|
429
|
+
}
|
|
430
|
+
this.args.onOpen?.();
|
|
431
|
+
};
|
|
432
|
+
closeDropdown = () => {
|
|
433
|
+
// Guard so onClose doesn't double-fire; several sources can call
|
|
434
|
+
// this when the dropdown is already closed.
|
|
435
|
+
const wasOpen = this.isOpen;
|
|
436
|
+
this.isOpen = false;
|
|
437
|
+
this.query = '';
|
|
438
|
+
this.activeId = null;
|
|
439
|
+
if (this.isAsync) {
|
|
440
|
+
this.remoteItems = null;
|
|
441
|
+
this.isLoadingInternal = false;
|
|
442
|
+
if (this.searchTimer) {
|
|
443
|
+
clearTimeout(this.searchTimer);
|
|
444
|
+
this.searchTimer = null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (wasOpen) this.args.onClose?.();
|
|
448
|
+
};
|
|
449
|
+
handleOpenChange = open => {
|
|
450
|
+
if (open) {
|
|
451
|
+
this.openDropdown();
|
|
452
|
+
} else {
|
|
453
|
+
this.closeDropdown();
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
// --------------------------------------------------------------------------
|
|
457
|
+
// Active-row management
|
|
458
|
+
// --------------------------------------------------------------------------
|
|
459
|
+
resetActiveToInitial() {
|
|
460
|
+
const rows = this.navigableRows;
|
|
461
|
+
if (rows.length === 0) {
|
|
462
|
+
this.activeId = null;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.activeId = rows[0].optionId;
|
|
466
|
+
}
|
|
467
|
+
resetActiveToFirstNavigable() {
|
|
468
|
+
const rows = this.navigableRows;
|
|
469
|
+
this.activeId = rows.length > 0 ? rows[0].optionId : null;
|
|
470
|
+
}
|
|
471
|
+
setActiveToFirst() {
|
|
472
|
+
const rows = this.navigableRows;
|
|
473
|
+
if (rows.length === 0) return;
|
|
474
|
+
this.activeId = rows[0].optionId;
|
|
475
|
+
this.scrollActiveIntoView();
|
|
476
|
+
}
|
|
477
|
+
setActiveToLast() {
|
|
478
|
+
const rows = this.navigableRows;
|
|
479
|
+
if (rows.length === 0) return;
|
|
480
|
+
this.activeId = rows[rows.length - 1].optionId;
|
|
481
|
+
this.scrollActiveIntoView();
|
|
482
|
+
}
|
|
483
|
+
moveActive(direction) {
|
|
484
|
+
const rows = this.navigableRows;
|
|
485
|
+
if (rows.length === 0) {
|
|
486
|
+
this.activeId = null;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const currentIndex = this.activeId ? rows.findIndex(r => r.optionId === this.activeId) : -1;
|
|
490
|
+
let nextIndex;
|
|
491
|
+
if (currentIndex === -1) {
|
|
492
|
+
nextIndex = direction === 1 ? 0 : rows.length - 1;
|
|
493
|
+
} else {
|
|
494
|
+
nextIndex = currentIndex + direction;
|
|
495
|
+
if (nextIndex < 0) nextIndex = rows.length - 1;
|
|
496
|
+
if (nextIndex >= rows.length) nextIndex = 0;
|
|
497
|
+
}
|
|
498
|
+
this.activeId = rows[nextIndex].optionId;
|
|
499
|
+
this.scrollActiveIntoView();
|
|
500
|
+
}
|
|
501
|
+
/** Move by `PAGE_STEP` rows; clamped (no wrap) at the list ends. */
|
|
502
|
+
movePage(direction) {
|
|
503
|
+
const rows = this.navigableRows;
|
|
504
|
+
if (rows.length === 0) {
|
|
505
|
+
this.activeId = null;
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const currentIndex = this.activeId ? rows.findIndex(r => r.optionId === this.activeId) : direction === 1 ? -1 : rows.length;
|
|
509
|
+
const targetIndex = currentIndex + direction * PAGE_STEP;
|
|
510
|
+
const clamped = Math.max(0, Math.min(rows.length - 1, targetIndex));
|
|
511
|
+
this.activeId = rows[clamped].optionId;
|
|
512
|
+
this.scrollActiveIntoView();
|
|
513
|
+
}
|
|
514
|
+
scrollActiveIntoView() {
|
|
515
|
+
if (!this.activeId) return;
|
|
516
|
+
const node = document.getElementById(this.activeId);
|
|
517
|
+
node?.scrollIntoView({
|
|
518
|
+
block: 'nearest'
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
// --------------------------------------------------------------------------
|
|
522
|
+
// Selection / activation
|
|
523
|
+
// --------------------------------------------------------------------------
|
|
524
|
+
activateActive() {
|
|
525
|
+
if (!this.activeId) return;
|
|
526
|
+
const row = this.navigableRows.find(r => r.optionId === this.activeId);
|
|
527
|
+
if (!row) return;
|
|
528
|
+
if (row.kind === 'create') {
|
|
529
|
+
this.fireCreate();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (row.option.isDisabled) return;
|
|
533
|
+
this.toggleOption(row.option);
|
|
534
|
+
}
|
|
535
|
+
toggleOption = option => {
|
|
536
|
+
if (option.isDisabled) return;
|
|
537
|
+
const selected = this.valueSet;
|
|
538
|
+
const nextValues = selected.has(option.value) ? this.values.filter(v => v !== option.value) : [...this.values, option.value];
|
|
539
|
+
this.emitChange(nextValues);
|
|
540
|
+
// Clear the query so the filter resets but keep the dropdown open
|
|
541
|
+
// — multi-select is explicit about staying open until the user
|
|
542
|
+
// dismisses.
|
|
543
|
+
this.query = '';
|
|
544
|
+
this.resetActiveToFirstNavigable();
|
|
545
|
+
this.inputElement?.focus({
|
|
546
|
+
preventScroll: true
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
fireCreate = () => {
|
|
550
|
+
if (!this.args.onCreate) return;
|
|
551
|
+
const query = this.query.trim();
|
|
552
|
+
if (!query) return;
|
|
553
|
+
this.args.onCreate(query);
|
|
554
|
+
this.query = '';
|
|
555
|
+
this.resetActiveToFirstNavigable();
|
|
556
|
+
this.inputElement?.focus({
|
|
557
|
+
preventScroll: true
|
|
558
|
+
});
|
|
559
|
+
};
|
|
560
|
+
removeLastChip() {
|
|
561
|
+
if (this.values.length === 0) return;
|
|
562
|
+
const nextValues = this.values.slice(0, -1);
|
|
563
|
+
this.emitChange(nextValues);
|
|
564
|
+
}
|
|
565
|
+
removeChip = value => {
|
|
566
|
+
const nextValues = this.values.filter(v => v !== value);
|
|
567
|
+
this.emitChange(nextValues);
|
|
568
|
+
this.inputElement?.focus({
|
|
569
|
+
preventScroll: true
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
handleChipRemoveMouseDown = event => {
|
|
573
|
+
event.preventDefault();
|
|
574
|
+
};
|
|
575
|
+
handleChipRemove = (value, event) => {
|
|
576
|
+
event.preventDefault();
|
|
577
|
+
event.stopPropagation();
|
|
578
|
+
this.removeChip(value);
|
|
579
|
+
};
|
|
580
|
+
clearAll() {
|
|
581
|
+
this.emitChange([]);
|
|
582
|
+
this.inputElement?.focus({
|
|
583
|
+
preventScroll: true
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
handleClearMouseDown = event => {
|
|
587
|
+
event.preventDefault();
|
|
588
|
+
};
|
|
589
|
+
handleClearClick = event => {
|
|
590
|
+
event.preventDefault();
|
|
591
|
+
event.stopPropagation();
|
|
592
|
+
this.clearAll();
|
|
593
|
+
};
|
|
594
|
+
handleOptionMouseDown = event => {
|
|
595
|
+
event.preventDefault();
|
|
596
|
+
};
|
|
597
|
+
handleOptionMouseEnter = optionId => {
|
|
598
|
+
this.activeId = optionId;
|
|
599
|
+
};
|
|
600
|
+
emitChange(nextValues) {
|
|
601
|
+
const flat = flattenOptions(this.effectiveItems);
|
|
602
|
+
const byValue = new Map(flat.map(o => [o.value, o]));
|
|
603
|
+
const nextOptions = nextValues.map(v => byValue.get(v) ?? {
|
|
604
|
+
value: v,
|
|
605
|
+
label: v
|
|
606
|
+
});
|
|
607
|
+
this.args.onChange?.(nextValues, nextOptions);
|
|
608
|
+
}
|
|
609
|
+
static {
|
|
610
|
+
setComponentTemplate(precompileTemplate("{{!-- Mousedown handlers preventDefault to keep focus on the input when\n clicking chips / chevron / options \u2014 without this, clicks move\n focus off the input and fire spurious blur events. --}}\n{{!-- template-lint-disable no-pointer-down-event-binding --}}\n<Popover @trigger=\"manual\" @open={{this.isOpen}} @onOpenChange={{this.handleOpenChange}} @dismissOnClickOutside={{true}} @dismissOnEscape={{false}} class=\"multi-combobox_e1a041f66\" data-invalid={{if @isInvalid \"true\"}} data-disabled={{if @isDisabled \"true\"}} data-open={{if this.isOpen \"true\"}} data-test-multi-combobox ...attributes as |popover|>\n {{#if @name}}\n {{#each this.values as |v|}}\n <input type=\"hidden\" name={{@name}} value={{v}} data-test-multi-combobox-input-hidden />\n {{/each}}\n {{/if}}\n\n {{!-- Visually-hidden aria-live region for result-count\n announcements. See combobox.gts for rationale. --}}\n <div class=\"multi-combobox-sr-only_e1a041f66\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" data-test-multi-combobox-announcement>{{this.announcement}}</div>\n\n <div {{popover.registerTrigger}} class=\"multi-combobox-trigger_e1a041f66\" data-test-multi-combobox-trigger-wrapper>\n <div class=\"multi-combobox-chips_e1a041f66\" data-test-multi-combobox-chips>\n {{#each this.visibleChips key=\"value\" as |chip|}}\n <span class=\"multi-combobox-chip_e1a041f66\" data-test-multi-combobox-chip data-value={{chip.value}}>\n <span class=\"multi-combobox-chip-label_e1a041f66\">{{chip.label}}</span>\n <button type=\"button\" class=\"multi-combobox-chip-remove_e1a041f66\" aria-label=\"Remove {{chip.label}}\" tabindex=\"-1\" {{on \"mousedown\" this.handleChipRemoveMouseDown}} {{on \"click\" (fn this.handleChipRemove chip.value)}} data-test-multi-combobox-chip-remove>\n <Icon @icon={{X}} @size=\"sm\" />\n </button>\n </span>\n {{/each}}\n {{#if this.hasOverflow}}\n <span class=\"multi-combobox-more_e1a041f66\" data-test-multi-combobox-more>+{{this.overflowCount}}\n more</span>\n {{/if}}\n\n {{!-- template-lint-disable no-redundant-role --}}\n <input {{this.setupInput}} {{on \"click\" this.handleInputClick}} {{on \"focus\" this.handleInputFocus}} {{on \"blur\" this.handleInputBlur}} {{on \"input\" this.handleInput}} {{on \"keydown\" this.handleInputKeyDown}} id={{@id}} type=\"text\" class=\"multi-combobox-input_e1a041f66\" role=\"combobox\" aria-autocomplete=\"list\" aria-haspopup=\"listbox\" aria-expanded={{this.ariaExpanded}} aria-controls={{this.listboxId}} aria-activedescendant={{this.activeId}} aria-required={{this.ariaRequired}} aria-invalid={{this.ariaInvalid}} aria-describedby={{@aria-describedby}} autocomplete=\"off\" spellcheck=\"false\" placeholder={{unless this.hasAnyValue @placeholder}} value={{this.query}} disabled={{@isDisabled}} data-test-multi-combobox-trigger data-test-multi-combobox-input />\n </div>\n\n {{#if this.showClearButton}}\n <button type=\"button\" class=\"multi-combobox-clear_e1a041f66\" aria-label=\"Clear all\" tabindex=\"-1\" {{on \"mousedown\" this.handleClearMouseDown}} {{on \"click\" this.handleClearClick}} data-test-multi-combobox-clear>\n <Icon @icon={{X}} @size=\"sm\" />\n </button>\n {{/if}}\n\n <button type=\"button\" class=\"multi-combobox-chevron_e1a041f66\" aria-label={{if this.isOpen \"Close\" \"Open\"}} tabindex=\"-1\" disabled={{@isDisabled}} {{on \"mousedown\" this.handleChevronMouseDown}} {{on \"click\" this.handleChevronClick}} data-test-multi-combobox-chevron>\n <Icon @icon={{ChevronDown}} @size=\"sm\" />\n </button>\n </div>\n\n <popover.Content @side=\"bottom\" @align=\"start\" @sideOffset={{4}} @animation=\"scale\" class=\"multi-combobox-popover-content_e1a041f66\">\n <div id={{this.listboxId}} role=\"listbox\" aria-multiselectable=\"true\" class=\"multi-combobox-listbox_e1a041f66\" tabindex=\"-1\" {{this.setupListbox}} data-test-multi-combobox-listbox>\n {{#each this.renderedGroups key=\"key\" as |group|}}\n {{#if group.label}}\n <div role=\"group\" aria-labelledby={{group.headingId}} class=\"multi-combobox-group_e1a041f66\" data-test-multi-combobox-group>\n <div id={{group.headingId}} class=\"multi-combobox-group-heading_e1a041f66\" data-test-multi-combobox-group-heading>{{group.label}}</div>\n {{!-- template-lint-disable require-context-role --}}\n {{#each group.rows key=\"optionId\" as |row|}}\n <div id={{row.optionId}} role=\"option\" class=\"multi-combobox-option_e1a041f66\" aria-selected={{if row.isSelected \"true\" \"false\"}} aria-disabled={{if row.option.isDisabled \"true\"}} data-active={{if row.isActive \"true\"}} data-selected={{if row.isSelected \"true\"}} data-disabled={{if row.option.isDisabled \"true\"}} data-option-id={{row.optionId}} data-value={{row.option.value}} {{on \"mousedown\" this.handleOptionMouseDown}} {{on \"click\" (fn this.toggleOption row.option)}} {{on \"mouseenter\" (fn this.handleOptionMouseEnter row.optionId)}} data-test-multi-combobox-option>\n <div class=\"multi-combobox-option-content_e1a041f66\">\n <span class=\"multi-combobox-option-label_e1a041f66\" data-test-multi-combobox-option-label>\n {{~#each (splitOnMatch row.option.label this.query) as |segment|~}}\n {{~#if segment.isMatch~}}\n <span class=\"multi-combobox-mark_e1a041f66\" data-test-multi-combobox-mark>{{segment.text}}</span>\n {{~else~}}\n {{segment.text}}\n {{~/if~}}\n {{~/each~}}\n </span>\n {{#if row.option.description}}\n <span class=\"multi-combobox-option-description_e1a041f66\" data-test-multi-combobox-option-description>\n {{~#each (splitOnMatch row.option.description this.query) as |segment|~}}\n {{~#if segment.isMatch~}}\n <span class=\"multi-combobox-mark_e1a041f66\" data-test-multi-combobox-mark>{{segment.text}}</span>\n {{~else~}}\n {{segment.text}}\n {{~/if~}}\n {{~/each~}}\n </span>\n {{/if}}\n </div>\n <span class=\"multi-combobox-checkbox_e1a041f66\" data-checked={{if row.isSelected \"true\"}} aria-hidden=\"true\"></span>\n </div>\n {{/each}}\n </div>\n {{else}}\n {{#each group.rows key=\"optionId\" as |row|}}\n <div id={{row.optionId}} role=\"option\" class=\"multi-combobox-option_e1a041f66\" aria-selected={{if row.isSelected \"true\" \"false\"}} aria-disabled={{if row.option.isDisabled \"true\"}} data-active={{if row.isActive \"true\"}} data-selected={{if row.isSelected \"true\"}} data-disabled={{if row.option.isDisabled \"true\"}} data-option-id={{row.optionId}} data-value={{row.option.value}} {{on \"mousedown\" this.handleOptionMouseDown}} {{on \"click\" (fn this.toggleOption row.option)}} {{on \"mouseenter\" (fn this.handleOptionMouseEnter row.optionId)}} data-test-multi-combobox-option>\n <div class=\"multi-combobox-option-content_e1a041f66\">\n <span class=\"multi-combobox-option-label_e1a041f66\" data-test-multi-combobox-option-label>\n {{~#each (splitOnMatch row.option.label this.query) as |segment|~}}\n {{~#if segment.isMatch~}}\n <span class=\"multi-combobox-mark_e1a041f66\" data-test-multi-combobox-mark>{{segment.text}}</span>\n {{~else~}}\n {{segment.text}}\n {{~/if~}}\n {{~/each~}}\n </span>\n {{#if row.option.description}}\n <span class=\"multi-combobox-option-description_e1a041f66\" data-test-multi-combobox-option-description>\n {{~#each (splitOnMatch row.option.description this.query) as |segment|~}}\n {{~#if segment.isMatch~}}\n <span class=\"multi-combobox-mark_e1a041f66\" data-test-multi-combobox-mark>{{segment.text}}</span>\n {{~else~}}\n {{segment.text}}\n {{~/if~}}\n {{~/each~}}\n </span>\n {{/if}}\n </div>\n <span class=\"multi-combobox-checkbox_e1a041f66\" data-checked={{if row.isSelected \"true\"}} aria-hidden=\"true\"></span>\n </div>\n {{/each}}\n {{/if}}\n {{/each}}\n\n {{#if this.createRow}}\n <div id={{this.createRow.optionId}} role=\"option\" class=\"multi-combobox-option_e1a041f66 multi-combobox-create_e1a041f66\" aria-selected=\"false\" data-active={{if this.createRow.isActive \"true\"}} data-option-id={{this.createRow.optionId}} {{on \"mousedown\" this.handleOptionMouseDown}} {{on \"click\" this.fireCreate}} {{on \"mouseenter\" (fn this.handleOptionMouseEnter this.createRow.optionId)}} data-test-multi-combobox-create>\n <span class=\"multi-combobox-create-icon_e1a041f66\" aria-hidden=\"true\">+</span>\n <span class=\"multi-combobox-option-label_e1a041f66\">{{this.createRow.label}}</span>\n </div>\n {{/if}}\n\n {{#if this.isLoading}}\n <div class=\"multi-combobox-loading_e1a041f66\" data-test-multi-combobox-loading>{{this.loadingText}}</div>\n {{else if this.showEmptyState}}\n <div class=\"multi-combobox-empty_e1a041f66\" data-test-multi-combobox-empty>{{this.noResultsText}}</div>\n {{/if}}\n </div>\n </popover.Content>\n</Popover>", {
|
|
611
|
+
strictMode: true,
|
|
612
|
+
scope: () => ({
|
|
613
|
+
Popover,
|
|
614
|
+
on,
|
|
615
|
+
fn,
|
|
616
|
+
Icon,
|
|
617
|
+
X,
|
|
618
|
+
ChevronDown,
|
|
619
|
+
splitOnMatch
|
|
620
|
+
})
|
|
621
|
+
}), this);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export { MultiComboBox as default };
|
|
626
|
+
//# sourceMappingURL=multi-combobox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-combobox.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -167,7 +167,7 @@ class NumberField extends Component {
|
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
169
|
static {
|
|
170
|
-
setComponentTemplate(precompileTemplate("<Control @isInvalid={{this.isInvalid}} @isDisabled={{@isDisabled}} @isRequired={{@isRequired}} @isReadOnly={{@isReadOnly}} @labelInfo={{@labelInfo}} data-max-width={{@maxWidth}} ...attributes as |ctrl|>\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <div class=\"number-field-input-wrapper_e9ff3bd72\" data-invalid={{if this.isInvalid \"true\" \"false\"}} data-disabled={{if @isDisabled \"true\" \"false\"}} data-readonly={{if @isReadOnly \"true\" \"false\"}} data-at-min={{if this.isAtMin \"true\" \"false\"}} data-at-max={{if this.isAtMax \"true\" \"false\"}} data-has-stepper={{if this.showStepper \"true\" \"false\"}} data-test-number-field-wrapper>\n {{#if @prefix}}\n <span class=\"number-field-affix_e9ff3bd72 number-field-prefix_e9ff3bd72\" data-test-number-field-prefix>\n {{@prefix}}\n </span>\n {{/if}}\n\n {{!-- template-lint-disable no-redundant-role --}}\n <input id={{ctrl.id}} class=\"number-field-input_e9ff3bd72\" type=\"text\" inputmode={{this.inputMode}} role=\"spinbutton\" name={{@name}} value={{this.displayValue}} placeholder={{@placeholder}} disabled={{@isDisabled}} readonly={{@isReadOnly}} aria-invalid={{if this.isInvalid \"true\"}} aria-required={{if @isRequired \"true\"}} aria-describedby={{this.getAriaDescribedBy ctrl.id}} aria-valuenow={{this.ariaValueNow}} aria-valuemin={{this.ariaValueMin}} aria-valuemax={{this.ariaValueMax}} {{on \"input\" this.handleInput}} {{on \"blur\" this.handleBlur}} {{on \"focus\" this.handleFocus}} {{on \"keydown\" this.handleKeyDown}} data-test-number-field-input />\n\n {{#if @suffix}}\n <span class=\"number-field-affix_e9ff3bd72 number-field-suffix_e9ff3bd72\" data-test-number-field-suffix>\n {{@suffix}}\n </span>\n {{/if}}\n\n {{#if this.showStepper}}\n <div class=\"number-field-stepper_e9ff3bd72\" data-test-number-field-stepper>\n <button type=\"button\" class=\"number-field-stepper-button_e9ff3bd72\" tabindex=\"-1\" aria-label=\"Increase\" disabled={{if @isDisabled true (if @isReadOnly true this.isAtMax)}} {{on \"click\" this.increment}} data-test-number-field-increment>\n <Icon @icon={{ChevronUp}} @size=\"xs\" />\n </button>\n <button type=\"button\" class=\"number-field-stepper-button_e9ff3bd72\" tabindex=\"-1\" aria-label=\"Decrease\" disabled={{if @isDisabled true (if @isReadOnly true this.isAtMin)}} {{on \"click\" this.decrement}} data-test-number-field-decrement>\n <Icon @icon={{ChevronDown}} @size=\"xs\" />\n </button>\n </div>\n {{/if}}\n </div>\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n</Control>", {
|
|
170
|
+
setComponentTemplate(precompileTemplate("<Control @isInvalid={{this.isInvalid}} @isDisabled={{@isDisabled}} @isRequired={{@isRequired}} @isReadOnly={{@isReadOnly}} @labelInfo={{@labelInfo}} @optionalIndicator={{@optionalIndicator}} data-max-width={{@maxWidth}} ...attributes as |ctrl|>\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <div class=\"number-field-input-wrapper_e9ff3bd72\" data-invalid={{if this.isInvalid \"true\" \"false\"}} data-disabled={{if @isDisabled \"true\" \"false\"}} data-readonly={{if @isReadOnly \"true\" \"false\"}} data-at-min={{if this.isAtMin \"true\" \"false\"}} data-at-max={{if this.isAtMax \"true\" \"false\"}} data-has-stepper={{if this.showStepper \"true\" \"false\"}} data-test-number-field-wrapper>\n {{#if @prefix}}\n <span class=\"number-field-affix_e9ff3bd72 number-field-prefix_e9ff3bd72\" data-test-number-field-prefix>\n {{@prefix}}\n </span>\n {{/if}}\n\n {{!-- template-lint-disable no-redundant-role --}}\n <input id={{ctrl.id}} class=\"number-field-input_e9ff3bd72\" type=\"text\" inputmode={{this.inputMode}} role=\"spinbutton\" name={{@name}} value={{this.displayValue}} placeholder={{@placeholder}} disabled={{@isDisabled}} readonly={{@isReadOnly}} aria-invalid={{if this.isInvalid \"true\"}} aria-required={{if @isRequired \"true\"}} aria-describedby={{this.getAriaDescribedBy ctrl.id}} aria-valuenow={{this.ariaValueNow}} aria-valuemin={{this.ariaValueMin}} aria-valuemax={{this.ariaValueMax}} {{on \"input\" this.handleInput}} {{on \"blur\" this.handleBlur}} {{on \"focus\" this.handleFocus}} {{on \"keydown\" this.handleKeyDown}} data-test-number-field-input />\n\n {{#if @suffix}}\n <span class=\"number-field-affix_e9ff3bd72 number-field-suffix_e9ff3bd72\" data-test-number-field-suffix>\n {{@suffix}}\n </span>\n {{/if}}\n\n {{#if this.showStepper}}\n <div class=\"number-field-stepper_e9ff3bd72\" data-test-number-field-stepper>\n <button type=\"button\" class=\"number-field-stepper-button_e9ff3bd72\" tabindex=\"-1\" aria-label=\"Increase\" disabled={{if @isDisabled true (if @isReadOnly true this.isAtMax)}} {{on \"click\" this.increment}} data-test-number-field-increment>\n <Icon @icon={{ChevronUp}} @size=\"xs\" />\n </button>\n <button type=\"button\" class=\"number-field-stepper-button_e9ff3bd72\" tabindex=\"-1\" aria-label=\"Decrease\" disabled={{if @isDisabled true (if @isReadOnly true this.isAtMin)}} {{on \"click\" this.decrement}} data-test-number-field-decrement>\n <Icon @icon={{ChevronDown}} @size=\"xs\" />\n </button>\n </div>\n {{/if}}\n </div>\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n</Control>", {
|
|
171
171
|
strictMode: true,
|
|
172
172
|
scope: () => ({
|
|
173
173
|
Control,
|
|
@@ -24,7 +24,7 @@ class SelectField extends Component {
|
|
|
24
24
|
return parts.length > 0 ? parts.join(' ') : undefined;
|
|
25
25
|
};
|
|
26
26
|
static {
|
|
27
|
-
setComponentTemplate(precompileTemplate("<Control @isInvalid={{this.isInvalid}} @isDisabled={{@isDisabled}} @isRequired={{@isRequired}} @labelInfo={{@labelInfo}} ...attributes as |ctrl|>\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <Select @id={{ctrl.id}} @name={{@name}} @value={{@value}} @placeholder={{@placeholder}} @isDisabled={{@isDisabled}} @isInvalid={{this.isInvalid}} @isRequired={{@isRequired}} @aria-describedby={{this.getAriaDescribedBy ctrl.id}} @onChange={{@onChange}} @onBlur={{@onBlur}} @onFocus={{@onFocus}} data-test-select-field as |select|>\n {{yield (hash Option=select.Option OptionGroup=select.OptionGroup)}}\n </Select>\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n</Control>", {
|
|
27
|
+
setComponentTemplate(precompileTemplate("<Control @isInvalid={{this.isInvalid}} @isDisabled={{@isDisabled}} @isRequired={{@isRequired}} @labelInfo={{@labelInfo}} @optionalIndicator={{@optionalIndicator}} ...attributes as |ctrl|>\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <Select @id={{ctrl.id}} @name={{@name}} @value={{@value}} @placeholder={{@placeholder}} @isDisabled={{@isDisabled}} @isInvalid={{this.isInvalid}} @isRequired={{@isRequired}} @aria-describedby={{this.getAriaDescribedBy ctrl.id}} @onChange={{@onChange}} @onBlur={{@onBlur}} @onFocus={{@onFocus}} data-test-select-field as |select|>\n {{yield (hash Option=select.Option OptionGroup=select.OptionGroup)}}\n </Select>\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n</Control>", {
|
|
28
28
|
strictMode: true,
|
|
29
29
|
scope: () => ({
|
|
30
30
|
Control,
|