@emuanalytics/flow-rdf-ui 4.1.27 → 4.1.28
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/dist/components/autocomplete/autocomplete.component.mjs +135 -28
- package/dist/custom-elements.json +1039 -1031
- package/dist/index.d.mts +49 -2
- package/dist/index.d.ts +49 -2
- package/dist/package.json.mjs +1 -1
- package/dist/vscode.html-custom-data.json +93 -93
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ import styles from './autocomplete.styles.mjs';
|
|
|
15
15
|
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.mjs';
|
|
16
16
|
import { stopAnimations, animateTo } from '../../internal/animate.mjs';
|
|
17
17
|
import { waitForEvent } from '../../internal/event.mjs';
|
|
18
|
+
import { scrollIntoView } from '../../internal/scroll.mjs';
|
|
18
19
|
|
|
19
20
|
var __defProp = Object.defineProperty;
|
|
20
21
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -27,6 +28,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
27
28
|
__defProp(target, key, result);
|
|
28
29
|
return result;
|
|
29
30
|
};
|
|
31
|
+
let nextAutocompleteId = 0;
|
|
30
32
|
class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
31
33
|
constructor() {
|
|
32
34
|
super(...arguments);
|
|
@@ -37,6 +39,19 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
37
39
|
this.pendingChangeEvent = false;
|
|
38
40
|
this.pendingInputEvent = false;
|
|
39
41
|
this.hasOptions = false;
|
|
42
|
+
/** @internal True while the user is typing to filter (input text has diverged from the committed value). */
|
|
43
|
+
this.inputChanged = false;
|
|
44
|
+
/** @internal Stable base used to generate option DOM ids when the host has no `id` of its own. */
|
|
45
|
+
this.optionIdBase = "";
|
|
46
|
+
/** @internal Monotonic per-host suffix for option DOM ids, so ids never collide across re-renders. */
|
|
47
|
+
this.nextOptionId = 0;
|
|
48
|
+
/**
|
|
49
|
+
* @internal True while hide()'s close animation is in flight. `open` stays true across that
|
|
50
|
+
* animation (so the popup remains visible while it fades out), which would otherwise make a
|
|
51
|
+
* concurrent show() no-op at its `!this.open` guard. show() flips this to false to cancel the
|
|
52
|
+
* close and reopen; hide() bails after its animation if it finds the close was cancelled.
|
|
53
|
+
*/
|
|
54
|
+
this.closing = false;
|
|
40
55
|
this.formControlController = new FormControlController(this, {
|
|
41
56
|
assumeInteractionOn: ["flow-ui-blur", "flow-ui-input"]
|
|
42
57
|
});
|
|
@@ -82,6 +97,11 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
82
97
|
this.hoist = boolean(this.hoist, true);
|
|
83
98
|
this.clearOnFocus = boolean(this.clearOnFocus, true);
|
|
84
99
|
}
|
|
100
|
+
disconnectedCallback() {
|
|
101
|
+
super.disconnectedCallback();
|
|
102
|
+
this.hasFocus = false;
|
|
103
|
+
this.removeListenersForDropdown();
|
|
104
|
+
}
|
|
85
105
|
/** Gets the associated form, if one exists. */
|
|
86
106
|
getForm() {
|
|
87
107
|
return this.formControlController.getForm();
|
|
@@ -116,8 +136,31 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
116
136
|
}
|
|
117
137
|
/**
|
|
118
138
|
* @internal
|
|
119
|
-
*
|
|
120
|
-
*
|
|
139
|
+
* Gives each slotted option a stable DOM `id` (`<host-id>-option-<n>`) used purely for
|
|
140
|
+
* ARIA wiring (`aria-activedescendant`). A consumer-provided `id` is preserved. This `id` is
|
|
141
|
+
* independent of the option's `value` (domain identity). See ADR-0001.
|
|
142
|
+
*
|
|
143
|
+
* The suffix comes from a monotonic per-host counter rather than the option's index: a filter
|
|
144
|
+
* re-render can retain some option elements while creating others, so an index-based suffix
|
|
145
|
+
* could hand a new element an id that a retained element already owns.
|
|
146
|
+
*/
|
|
147
|
+
assignOptionIds(options) {
|
|
148
|
+
if (!this.optionIdBase) {
|
|
149
|
+
this.optionIdBase = this.id || `flow-ui-autocomplete-${++nextAutocompleteId}`;
|
|
150
|
+
}
|
|
151
|
+
options.forEach((option) => {
|
|
152
|
+
if (!option.id) {
|
|
153
|
+
option.id = `${this.optionIdBase}-option-${this.nextOptionId++}`;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @internal
|
|
159
|
+
* Un-highlights currently highlighted option and highlights the given option.
|
|
160
|
+
*
|
|
161
|
+
* DOM focus stays on the `<input>` (per the APG editable-combobox pattern); the highlighted
|
|
162
|
+
* ("current") option is conveyed to assistive tech via the input's `aria-activedescendant`,
|
|
163
|
+
* not by moving focus. See ADR-0001.
|
|
121
164
|
*/
|
|
122
165
|
setCurrentOption(option) {
|
|
123
166
|
if (this.currentOption) {
|
|
@@ -125,8 +168,19 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
125
168
|
}
|
|
126
169
|
this.currentOption = option;
|
|
127
170
|
option.current = true;
|
|
128
|
-
option.
|
|
129
|
-
|
|
171
|
+
this.setActiveDescendant(option.id);
|
|
172
|
+
if (this.listbox) {
|
|
173
|
+
scrollIntoView(option, this.listbox, "vertical", "auto");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @internal
|
|
178
|
+
* Sets the input's `aria-activedescendant` to the given option `id`, or clears it (to `''`,
|
|
179
|
+
* per the APG reference) when passed `null` or an empty id. Centralises the ARIA wiring so the
|
|
180
|
+
* highlight and close paths stay in sync. See ADR-0001.
|
|
181
|
+
*/
|
|
182
|
+
setActiveDescendant(id) {
|
|
183
|
+
this.input?.setAttribute("aria-activedescendant", id || "");
|
|
130
184
|
}
|
|
131
185
|
/**
|
|
132
186
|
* @internal
|
|
@@ -171,6 +225,10 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
171
225
|
/** @internal Handle the `flow-ui-input` event from the input box. */
|
|
172
226
|
handleInput(event) {
|
|
173
227
|
event.stopPropagation();
|
|
228
|
+
this.inputChanged = true;
|
|
229
|
+
if (this.open) {
|
|
230
|
+
this.highlightCurrentOption();
|
|
231
|
+
}
|
|
174
232
|
this.debouncedInput(this.input.value);
|
|
175
233
|
}
|
|
176
234
|
/** @internal Handle the `flow-ui-change` event from the input box */
|
|
@@ -226,12 +284,6 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
226
284
|
const currentIndex = this.currentOption ? allOptions.indexOf(this.currentOption) : 0;
|
|
227
285
|
let newIndex = Math.max(0, currentIndex);
|
|
228
286
|
event.preventDefault();
|
|
229
|
-
if (!this.open) {
|
|
230
|
-
this.show();
|
|
231
|
-
if (this.currentOption) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
287
|
switch (event.key) {
|
|
236
288
|
case "ArrowDown": {
|
|
237
289
|
newIndex = currentIndex + 1;
|
|
@@ -271,25 +323,39 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
271
323
|
removeListenersForDropdown() {
|
|
272
324
|
document.removeEventListener("mousedown", this.handleDocumentEvent);
|
|
273
325
|
document.removeEventListener("focusin", this.handleDocumentEvent);
|
|
274
|
-
document.
|
|
326
|
+
document.removeEventListener("keydown", this.handleDocumentEvent);
|
|
275
327
|
}
|
|
276
328
|
/** @internal Handles changes in the default slot */
|
|
277
329
|
handleDefaultSlotChange() {
|
|
278
330
|
const options = this.getAllOptions();
|
|
279
331
|
this.hasOptions = options.length > 0;
|
|
332
|
+
this.assignOptionIds(options);
|
|
280
333
|
this.toggleListbox();
|
|
281
|
-
const firstOption = this.getFirstOption();
|
|
282
|
-
if (firstOption) {
|
|
283
|
-
this.setCurrentOption(firstOption);
|
|
284
|
-
}
|
|
285
334
|
this.setSelectedOption(null);
|
|
286
335
|
for (const option of options) {
|
|
287
336
|
if (option.value === this.value) {
|
|
288
337
|
this.setSelectedOption(option);
|
|
289
|
-
this.setCurrentOption(option);
|
|
290
338
|
break;
|
|
291
339
|
}
|
|
292
340
|
}
|
|
341
|
+
this.highlightCurrentOption();
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* @internal
|
|
345
|
+
* Establishes the highlighted ("current") option per the APG rules (ADR-0001):
|
|
346
|
+
* - while filtering (input text has changed): always the first option, so type-then-Enter
|
|
347
|
+
* selects the narrowed-down top option.
|
|
348
|
+
* - on open with unchanged input: the selected option if one matches, else the first.
|
|
349
|
+
*
|
|
350
|
+
* Called both on slot change (list re-rendered) and on open (listbox shown without a
|
|
351
|
+
* re-render, e.g. clicking back into the input), since the latter fires no slot change.
|
|
352
|
+
*/
|
|
353
|
+
highlightCurrentOption() {
|
|
354
|
+
const firstOption = this.getFirstOption();
|
|
355
|
+
const optionToHighlight = this.inputChanged ? firstOption : this.selectedOption ?? firstOption;
|
|
356
|
+
if (optionToHighlight) {
|
|
357
|
+
this.setCurrentOption(optionToHighlight);
|
|
358
|
+
}
|
|
293
359
|
}
|
|
294
360
|
/** @internal Toggles listbox based on focus and options in the default slot. */
|
|
295
361
|
toggleListbox() {
|
|
@@ -310,13 +376,17 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
310
376
|
}
|
|
311
377
|
async handleValueChange(oldValue, value) {
|
|
312
378
|
if (value !== oldValue) {
|
|
379
|
+
this.inputChanged = false;
|
|
313
380
|
await this.syncDisplayValue();
|
|
314
381
|
const options = this.getAllOptions();
|
|
382
|
+
this.assignOptionIds(options);
|
|
315
383
|
this.setSelectedOption(null);
|
|
316
384
|
for (const option of options) {
|
|
317
385
|
if (option.value === this.value) {
|
|
318
386
|
this.setSelectedOption(option);
|
|
319
|
-
this.
|
|
387
|
+
if (this.open) {
|
|
388
|
+
this.setCurrentOption(option);
|
|
389
|
+
}
|
|
320
390
|
break;
|
|
321
391
|
}
|
|
322
392
|
}
|
|
@@ -358,25 +428,59 @@ class FlowUiAutocomplete extends FlowRdfFormControl {
|
|
|
358
428
|
}
|
|
359
429
|
/** Shows the listbox. */
|
|
360
430
|
async show() {
|
|
431
|
+
if (this.open && !this.closing) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const wasClosing = this.closing;
|
|
435
|
+
this.closing = false;
|
|
361
436
|
if (!this.open) {
|
|
362
437
|
this.emit("flow-ui-show");
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
438
|
+
}
|
|
439
|
+
if (this.popup?.popup) {
|
|
440
|
+
await stopAnimations(this.popup.popup);
|
|
441
|
+
}
|
|
442
|
+
this.open = true;
|
|
443
|
+
await this.updateComplete;
|
|
444
|
+
this.highlightCurrentOption();
|
|
445
|
+
const { keyframes, options } = getAnimation(this, "autocomplete.show", { dir: this.localize.dir() });
|
|
446
|
+
await animateTo(this.popup.popup, keyframes, options);
|
|
447
|
+
if (this.currentOption && this.listbox) {
|
|
448
|
+
scrollIntoView(this.currentOption, this.listbox, "vertical", "auto");
|
|
449
|
+
}
|
|
450
|
+
if (!wasClosing) {
|
|
367
451
|
this.emit("flow-ui-after-show");
|
|
368
452
|
}
|
|
369
453
|
}
|
|
370
454
|
/** Hides the listbox. */
|
|
371
455
|
async hide() {
|
|
372
|
-
if (this.open) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
this.
|
|
456
|
+
if (!this.open || this.closing) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
this.closing = true;
|
|
460
|
+
this.emit("flow-ui-hide");
|
|
461
|
+
if (this.currentOption) {
|
|
462
|
+
this.currentOption.current = false;
|
|
463
|
+
}
|
|
464
|
+
this.currentOption = null;
|
|
465
|
+
this.setActiveDescendant(null);
|
|
466
|
+
if (this.popup?.popup) {
|
|
467
|
+
await stopAnimations(this.popup.popup);
|
|
379
468
|
}
|
|
469
|
+
const { keyframes, options } = getAnimation(this, "autocomplete.hide", { dir: this.localize.dir() });
|
|
470
|
+
await animateTo(this.popup.popup, keyframes, options);
|
|
471
|
+
if (!this.closing) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
this.open = false;
|
|
475
|
+
this.closing = false;
|
|
476
|
+
this.inputChanged = false;
|
|
477
|
+
const strayOption = this.currentOption;
|
|
478
|
+
if (strayOption) {
|
|
479
|
+
strayOption.current = false;
|
|
480
|
+
}
|
|
481
|
+
this.currentOption = null;
|
|
482
|
+
this.setActiveDescendant(null);
|
|
483
|
+
this.emit("flow-ui-after-hide");
|
|
380
484
|
}
|
|
381
485
|
/** Gets the current value of the text input */
|
|
382
486
|
getInputText() {
|
|
@@ -609,6 +713,9 @@ __decorateClass([
|
|
|
609
713
|
__decorateClass([
|
|
610
714
|
query(".autocomplete")
|
|
611
715
|
], FlowUiAutocomplete.prototype, "popup", 2);
|
|
716
|
+
__decorateClass([
|
|
717
|
+
query(".autocomplete__listbox")
|
|
718
|
+
], FlowUiAutocomplete.prototype, "listbox", 2);
|
|
612
719
|
__decorateClass([
|
|
613
720
|
watch("debounceDelay")
|
|
614
721
|
], FlowUiAutocomplete.prototype, "updateDebounceDelay", 1);
|