@internetarchive/collection-browser 3.4.1-alpha-webdev7761.2 → 3.4.1-alpha-webdev7761.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.
- package/dist/src/collection-browser.js +7 -0
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/combo-box/caret-closed.js +8 -11
- package/dist/src/combo-box/caret-closed.js.map +1 -1
- package/dist/src/combo-box/caret-open.js +8 -11
- package/dist/src/combo-box/caret-open.js.map +1 -1
- package/dist/src/combo-box/clear.d.ts +2 -0
- package/dist/src/combo-box/clear.js +11 -0
- package/dist/src/combo-box/clear.js.map +1 -0
- package/dist/src/combo-box/ia-combo-box.d.ts +40 -9
- package/dist/src/combo-box/ia-combo-box.js +189 -84
- package/dist/src/combo-box/ia-combo-box.js.map +1 -1
- package/dist/src/combo-box/models.d.ts +14 -0
- package/dist/src/combo-box/models.js +31 -0
- package/dist/src/combo-box/models.js.map +1 -1
- package/package.json +1 -1
- package/src/collection-browser.ts +7 -0
- package/src/combo-box/caret-closed.ts +8 -11
- package/src/combo-box/caret-open.ts +8 -11
- package/src/combo-box/clear.ts +11 -0
- package/src/combo-box/ia-combo-box.ts +193 -79
- package/src/combo-box/models.ts +30 -0
|
@@ -16,6 +16,7 @@ import { msg } from '@lit/localize';
|
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
18
|
hasAnyOf,
|
|
19
|
+
isSubsequence,
|
|
19
20
|
type IAComboBoxBehavior,
|
|
20
21
|
type IAComboBoxFilterFunction,
|
|
21
22
|
type IAComboBoxFilterOption,
|
|
@@ -23,36 +24,9 @@ import {
|
|
|
23
24
|
type IAComboBoxOption,
|
|
24
25
|
} from './models';
|
|
25
26
|
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Tests whether the given `haystack` string has the given `needle` as a subsequence.
|
|
31
|
-
* Returns `true` if the characters of `needle` appear in order within `haystack`,
|
|
32
|
-
* regardless of whether they are contiguous. Returns `false` otherwise.
|
|
33
|
-
*
|
|
34
|
-
* E.g., `ace` is a subsequence of `archive` (but not a contiguous substring).
|
|
35
|
-
*
|
|
36
|
-
* Note: The empty string is a subsequence of any string, including itself.
|
|
37
|
-
*
|
|
38
|
-
* @param needle The potential subsequence to check for inside `haystack`.
|
|
39
|
-
* @param haystack The string to be tested for containing the `needle` subsequence.
|
|
40
|
-
* @returns Whether `haystack` has `needle` as a subsequence.
|
|
41
|
-
*/
|
|
42
|
-
const isSubsequence = (needle: string, haystack: string): boolean => {
|
|
43
|
-
const needleLen = needle.length;
|
|
44
|
-
const haystackLen = haystack.length;
|
|
45
|
-
if (needleLen === 0) return true;
|
|
46
|
-
|
|
47
|
-
let needleIdx = 0;
|
|
48
|
-
let haystackIdx = 0;
|
|
49
|
-
while (haystackIdx < haystackLen) {
|
|
50
|
-
if (haystack[haystackIdx] === needle[needleIdx]) needleIdx += 1;
|
|
51
|
-
if (needleIdx >= needleLen) return true;
|
|
52
|
-
haystackIdx += 1;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
};
|
|
27
|
+
import caretClosedIcon from './caret-closed';
|
|
28
|
+
import caretOpenIcon from './caret-open';
|
|
29
|
+
import clearIcon from './clear';
|
|
56
30
|
|
|
57
31
|
/**
|
|
58
32
|
* Map from filter preset keys to their associated filtering function.
|
|
@@ -151,7 +125,8 @@ export class IAComboBox extends LitElement {
|
|
|
151
125
|
caseSensitive = false;
|
|
152
126
|
|
|
153
127
|
/**
|
|
154
|
-
* Whether the filtered options should be listed in lexicographically-sorted order
|
|
128
|
+
* Whether the filtered options should be listed in lexicographically-sorted order,
|
|
129
|
+
* respecting the current `caseSensitive` setting.
|
|
155
130
|
* Default is `false`, displaying them in the same order as the provided options array.
|
|
156
131
|
*/
|
|
157
132
|
@property({ type: Boolean, reflect: true }) sort = false;
|
|
@@ -179,6 +154,12 @@ export class IAComboBox extends LitElement {
|
|
|
179
154
|
@property({ type: Boolean, reflect: true, attribute: 'stay-open' })
|
|
180
155
|
stayOpen = false;
|
|
181
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Whether the combo box shows a clear button when a value is selected.
|
|
159
|
+
* Default is `false`.
|
|
160
|
+
*/
|
|
161
|
+
@property({ type: Boolean, reflect: true }) clearable = false;
|
|
162
|
+
|
|
182
163
|
/**
|
|
183
164
|
* Whether the combo box's option menu is currently expanded. Default is `false`.
|
|
184
165
|
*/
|
|
@@ -233,8 +214,6 @@ export class IAComboBox extends LitElement {
|
|
|
233
214
|
|
|
234
215
|
@query('#text-input') private textInput!: HTMLInputElement;
|
|
235
216
|
|
|
236
|
-
@query('#caret-button') private caretButton!: HTMLInputElement;
|
|
237
|
-
|
|
238
217
|
@query('#options-list') private optionsList!: HTMLUListElement;
|
|
239
218
|
|
|
240
219
|
static formAssociated = true;
|
|
@@ -279,19 +258,18 @@ export class IAComboBox extends LitElement {
|
|
|
279
258
|
}
|
|
280
259
|
|
|
281
260
|
render(): TemplateResult | typeof nothing {
|
|
261
|
+
const mainWidgetClasses = classMap({
|
|
262
|
+
disabled: this.disabled,
|
|
263
|
+
focused: this.hasFocus,
|
|
264
|
+
});
|
|
265
|
+
|
|
282
266
|
return html`
|
|
283
|
-
<div
|
|
284
|
-
id="container"
|
|
285
|
-
class=${classMap({ focused: this.hasFocus })}
|
|
286
|
-
part="container"
|
|
287
|
-
>
|
|
267
|
+
<div id="container" part="container">
|
|
288
268
|
${this.labelTemplate}
|
|
289
|
-
<div
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
>
|
|
294
|
-
${this.textInputTemplate} ${this.caretButtonTemplate}
|
|
269
|
+
<div id="main-widget-row" class=${mainWidgetClasses} part="combo-box">
|
|
270
|
+
${this.textInputTemplate}
|
|
271
|
+
${this.clearable ? this.clearButtonTemplate : nothing}
|
|
272
|
+
${this.caretButtonTemplate}
|
|
295
273
|
</div>
|
|
296
274
|
${this.optionsListTemplate}
|
|
297
275
|
</div>
|
|
@@ -369,10 +347,14 @@ export class IAComboBox extends LitElement {
|
|
|
369
347
|
/**
|
|
370
348
|
* Template for the main label for the combo box.
|
|
371
349
|
*
|
|
372
|
-
* Uses the contents of the
|
|
350
|
+
* Uses the contents of the `label` named slot as the label text.
|
|
373
351
|
*/
|
|
374
352
|
private get labelTemplate(): TemplateResult {
|
|
375
|
-
return html
|
|
353
|
+
return html`
|
|
354
|
+
<label id="label" for="text-input">
|
|
355
|
+
<slot name="label"></slot>
|
|
356
|
+
</label>
|
|
357
|
+
`;
|
|
376
358
|
}
|
|
377
359
|
|
|
378
360
|
/**
|
|
@@ -381,7 +363,7 @@ export class IAComboBox extends LitElement {
|
|
|
381
363
|
*/
|
|
382
364
|
private get textInputTemplate(): TemplateResult {
|
|
383
365
|
const textInputClasses = classMap({
|
|
384
|
-
|
|
366
|
+
'clear-padding': this.clearable && !this.shouldShowClearButton,
|
|
385
367
|
});
|
|
386
368
|
|
|
387
369
|
return html`
|
|
@@ -398,6 +380,7 @@ export class IAComboBox extends LitElement {
|
|
|
398
380
|
aria-controls="options-list"
|
|
399
381
|
aria-expanded=${this.open}
|
|
400
382
|
aria-activedescendant=${ifDefined(this.highlightedOption?.id)}
|
|
383
|
+
?readonly=${this.behavior === 'select-only'}
|
|
401
384
|
?disabled=${this.disabled}
|
|
402
385
|
?required=${this.required}
|
|
403
386
|
@click=${this.handleComboBoxClick}
|
|
@@ -409,14 +392,40 @@ export class IAComboBox extends LitElement {
|
|
|
409
392
|
`;
|
|
410
393
|
}
|
|
411
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Template for the clear button that is shown when the `clearable` property
|
|
397
|
+
* is true.
|
|
398
|
+
*/
|
|
399
|
+
private get clearButtonTemplate(): TemplateResult {
|
|
400
|
+
return html`
|
|
401
|
+
<button
|
|
402
|
+
type="button"
|
|
403
|
+
id="clear-button"
|
|
404
|
+
part="clear-button"
|
|
405
|
+
tabindex="-1"
|
|
406
|
+
?hidden=${!this.shouldShowClearButton}
|
|
407
|
+
@click=${this.handleClearButtonClick}
|
|
408
|
+
>
|
|
409
|
+
<span class="sr-only">${msg('Clear')}</span>
|
|
410
|
+
<slot name="clear-button">
|
|
411
|
+
${clearIcon}
|
|
412
|
+
</slot>
|
|
413
|
+
</button>
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
|
|
412
417
|
/**
|
|
413
418
|
* Template for the caret open/closed icons to show beside the text input.
|
|
414
419
|
* The icons are wrapped in named slots to allow consumers to override them.
|
|
415
420
|
*/
|
|
416
421
|
private get caretTemplate(): TemplateResult {
|
|
417
422
|
return html`
|
|
418
|
-
<slot name="caret-closed" ?hidden=${this.open}>
|
|
419
|
-
|
|
423
|
+
<slot name="caret-closed" ?hidden=${this.open}>
|
|
424
|
+
${caretClosedIcon}
|
|
425
|
+
</slot>
|
|
426
|
+
<slot name="caret-open" ?hidden=${!this.open}>
|
|
427
|
+
${caretOpenIcon}
|
|
428
|
+
</slot>
|
|
420
429
|
`;
|
|
421
430
|
}
|
|
422
431
|
|
|
@@ -438,6 +447,7 @@ export class IAComboBox extends LitElement {
|
|
|
438
447
|
@focus=${this.handleFocus}
|
|
439
448
|
@blur=${this.handleBlur}
|
|
440
449
|
>
|
|
450
|
+
<span class="sr-only">${msg('Toggle options')}</span>
|
|
441
451
|
${this.caretTemplate}
|
|
442
452
|
</button>
|
|
443
453
|
`;
|
|
@@ -452,6 +462,7 @@ export class IAComboBox extends LitElement {
|
|
|
452
462
|
id="options-list"
|
|
453
463
|
part="options-list"
|
|
454
464
|
role="listbox"
|
|
465
|
+
tabindex="-1"
|
|
455
466
|
popover
|
|
456
467
|
?hidden=${!this.open}
|
|
457
468
|
@focus=${this.handleFocus}
|
|
@@ -485,6 +496,7 @@ export class IAComboBox extends LitElement {
|
|
|
485
496
|
id=${opt.id}
|
|
486
497
|
class=${optionClasses}
|
|
487
498
|
part="option"
|
|
499
|
+
role="option"
|
|
488
500
|
tabindex="-1"
|
|
489
501
|
@pointerenter=${this.handleOptionPointerEnter}
|
|
490
502
|
@pointermove=${this.handleOptionPointerMove}
|
|
@@ -525,7 +537,7 @@ export class IAComboBox extends LitElement {
|
|
|
525
537
|
* Handler for when the pointer device is moved within an option in the dropdown.
|
|
526
538
|
*/
|
|
527
539
|
private handleOptionPointerMove(e: PointerEvent): void {
|
|
528
|
-
const target = e.
|
|
540
|
+
const target = e.currentTarget as HTMLLIElement;
|
|
529
541
|
const option = this.getOptionFor(target.id);
|
|
530
542
|
if (option) this.setHighlightedOption(option);
|
|
531
543
|
}
|
|
@@ -534,7 +546,7 @@ export class IAComboBox extends LitElement {
|
|
|
534
546
|
* Handler for when the user clicks on an option in the dropdown.
|
|
535
547
|
*/
|
|
536
548
|
private handleOptionClick(e: PointerEvent): void {
|
|
537
|
-
const target = e.
|
|
549
|
+
const target = e.currentTarget as HTMLLIElement;
|
|
538
550
|
const option = this.getOptionFor(target.id);
|
|
539
551
|
if (option) {
|
|
540
552
|
this.setSelectedOption(option.id);
|
|
@@ -568,10 +580,15 @@ export class IAComboBox extends LitElement {
|
|
|
568
580
|
}
|
|
569
581
|
break;
|
|
570
582
|
case 'Tab':
|
|
571
|
-
this.
|
|
572
|
-
return;
|
|
583
|
+
this.handleTabPressed();
|
|
584
|
+
return; // Never cancel the default behavior for Tab
|
|
585
|
+
case ' ':
|
|
586
|
+
this.handleSpacePressed();
|
|
587
|
+
// In the specific case of picking an option in select-only, we skip the defaults
|
|
588
|
+
if (this.behavior === 'select-only' && this.highlightedOption) break;
|
|
589
|
+
return; // Otherwise, don't cancel the default behavior
|
|
573
590
|
default:
|
|
574
|
-
// Do nothing and allow propagation
|
|
591
|
+
// Do nothing and allow propagation for all other keys
|
|
575
592
|
return;
|
|
576
593
|
}
|
|
577
594
|
|
|
@@ -659,6 +676,26 @@ export class IAComboBox extends LitElement {
|
|
|
659
676
|
this.openOptionsMenu();
|
|
660
677
|
}
|
|
661
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Handler for when the Tab key is pressed
|
|
681
|
+
*/
|
|
682
|
+
private handleTabPressed(): void {
|
|
683
|
+
if (this.highlightedOption) {
|
|
684
|
+
this.setSelectedOption(this.highlightedOption.id);
|
|
685
|
+
if (!this.stayOpen) this.open = false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Handler for when the Space key is pressed
|
|
691
|
+
*/
|
|
692
|
+
private handleSpacePressed(): void {
|
|
693
|
+
if (this.behavior === 'select-only' && this.highlightedOption) {
|
|
694
|
+
this.setSelectedOption(this.highlightedOption.id);
|
|
695
|
+
if (!this.stayOpen) this.open = false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
662
699
|
/**
|
|
663
700
|
* Handler for clicks on the combo box input field or caret button.
|
|
664
701
|
*/
|
|
@@ -666,13 +703,21 @@ export class IAComboBox extends LitElement {
|
|
|
666
703
|
this.toggleOptionsMenu();
|
|
667
704
|
}
|
|
668
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Handler for when the clear button is clicked.
|
|
708
|
+
*/
|
|
709
|
+
private handleClearButtonClick(): void {
|
|
710
|
+
this.clearSelectedOption();
|
|
711
|
+
this.textInput.focus();
|
|
712
|
+
this.openOptionsMenu();
|
|
713
|
+
}
|
|
714
|
+
|
|
669
715
|
/**
|
|
670
716
|
* Handler for when any part of the combo box receives focus.
|
|
671
717
|
*/
|
|
672
718
|
private handleFocus(): void {
|
|
673
|
-
if (this.behavior
|
|
674
|
-
|
|
675
|
-
} else {
|
|
719
|
+
if (this.behavior !== 'select-only') {
|
|
720
|
+
// Always keep focus on the text input if it's editable
|
|
676
721
|
this.textInput.focus();
|
|
677
722
|
}
|
|
678
723
|
this.hasFocus = true;
|
|
@@ -685,11 +730,23 @@ export class IAComboBox extends LitElement {
|
|
|
685
730
|
private handleBlur(): void {
|
|
686
731
|
this.hasFocus = false;
|
|
687
732
|
this.losingFocus = true;
|
|
733
|
+
|
|
734
|
+
// On the next tick, check whether we've actually lost focus to some other element,
|
|
735
|
+
// or just had a momentary internal focus switch. If it's the former, we should
|
|
736
|
+
// close the menu and possibly make a selection (depending on desired behavior).
|
|
688
737
|
setTimeout(() => {
|
|
689
738
|
if (this.losingFocus && !this.shadowRoot?.activeElement) {
|
|
690
739
|
this.losingFocus = false;
|
|
691
740
|
this.closeOptionsMenu();
|
|
692
|
-
|
|
741
|
+
|
|
742
|
+
if (this.behavior === 'list') {
|
|
743
|
+
this.setTextValue(this.selectedOption?.text ?? '', false);
|
|
744
|
+
} else if (
|
|
745
|
+
this.behavior === 'freeform' &&
|
|
746
|
+
(this.enteredText || this.value)
|
|
747
|
+
) {
|
|
748
|
+
this.setValue(this.enteredText);
|
|
749
|
+
}
|
|
693
750
|
}
|
|
694
751
|
}, 0);
|
|
695
752
|
}
|
|
@@ -798,7 +855,8 @@ export class IAComboBox extends LitElement {
|
|
|
798
855
|
const prevValue = this.value;
|
|
799
856
|
this.value = option.id;
|
|
800
857
|
this.internals.setFormValue(this.value);
|
|
801
|
-
this.setTextValue(option.text);
|
|
858
|
+
this.setTextValue(option.text, false);
|
|
859
|
+
this.setFilterText('');
|
|
802
860
|
if (this.value !== prevValue) this.emitChangeEvent();
|
|
803
861
|
|
|
804
862
|
// Invoke the option's select callback if defined
|
|
@@ -843,10 +901,20 @@ export class IAComboBox extends LitElement {
|
|
|
843
901
|
/**
|
|
844
902
|
* Changes the value of the text input box, and updates the filter accordingly.
|
|
845
903
|
*/
|
|
846
|
-
private setTextValue(value: string): void {
|
|
904
|
+
private setTextValue(value: string, setFilter = true): void {
|
|
847
905
|
this.textInput.value = value;
|
|
848
906
|
this.enteredText = value;
|
|
849
|
-
this.setFilterText(value);
|
|
907
|
+
if (setFilter) this.setFilterText(value);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Sets the current filter text based on the provided string. The resulting filter
|
|
912
|
+
* text might not exactly match the provided value, depending on the current case
|
|
913
|
+
* sensitivity.
|
|
914
|
+
*/
|
|
915
|
+
private setFilterText(baseFilterText: string): void {
|
|
916
|
+
const { caseTransform } = this;
|
|
917
|
+
this.filterText = caseTransform(baseFilterText);
|
|
850
918
|
}
|
|
851
919
|
|
|
852
920
|
openOptionsMenu(): void {
|
|
@@ -896,6 +964,21 @@ export class IAComboBox extends LitElement {
|
|
|
896
964
|
// HELPERS
|
|
897
965
|
//
|
|
898
966
|
|
|
967
|
+
/**
|
|
968
|
+
* True iff no selection has been made and no text has been entered.
|
|
969
|
+
*/
|
|
970
|
+
private get isEmpty(): boolean {
|
|
971
|
+
return !this.selectedOption && !this.enteredText;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* We only show the clear button when the `clearable` property is set
|
|
976
|
+
* and the combo box is neither empty nor disabled.
|
|
977
|
+
*/
|
|
978
|
+
private get shouldShowClearButton(): boolean {
|
|
979
|
+
return this.clearable && !this.disabled && !this.isEmpty;
|
|
980
|
+
}
|
|
981
|
+
|
|
899
982
|
/**
|
|
900
983
|
* Sets the size and position of the options menu to match the size and position of
|
|
901
984
|
* the combo box widget. Prefers to position below the main widget, but will flip
|
|
@@ -910,12 +993,12 @@ export class IAComboBox extends LitElement {
|
|
|
910
993
|
const usableHeightBelow = innerHeight - mainWidgetRect.bottom;
|
|
911
994
|
|
|
912
995
|
// We still want to respect any CSS var specified by the consumer
|
|
913
|
-
const maxHeightVar = 'var(--
|
|
996
|
+
const maxHeightVar = 'var(--combo-box-list-max-height, 250px)';
|
|
914
997
|
|
|
915
998
|
const optionsListStyles: Record<string, string> = {
|
|
916
999
|
top: `${mainWidgetRect.bottom + scrollY}px`,
|
|
917
1000
|
left: `${mainWidgetRect.left + scrollX}px`,
|
|
918
|
-
width: `var(--
|
|
1001
|
+
width: `var(--combo-box-list-width, ${mainWidgetRect.width}px)`,
|
|
919
1002
|
maxHeight: `min(${usableHeightBelow}px, ${maxHeightVar})`,
|
|
920
1003
|
};
|
|
921
1004
|
|
|
@@ -942,16 +1025,6 @@ export class IAComboBox extends LitElement {
|
|
|
942
1025
|
return this.caseSensitive ? STRING_IDENTITY_FN : STRING_LOWER_CASE_FN;
|
|
943
1026
|
}
|
|
944
1027
|
|
|
945
|
-
/**
|
|
946
|
-
* Sets the current filter text based on the provided string. The resulting filter
|
|
947
|
-
* text might not exactly match the provided value, depending on the current case
|
|
948
|
-
* sensitivity.
|
|
949
|
-
*/
|
|
950
|
-
private setFilterText(baseFilterText: string): void {
|
|
951
|
-
const { caseTransform } = this;
|
|
952
|
-
this.filterText = caseTransform(baseFilterText);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
1028
|
/**
|
|
956
1029
|
* Returns the combo box option having the given ID, or null if none exists.
|
|
957
1030
|
*/
|
|
@@ -1075,6 +1148,11 @@ export class IAComboBox extends LitElement {
|
|
|
1075
1148
|
|
|
1076
1149
|
static get styles(): CSSResultGroup {
|
|
1077
1150
|
return css`
|
|
1151
|
+
#container {
|
|
1152
|
+
display: inline-block;
|
|
1153
|
+
width: var(--combo-box-width, auto);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1078
1156
|
#label {
|
|
1079
1157
|
display: block;
|
|
1080
1158
|
width: fit-content;
|
|
@@ -1086,9 +1164,15 @@ export class IAComboBox extends LitElement {
|
|
|
1086
1164
|
flex-wrap: nowrap;
|
|
1087
1165
|
background: white;
|
|
1088
1166
|
border: 1px solid black;
|
|
1167
|
+
width: 100%;
|
|
1089
1168
|
}
|
|
1090
1169
|
|
|
1091
|
-
|
|
1170
|
+
#main-widget-row:not(.focused):hover,
|
|
1171
|
+
#main-widget-row:not(.focused):active {
|
|
1172
|
+
background: #fafafa;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
#main-widget-row.focused {
|
|
1092
1176
|
outline: black auto 1px;
|
|
1093
1177
|
outline-offset: 3px;
|
|
1094
1178
|
}
|
|
@@ -1097,27 +1181,47 @@ export class IAComboBox extends LitElement {
|
|
|
1097
1181
|
appearance: none;
|
|
1098
1182
|
background: transparent;
|
|
1099
1183
|
border: none;
|
|
1100
|
-
padding: var(--
|
|
1184
|
+
padding: var(--combo-box-padding, 5px);
|
|
1185
|
+
padding-right: 0;
|
|
1101
1186
|
width: 100%;
|
|
1102
1187
|
font-size: inherit;
|
|
1188
|
+
color: inherit;
|
|
1103
1189
|
outline: none;
|
|
1190
|
+
text-overflow: ellipsis;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
#text-input.clear-padding {
|
|
1194
|
+
padding-right: 30px;
|
|
1104
1195
|
}
|
|
1105
1196
|
|
|
1106
|
-
#text-input:
|
|
1197
|
+
#text-input:read-only {
|
|
1107
1198
|
cursor: pointer;
|
|
1108
1199
|
}
|
|
1109
1200
|
|
|
1201
|
+
#clear-button,
|
|
1110
1202
|
#caret-button {
|
|
1111
1203
|
display: inline-flex;
|
|
1112
1204
|
align-items: center;
|
|
1113
1205
|
appearance: none;
|
|
1114
1206
|
background: transparent;
|
|
1115
1207
|
border: none;
|
|
1116
|
-
padding: var(--
|
|
1208
|
+
padding: var(--combo-box-padding, 5px) 5px;
|
|
1117
1209
|
outline: none;
|
|
1118
1210
|
cursor: pointer;
|
|
1119
1211
|
}
|
|
1120
1212
|
|
|
1213
|
+
#clear-button {
|
|
1214
|
+
flex: 0 0 30px;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
#clear-button[hidden] {
|
|
1218
|
+
display: none;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
#caret-button {
|
|
1222
|
+
padding-right: var(--combo-box-padding, 5px);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1121
1225
|
#options-list {
|
|
1122
1226
|
position: absolute;
|
|
1123
1227
|
list-style-type: none;
|
|
@@ -1142,13 +1246,23 @@ export class IAComboBox extends LitElement {
|
|
|
1142
1246
|
text-align: center;
|
|
1143
1247
|
}
|
|
1144
1248
|
|
|
1145
|
-
|
|
1249
|
+
#caret-button svg {
|
|
1146
1250
|
width: 14px;
|
|
1147
1251
|
height: 14px;
|
|
1148
1252
|
}
|
|
1149
1253
|
|
|
1254
|
+
#clear-button svg {
|
|
1255
|
+
width: var(--combo-box-clear-icon-size, 16px);
|
|
1256
|
+
height: var(--combo-box-clear-icon-size, 16px);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1150
1259
|
.option {
|
|
1151
|
-
padding: 5px;
|
|
1260
|
+
padding: 7px 5px;
|
|
1261
|
+
width: 100%;
|
|
1262
|
+
box-sizing: border-box;
|
|
1263
|
+
line-height: 1.1;
|
|
1264
|
+
text-overflow: ellipsis;
|
|
1265
|
+
overflow: hidden;
|
|
1152
1266
|
cursor: pointer;
|
|
1153
1267
|
}
|
|
1154
1268
|
|
package/src/combo-box/models.ts
CHANGED
|
@@ -81,3 +81,33 @@ export type IAComboBoxFilterOption =
|
|
|
81
81
|
export function hasAnyOf<T>(map: Map<T, unknown>, keys: T[]): boolean {
|
|
82
82
|
return keys.some((prop) => map.has(prop));
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Tests whether the given `haystack` string has the given `needle` as a subsequence.
|
|
87
|
+
* Returns `true` if the characters of `needle` appear in order within `haystack`,
|
|
88
|
+
* regardless of whether they are contiguous. Returns `false` otherwise.
|
|
89
|
+
*
|
|
90
|
+
* E.g., `ace` is a subsequence of `archive` (but not a contiguous substring).
|
|
91
|
+
*
|
|
92
|
+
* Note: The empty string is a subsequence of any string, including itself.
|
|
93
|
+
*
|
|
94
|
+
* @param needle The potential subsequence to check for inside `haystack`.
|
|
95
|
+
* @param haystack The string to be tested for containing the `needle` subsequence.
|
|
96
|
+
* @returns Whether `haystack` has `needle` as a subsequence.
|
|
97
|
+
*/
|
|
98
|
+
export function isSubsequence(needle: string, haystack: string): boolean {
|
|
99
|
+
const needleChars = [...needle]; // Split out the full code points
|
|
100
|
+
const haystackChars = [...haystack];
|
|
101
|
+
const needleLen = needleChars.length;
|
|
102
|
+
const haystackLen = haystackChars.length;
|
|
103
|
+
if (needleLen === 0) return true;
|
|
104
|
+
|
|
105
|
+
let needleIdx = 0;
|
|
106
|
+
let haystackIdx = 0;
|
|
107
|
+
while (haystackIdx < haystackLen) {
|
|
108
|
+
if (haystackChars[haystackIdx] === needleChars[needleIdx]) needleIdx += 1;
|
|
109
|
+
if (needleIdx >= needleLen) return true;
|
|
110
|
+
haystackIdx += 1;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|