@limetech/lime-elements 40.18.1 → 40.18.3

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 (33) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/limel-breadcrumbs_6.cjs.entry.js +68 -29
  3. package/dist/cjs/limel-callout.cjs.entry.js +1 -1
  4. package/dist/cjs/limel-helper-line_3.cjs.entry.js +9 -19
  5. package/dist/cjs/limel-picker.cjs.entry.js +9 -14
  6. package/dist/cjs/limel-select.cjs.entry.js +9 -168
  7. package/dist/cjs/typeahead-CnyIjog_.js +194 -0
  8. package/dist/collection/components/callout/callout.css +1 -0
  9. package/dist/collection/components/input-field/input-field.js +68 -29
  10. package/dist/collection/components/list/list.js +9 -19
  11. package/dist/collection/components/picker/picker.js +9 -14
  12. package/dist/collection/util/typeahead.js +26 -0
  13. package/dist/esm/limel-breadcrumbs_6.entry.js +69 -30
  14. package/dist/esm/limel-callout.entry.js +1 -1
  15. package/dist/esm/limel-helper-line_3.entry.js +9 -19
  16. package/dist/esm/limel-picker.entry.js +9 -14
  17. package/dist/esm/limel-select.entry.js +1 -160
  18. package/dist/esm/typeahead-DOlnT3wh.js +188 -0
  19. package/dist/lime-elements/lime-elements.esm.js +1 -1
  20. package/dist/lime-elements/p-70272ea2.entry.js +1 -0
  21. package/dist/lime-elements/p-93a2d3b3.entry.js +159 -0
  22. package/dist/lime-elements/p-a2dc6645.entry.js +1 -0
  23. package/dist/lime-elements/p-c1678d3a.entry.js +23 -0
  24. package/dist/lime-elements/p-ea11f365.entry.js +1 -0
  25. package/dist/lime-elements/p-fYs7n6yY.js +1 -0
  26. package/dist/types/components/input-field/input-field.d.ts +6 -6
  27. package/dist/types/util/typeahead.d.ts +16 -0
  28. package/package.json +1 -1
  29. package/dist/lime-elements/p-27d7c255.entry.js +0 -159
  30. package/dist/lime-elements/p-3c861ca8.entry.js +0 -23
  31. package/dist/lime-elements/p-5f82ea0b.entry.js +0 -1
  32. package/dist/lime-elements/p-6aae3f58.entry.js +0 -1
  33. package/dist/lime-elements/p-7af16e1d.entry.js +0 -1
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ var keycodes = require('./keycodes-BQ_AERli.js');
4
+
5
+ /**
6
+ * These helpers implement "type to jump", the behavior a native `<select>` has:
7
+ * typing characters moves to the option whose text starts with those
8
+ * characters. Characters accumulate into a short-lived buffer, so typing
9
+ * `n`, `e` finds "Netherlands" instead of re-matching on `n` every time.
10
+ *
11
+ * The matching itself is pure and stateless — all the state lives in a
12
+ * `TypeaheadBuffer`, which owns the timer that discards the buffer once the
13
+ * user stops typing.
14
+ *
15
+ * `isTextEditingKey` is for the other side of a list: a field with a list
16
+ * under it uses it to tell which keys belong to the field.
17
+ */
18
+ /**
19
+ * How long a buffer of typed characters is kept before it is discarded, in
20
+ * milliseconds. Roughly matches a native `<select>`.
21
+ */
22
+ const TYPEAHEAD_BUFFER_TIMEOUT = 1000;
23
+ /**
24
+ * Returned by `findTypeaheadMatch` when nothing matched.
25
+ */
26
+ const NO_TYPEAHEAD_MATCH = -1;
27
+ /**
28
+ * Whether a keyboard event should be treated as a typed character.
29
+ *
30
+ * The space bar counts as a character here. Whether a *bare* space should
31
+ * start a buffer, or keep whatever meaning it has in the consuming component,
32
+ * is the consumer's decision.
33
+ *
34
+ * `shiftKey` is allowed, since capital letters are typed with it, and
35
+ * `event.repeat` is allowed, since holding a key down cycles through matches
36
+ * in a native `<select>` too.
37
+ *
38
+ * @param event - the keyboard event to inspect
39
+ * @returns `true` when the event represents a typed character
40
+ */
41
+ function isTypeaheadKey(event) {
42
+ return (event.key.length === 1 &&
43
+ !event.altKey &&
44
+ !event.ctrlKey &&
45
+ !event.metaKey &&
46
+ !event.isComposing);
47
+ }
48
+ /**
49
+ * Find the candidate to move to for a given buffer of typed characters.
50
+ *
51
+ * Two different searches are used, mirroring a native `<select>`:
52
+ * - When the buffer is a single character, possibly repeated (`b`, `bb`,
53
+ * `bbb`), only that character is matched and the search starts *after*
54
+ * `currentIndex`. Pressing the same key repeatedly therefore cycles through
55
+ * all candidates starting with it.
56
+ * - Otherwise the whole buffer is matched and the search starts *at*
57
+ * `currentIndex`, so continuing to type on an already-matching candidate
58
+ * keeps it rather than jumping to the next one.
59
+ *
60
+ * Both searches wrap around the end of the list.
61
+ *
62
+ * @param candidates - the candidates to search, index-aligned with the rows
63
+ * they are rendered as. Use `null` for rows that can never match, such as
64
+ * separators, so that they still occupy their index.
65
+ * @param buffer - the characters typed so far
66
+ * @param currentIndex - the index to search from. Anything outside
67
+ * `candidates` is treated as "no current candidate", and the search starts
68
+ * from the beginning.
69
+ * @returns the index of the matching candidate, or `NO_TYPEAHEAD_MATCH`
70
+ */
71
+ function findTypeaheadMatch(candidates, buffer, currentIndex) {
72
+ const count = candidates.length;
73
+ const query = buffer.toLowerCase();
74
+ if (!query || count === 0) {
75
+ return NO_TYPEAHEAD_MATCH;
76
+ }
77
+ const characters = [...query];
78
+ const cycles = isSingleDistinctCharacter(characters);
79
+ const needle = cycles ? characters[0] : query;
80
+ // Cycling starts one past the current candidate, so that pressing the same
81
+ // key again advances. Matching several characters starts at it, so that
82
+ // continuing to type keeps a candidate that already matches.
83
+ const offset = cycles ? 1 : 0;
84
+ const hasCurrent = currentIndex >= 0 && currentIndex < count;
85
+ const start = hasCurrent ? (currentIndex + offset) % count : 0;
86
+ for (let step = 0; step < count; step += 1) {
87
+ const index = (start + step) % count;
88
+ if (candidateMatches(candidates[index], needle)) {
89
+ return index;
90
+ }
91
+ }
92
+ return NO_TYPEAHEAD_MATCH;
93
+ }
94
+ /**
95
+ * Whether a string consists of a single character, possibly repeated. Also
96
+ * `true` for one character on its own, which is what makes a single key press
97
+ * advance past the current candidate rather than re-matching it.
98
+ *
99
+ * Takes the characters already split apart, so that the caller and this
100
+ * function agree on what "a character" is for anything outside the basic
101
+ * multilingual plane.
102
+ *
103
+ * @param characters - the characters of the string
104
+ * @returns `true` when every character is the same
105
+ */
106
+ function isSingleDistinctCharacter(characters) {
107
+ return characters.every((character) => character === characters[0]);
108
+ }
109
+ /**
110
+ * @param candidate - the candidate to test, or `null` for a row that can
111
+ * never match
112
+ * @param needle - the lower cased characters to match
113
+ * @returns `true` when the candidate starts with the given characters
114
+ */
115
+ function candidateMatches(candidate, needle) {
116
+ var _a;
117
+ if (!candidate || candidate.disabled) {
118
+ return false;
119
+ }
120
+ const text = (_a = candidate.text) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
121
+ return !!text && text.startsWith(needle);
122
+ }
123
+ /**
124
+ * Accumulates typed characters, and discards them again once the user stops
125
+ * typing for `TYPEAHEAD_BUFFER_TIMEOUT`.
126
+ */
127
+ class TypeaheadBuffer {
128
+ constructor(timeout = TYPEAHEAD_BUFFER_TIMEOUT) {
129
+ this.buffer = '';
130
+ this.timeout = timeout;
131
+ }
132
+ /**
133
+ * The characters typed so far.
134
+ */
135
+ get value() {
136
+ return this.buffer;
137
+ }
138
+ /**
139
+ * Whether nothing has been typed, or the buffer has been discarded.
140
+ */
141
+ get isEmpty() {
142
+ return this.buffer === '';
143
+ }
144
+ /**
145
+ * Append a character, and restart the timer that discards the buffer.
146
+ *
147
+ * @param character - the character to append
148
+ * @returns the buffer, including the appended character
149
+ */
150
+ append(character) {
151
+ this.buffer += character;
152
+ clearTimeout(this.resetTimeoutId);
153
+ this.resetTimeoutId = setTimeout(() => {
154
+ this.clear();
155
+ }, this.timeout);
156
+ return this.buffer;
157
+ }
158
+ /**
159
+ * Discard the buffer, and cancel the timer that would have discarded it.
160
+ */
161
+ clear() {
162
+ this.buffer = '';
163
+ clearTimeout(this.resetTimeoutId);
164
+ this.resetTimeoutId = undefined;
165
+ }
166
+ }
167
+ /**
168
+ * Whether a keyboard event edits text: a typed character, or Backspace or
169
+ * Delete. A bare space is left out, since on an option of a list it selects
170
+ * the option.
171
+ *
172
+ * A character typed with Option, or with AltGr, which is reported as Ctrl and
173
+ * Alt together, counts, since many layouts type `@` and `$` that way. A
174
+ * shortcut with Ctrl or Meta does not.
175
+ *
176
+ * @param event - the keyboard event to inspect
177
+ * @returns `true` when the event edits text
178
+ */
179
+ function isTextEditingKey(event) {
180
+ if (event.key === keycodes.BACKSPACE || event.key === keycodes.DELETE) {
181
+ return true;
182
+ }
183
+ const isCharacter = event.key.length === 1 &&
184
+ event.key !== keycodes.SPACEBAR &&
185
+ !event.metaKey &&
186
+ !event.isComposing;
187
+ return isCharacter && (!event.ctrlKey || event.altKey);
188
+ }
189
+
190
+ exports.NO_TYPEAHEAD_MATCH = NO_TYPEAHEAD_MATCH;
191
+ exports.TypeaheadBuffer = TypeaheadBuffer;
192
+ exports.findTypeaheadMatch = findTypeaheadMatch;
193
+ exports.isTextEditingKey = isTextEditingKey;
194
+ exports.isTypeaheadKey = isTypeaheadKey;
@@ -16,6 +16,7 @@
16
16
  display: flex;
17
17
  border-radius: 0.5rem;
18
18
  overflow: hidden;
19
+ border: 0.0625rem solid color-mix(in srgb, var(--limel-callout-tint-color), transparent 70%);
19
20
  --limel-callout-tint-color: var(
20
21
  --callout-color,
21
22
  rgb(var(--color-contrast-100))
@@ -4,9 +4,11 @@ import { debounce } from "lodash-es";
4
4
  import { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE, SPACE, TAB, } from "../../util/keycodes";
5
5
  import { getHref, getTarget, getRel } from "../../util/link-helper";
6
6
  import { createRandomString } from "../../util/random-string";
7
+ import { isTextEditingKey } from "../../util/typeahead";
7
8
  import { globalConfig } from "../../global/config";
8
9
  const CHANGE_EVENT_DEBOUNCE_TIMEOUT = 300;
9
10
  const RESIZE_HANDLER_DEBOUNCE_TIMEOUT = 100;
11
+ const MAX_FRAMES_TO_WAIT_FOR_LIST = 20;
10
12
  /**
11
13
  * @exampleComponent limel-example-input-field-text
12
14
  * @exampleComponent limel-example-input-field-placeholder
@@ -367,41 +369,64 @@ export class InputField {
367
369
  }
368
370
  return (h("span", { class: "lime-formatted-input lime-looks-like-input-value", "aria-hidden": this.readonly ? 'true' : undefined }, renderValue));
369
371
  };
370
- /**
371
- * Key handler for the input field
372
- * Will change focus to the first/last item in the dropdown list to enable selection with the keyboard
373
- *
374
- * @param event - event
375
- */
376
372
  this.onKeyDown = (event) => {
377
- this.showCompletions = true;
378
- const isForwardTab = event.key === TAB &&
373
+ if (event.key === ESCAPE) {
374
+ this.closeListWithEscape(event);
375
+ return;
376
+ }
377
+ const isTab = event.key === TAB &&
379
378
  !event.altKey &&
380
379
  !event.metaKey &&
381
- !event.shiftKey;
382
- const isUp = event.key === ARROW_UP;
383
- const isDown = event.key === ARROW_DOWN;
384
- if (event.key === TAB && event.shiftKey) {
380
+ !event.ctrlKey;
381
+ if (isTab) {
385
382
  this.showCompletions = false;
386
- }
387
- if (!isForwardTab && !isUp && !isDown) {
388
383
  return;
389
384
  }
390
- const list = document.querySelector(` #${this.portalId} limel-list`);
391
- if (!list) {
385
+ const wasOpen = this.isSuggestionListOpen();
386
+ this.showCompletions = true;
387
+ if (event.key === ARROW_DOWN || event.key === ARROW_UP) {
388
+ this.moveIntoList(event, wasOpen);
389
+ }
390
+ };
391
+ this.moveIntoList = (event, wasOpen) => {
392
+ var _a;
393
+ if (!this.isSuggestionListOpen()) {
392
394
  return;
393
395
  }
394
396
  event.preventDefault();
395
- if (isForwardTab || isDown) {
396
- const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:first-child');
397
- listElement.focus();
397
+ const position = event.key === ARROW_DOWN ? 'first' : 'last';
398
+ if (wasOpen) {
399
+ (_a = this.getListOption(position)) === null || _a === void 0 ? void 0 : _a.focus();
398
400
  return;
399
401
  }
400
- if (!isUp) {
402
+ this.focusOptionWhenShown(position);
403
+ };
404
+ this.getListOption = (position) => {
405
+ const list = document.querySelector(` #${this.portalId} limel-list`);
406
+ return list === null || list === void 0 ? void 0 : list.shadowRoot.querySelector(`.mdc-deprecated-list-item:${position}-child`);
407
+ };
408
+ this.focusOptionWhenShown = (position) => {
409
+ let frames = 0;
410
+ const focusOption = () => {
411
+ const option = this.getListOption(position);
412
+ if ((option === null || option === void 0 ? void 0 : option.getClientRects().length) > 0) {
413
+ option.focus();
414
+ return;
415
+ }
416
+ frames += 1;
417
+ if (frames < MAX_FRAMES_TO_WAIT_FOR_LIST) {
418
+ requestAnimationFrame(focusOption);
419
+ }
420
+ };
421
+ requestAnimationFrame(focusOption);
422
+ };
423
+ this.closeListWithEscape = (event) => {
424
+ if (!this.isSuggestionListOpen()) {
401
425
  return;
402
426
  }
403
- const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:last-child');
404
- listElement.focus();
427
+ event.preventDefault();
428
+ event.stopPropagation();
429
+ this.showCompletions = false;
405
430
  };
406
431
  this.handleCompletionChange = (event) => {
407
432
  event.stopPropagation();
@@ -418,10 +443,18 @@ export class InputField {
418
443
  this.changeEmitter(event.detail.text);
419
444
  this.changeEmitter.flush();
420
445
  };
446
+ this.hasSuggestions = () => {
447
+ return (!this.readonly &&
448
+ this.type !== 'textarea' &&
449
+ this.completions.length > 0);
450
+ };
451
+ this.isSuggestionListOpen = () => {
452
+ return (this.hasSuggestions() &&
453
+ this.showCompletions &&
454
+ this.filterCompletions(this.getCurrentValue()).length > 0);
455
+ };
421
456
  this.renderAutocompleteList = () => {
422
- if (this.readonly ||
423
- this.type === 'textarea' ||
424
- this.completions.length === 0) {
457
+ if (!this.hasSuggestions()) {
425
458
  return;
426
459
  }
427
460
  const dropdownZIndex = getComputedStyle(this.limelInputField).getPropertyValue('--dropdown-z-index');
@@ -439,8 +472,14 @@ export class InputField {
439
472
  return (h("limel-list", { onChange: this.handleCompletionChange, onKeyDown: this.handleKeyDownInDropdown, type: "selectable", items: filteredCompletions }));
440
473
  };
441
474
  this.handleKeyDownInDropdown = (event) => {
442
- const keyFound = [TAB, ESCAPE, ENTER].includes(event.key);
443
- if (keyFound) {
475
+ if (event.key === ESCAPE) {
476
+ event.preventDefault();
477
+ event.stopPropagation();
478
+ this.setFocus();
479
+ this.showCompletions = false;
480
+ return;
481
+ }
482
+ if ([TAB, ENTER].includes(event.key) || isTextEditingKey(event)) {
444
483
  this.setFocus();
445
484
  }
446
485
  };
@@ -598,10 +637,10 @@ export class InputField {
598
637
  if (this.isInvalid()) {
599
638
  properties['aria-invalid'] = 'true';
600
639
  }
601
- if (this.renderAutocompleteList()) {
640
+ if (this.hasSuggestions()) {
602
641
  properties['aria-controls'] = this.portalId;
603
642
  }
604
- return (h(Host, { key: '3abb0e074f55f8e47f46cf77ed885797047e44b9' }, h("limel-notched-outline", { key: '0d25df2774e7cda39da90163b0832a1ea117d7c8', labelId: this.inputId, labelElementId: this.labelElementId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!this.value, hasLeadingIcon: !!this.leadingIcon }, h("label", { key: 'a60a2bfcdafe232b32a0d67f972c3885fc5b6428', slot: "content", class: this.getContainerClassList() }, this.renderLeadingIcon(), this.renderPrefix(), this.renderFormattedNumber(), this.renderInput(properties), this.renderSuffix(), this.renderTextarea(properties), this.renderTrailingLinkOrButton())), this.renderHelperLine(), this.renderAutocompleteList()));
643
+ return (h(Host, { key: 'e09ee9e3102d6a15d0806602c65361001cd20c63' }, h("limel-notched-outline", { key: 'c2301d9fd1af41e8b4396dc5f2978e51b260fe2f', labelId: this.inputId, labelElementId: this.labelElementId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!this.value, hasLeadingIcon: !!this.leadingIcon }, h("label", { key: '69e02e5df31a6ce10743c3c8a9e78c5765794a18', slot: "content", class: this.getContainerClassList() }, this.renderLeadingIcon(), this.renderPrefix(), this.renderFormattedNumber(), this.renderInput(properties), this.renderSuffix(), this.renderTextarea(properties), this.renderTrailingLinkOrButton())), this.renderHelperLine(), this.renderAutocompleteList()));
605
644
  }
606
645
  valueWatcher(newValue) {
607
646
  if (!this.mdcTextField || this.changeWaiting) {
@@ -50,26 +50,16 @@ export class List {
50
50
  this.listElement.removeEventListener('mousedown', this.handleItemMouseDown);
51
51
  this.listElement.addEventListener('mousedown', this.handleItemMouseDown);
52
52
  this.mdcList = new MDCList(element);
53
- // NOTE: This has no effect. It is kept as a marker rather than
54
- // removed, so that its absence isn't mistaken for an oversight.
53
+ // MDC's typeahead (`hasTypeahead`) is left off, so that typed
54
+ // characters are not taken from a field with a list under it. The list
55
+ // test in `list.e2e.tsx` fails if it is turned on. `limel-select` has
56
+ // its own typeahead, see `src/util/typeahead.ts`.
55
57
  //
56
- // MDC indexes each row for typeahead by looking for
57
- // `.mdc-deprecated-list-item__primary-text` inside it, while
58
- // `limel-list-item` renders its label as `<span class="label">`. MDC
59
- // therefore builds an empty index, and typing does nothing.
60
- //
61
- // Do not "fix" this by adding MDC's class name to `limel-list-item`.
62
- // That revives MDC's typeahead along with three behaviors that cannot
63
- // be configured from the outside: a hard coded 300ms buffer, `Enter`
64
- // being ignored for as long as that buffer lives (its `notifyAction`
65
- // is guarded by `isTypeaheadInProgress`), and the space bar being
66
- // excluded from matches. It would also collide with `limel-menu`,
67
- // where single characters are used as item hotkeys.
68
- //
69
- // `limel-select` implements its own typeahead instead, see
70
- // `src/util/typeahead.ts`. It intercepts typed characters in the
71
- // capture phase on this very element, so they never reach MDC.
72
- this.mdcList.hasTypeahead = true;
58
+ // Adding MDC's class name to `limel-list-item` does nothing while it is
59
+ // off. Turning it on as well would bring its 300ms buffer, `Enter`
60
+ // being ignored while the buffer lives and the space bar being left out
61
+ // of matches, none of which can be configured, and it would collide
62
+ // with the single characters that `limel-menu` uses as hotkeys.
73
63
  };
74
64
  /**
75
65
  * Focus the pressed list item directly, without scrolling.
@@ -3,6 +3,7 @@ import { h, } from "@stencil/core";
3
3
  import { isDescendant } from "../../util/dom";
4
4
  import { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE, TAB } from "../../util/keycodes";
5
5
  import { createRandomString } from "../../util/random-string";
6
+ import { isTextEditingKey } from "../../util/typeahead";
6
7
  import { getIconFillColor, getIconName } from "../icon/get-icon-props";
7
8
  import { debounce } from "lodash-es";
8
9
  const SEARCH_DEBOUNCE = 300;
@@ -199,7 +200,7 @@ export class Picker {
199
200
  props.maxItems = 1;
200
201
  }
201
202
  return [
202
- h("limel-chip-set", Object.assign({ key: 'eed0789b538bd50638936e1c6ad7e9deaef954ec', type: "input", inputType: "search", label: this.label, helperText: this.helperText, leadingIcon: this.leadingIcon, value: this.chips, disabled: this.disabled, invalid: this.invalid, delimiter: this.renderDelimiter(), readonly: this.readonly, required: this.required, searchLabel: this.searchLabel, language: this.language, onInput: this.handleTextInput, onKeyDown: this.handleInputKeyDown, onChange: this.handleChange, onInteract: this.handleInteract, onStartEdit: this.handleInputFieldFocus, onStopEdit: this.handleStopEditAndBlur, emptyInputOnBlur: false, emptyInputOnChange: false, clearAllButton: this.multiple && !this.chipSetEditMode }, props)),
203
+ h("limel-chip-set", Object.assign({ key: 'c3f4fd001d63a287a8a9f447f7c9df65279fbc34', type: "input", inputType: "search", label: this.label, helperText: this.helperText, leadingIcon: this.leadingIcon, value: this.chips, disabled: this.disabled, invalid: this.invalid, delimiter: this.renderDelimiter(), readonly: this.readonly, required: this.required, searchLabel: this.searchLabel, language: this.language, onInput: this.handleTextInput, onKeyDown: this.handleInputKeyDown, onChange: this.handleChange, onInteract: this.handleInteract, onStartEdit: this.handleInputFieldFocus, onStopEdit: this.handleStopEditAndBlur, emptyInputOnBlur: false, emptyInputOnChange: false, clearAllButton: this.multiple && !this.chipSetEditMode }, props)),
203
204
  this.renderDropdown(),
204
205
  ];
205
206
  }
@@ -247,7 +248,7 @@ export class Picker {
247
248
  'is-on-top': this.actionPosition === 'top',
248
249
  'is-at-bottom': this.actionPosition === 'bottom',
249
250
  'has-position-sticky': this.actionScrollBehavior === 'sticky',
250
- }, badgeIcons: true, type: 'selectable', onChange: this.handleActionListChange, items: this.actions.map(this.removeUnusedPropertiesOnAction) }),
251
+ }, badgeIcons: true, type: 'selectable', onChange: this.handleActionListChange, onKeyDown: this.onListKeyDown, items: this.actions.map(this.removeUnusedPropertiesOnAction) }),
251
252
  ];
252
253
  }
253
254
  removeUnusedPropertiesOnAction(action) {
@@ -311,7 +312,7 @@ export class Picker {
311
312
  return h("p", { style: style }, text);
312
313
  }
313
314
  renderListResult() {
314
- return (h("limel-list", { badgeIcons: this.badgeIcons, onChange: this.handleListChange, onKeyDown: this.onListKeyDown, type: "selectable", items: this.items }));
315
+ return (h("limel-list", { "data-suggestions": "", badgeIcons: this.badgeIcons, onChange: this.handleListChange, onKeyDown: this.onListKeyDown, type: "selectable", items: this.items }));
315
316
  }
316
317
  onListKeyDown(event) {
317
318
  if (event.key === ESCAPE) {
@@ -323,7 +324,7 @@ export class Picker {
323
324
  this.chipSet.setFocus();
324
325
  return;
325
326
  }
326
- if ([TAB, ENTER].includes(event.key)) {
327
+ if ([TAB, ENTER].includes(event.key) || isTextEditingKey(event)) {
327
328
  this.chipSet.setFocus();
328
329
  }
329
330
  }
@@ -452,10 +453,7 @@ export class Picker {
452
453
  * @param event - event
453
454
  */
454
455
  handleInputKeyDown(event) {
455
- const isForwardTab = event.key === TAB &&
456
- !event.altKey &&
457
- !event.metaKey &&
458
- !event.shiftKey;
456
+ var _a;
459
457
  const isUp = event.key === ARROW_UP;
460
458
  const isDown = event.key === ARROW_DOWN;
461
459
  const isEscape = event.key === ESCAPE;
@@ -465,14 +463,14 @@ export class Picker {
465
463
  this.handleEscape();
466
464
  return;
467
465
  }
468
- if (!isForwardTab && !isUp && !isDown) {
466
+ if (!isUp && !isDown) {
469
467
  return;
470
468
  }
471
- const list = document.querySelector(` #${this.portalId} limel-list`);
469
+ const list = (_a = document.querySelector(` #${this.portalId} limel-list[data-suggestions]`)) !== null && _a !== void 0 ? _a : document.querySelector(` #${this.portalId} limel-list`);
472
470
  if (!list) {
473
471
  return;
474
472
  }
475
- if (isForwardTab || isDown) {
473
+ if (isDown) {
476
474
  // Match by class only (not `:first-child`) so a leading
477
475
  // `ListSeparator` doesn't shadow the first focusable item.
478
476
  const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item');
@@ -483,9 +481,6 @@ export class Picker {
483
481
  listElement.focus();
484
482
  return;
485
483
  }
486
- if (!isUp) {
487
- return;
488
- }
489
484
  const listItems = list.shadowRoot.querySelectorAll('.mdc-deprecated-list-item');
490
485
  const listElement = [...listItems].at(-1);
491
486
  if (!listElement) {
@@ -1,3 +1,4 @@
1
+ import { BACKSPACE, DELETE, SPACEBAR } from "./keycodes";
1
2
  /**
2
3
  * These helpers implement "type to jump", the behavior a native `<select>` has:
3
4
  * typing characters moves to the option whose text starts with those
@@ -7,6 +8,9 @@
7
8
  * The matching itself is pure and stateless — all the state lives in a
8
9
  * `TypeaheadBuffer`, which owns the timer that discards the buffer once the
9
10
  * user stops typing.
11
+ *
12
+ * `isTextEditingKey` is for the other side of a list: a field with a list
13
+ * under it uses it to tell which keys belong to the field.
10
14
  */
11
15
  /**
12
16
  * How long a buffer of typed characters is kept before it is discarded, in
@@ -157,3 +161,25 @@ export class TypeaheadBuffer {
157
161
  this.resetTimeoutId = undefined;
158
162
  }
159
163
  }
164
+ /**
165
+ * Whether a keyboard event edits text: a typed character, or Backspace or
166
+ * Delete. A bare space is left out, since on an option of a list it selects
167
+ * the option.
168
+ *
169
+ * A character typed with Option, or with AltGr, which is reported as Ctrl and
170
+ * Alt together, counts, since many layouts type `@` and `$` that way. A
171
+ * shortcut with Ctrl or Meta does not.
172
+ *
173
+ * @param event - the keyboard event to inspect
174
+ * @returns `true` when the event edits text
175
+ */
176
+ export function isTextEditingKey(event) {
177
+ if (event.key === BACKSPACE || event.key === DELETE) {
178
+ return true;
179
+ }
180
+ const isCharacter = event.key.length === 1 &&
181
+ event.key !== SPACEBAR &&
182
+ !event.metaKey &&
183
+ !event.isComposing;
184
+ return isCharacter && (!event.ctrlKey || event.altKey);
185
+ }