@ni/nimble-components 21.4.0 → 21.5.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.
Files changed (33) hide show
  1. package/dist/all-components-bundle.js +864 -73
  2. package/dist/all-components-bundle.js.map +1 -1
  3. package/dist/all-components-bundle.min.js +3598 -3450
  4. package/dist/all-components-bundle.min.js.map +1 -1
  5. package/dist/esm/anchored-region/styles.js +6 -1
  6. package/dist/esm/anchored-region/styles.js.map +1 -1
  7. package/dist/esm/label-provider/core/index.d.ts +6 -0
  8. package/dist/esm/label-provider/core/index.js +10 -2
  9. package/dist/esm/label-provider/core/index.js.map +1 -1
  10. package/dist/esm/label-provider/core/label-token-defaults.js +3 -1
  11. package/dist/esm/label-provider/core/label-token-defaults.js.map +1 -1
  12. package/dist/esm/label-provider/core/label-tokens.d.ts +2 -0
  13. package/dist/esm/label-provider/core/label-tokens.js +8 -0
  14. package/dist/esm/label-provider/core/label-tokens.js.map +1 -1
  15. package/dist/esm/patterns/dropdown/styles.js +0 -4
  16. package/dist/esm/patterns/dropdown/styles.js.map +1 -1
  17. package/dist/esm/select/index.d.ts +251 -7
  18. package/dist/esm/select/index.js +653 -19
  19. package/dist/esm/select/index.js.map +1 -1
  20. package/dist/esm/select/models/select-form-associated.d.ts +16 -0
  21. package/dist/esm/select/models/select-form-associated.js +19 -0
  22. package/dist/esm/select/models/select-form-associated.js.map +1 -0
  23. package/dist/esm/select/styles.js +89 -1
  24. package/dist/esm/select/styles.js.map +1 -1
  25. package/dist/esm/select/template.js +71 -37
  26. package/dist/esm/select/template.js.map +1 -1
  27. package/dist/esm/select/testing/select.pageobject.d.ts +32 -0
  28. package/dist/esm/select/testing/select.pageobject.js +128 -0
  29. package/dist/esm/select/testing/select.pageobject.js.map +1 -0
  30. package/dist/esm/select/types.d.ts +9 -0
  31. package/dist/esm/select/types.js +8 -0
  32. package/dist/esm/select/types.js.map +1 -1
  33. package/package.json +4 -1
@@ -12570,12 +12570,12 @@
12570
12570
  *
12571
12571
  * @internal
12572
12572
  */
12573
- class FormAssociatedSelect extends FormAssociated(_Select) {
12573
+ let FormAssociatedSelect$1 = class FormAssociatedSelect extends FormAssociated(_Select) {
12574
12574
  constructor() {
12575
12575
  super(...arguments);
12576
12576
  this.proxy = document.createElement("select");
12577
12577
  }
12578
- }
12578
+ };
12579
12579
 
12580
12580
  /**
12581
12581
  * A Select Custom HTML Element.
@@ -12596,7 +12596,7 @@
12596
12596
  *
12597
12597
  * @public
12598
12598
  */
12599
- let Select$1 = class Select extends FormAssociatedSelect {
12599
+ let Select$2 = class Select extends FormAssociatedSelect$1 {
12600
12600
  constructor() {
12601
12601
  super(...arguments);
12602
12602
  /**
@@ -13031,22 +13031,22 @@
13031
13031
  };
13032
13032
  __decorate([
13033
13033
  attr({ attribute: "open", mode: "boolean" })
13034
- ], Select$1.prototype, "open", void 0);
13034
+ ], Select$2.prototype, "open", void 0);
13035
13035
  __decorate([
13036
13036
  volatile
13037
- ], Select$1.prototype, "collapsible", null);
13037
+ ], Select$2.prototype, "collapsible", null);
13038
13038
  __decorate([
13039
13039
  observable
13040
- ], Select$1.prototype, "control", void 0);
13040
+ ], Select$2.prototype, "control", void 0);
13041
13041
  __decorate([
13042
13042
  attr({ attribute: "position" })
13043
- ], Select$1.prototype, "positionAttribute", void 0);
13043
+ ], Select$2.prototype, "positionAttribute", void 0);
13044
13044
  __decorate([
13045
13045
  observable
13046
- ], Select$1.prototype, "position", void 0);
13046
+ ], Select$2.prototype, "position", void 0);
13047
13047
  __decorate([
13048
13048
  observable
13049
- ], Select$1.prototype, "maxHeight", void 0);
13049
+ ], Select$2.prototype, "maxHeight", void 0);
13050
13050
  /**
13051
13051
  * Includes ARIA states and properties relating to the ARIA select role.
13052
13052
  *
@@ -13058,7 +13058,7 @@
13058
13058
  observable
13059
13059
  ], DelegatesARIASelect.prototype, "ariaControls", void 0);
13060
13060
  applyMixins(DelegatesARIASelect, DelegatesARIAListbox);
13061
- applyMixins(Select$1, StartEnd, DelegatesARIASelect);
13061
+ applyMixins(Select$2, StartEnd, DelegatesARIASelect);
13062
13062
 
13063
13063
  class _Switch extends FoundationElement {
13064
13064
  }
@@ -16301,7 +16301,7 @@
16301
16301
 
16302
16302
  /**
16303
16303
  * Do not edit directly
16304
- * Generated on Wed, 14 Feb 2024 20:48:02 GMT
16304
+ * Generated on Thu, 15 Feb 2024 20:33:21 GMT
16305
16305
  */
16306
16306
 
16307
16307
  const Information100DarkUi = "#a46eff";
@@ -18576,10 +18576,15 @@
18576
18576
 
18577
18577
  const styles$P = css `
18578
18578
  :host {
18579
- contain: layout;
18579
+ /* Avoid using the 'display' helper to customize hidden behavior */
18580
18580
  display: block;
18581
+ contain: layout;
18581
18582
  z-index: ${ZIndexLevels.zIndex1000};
18582
18583
  }
18584
+
18585
+ :host([hidden]) {
18586
+ visibility: hidden;
18587
+ }
18583
18588
  `;
18584
18589
 
18585
18590
  // When the anchor element changes position on the page, it is the client's responsibility to update the position
@@ -19730,7 +19735,9 @@
19730
19735
  numericDecrementLabel: 'Decrement',
19731
19736
  errorIconLabel: 'Error',
19732
19737
  warningIconLabel: 'Warning',
19733
- informationIconLabel: 'Information'
19738
+ informationIconLabel: 'Information',
19739
+ filterSearchLabel: 'Search',
19740
+ filterNoResultsLabel: 'No items found'
19734
19741
  };
19735
19742
 
19736
19743
  const popupDismissLabel = DesignToken.create({
@@ -19757,6 +19764,14 @@
19757
19764
  name: 'information-icon-label',
19758
19765
  cssCustomPropertyName: null
19759
19766
  }).withDefault(coreLabelDefaults.informationIconLabel);
19767
+ const filterSearchLabel = DesignToken.create({
19768
+ name: 'filter-search-label',
19769
+ cssCustomPropertyName: null
19770
+ }).withDefault(coreLabelDefaults.filterSearchLabel);
19771
+ const filterNoResultsLabel = DesignToken.create({
19772
+ name: 'filter-no-results-label',
19773
+ cssCustomPropertyName: null
19774
+ }).withDefault(coreLabelDefaults.filterNoResultsLabel);
19760
19775
 
19761
19776
  // prettier-ignore
19762
19777
  const template$x = html `
@@ -20643,10 +20658,6 @@
20643
20658
  border-bottom-color: ${failColor};
20644
20659
  }
20645
20660
 
20646
- .anchored-region[hidden] {
20647
- visibility: hidden;
20648
- }
20649
-
20650
20661
  .listbox {
20651
20662
  box-sizing: border-box;
20652
20663
  display: inline-flex;
@@ -20793,6 +20804,19 @@
20793
20804
  }
20794
20805
  `;
20795
20806
 
20807
+ /**
20808
+ * Types of dropdown appearance.
20809
+ * @public
20810
+ */
20811
+ /**
20812
+ * Types of select filter mode.
20813
+ * @public
20814
+ */
20815
+ const FilterMode = {
20816
+ none: undefined,
20817
+ standard: 'standard'
20818
+ };
20819
+
20796
20820
  const styles$D = css `
20797
20821
  ${styles$F}
20798
20822
  ${styles$E}
@@ -23135,6 +23159,7 @@
23135
23159
  }
23136
23160
  }
23137
23161
  registerIcon('icon-magnifying-glass', IconMagnifyingGlass);
23162
+ const iconMagnifyingGlassTag = 'nimble-icon-magnifying-glass';
23138
23163
 
23139
23164
  // AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
23140
23165
  // See generation source in nimble-components/build/generate-icons
@@ -23920,7 +23945,9 @@
23920
23945
  numericIncrement: numericIncrementLabel,
23921
23946
  errorIcon: errorIconLabel,
23922
23947
  warningIcon: warningIconLabel,
23923
- informationIcon: informationIconLabel
23948
+ informationIcon: informationIconLabel,
23949
+ filterSearch: filterSearchLabel,
23950
+ filterNoResults: filterNoResultsLabel
23924
23951
  };
23925
23952
  /**
23926
23953
  * Core label provider for Nimble
@@ -23949,6 +23976,12 @@
23949
23976
  __decorate$1([
23950
23977
  attr({ attribute: 'information-icon' })
23951
23978
  ], LabelProviderCore.prototype, "informationIcon", void 0);
23979
+ __decorate$1([
23980
+ attr({ attribute: 'filter-search' })
23981
+ ], LabelProviderCore.prototype, "filterSearch", void 0);
23982
+ __decorate$1([
23983
+ attr({ attribute: 'filter-no-results' })
23984
+ ], LabelProviderCore.prototype, "filterNoResults", void 0);
23952
23985
  const nimbleLabelProviderCore = LabelProviderCore.compose({
23953
23986
  baseName: 'label-provider-core'
23954
23987
  });
@@ -57724,13 +57757,97 @@ img.ProseMirror-separator {
57724
57757
  [part='end'] {
57725
57758
  display: contents;
57726
57759
  }
57760
+
57761
+ .listbox {
57762
+ overflow-x: clip;
57763
+ }
57764
+
57765
+ .listbox.empty slot {
57766
+ display: none;
57767
+ }
57768
+
57769
+ .listbox.above {
57770
+ flex-flow: column-reverse;
57771
+ }
57772
+
57773
+ .filter-field {
57774
+ display: flex;
57775
+ flex-direction: row;
57776
+ align-items: center;
57777
+ height: ${controlHeight};
57778
+ background: transparent;
57779
+ }
57780
+
57781
+ .filter-field::before {
57782
+ content: '';
57783
+ position: absolute;
57784
+ height: 0px;
57785
+ border-bottom: rgba(${borderRgbPartialColor}, 0.1) 2px solid;
57786
+ bottom: calc(${controlHeight} + ${smallPadding} - ${borderWidth});
57787
+ }
57788
+
57789
+ .filter-field.above::before {
57790
+ width: calc(100% - (2 * ${borderWidth}));
57791
+ }
57792
+
57793
+ .filter-field::after {
57794
+ content: '';
57795
+ position: absolute;
57796
+ height: 0px;
57797
+ border-bottom: rgba(${borderRgbPartialColor}, 0.1) 2px solid;
57798
+ top: calc(${controlHeight} + ${smallPadding} - ${borderWidth});
57799
+ }
57800
+
57801
+ .filter-field:not(.above)::after {
57802
+ width: calc(100% - (2 * ${borderWidth}));
57803
+ }
57804
+
57805
+ .filter-icon {
57806
+ padding-left: ${smallPadding};
57807
+ }
57808
+
57809
+ .filter-input {
57810
+ background: transparent;
57811
+ border: none;
57812
+ color: inherit;
57813
+ font: inherit;
57814
+ height: var(--ni-nimble-control-height);
57815
+ padding: 0 ${smallPadding} 0 ${mediumPadding};
57816
+ width: 100%;
57817
+ }
57818
+
57819
+ .filter-input::placeholder {
57820
+ color: ${placeholderFontColor};
57821
+ }
57822
+
57823
+ .filter-input${focusVisible} {
57824
+ outline: 0px;
57825
+ }
57826
+
57827
+ .scrollable-region {
57828
+ overflow: auto;
57829
+ }
57830
+
57831
+ .no-results-label {
57832
+ color: ${placeholderFontColor};
57833
+ height: ${controlHeight};
57834
+ display: inline-flex;
57835
+ align-items: center;
57836
+ padding: ${smallPadding} ${mediumPadding};
57837
+ }
57727
57838
  `.withBehaviors(appearanceBehavior(DropdownAppearance.block, css `
57728
57839
  :host([error-visible]) .control {
57729
57840
  border-bottom-width: ${borderWidth};
57730
57841
  padding-bottom: 0;
57731
57842
  }
57843
+ `), themeBehavior(Theme.color, css `
57844
+ .filter-field,
57845
+ .no-results-label {
57846
+ background: ${hexToRgbaCssColor(White, 0.15)};
57847
+ }
57732
57848
  `));
57733
57849
 
57850
+ /* eslint-disable @typescript-eslint/indent */
57734
57851
  // prettier-ignore
57735
57852
  const template$l = (context, definition) => html `
57736
57853
  <template
@@ -57739,11 +57856,9 @@ img.ProseMirror-separator {
57739
57856
  x.collapsible && x.open && 'open',
57740
57857
  x.disabled && 'disabled',
57741
57858
  x.collapsible && x.position,
57742
- ]
57743
- .filter(Boolean)
57744
- .join(' ')}"
57745
- aria-activedescendant="${x => x.ariaActiveDescendant}"
57746
- aria-controls="${x => x.ariaControls}"
57859
+ ].filter(Boolean).join(' ')}"
57860
+ aria-activedescendant="${x => (x.filterMode === FilterMode.none ? x.ariaActiveDescendant : null)}"
57861
+ aria-controls="${x => (x.filterMode === FilterMode.none ? x.ariaControls : null)}"
57747
57862
  aria-disabled="${x => x.ariaDisabled}"
57748
57863
  aria-expanded="${x => x.ariaExpanded}"
57749
57864
  aria-haspopup="${x => (x.collapsible ? 'listbox' : null)}"
@@ -57752,34 +57867,36 @@ img.ProseMirror-separator {
57752
57867
  role="combobox"
57753
57868
  tabindex="${x => (!x.disabled ? '0' : null)}"
57754
57869
  @click="${(x, c) => x.clickHandler(c.event)}"
57870
+ @change="${x => x.changeValueHandler()}"
57871
+ @contentchange="${x => x.updateDisplayValue()}"
57755
57872
  @focusin="${(x, c) => x.focusinHandler(c.event)}"
57756
57873
  @focusout="${(x, c) => x.focusoutHandler(c.event)}"
57757
57874
  @keydown="${(x, c) => x.keydownHandler(c.event)}"
57758
57875
  @mousedown="${(x, c) => x.mousedownHandler(c.event)}"
57759
57876
  >
57760
57877
  ${when(x => x.collapsible, html `
57761
- <div
57762
- class="control"
57763
- part="control"
57764
- ?disabled="${x => x.disabled}"
57765
- ${ref('control')}
57766
- >
57767
- ${startSlotTemplate(context, definition)}
57768
- <slot name="button-container">
57769
- <div class="selected-value" part="selected-value" ${overflow('hasOverflow')} title=${x => (x.hasOverflow && x.displayValue ? x.displayValue : null)}>
57770
- <slot name="selected-value">${x => x.displayValue}</slot>
57771
- </div>
57772
- <div aria-hidden="true" class="indicator" part="indicator">
57773
- <slot name="indicator">
57774
- ${definition.indicator || ''}
57775
- </slot>
57776
- </div>
57777
- </slot>
57778
- ${endSlotTemplate(context, definition)}
57779
- </div>
57780
- `)}
57878
+ <div
57879
+ class="control"
57880
+ part="control"
57881
+ ?disabled="${x => x.disabled}"
57882
+ ${ref('control')}
57883
+ >
57884
+ ${startSlotTemplate(context, definition)}
57885
+ <slot name="button-container">
57886
+ <div class="selected-value" part="selected-value" ${overflow('hasOverflow')} title=${x => (x.hasOverflow && x.displayValue ? x.displayValue : null)}>
57887
+ <slot name="selected-value">${x => x.displayValue}</slot>
57888
+ </div>
57889
+ <div aria-hidden="true" class="indicator" part="indicator">
57890
+ <slot name="indicator">
57891
+ ${definition.indicator || ''}
57892
+ </slot>
57893
+ </div>
57894
+ </slot>
57895
+ ${endSlotTemplate(context, definition)}
57896
+ </div>
57897
+ `)}
57781
57898
  <${anchoredRegionTag}
57782
- ${ref('region')}
57899
+ ${ref('anchoredRegion')}
57783
57900
  class="anchored-region"
57784
57901
  fixed-placement
57785
57902
  auto-update-mode="auto"
@@ -57788,93 +57905,766 @@ img.ProseMirror-separator {
57788
57905
  horizontal-default-position="center"
57789
57906
  horizontal-positioning-mode="locktodefault"
57790
57907
  horizontal-scaling="anchor"
57908
+ @loaded="${x => x.regionLoadedHandler()}"
57791
57909
  ?hidden="${x => (x.collapsible ? !x.open : false)}">
57792
- <div
57793
- class="listbox"
57794
- id="${x => x.listboxId}"
57795
- part="listbox"
57796
- role="listbox"
57797
- ?disabled="${x => x.disabled}"
57798
- ${ref('listbox')}
57799
- >
57800
- <slot
57801
- ${slotted({
57910
+ <div class="listbox-background">
57911
+ <div
57912
+ class="
57913
+ listbox
57914
+ ${x => (x.filteredOptions.length === 0 ? 'empty' : '')}
57915
+ ${x => x.positionAttribute}
57916
+ "
57917
+ id="${x => x.listboxId}"
57918
+ part="listbox"
57919
+ role="listbox"
57920
+ ?disabled="${x => x.disabled}"
57921
+ ${ref('listbox')}
57922
+ >
57923
+ ${when(x => x.filterMode !== FilterMode.none, html `
57924
+ <div class="filter-field ${x => x.positionAttribute}">
57925
+ <${iconMagnifyingGlassTag} class="filter-icon"></${iconMagnifyingGlassTag}>
57926
+ <input
57927
+ ${ref('filterInput')}
57928
+ class="filter-input"
57929
+ aria-controls="${x => x.ariaControls}"
57930
+ aria-activedescendant="${x => x.ariaActiveDescendant}"
57931
+ @input="${(x, c) => x.inputHandler(c.event)}"
57932
+ @click="${(x, c) => x.inputClickHandler(c.event)}"
57933
+ placeholder="${x => filterSearchLabel.getValueFor(x)}"
57934
+ value="${x => x.filter}"
57935
+ />
57936
+ </div>
57937
+ `)}
57938
+ <div ${ref('scrollableRegion')}
57939
+ class="scrollable-region">
57940
+ <slot
57941
+ ${slotted({
57802
57942
  filter: (n) => n instanceof HTMLElement && Listbox$1.slottedOptionFilter(n),
57803
57943
  flatten: true,
57804
57944
  property: 'slottedOptions',
57805
57945
  })}
57806
- ></slot>
57946
+ ></slot>
57947
+ </div>
57948
+ ${when(x => (x.filterMode !== FilterMode.none && x.filteredOptions.length === 0), html `
57949
+ <span class="no-results-label ${x => x.positionAttribute}">
57950
+ ${x => filterNoResultsLabel.getValueFor(x)}
57951
+ </span>
57952
+ `)}
57953
+ </div>
57807
57954
  </div>
57808
57955
  </${anchoredRegionTag}>
57809
57956
  </template>
57810
57957
  `;
57811
57958
 
57959
+ // Based on: https://github.com/microsoft/fast/blob/%40microsoft/fast-foundation_v2.49.5/packages/web-components/fast-foundation/src/select/select.form-associated.ts
57960
+ /* eslint-disable max-classes-per-file */
57961
+ // eslint-disable-next-line jsdoc/require-jsdoc
57962
+ let Select$1 = class Select extends ListboxElement {
57963
+ };
57964
+ /**
57965
+ * A form-associated base class for the Select component. This was copied from the
57966
+ * FAST FormAssociatedSelect (which is not exported by fast-foundation)
57967
+ *
57968
+ * @internal
57969
+ */
57970
+ class FormAssociatedSelect extends FormAssociated(Select$1) {
57971
+ constructor() {
57972
+ super(...arguments);
57973
+ this.proxy = document.createElement('select');
57974
+ }
57975
+ }
57976
+
57812
57977
  /**
57813
- * A nimble-styled HTML select
57978
+ * A nimble-styled HTML select.
57814
57979
  */
57815
- class Select extends Select$1 {
57980
+ class Select extends FormAssociatedSelect {
57816
57981
  constructor() {
57817
57982
  super(...arguments);
57818
57983
  this.appearance = DropdownAppearance.underline;
57819
57984
  this.errorVisible = false;
57985
+ this.filterMode = FilterMode.none;
57986
+ /**
57987
+ * @internal
57988
+ */
57989
+ this.open = false;
57990
+ /**
57991
+ * The unique id for the internal listbox element.
57992
+ *
57993
+ * @internal
57994
+ */
57995
+ this.listboxId = uniqueId('listbox-');
57820
57996
  /** @internal */
57821
57997
  this.hasOverflow = false;
57998
+ /**
57999
+ * @internal
58000
+ */
58001
+ this.filteredOptions = [];
58002
+ /**
58003
+ * @internal
58004
+ */
58005
+ this.filter = '';
58006
+ /**
58007
+ * @internal
58008
+ */
58009
+ this.committedSelectedOption = undefined;
58010
+ /**
58011
+ * The max height for the listbox when opened.
58012
+ *
58013
+ * @internal
58014
+ */
58015
+ this.maxHeight = 0;
58016
+ this._value = '';
58017
+ this.forcedPosition = false;
58018
+ }
58019
+ /**
58020
+ * The component is collapsible when in single-selection mode with no size attribute.
58021
+ *
58022
+ * @internal
58023
+ */
58024
+ get collapsible() {
58025
+ return !(this.multiple || typeof this.size === 'number');
58026
+ }
58027
+ /**
58028
+ * @internal
58029
+ */
58030
+ connectedCallback() {
58031
+ super.connectedCallback();
58032
+ this.forcedPosition = !!this.positionAttribute;
58033
+ this.initializeOpenState();
58034
+ }
58035
+ /**
58036
+ * The list of options. This mirrors FAST's override implementation for this
58037
+ * member for the Combobox to support a filtered list in the dropdown.
58038
+ *
58039
+ * @public
58040
+ * @remarks
58041
+ * Overrides `Listbox.options`.
58042
+ */
58043
+ get options() {
58044
+ Observable.track(this, 'options');
58045
+ return this.filteredOptions?.length
58046
+ ? this.filteredOptions
58047
+ : this._options;
58048
+ }
58049
+ set options(value) {
58050
+ this._options = value;
58051
+ Observable.notify(this, 'options');
58052
+ }
58053
+ get value() {
58054
+ Observable.track(this, 'value');
58055
+ return this._value;
58056
+ }
58057
+ set value(next) {
58058
+ const prev = this._value;
58059
+ let newValue = next;
58060
+ // use 'options' here instead of '_options' as 'selectedIndex' may be relative
58061
+ // to filtered set
58062
+ if (this.options?.length) {
58063
+ const newValueIndex = this.options.findIndex(el => el.value === newValue);
58064
+ const prevSelectedValue = this.options[this.selectedIndex]?.value ?? null;
58065
+ const nextSelectedValue = this.options[newValueIndex]?.value ?? null;
58066
+ if (newValueIndex === -1
58067
+ || prevSelectedValue !== nextSelectedValue) {
58068
+ newValue = '';
58069
+ this.selectedIndex = newValueIndex;
58070
+ }
58071
+ newValue = this.firstSelectedOption?.value ?? newValue;
58072
+ }
58073
+ if (prev !== newValue && !(this.open && this.selectedIndex < 0)) {
58074
+ this._value = newValue;
58075
+ super.valueChanged(prev, newValue);
58076
+ if (!this.open) {
58077
+ this.committedSelectedOption = this._options.find(o => o.value === newValue);
58078
+ }
58079
+ Observable.notify(this, 'value');
58080
+ if (this.collapsible) {
58081
+ Observable.notify(this, 'displayValue');
58082
+ }
58083
+ }
58084
+ }
58085
+ /**
58086
+ * @internal
58087
+ */
58088
+ get displayValue() {
58089
+ Observable.track(this, 'displayValue');
58090
+ return this.committedSelectedOption?.text ?? '';
58091
+ }
58092
+ /**
58093
+ * @internal
58094
+ */
58095
+ anchoredRegionChanged(_prev, _next) {
58096
+ if (this.anchoredRegion && this.control) {
58097
+ this.anchoredRegion.anchorElement = this.control;
58098
+ }
58099
+ }
58100
+ /**
58101
+ * @internal
58102
+ */
58103
+ controlChanged(_prev, _next) {
58104
+ if (this.anchoredRegion && this.control) {
58105
+ this.anchoredRegion.anchorElement = this.control;
58106
+ }
58107
+ }
58108
+ /**
58109
+ * @internal
58110
+ */
58111
+ slottedOptionsChanged(prev, next) {
58112
+ const value = this.value;
58113
+ this._options.forEach(o => {
58114
+ const notifier = Observable.getNotifier(o);
58115
+ notifier.unsubscribe(this, 'value');
58116
+ });
58117
+ super.slottedOptionsChanged(prev, next);
58118
+ this._options.forEach(o => {
58119
+ const notifier = Observable.getNotifier(o);
58120
+ notifier.subscribe(this, 'value');
58121
+ });
58122
+ this.setProxyOptions();
58123
+ this.updateValue();
58124
+ // We need to force an update to the filteredOptions observable
58125
+ // (by calling 'filterOptions()) so that the template correctly updates.
58126
+ this.filterOptions();
58127
+ if (value) {
58128
+ this.value = value;
58129
+ }
58130
+ this.committedSelectedOption = this.options[this.selectedIndex];
58131
+ }
58132
+ /**
58133
+ * @internal
58134
+ */
58135
+ clickHandler(e) {
58136
+ // do nothing if the select is disabled
58137
+ if (this.disabled) {
58138
+ return;
58139
+ }
58140
+ if (this.open) {
58141
+ const captured = e.target.closest('option,[role=option]');
58142
+ if (!captured?.disabled) {
58143
+ this.updateSelectedIndexFromFilteredSet();
58144
+ }
58145
+ if (captured?.disabled) {
58146
+ return;
58147
+ }
58148
+ }
58149
+ super.clickHandler(e);
58150
+ this.open = this.collapsible && !this.open;
58151
+ if (!this.open && this.indexWhenOpened !== this.selectedIndex) {
58152
+ this.updateValue(true);
58153
+ }
58154
+ }
58155
+ /**
58156
+ * Updates the value when an option's value changes.
58157
+ *
58158
+ * @param source - the source object
58159
+ * @param propertyName - the property to evaluate
58160
+ *
58161
+ * @internal
58162
+ * @override
58163
+ */
58164
+ handleChange(source, propertyName) {
58165
+ super.handleChange(source, propertyName);
58166
+ if (propertyName === 'value') {
58167
+ this.updateValue();
58168
+ }
58169
+ }
58170
+ /**
58171
+ * Prevents focus when size is set and a scrollbar is clicked.
58172
+ *
58173
+ * @param e - the mouse event object
58174
+ *
58175
+ * @override
58176
+ * @internal
58177
+ */
58178
+ mousedownHandler(e) {
58179
+ if (e.offsetX >= 0 && e.offsetX <= this.listbox?.scrollWidth) {
58180
+ return super.mousedownHandler(e);
58181
+ }
58182
+ return this.collapsible;
58183
+ }
58184
+ /**
58185
+ * @internal
58186
+ */
58187
+ regionLoadedHandler() {
58188
+ this.focusAndScrollOptionIntoView();
58189
+ }
58190
+ /**
58191
+ * Sets the multiple property on the proxy element.
58192
+ *
58193
+ * @param prev - the previous multiple value
58194
+ * @param next - the current multiple value
58195
+ */
58196
+ multipleChanged(prev, next) {
58197
+ super.multipleChanged(prev, next);
58198
+ if (this.proxy) {
58199
+ this.proxy.multiple = next;
58200
+ }
58201
+ }
58202
+ /**
58203
+ * @internal
58204
+ */
58205
+ inputClickHandler(e) {
58206
+ e.stopPropagation(); // clicking in filter input shouldn't close dropdown
58207
+ }
58208
+ /**
58209
+ * @internal
58210
+ */
58211
+ changeValueHandler() {
58212
+ this.committedSelectedOption = this.options.find(option => option.selected);
58213
+ }
58214
+ /**
58215
+ * @internal
58216
+ */
58217
+ updateDisplayValue() {
58218
+ if (this.collapsible) {
58219
+ Observable.notify(this, 'displayValue');
58220
+ }
58221
+ }
58222
+ /**
58223
+ * Handle content changes on the control input.
58224
+ *
58225
+ * @param e - the input event
58226
+ * @internal
58227
+ */
58228
+ inputHandler(e) {
58229
+ this.filter = this.filterInput?.value ?? '';
58230
+ if (!this.committedSelectedOption) {
58231
+ this.committedSelectedOption = this._options.find(option => option.selected);
58232
+ }
58233
+ this.clearSelection();
58234
+ this.filterOptions();
58235
+ if (this.filteredOptions.length > 0
58236
+ && this.committedSelectedOption
58237
+ && !this.filteredOptions.includes(this.committedSelectedOption)) {
58238
+ const enabledOptions = this.filteredOptions.filter(o => !o.disabled);
58239
+ if (enabledOptions.length > 0) {
58240
+ enabledOptions[0].selected = true;
58241
+ }
58242
+ else {
58243
+ // only filtered option is disabled
58244
+ this.selectedOptions = [];
58245
+ this.selectedIndex = -1;
58246
+ }
58247
+ }
58248
+ else if (this.committedSelectedOption) {
58249
+ this.committedSelectedOption.selected = true;
58250
+ }
58251
+ if (e.inputType.includes('deleteContent') || !this.filter.length) {
58252
+ return true;
58253
+ }
58254
+ e.stopPropagation();
58255
+ return true;
58256
+ }
58257
+ /**
58258
+ * @internal
58259
+ */
58260
+ focusoutHandler(e) {
58261
+ this.updateSelectedIndexFromFilteredSet();
58262
+ super.focusoutHandler(e);
58263
+ if (!this.open) {
58264
+ return true;
58265
+ }
58266
+ const focusTarget = e.relatedTarget;
58267
+ if (this.isSameNode(focusTarget)) {
58268
+ this.focus();
58269
+ return true;
58270
+ }
58271
+ if (!this.options?.includes(focusTarget)) {
58272
+ this.open = false;
58273
+ if (this.indexWhenOpened !== this.selectedIndex) {
58274
+ this.updateValue(true);
58275
+ }
58276
+ }
58277
+ return true;
58278
+ }
58279
+ /**
58280
+ * @internal
58281
+ */
58282
+ keydownHandler(e) {
58283
+ super.keydownHandler(e);
58284
+ const key = e.key;
58285
+ if (e.ctrlKey || e.shiftKey) {
58286
+ return true;
58287
+ }
58288
+ switch (key) {
58289
+ case keySpace: {
58290
+ // when dropdown is open allow user to enter a space for filter text
58291
+ if (this.open && this.filterMode !== FilterMode.none) {
58292
+ break;
58293
+ }
58294
+ e.preventDefault();
58295
+ if (this.collapsible && this.typeAheadExpired) {
58296
+ this.open = !this.open;
58297
+ }
58298
+ if (!this.open) {
58299
+ this.focus();
58300
+ }
58301
+ break;
58302
+ }
58303
+ case keyHome:
58304
+ case keyEnd: {
58305
+ e.preventDefault();
58306
+ break;
58307
+ }
58308
+ case keyEnter: {
58309
+ e.preventDefault();
58310
+ if (this.filteredOptions.length === 0
58311
+ || this.filteredOptions.every(o => o.disabled)) {
58312
+ return false;
58313
+ }
58314
+ this.updateSelectedIndexFromFilteredSet();
58315
+ this.open = !this.open;
58316
+ if (!this.open) {
58317
+ this.focus();
58318
+ }
58319
+ break;
58320
+ }
58321
+ case keyEscape: {
58322
+ // clear filter as update to "selectedIndex" will result in processing
58323
+ // "options" and not "_options"
58324
+ this.filter = '';
58325
+ if (this.committedSelectedOption) {
58326
+ this.clearSelection();
58327
+ this.selectedIndex = this._options.indexOf(this.committedSelectedOption);
58328
+ }
58329
+ if (this.collapsible && this.open) {
58330
+ e.preventDefault();
58331
+ this.open = false;
58332
+ }
58333
+ // reset 'selected' state otherwise the selected state doesn't stick.
58334
+ const selectedOption = this._options[this.selectedIndex];
58335
+ if (selectedOption) {
58336
+ selectedOption.selected = true;
58337
+ }
58338
+ this.focus();
58339
+ break;
58340
+ }
58341
+ case keyTab: {
58342
+ if (this.collapsible && this.open) {
58343
+ e.preventDefault();
58344
+ this.open = false;
58345
+ }
58346
+ return true;
58347
+ }
58348
+ }
58349
+ if (!this.open && this.indexWhenOpened !== this.selectedIndex) {
58350
+ this.updateValue(true);
58351
+ this.indexWhenOpened = this.selectedIndex;
58352
+ }
58353
+ return !(key === keyArrowDown || key === keyArrowUp);
58354
+ }
58355
+ /**
58356
+ * Updates the proxy value when the selected index changes.
58357
+ *
58358
+ * @param prev - the previous selected index
58359
+ * @param next - the next selected index
58360
+ *
58361
+ * @internal
58362
+ */
58363
+ selectedIndexChanged(prev, next) {
58364
+ super.selectedIndexChanged(prev, next);
58365
+ this.updateValue();
58366
+ }
58367
+ /**
58368
+ * Synchronize the `aria-disabled` property when the `disabled` property changes.
58369
+ *
58370
+ * @param prev - The previous disabled value
58371
+ * @param next - The next disabled value
58372
+ *
58373
+ * @internal
58374
+ */
58375
+ disabledChanged(prev, next) {
58376
+ if (super.disabledChanged) {
58377
+ super.disabledChanged(prev, next);
58378
+ }
58379
+ this.ariaDisabled = this.disabled ? 'true' : 'false';
58380
+ }
58381
+ /**
58382
+ * Reset the element to its first selectable option when its parent form is reset.
58383
+ *
58384
+ * @internal
58385
+ */
58386
+ formResetCallback() {
58387
+ this.setProxyOptions();
58388
+ // Call the base class's implementation setDefaultSelectedOption instead of the select's
58389
+ // override, in order to reset the selectedIndex without using the value property.
58390
+ super.setDefaultSelectedOption();
58391
+ if (this.selectedIndex === -1) {
58392
+ this.selectedIndex = 0;
58393
+ }
58394
+ }
58395
+ // Prevents parent classes from resetting selectedIndex to a positive
58396
+ // value while filtering, which can result in a disabled option being
58397
+ // selected.
58398
+ setSelectedOptions() {
58399
+ if (this.open && this.selectedIndex === -1) {
58400
+ return;
58401
+ }
58402
+ super.setSelectedOptions();
58403
+ }
58404
+ focusAndScrollOptionIntoView() {
58405
+ super.focusAndScrollOptionIntoView();
58406
+ if (this.open) {
58407
+ window.requestAnimationFrame(() => {
58408
+ this.filterInput?.focus();
58409
+ });
58410
+ }
58411
+ }
58412
+ positionChanged(_, next) {
58413
+ this.positionAttribute = next;
58414
+ this.setPositioning();
58415
+ }
58416
+ /**
58417
+ * Updates the proxy's size property when the size attribute changes.
58418
+ *
58419
+ * @param prev - the previous size
58420
+ * @param next - the current size
58421
+ *
58422
+ * @override
58423
+ * @internal
58424
+ */
58425
+ sizeChanged(prev, next) {
58426
+ super.sizeChanged(prev, next);
58427
+ if (this.proxy) {
58428
+ this.proxy.size = next;
58429
+ }
58430
+ }
58431
+ openChanged() {
58432
+ if (!this.collapsible) {
58433
+ return;
58434
+ }
58435
+ if (this.open) {
58436
+ this.initializeOpenState();
58437
+ this.indexWhenOpened = this.selectedIndex;
58438
+ return;
58439
+ }
58440
+ this.filter = '';
58441
+ if (this.filterInput) {
58442
+ this.filterInput.value = '';
58443
+ }
58444
+ this.ariaControls = '';
58445
+ this.ariaExpanded = 'false';
58446
+ }
58447
+ /**
58448
+ * Updates the selectedness of each option when the list of selected options changes.
58449
+ *
58450
+ * @param prev - the previous list of selected options
58451
+ * @param next - the current list of selected options
58452
+ *
58453
+ * @override
58454
+ * @internal
58455
+ */
58456
+ selectedOptionsChanged(prev, next) {
58457
+ super.selectedOptionsChanged(prev, next);
58458
+ this.options?.forEach((o, i) => {
58459
+ const proxyOption = this.proxy?.options.item(i);
58460
+ if (proxyOption) {
58461
+ proxyOption.selected = o.selected;
58462
+ }
58463
+ });
58464
+ }
58465
+ /**
58466
+ * Sets the selected index to match the first option with the selected attribute, or
58467
+ * the first selectable option.
58468
+ *
58469
+ * @override
58470
+ * @internal
58471
+ */
58472
+ setDefaultSelectedOption() {
58473
+ const options = this.options
58474
+ ?? Array.from(this.children).filter(o => Listbox$1.slottedOptionFilter(o));
58475
+ const selectedIndex = options?.findIndex(el => el.hasAttribute('selected')
58476
+ || el.selected
58477
+ || el.value === this.value);
58478
+ if (selectedIndex !== -1) {
58479
+ this.selectedIndex = selectedIndex;
58480
+ return;
58481
+ }
58482
+ this.selectedIndex = 0;
57822
58483
  }
57823
- // Workaround for https://github.com/microsoft/fast/issues/5123
57824
58484
  setPositioning() {
57825
58485
  if (!this.$fastController.isConnected) {
57826
58486
  // Don't call setPositioning() until we're connected,
57827
58487
  // since this.forcedPosition isn't initialized yet.
57828
58488
  return;
57829
58489
  }
57830
- super.setPositioning();
58490
+ const currentBox = this.getBoundingClientRect();
58491
+ const viewportHeight = window.innerHeight;
58492
+ const availableBottom = viewportHeight - currentBox.bottom;
58493
+ if (this.forcedPosition) {
58494
+ this.position = this.positionAttribute;
58495
+ }
58496
+ else if (currentBox.top > availableBottom) {
58497
+ this.position = SelectPosition.above;
58498
+ }
58499
+ else {
58500
+ this.position = SelectPosition.below;
58501
+ }
58502
+ this.positionAttribute = this.forcedPosition
58503
+ ? this.positionAttribute
58504
+ : this.position;
58505
+ this.maxHeight = this.position === SelectPosition.above
58506
+ ? Math.trunc(currentBox.top)
58507
+ : Math.trunc(availableBottom);
57831
58508
  this.updateListboxMaxHeightCssVariable();
57832
58509
  }
57833
- // Workaround for https://github.com/microsoft/fast/issues/5773
57834
- slottedOptionsChanged(prev, next) {
57835
- const value = this.value;
57836
- super.slottedOptionsChanged(prev, next);
57837
- if (value) {
57838
- this.value = value;
58510
+ /**
58511
+ * Filter available options by text value.
58512
+ *
58513
+ * @public
58514
+ */
58515
+ filterOptions() {
58516
+ const filter = this.filter.toLowerCase();
58517
+ if (filter) {
58518
+ this.filteredOptions = this._options.filter(option => {
58519
+ return diacriticInsensitiveStringNormalizer(option.text).includes(diacriticInsensitiveStringNormalizer(filter));
58520
+ });
58521
+ }
58522
+ else {
58523
+ this.filteredOptions = this._options;
57839
58524
  }
58525
+ this._options.forEach(o => {
58526
+ o.hidden = !this.filteredOptions.includes(o);
58527
+ });
57840
58528
  }
57841
- regionChanged(_prev, _next) {
57842
- if (this.region && this.control) {
57843
- this.region.anchorElement = this.control;
58529
+ /**
58530
+ * Sets the value and display value to match the first selected option.
58531
+ *
58532
+ * @param shouldEmit - if true, the input and change events will be emitted
58533
+ *
58534
+ * @internal
58535
+ */
58536
+ updateValue(shouldEmit) {
58537
+ if (this.$fastController.isConnected) {
58538
+ this.value = this.firstSelectedOption?.value ?? '';
58539
+ }
58540
+ if (shouldEmit) {
58541
+ this.$emit('input');
58542
+ this.$emit('change', this, {
58543
+ bubbles: true,
58544
+ composed: undefined
58545
+ });
57844
58546
  }
57845
58547
  }
57846
- controlChanged(_prev, _next) {
57847
- if (this.region && this.control) {
57848
- this.region.anchorElement = this.control;
58548
+ /**
58549
+ * Resets and fills the proxy to match the component's options.
58550
+ *
58551
+ * @internal
58552
+ */
58553
+ setProxyOptions() {
58554
+ if (this.proxy instanceof HTMLSelectElement && this.options) {
58555
+ this.proxy.options.length = 0;
58556
+ this.options.forEach(option => {
58557
+ const proxyOption = option.proxy
58558
+ || (option instanceof HTMLOptionElement
58559
+ ? option.cloneNode()
58560
+ : null);
58561
+ if (proxyOption) {
58562
+ this.proxy.options.add(proxyOption);
58563
+ }
58564
+ });
57849
58565
  }
57850
58566
  }
58567
+ clearSelection() {
58568
+ this.options.forEach(option => {
58569
+ option.selected = false;
58570
+ });
58571
+ }
58572
+ filterChanged() {
58573
+ this.filterOptions();
58574
+ }
57851
58575
  maxHeightChanged() {
57852
58576
  this.updateListboxMaxHeightCssVariable();
57853
58577
  }
58578
+ initializeOpenState() {
58579
+ if (!this.open) {
58580
+ this.ariaExpanded = 'false';
58581
+ this.ariaControls = '';
58582
+ return;
58583
+ }
58584
+ this.committedSelectedOption = this._options[this.selectedIndex];
58585
+ this.ariaControls = this.listboxId;
58586
+ this.ariaExpanded = 'true';
58587
+ this.setPositioning();
58588
+ this.focusAndScrollOptionIntoView();
58589
+ }
57854
58590
  updateListboxMaxHeightCssVariable() {
57855
58591
  if (this.listbox) {
57856
58592
  this.listbox.style.setProperty('--ni-private-select-max-height', `${this.maxHeight}px`);
57857
58593
  }
57858
58594
  }
58595
+ updateSelectedIndexFromFilteredSet() {
58596
+ const selectedItem = this.filteredOptions.length > 0
58597
+ ? this.options[this.selectedIndex]
58598
+ ?? this.committedSelectedOption
58599
+ : this.committedSelectedOption;
58600
+ if (!selectedItem) {
58601
+ return;
58602
+ }
58603
+ // Clear filter so any logic resolving against 'this.options' resolves against all options,
58604
+ // since selectedIndex should be relative to entire set.
58605
+ this.filter = '';
58606
+ // translate selectedIndex for filtered list to selectedIndex for all items
58607
+ this.selectedIndex = this._options.indexOf(selectedItem);
58608
+ // force selected to true again if the selection hasn't actually changed
58609
+ if (selectedItem === this.committedSelectedOption) {
58610
+ selectedItem.selected = true;
58611
+ }
58612
+ }
57859
58613
  }
57860
58614
  __decorate$1([
57861
58615
  attr
57862
58616
  ], Select.prototype, "appearance", void 0);
58617
+ __decorate$1([
58618
+ attr({ attribute: 'position' })
58619
+ ], Select.prototype, "positionAttribute", void 0);
57863
58620
  __decorate$1([
57864
58621
  attr({ attribute: 'error-text' })
57865
58622
  ], Select.prototype, "errorText", void 0);
57866
58623
  __decorate$1([
57867
58624
  attr({ attribute: 'error-visible', mode: 'boolean' })
57868
58625
  ], Select.prototype, "errorVisible", void 0);
58626
+ __decorate$1([
58627
+ attr({ attribute: 'filter-mode' })
58628
+ ], Select.prototype, "filterMode", void 0);
58629
+ __decorate$1([
58630
+ attr({ attribute: 'open', mode: 'boolean' })
58631
+ ], Select.prototype, "open", void 0);
58632
+ __decorate$1([
58633
+ observable
58634
+ ], Select.prototype, "position", void 0);
58635
+ __decorate$1([
58636
+ observable
58637
+ ], Select.prototype, "control", void 0);
57869
58638
  __decorate$1([
57870
58639
  observable
57871
- ], Select.prototype, "region", void 0);
58640
+ ], Select.prototype, "scrollableRegion", void 0);
58641
+ __decorate$1([
58642
+ observable
58643
+ ], Select.prototype, "filterInput", void 0);
58644
+ __decorate$1([
58645
+ observable
58646
+ ], Select.prototype, "anchoredRegion", void 0);
57872
58647
  __decorate$1([
57873
58648
  observable
57874
58649
  ], Select.prototype, "hasOverflow", void 0);
58650
+ __decorate$1([
58651
+ observable
58652
+ ], Select.prototype, "filteredOptions", void 0);
58653
+ __decorate$1([
58654
+ observable
58655
+ ], Select.prototype, "filter", void 0);
58656
+ __decorate$1([
58657
+ observable
58658
+ ], Select.prototype, "committedSelectedOption", void 0);
58659
+ __decorate$1([
58660
+ observable
58661
+ ], Select.prototype, "maxHeight", void 0);
58662
+ __decorate$1([
58663
+ volatile
58664
+ ], Select.prototype, "collapsible", null);
57875
58665
  const nimbleSelect = Select.compose({
57876
58666
  baseName: 'select',
57877
- baseClass: Select$1,
58667
+ baseClass: Select$2,
57878
58668
  template: template$l,
57879
58669
  styles: styles$o,
57880
58670
  indicator: arrowExpanderDown16X16.data,
@@ -57886,6 +58676,7 @@ img.ProseMirror-separator {
57886
58676
  ${errorTextTemplate}
57887
58677
  `
57888
58678
  });
58679
+ applyMixins(Select, StartEnd, DelegatesARIASelect);
57889
58680
  DesignSystem.getOrCreate().withPrefix('nimble').register(nimbleSelect());
57890
58681
 
57891
58682
  const styles$n = css `