@vonage/vivid 4.18.0 → 4.19.0-preview.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  const definition$1 = require('./definition11.cjs');
4
4
  const definition = require('./definition65.cjs');
5
- const definition$2 = require('./definition28.cjs');
5
+ const definition$3 = require('./definition28.cjs');
6
6
  const vividElement = require('./vivid-element.cjs');
7
7
  const applyMixinsWithObservables = require('./applyMixinsWithObservables.cjs');
8
8
  const scrollIntoView = require('./scrollIntoView.cjs');
@@ -10,8 +10,9 @@ const formAssociated = require('./form-associated.cjs');
10
10
  const affix = require('./affix.cjs');
11
11
  const localized = require('./localized.cjs');
12
12
  const formElements = require('./form-elements.cjs');
13
- const listbox = require('./listbox.cjs');
14
13
  const applyMixins = require('./apply-mixins.cjs');
14
+ const option = require('./option.cjs');
15
+ const definition$2 = require('./definition38.cjs');
15
16
  const when = require('./when.cjs');
16
17
  const ref = require('./ref.cjs');
17
18
  const slotted = require('./slotted.cjs');
@@ -49,9 +50,8 @@ var __decorateClass$1 = (decorators, target, key, kind) => {
49
50
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
50
51
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
51
52
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
52
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
53
53
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
54
- var _SearchableSelect_instances, updateValuesThroughUserInteraction_fn, updateValuesWhileMaintainingOrder_fn, isValidValue_fn, _slottedOptionsChangeHandler, updateSelectedOnSlottedOptions_fn, handleOptionInteraction_fn, _clonedTagIcons, tagIconOfOption_fn, updateClonedTagIconOfOption_fn, _suppressFilter, updateFilteredOptions_fn, transitionHighlightedOptionTo_fn, selectHighlightedOption_fn, highlightFirstOption_fn, highlightLastOption_fn, highlightPrevPage_fn, highlightNextPage_fn, highlightPreviousOption_fn, highlightNextOption_fn, textForValue_fn, measureTagWidth_fn, updateTagLayout_fn, moveTagFocusTo_fn, nextTagIndexLeft_fn, nextTagIndexRight_fn, nextTagIndexForRemoved_fn, determineInitialValues_fn, updateFormValue_fn, _resizeObserver;
54
+ var _SearchableSelect_instances, updateValuesThroughUserInteraction_fn, updateValuesWhileMaintainingOrder_fn, isValidValue_fn, _slottedOptionsChangeHandler, updateSelectedOnSlottedOptions_fn, handleOptionInteraction_fn, _clonedTagIcons, tagIconOfOption_fn, updateClonedTagIconOfOption_fn, updateFilteredOptions_fn, transitionHighlightedOptionTo_fn, selectHighlightedOption_fn, highlightFirstOption_fn, highlightLastOption_fn, highlightPrevPage_fn, highlightNextPage_fn, highlightPreviousOption_fn, highlightNextOption_fn, textForValue_fn, measureTagWidth_fn, updateTagLayout_fn, moveTagFocusTo_fn, nextTagIndexLeft_fn, nextTagIndexRight_fn, nextTagIndexForRemoved_fn, determineInitialValues_fn, updateFormValue_fn, _resizeObserver;
55
55
  const TagGapPx = 8;
56
56
  const InputMinWidthPx = 100;
57
57
  const PageSize = 10;
@@ -67,7 +67,7 @@ exports.SearchableSelect = class SearchableSelect extends FormAssociatedSearchab
67
67
  this.maxLines = null;
68
68
  this.values = [];
69
69
  this.initialValues = [];
70
- this._inputValue = "";
70
+ this._internalSearchText = null;
71
71
  // --- Slotted options ---
72
72
  /**
73
73
  * @internal
@@ -86,7 +86,7 @@ exports.SearchableSelect = class SearchableSelect extends FormAssociatedSearchab
86
86
  __privateAdd(this, _clonedTagIcons, /* @__PURE__ */ new Map());
87
87
  this._filteredOptions = [];
88
88
  this._filteredEnabledOptions = [];
89
- __privateAdd(this, _suppressFilter, false);
89
+ this.loading = false;
90
90
  this._highlightedOptionIndex = null;
91
91
  this._numElidedTags = 0;
92
92
  this._tagRows = [];
@@ -127,14 +127,6 @@ exports.SearchableSelect = class SearchableSelect extends FormAssociatedSearchab
127
127
  this.values = this.values.filter((value) => __privateMethod(this, _SearchableSelect_instances, isValidValue_fn).call(this, value));
128
128
  return;
129
129
  }
130
- if (!this.multiple) {
131
- if (this.values.length) {
132
- __privateSet(this, _suppressFilter, true);
133
- this._inputValue = __privateMethod(this, _SearchableSelect_instances, textForValue_fn).call(this, this.values[0]);
134
- } else {
135
- this._inputValue = "";
136
- }
137
- }
138
130
  this.value = this.values.length ? this.values[0] : "";
139
131
  __privateMethod(this, _SearchableSelect_instances, updateSelectedOnSlottedOptions_fn).call(this);
140
132
  if (this.$fastController.isConnected) {
@@ -189,21 +181,34 @@ exports.SearchableSelect = class SearchableSelect extends FormAssociatedSearchab
189
181
  /**
190
182
  * @internal
191
183
  */
192
- _inputValueChanged() {
193
- __privateMethod(this, _SearchableSelect_instances, updateFilteredOptions_fn).call(this);
184
+ _internalSearchTextChanged(newValue, oldValue) {
185
+ if (oldValue !== newValue) {
186
+ __privateMethod(this, _SearchableSelect_instances, updateFilteredOptions_fn).call(this);
187
+ this.$emit("input:search-text", void 0, {
188
+ bubbles: false,
189
+ composed: false
190
+ });
191
+ }
192
+ }
193
+ get searchText() {
194
+ return this._internalSearchText ?? "";
195
+ }
196
+ /**
197
+ * @internal
198
+ */
199
+ get _inputValue() {
200
+ return this._internalSearchText ?? (!this.multiple && this.value !== "" ? __privateMethod(this, _SearchableSelect_instances, textForValue_fn).call(this, this.value) : "");
194
201
  }
195
202
  /**
196
203
  * @internal
197
204
  */
198
205
  _onInputInput(event) {
199
- __privateSet(this, _suppressFilter, false);
200
- this._inputValue = event.target.value;
206
+ this._internalSearchText = event.target.value;
201
207
  }
202
208
  /**
203
209
  * @internal
204
210
  */
205
211
  _onInputFocus(_) {
206
- __privateSet(this, _suppressFilter, true);
207
212
  __privateMethod(this, _SearchableSelect_instances, updateFilteredOptions_fn).call(this);
208
213
  this.open = true;
209
214
  }
@@ -212,15 +217,7 @@ exports.SearchableSelect = class SearchableSelect extends FormAssociatedSearchab
212
217
  */
213
218
  _onInputBlur(_) {
214
219
  this.open = false;
215
- if (this.multiple) {
216
- this._inputValue = "";
217
- } else {
218
- if (this.values.length === 0) {
219
- this._inputValue = "";
220
- } else {
221
- this._inputValue = __privateMethod(this, _SearchableSelect_instances, textForValue_fn).call(this, this.values[0]);
222
- }
223
- }
220
+ this._internalSearchText = null;
224
221
  this._changeDescription = "";
225
222
  }
226
223
  /**
@@ -497,6 +494,9 @@ updateValuesThroughUserInteraction_fn = function(newValues) {
497
494
  updateValuesWhileMaintainingOrder_fn = function(newValues) {
498
495
  const oldSet = new Set(this.values);
499
496
  const newSet = new Set(newValues);
497
+ if (oldSet.size === newSet.size && [...oldSet].every((v) => newSet.has(v))) {
498
+ return;
499
+ }
500
500
  this.values = [...this.values].filter((v) => newSet.has(v)).concat([...newValues].filter((v) => !oldSet.has(v)));
501
501
  };
502
502
  isValidValue_fn = function(value) {
@@ -512,6 +512,7 @@ updateSelectedOnSlottedOptions_fn = function() {
512
512
  handleOptionInteraction_fn = function(option) {
513
513
  const value = option.value;
514
514
  let newValues;
515
+ let shouldClearSearchText = false;
515
516
  const isSelection = !this.values.includes(value);
516
517
  if (this.multiple) {
517
518
  if (isSelection) {
@@ -519,11 +520,11 @@ handleOptionInteraction_fn = function(option) {
519
520
  } else {
520
521
  newValues = this.values.filter((option2) => option2 !== value);
521
522
  }
522
- this._inputValue = "";
523
+ shouldClearSearchText = true;
523
524
  } else {
524
525
  if (isSelection) {
525
526
  newValues = [value];
526
- this._inputValue = option.text;
527
+ shouldClearSearchText = true;
527
528
  } else {
528
529
  newValues = [];
529
530
  }
@@ -531,6 +532,9 @@ handleOptionInteraction_fn = function(option) {
531
532
  }
532
533
  this._changeDescription = isSelection ? this.locale.searchableSelect.optionSelectedMessage(option.text) : this.locale.searchableSelect.optionDeselectedMessage(option.text);
533
534
  __privateMethod(this, _SearchableSelect_instances, updateValuesThroughUserInteraction_fn).call(this, newValues);
535
+ if (shouldClearSearchText) {
536
+ this._internalSearchText = null;
537
+ }
534
538
  };
535
539
  _clonedTagIcons = new WeakMap();
536
540
  tagIconOfOption_fn = function(option) {
@@ -553,20 +557,19 @@ updateClonedTagIconOfOption_fn = function(option) {
553
557
  }
554
558
  }
555
559
  };
556
- _suppressFilter = new WeakMap();
557
560
  updateFilteredOptions_fn = function() {
558
561
  const newFilteredOptions = [];
562
+ const filterOption = this.filterOption ?? ((option, searchText) => option.text.toLowerCase().includes(searchText.toLowerCase()));
559
563
  for (const option of this._slottedOptions ?? []) {
560
- if (__privateGet(this, _suppressFilter) || this._inputValue === "") {
561
- option.hidden = false;
562
- option._matchedRange = null;
564
+ if (this.searchText === "") {
565
+ option._hidden = false;
566
+ option._internalHighlightText = "";
563
567
  } else {
564
- const matchIndex = option.text.toLowerCase().indexOf(this._inputValue.toLowerCase());
565
- const matchedRange = matchIndex === -1 ? null : { from: matchIndex, to: matchIndex + this._inputValue.length };
566
- option.hidden = !matchedRange;
567
- option._matchedRange = matchedRange;
568
+ const matches = filterOption(option, this.searchText);
569
+ option._hidden = !matches;
570
+ option._internalHighlightText = matches ? this.searchText : "";
568
571
  }
569
- if (!option.hidden) {
572
+ if (!option.hidden && !option._hidden) {
570
573
  newFilteredOptions.push(option);
571
574
  }
572
575
  }
@@ -798,7 +801,7 @@ __decorateClass$1([
798
801
  ], exports.SearchableSelect.prototype, "_input", 2);
799
802
  __decorateClass$1([
800
803
  vividElement.observable
801
- ], exports.SearchableSelect.prototype, "_inputValue", 2);
804
+ ], exports.SearchableSelect.prototype, "_internalSearchText", 2);
802
805
  __decorateClass$1([
803
806
  vividElement.observable
804
807
  ], exports.SearchableSelect.prototype, "_slottedOptions", 2);
@@ -808,6 +811,14 @@ __decorateClass$1([
808
811
  __decorateClass$1([
809
812
  vividElement.observable
810
813
  ], exports.SearchableSelect.prototype, "_filteredEnabledOptions", 2);
814
+ __decorateClass$1([
815
+ vividElement.observable
816
+ ], exports.SearchableSelect.prototype, "filterOption", 2);
817
+ __decorateClass$1([
818
+ vividElement.attr({
819
+ mode: "boolean"
820
+ })
821
+ ], exports.SearchableSelect.prototype, "loading", 2);
811
822
  __decorateClass$1([
812
823
  vividElement.observable
813
824
  ], exports.SearchableSelect.prototype, "_highlightedOptionIndex", 2);
@@ -933,6 +944,7 @@ const elidedTagTemplateFactory = (context, getComponent) => {
933
944
  };
934
945
  function renderFieldset(context) {
935
946
  const buttonTag = context.tagFor(definition$1.Button);
947
+ const progressRingTag = context.tagFor(definition$2.ProgressRing);
936
948
  const affixIconTemplate = affix.affixIconTemplateFactory(context);
937
949
  const chevronTemplate = definition$1.chevronTemplateFactory(context);
938
950
  const tagTemplate = tagTemplateFactory(context, (c) => c.parent);
@@ -990,7 +1002,13 @@ function renderFieldset(context) {
990
1002
  type="text"
991
1003
  ?disabled="${(x) => x.disabled}"
992
1004
  :value="${(x) => x._inputValue}"
993
- @input="${(x, c) => x._onInputInput(c.event)}"
1005
+ @input="${(x, c) => {
1006
+ x._onInputInput(c.event);
1007
+ c.event.stopPropagation();
1008
+ }}"
1009
+ @change="${(_, c) => {
1010
+ c.event.stopPropagation();
1011
+ }}"
994
1012
  @focus="${(x, c) => x._onInputFocus(c.event)}"
995
1013
  @blur="${(x, c) => x._onInputBlur(c.event)}"
996
1014
  @keydown="${(x, c) => x._onInputKeydown(c.event)}"
@@ -1014,7 +1032,11 @@ function renderFieldset(context) {
1014
1032
  ></${buttonTag}>`
1015
1033
  )}
1016
1034
  <div @mousedown="${() => false}" @click="${(x) => x._onChevronClick()}">
1017
- ${chevronTemplate}
1035
+ ${when.when(
1036
+ (x) => x.loading,
1037
+ vividElement.html`<${progressRingTag} indeterminate size="-6"></${progressRingTag}>`
1038
+ )}
1039
+ ${when.when((x) => !x.loading, chevronTemplate)}
1018
1040
  </div>
1019
1041
  </div>
1020
1042
  `;
@@ -1052,7 +1074,7 @@ function renderControl(context) {
1052
1074
  >
1053
1075
  <slot
1054
1076
  ${slotted.slotted({
1055
- filter: listbox.Listbox.slottedOptionFilter,
1077
+ filter: option.isListboxOption,
1056
1078
  flatten: true,
1057
1079
  property: "_slottedOptions"
1058
1080
  })}>
@@ -1061,13 +1083,19 @@ function renderControl(context) {
1061
1083
  (x) => x._filteredOptions.length === 0,
1062
1084
  vividElement.html`<div class="empty-message">
1063
1085
  ${when.when(
1064
- (x) => x._inputValue === "",
1086
+ (x) => x.loading,
1087
+ vividElement.html` <slot name="loading-options">
1088
+ Loading...
1089
+ </slot>`
1090
+ )}
1091
+ ${when.when(
1092
+ (x) => !x.loading && x.searchText === "",
1065
1093
  vividElement.html`<slot name="no-options">
1066
1094
  ${(x) => x.locale.searchableSelect.noOptionsMessage}
1067
1095
  </slot>`
1068
1096
  )}
1069
1097
  ${when.when(
1070
- (x) => x._inputValue !== "",
1098
+ (x) => !x.loading && x.searchText !== "",
1071
1099
  vividElement.html`<slot name="no-matches">
1072
1100
  ${(x) => x.locale.searchableSelect.noMatchesMessage}
1073
1101
  </slot>`
@@ -1114,7 +1142,7 @@ function renderRemoveButton(iconTag) {
1114
1142
  `;
1115
1143
  }
1116
1144
  const optionTagTemplate = (context) => {
1117
- const iconTag = context.tagFor(definition$2.Icon);
1145
+ const iconTag = context.tagFor(definition$3.Icon);
1118
1146
  return vividElement.html`<span class="${getClasses}" aria-disabled="${(x) => x.disabled}">
1119
1147
  <slot name="icon" aria-hidden="true">
1120
1148
  ${when.when(
@@ -1134,7 +1162,7 @@ const optionTagDefinition = vividElement.defineVividComponent(
1134
1162
  "option-tag",
1135
1163
  OptionTag,
1136
1164
  optionTagTemplate,
1137
- [definition$2.iconDefinition],
1165
+ [definition$3.iconDefinition],
1138
1166
  {
1139
1167
  styles: [optionTagStyles],
1140
1168
  shadowOptions: {
@@ -1146,7 +1174,13 @@ const searchableSelectDefinition = vividElement.defineVividComponent(
1146
1174
  "searchable-select",
1147
1175
  exports.SearchableSelect,
1148
1176
  SearchableSelectTemplate,
1149
- [definition$1.buttonDefinition, definition.popupDefinition, definition$2.iconDefinition, optionTagDefinition],
1177
+ [
1178
+ definition$1.buttonDefinition,
1179
+ definition.popupDefinition,
1180
+ definition$3.iconDefinition,
1181
+ optionTagDefinition,
1182
+ definition$2.progressRingDefinition
1183
+ ],
1150
1184
  {
1151
1185
  styles
1152
1186
  }
@@ -8,8 +8,9 @@ import { F as FormAssociated } from './form-associated.js';
8
8
  import { A as AffixIconWithTrailing, a as affixIconTemplateFactory, I as IconWrapper } from './affix.js';
9
9
  import { L as Localized } from './localized.js';
10
10
  import { F as FormElementSuccessText, a as FormElementHelperText, e as errorText, f as formElements, g as getFeedbackTemplate } from './form-elements.js';
11
- import { L as Listbox } from './listbox.js';
12
11
  import { a as applyMixins } from './apply-mixins.js';
12
+ import { i as isListboxOption } from './option.js';
13
+ import { P as ProgressRing, p as progressRingDefinition } from './definition38.js';
13
14
  import { w as when } from './when.js';
14
15
  import { r as ref } from './ref.js';
15
16
  import { s as slotted } from './slotted.js';
@@ -47,9 +48,8 @@ var __decorateClass$1 = (decorators, target, key, kind) => {
47
48
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
48
49
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
49
50
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
50
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
51
51
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
52
- var _SearchableSelect_instances, updateValuesThroughUserInteraction_fn, updateValuesWhileMaintainingOrder_fn, isValidValue_fn, _slottedOptionsChangeHandler, updateSelectedOnSlottedOptions_fn, handleOptionInteraction_fn, _clonedTagIcons, tagIconOfOption_fn, updateClonedTagIconOfOption_fn, _suppressFilter, updateFilteredOptions_fn, transitionHighlightedOptionTo_fn, selectHighlightedOption_fn, highlightFirstOption_fn, highlightLastOption_fn, highlightPrevPage_fn, highlightNextPage_fn, highlightPreviousOption_fn, highlightNextOption_fn, textForValue_fn, measureTagWidth_fn, updateTagLayout_fn, moveTagFocusTo_fn, nextTagIndexLeft_fn, nextTagIndexRight_fn, nextTagIndexForRemoved_fn, determineInitialValues_fn, updateFormValue_fn, _resizeObserver;
52
+ var _SearchableSelect_instances, updateValuesThroughUserInteraction_fn, updateValuesWhileMaintainingOrder_fn, isValidValue_fn, _slottedOptionsChangeHandler, updateSelectedOnSlottedOptions_fn, handleOptionInteraction_fn, _clonedTagIcons, tagIconOfOption_fn, updateClonedTagIconOfOption_fn, updateFilteredOptions_fn, transitionHighlightedOptionTo_fn, selectHighlightedOption_fn, highlightFirstOption_fn, highlightLastOption_fn, highlightPrevPage_fn, highlightNextPage_fn, highlightPreviousOption_fn, highlightNextOption_fn, textForValue_fn, measureTagWidth_fn, updateTagLayout_fn, moveTagFocusTo_fn, nextTagIndexLeft_fn, nextTagIndexRight_fn, nextTagIndexForRemoved_fn, determineInitialValues_fn, updateFormValue_fn, _resizeObserver;
53
53
  const TagGapPx = 8;
54
54
  const InputMinWidthPx = 100;
55
55
  const PageSize = 10;
@@ -65,7 +65,7 @@ let SearchableSelect = class extends FormAssociatedSearchableSelect {
65
65
  this.maxLines = null;
66
66
  this.values = [];
67
67
  this.initialValues = [];
68
- this._inputValue = "";
68
+ this._internalSearchText = null;
69
69
  // --- Slotted options ---
70
70
  /**
71
71
  * @internal
@@ -84,7 +84,7 @@ let SearchableSelect = class extends FormAssociatedSearchableSelect {
84
84
  __privateAdd(this, _clonedTagIcons, /* @__PURE__ */ new Map());
85
85
  this._filteredOptions = [];
86
86
  this._filteredEnabledOptions = [];
87
- __privateAdd(this, _suppressFilter, false);
87
+ this.loading = false;
88
88
  this._highlightedOptionIndex = null;
89
89
  this._numElidedTags = 0;
90
90
  this._tagRows = [];
@@ -125,14 +125,6 @@ let SearchableSelect = class extends FormAssociatedSearchableSelect {
125
125
  this.values = this.values.filter((value) => __privateMethod(this, _SearchableSelect_instances, isValidValue_fn).call(this, value));
126
126
  return;
127
127
  }
128
- if (!this.multiple) {
129
- if (this.values.length) {
130
- __privateSet(this, _suppressFilter, true);
131
- this._inputValue = __privateMethod(this, _SearchableSelect_instances, textForValue_fn).call(this, this.values[0]);
132
- } else {
133
- this._inputValue = "";
134
- }
135
- }
136
128
  this.value = this.values.length ? this.values[0] : "";
137
129
  __privateMethod(this, _SearchableSelect_instances, updateSelectedOnSlottedOptions_fn).call(this);
138
130
  if (this.$fastController.isConnected) {
@@ -187,21 +179,34 @@ let SearchableSelect = class extends FormAssociatedSearchableSelect {
187
179
  /**
188
180
  * @internal
189
181
  */
190
- _inputValueChanged() {
191
- __privateMethod(this, _SearchableSelect_instances, updateFilteredOptions_fn).call(this);
182
+ _internalSearchTextChanged(newValue, oldValue) {
183
+ if (oldValue !== newValue) {
184
+ __privateMethod(this, _SearchableSelect_instances, updateFilteredOptions_fn).call(this);
185
+ this.$emit("input:search-text", void 0, {
186
+ bubbles: false,
187
+ composed: false
188
+ });
189
+ }
190
+ }
191
+ get searchText() {
192
+ return this._internalSearchText ?? "";
193
+ }
194
+ /**
195
+ * @internal
196
+ */
197
+ get _inputValue() {
198
+ return this._internalSearchText ?? (!this.multiple && this.value !== "" ? __privateMethod(this, _SearchableSelect_instances, textForValue_fn).call(this, this.value) : "");
192
199
  }
193
200
  /**
194
201
  * @internal
195
202
  */
196
203
  _onInputInput(event) {
197
- __privateSet(this, _suppressFilter, false);
198
- this._inputValue = event.target.value;
204
+ this._internalSearchText = event.target.value;
199
205
  }
200
206
  /**
201
207
  * @internal
202
208
  */
203
209
  _onInputFocus(_) {
204
- __privateSet(this, _suppressFilter, true);
205
210
  __privateMethod(this, _SearchableSelect_instances, updateFilteredOptions_fn).call(this);
206
211
  this.open = true;
207
212
  }
@@ -210,15 +215,7 @@ let SearchableSelect = class extends FormAssociatedSearchableSelect {
210
215
  */
211
216
  _onInputBlur(_) {
212
217
  this.open = false;
213
- if (this.multiple) {
214
- this._inputValue = "";
215
- } else {
216
- if (this.values.length === 0) {
217
- this._inputValue = "";
218
- } else {
219
- this._inputValue = __privateMethod(this, _SearchableSelect_instances, textForValue_fn).call(this, this.values[0]);
220
- }
221
- }
218
+ this._internalSearchText = null;
222
219
  this._changeDescription = "";
223
220
  }
224
221
  /**
@@ -495,6 +492,9 @@ updateValuesThroughUserInteraction_fn = function(newValues) {
495
492
  updateValuesWhileMaintainingOrder_fn = function(newValues) {
496
493
  const oldSet = new Set(this.values);
497
494
  const newSet = new Set(newValues);
495
+ if (oldSet.size === newSet.size && [...oldSet].every((v) => newSet.has(v))) {
496
+ return;
497
+ }
498
498
  this.values = [...this.values].filter((v) => newSet.has(v)).concat([...newValues].filter((v) => !oldSet.has(v)));
499
499
  };
500
500
  isValidValue_fn = function(value) {
@@ -510,6 +510,7 @@ updateSelectedOnSlottedOptions_fn = function() {
510
510
  handleOptionInteraction_fn = function(option) {
511
511
  const value = option.value;
512
512
  let newValues;
513
+ let shouldClearSearchText = false;
513
514
  const isSelection = !this.values.includes(value);
514
515
  if (this.multiple) {
515
516
  if (isSelection) {
@@ -517,11 +518,11 @@ handleOptionInteraction_fn = function(option) {
517
518
  } else {
518
519
  newValues = this.values.filter((option2) => option2 !== value);
519
520
  }
520
- this._inputValue = "";
521
+ shouldClearSearchText = true;
521
522
  } else {
522
523
  if (isSelection) {
523
524
  newValues = [value];
524
- this._inputValue = option.text;
525
+ shouldClearSearchText = true;
525
526
  } else {
526
527
  newValues = [];
527
528
  }
@@ -529,6 +530,9 @@ handleOptionInteraction_fn = function(option) {
529
530
  }
530
531
  this._changeDescription = isSelection ? this.locale.searchableSelect.optionSelectedMessage(option.text) : this.locale.searchableSelect.optionDeselectedMessage(option.text);
531
532
  __privateMethod(this, _SearchableSelect_instances, updateValuesThroughUserInteraction_fn).call(this, newValues);
533
+ if (shouldClearSearchText) {
534
+ this._internalSearchText = null;
535
+ }
532
536
  };
533
537
  _clonedTagIcons = new WeakMap();
534
538
  tagIconOfOption_fn = function(option) {
@@ -551,20 +555,19 @@ updateClonedTagIconOfOption_fn = function(option) {
551
555
  }
552
556
  }
553
557
  };
554
- _suppressFilter = new WeakMap();
555
558
  updateFilteredOptions_fn = function() {
556
559
  const newFilteredOptions = [];
560
+ const filterOption = this.filterOption ?? ((option, searchText) => option.text.toLowerCase().includes(searchText.toLowerCase()));
557
561
  for (const option of this._slottedOptions ?? []) {
558
- if (__privateGet(this, _suppressFilter) || this._inputValue === "") {
559
- option.hidden = false;
560
- option._matchedRange = null;
562
+ if (this.searchText === "") {
563
+ option._hidden = false;
564
+ option._internalHighlightText = "";
561
565
  } else {
562
- const matchIndex = option.text.toLowerCase().indexOf(this._inputValue.toLowerCase());
563
- const matchedRange = matchIndex === -1 ? null : { from: matchIndex, to: matchIndex + this._inputValue.length };
564
- option.hidden = !matchedRange;
565
- option._matchedRange = matchedRange;
566
+ const matches = filterOption(option, this.searchText);
567
+ option._hidden = !matches;
568
+ option._internalHighlightText = matches ? this.searchText : "";
566
569
  }
567
- if (!option.hidden) {
570
+ if (!option.hidden && !option._hidden) {
568
571
  newFilteredOptions.push(option);
569
572
  }
570
573
  }
@@ -796,7 +799,7 @@ __decorateClass$1([
796
799
  ], SearchableSelect.prototype, "_input", 2);
797
800
  __decorateClass$1([
798
801
  observable
799
- ], SearchableSelect.prototype, "_inputValue", 2);
802
+ ], SearchableSelect.prototype, "_internalSearchText", 2);
800
803
  __decorateClass$1([
801
804
  observable
802
805
  ], SearchableSelect.prototype, "_slottedOptions", 2);
@@ -806,6 +809,14 @@ __decorateClass$1([
806
809
  __decorateClass$1([
807
810
  observable
808
811
  ], SearchableSelect.prototype, "_filteredEnabledOptions", 2);
812
+ __decorateClass$1([
813
+ observable
814
+ ], SearchableSelect.prototype, "filterOption", 2);
815
+ __decorateClass$1([
816
+ attr({
817
+ mode: "boolean"
818
+ })
819
+ ], SearchableSelect.prototype, "loading", 2);
809
820
  __decorateClass$1([
810
821
  observable
811
822
  ], SearchableSelect.prototype, "_highlightedOptionIndex", 2);
@@ -931,6 +942,7 @@ const elidedTagTemplateFactory = (context, getComponent) => {
931
942
  };
932
943
  function renderFieldset(context) {
933
944
  const buttonTag = context.tagFor(Button);
945
+ const progressRingTag = context.tagFor(ProgressRing);
934
946
  const affixIconTemplate = affixIconTemplateFactory(context);
935
947
  const chevronTemplate = chevronTemplateFactory(context);
936
948
  const tagTemplate = tagTemplateFactory(context, (c) => c.parent);
@@ -988,7 +1000,13 @@ function renderFieldset(context) {
988
1000
  type="text"
989
1001
  ?disabled="${(x) => x.disabled}"
990
1002
  :value="${(x) => x._inputValue}"
991
- @input="${(x, c) => x._onInputInput(c.event)}"
1003
+ @input="${(x, c) => {
1004
+ x._onInputInput(c.event);
1005
+ c.event.stopPropagation();
1006
+ }}"
1007
+ @change="${(_, c) => {
1008
+ c.event.stopPropagation();
1009
+ }}"
992
1010
  @focus="${(x, c) => x._onInputFocus(c.event)}"
993
1011
  @blur="${(x, c) => x._onInputBlur(c.event)}"
994
1012
  @keydown="${(x, c) => x._onInputKeydown(c.event)}"
@@ -1012,7 +1030,11 @@ function renderFieldset(context) {
1012
1030
  ></${buttonTag}>`
1013
1031
  )}
1014
1032
  <div @mousedown="${() => false}" @click="${(x) => x._onChevronClick()}">
1015
- ${chevronTemplate}
1033
+ ${when(
1034
+ (x) => x.loading,
1035
+ html`<${progressRingTag} indeterminate size="-6"></${progressRingTag}>`
1036
+ )}
1037
+ ${when((x) => !x.loading, chevronTemplate)}
1016
1038
  </div>
1017
1039
  </div>
1018
1040
  `;
@@ -1050,7 +1072,7 @@ function renderControl(context) {
1050
1072
  >
1051
1073
  <slot
1052
1074
  ${slotted({
1053
- filter: Listbox.slottedOptionFilter,
1075
+ filter: isListboxOption,
1054
1076
  flatten: true,
1055
1077
  property: "_slottedOptions"
1056
1078
  })}>
@@ -1059,13 +1081,19 @@ function renderControl(context) {
1059
1081
  (x) => x._filteredOptions.length === 0,
1060
1082
  html`<div class="empty-message">
1061
1083
  ${when(
1062
- (x) => x._inputValue === "",
1084
+ (x) => x.loading,
1085
+ html` <slot name="loading-options">
1086
+ Loading...
1087
+ </slot>`
1088
+ )}
1089
+ ${when(
1090
+ (x) => !x.loading && x.searchText === "",
1063
1091
  html`<slot name="no-options">
1064
1092
  ${(x) => x.locale.searchableSelect.noOptionsMessage}
1065
1093
  </slot>`
1066
1094
  )}
1067
1095
  ${when(
1068
- (x) => x._inputValue !== "",
1096
+ (x) => !x.loading && x.searchText !== "",
1069
1097
  html`<slot name="no-matches">
1070
1098
  ${(x) => x.locale.searchableSelect.noMatchesMessage}
1071
1099
  </slot>`
@@ -1144,7 +1172,13 @@ const searchableSelectDefinition = defineVividComponent(
1144
1172
  "searchable-select",
1145
1173
  SearchableSelect,
1146
1174
  SearchableSelectTemplate,
1147
- [buttonDefinition, popupDefinition, iconDefinition, optionTagDefinition],
1175
+ [
1176
+ buttonDefinition,
1177
+ popupDefinition,
1178
+ iconDefinition,
1179
+ optionTagDefinition,
1180
+ progressRingDefinition
1181
+ ],
1148
1182
  {
1149
1183
  styles
1150
1184
  }