@vaadin/combo-box 25.0.0-alpha8 → 25.0.0-alpha9

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.
@@ -3,10 +3,8 @@
3
3
  * Copyright (c) 2015 - 2025 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { get } from '@vaadin/component-base/src/path-utils.js';
7
6
  import { ValidateMixin } from '@vaadin/field-base/src/validate-mixin.js';
8
- import { ComboBoxBaseMixin } from './vaadin-combo-box-base-mixin.js';
9
- import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js';
7
+ import { ComboBoxItemsMixin } from './vaadin-combo-box-items-mixin.js';
10
8
 
11
9
  /**
12
10
  * Checks if the value is supported as an item value in this control.
@@ -18,32 +16,14 @@ function isValidValue(value) {
18
16
  return value !== undefined && value !== null;
19
17
  }
20
18
 
21
- /**
22
- * Returns the index of the first item that satisfies the provided testing function
23
- * ignoring placeholder items.
24
- *
25
- * @param {Array<ComboBoxItem | string>} items
26
- * @param {Function} callback
27
- * @return {number}
28
- */
29
- function findItemIndex(items, callback) {
30
- return items.findIndex((item) => {
31
- if (item instanceof ComboBoxPlaceholder) {
32
- return false;
33
- }
34
-
35
- return callback(item);
36
- });
37
- }
38
-
39
19
  /**
40
20
  * @polymerMixin
41
- * @mixes ComboBoxBaseMixin
21
+ * @mixes ComboBoxItemsMixin
42
22
  * @mixes ValidateMixin
43
23
  * @param {function(new:HTMLElement)} superClass
44
24
  */
45
25
  export const ComboBoxMixin = (superClass) =>
46
- class ComboBoxMixinClass extends ValidateMixin(ComboBoxBaseMixin(superClass)) {
26
+ class ComboBoxMixinClass extends ValidateMixin(ComboBoxItemsMixin(superClass)) {
47
27
  static get properties() {
48
28
  return {
49
29
  /**
@@ -63,17 +43,6 @@ export const ComboBoxMixin = (superClass) =>
63
43
  sync: true,
64
44
  },
65
45
 
66
- /**
67
- * A full set of items to filter the visible options from.
68
- * The items can be of either `String` or `Object` type.
69
- * @type {!Array<!ComboBoxItem | string> | undefined}
70
- */
71
- items: {
72
- type: Array,
73
- sync: true,
74
- observer: '_itemsChanged',
75
- },
76
-
77
46
  /**
78
47
  * If `true`, the user can input a value that is not present in the items list.
79
48
  * `value` property will be set to the input value in this case.
@@ -87,18 +56,6 @@ export const ComboBoxMixin = (superClass) =>
87
56
  value: false,
88
57
  },
89
58
 
90
- /**
91
- * A subset of items, filtered based on the user input. Filtered items
92
- * can be assigned directly to omit the internal filtering functionality.
93
- * The items can be of either `String` or `Object` type.
94
- * @type {!Array<!ComboBoxItem | string> | undefined}
95
- */
96
- filteredItems: {
97
- type: Array,
98
- observer: '_filteredItemsChanged',
99
- sync: true,
100
- },
101
-
102
59
  /**
103
60
  * When set to `true`, "loading" attribute is added to host and the overlay element.
104
61
  * @type {boolean}
@@ -110,17 +67,6 @@ export const ComboBoxMixin = (superClass) =>
110
67
  sync: true,
111
68
  },
112
69
 
113
- /**
114
- * Filtering string the user has typed into the input field.
115
- * @type {string}
116
- */
117
- filter: {
118
- type: String,
119
- value: '',
120
- notify: true,
121
- sync: true,
122
- },
123
-
124
70
  /**
125
71
  * The selected item from the `items` array.
126
72
  * @type {ComboBoxItem | string | undefined}
@@ -141,39 +87,6 @@ export const ComboBoxMixin = (superClass) =>
141
87
  type: Object,
142
88
  },
143
89
 
144
- /**
145
- * Path for label of the item. If `items` is an array of objects, the
146
- * `itemLabelPath` is used to fetch the displayed string label for each
147
- * item.
148
- *
149
- * The item label is also used for matching items when processing user
150
- * input, i.e., for filtering and selecting items.
151
- * @attr {string} item-label-path
152
- * @type {string}
153
- */
154
- itemLabelPath: {
155
- type: String,
156
- value: 'label',
157
- observer: '_itemLabelPathChanged',
158
- sync: true,
159
- },
160
-
161
- /**
162
- * Path for the value of the item. If `items` is an array of objects, the
163
- * `itemValuePath:` is used to fetch the string value for the selected
164
- * item.
165
- *
166
- * The item value is used in the `value` property of the combo box,
167
- * to provide the form value.
168
- * @attr {string} item-value-path
169
- * @type {string}
170
- */
171
- itemValuePath: {
172
- type: String,
173
- value: 'value',
174
- sync: true,
175
- },
176
-
177
90
  /**
178
91
  * Path for the id of the item. If `items` is an array of objects,
179
92
  * the `itemIdPath` is used to compare and identify the same item
@@ -245,10 +158,6 @@ export const ComboBoxMixin = (superClass) =>
245
158
  this._scroller[prop] = this[prop];
246
159
  }
247
160
  });
248
-
249
- if (props.has('filter')) {
250
- this._filterChanged(this.filter);
251
- }
252
161
  }
253
162
 
254
163
  /** @private */
@@ -301,28 +210,6 @@ export const ComboBoxMixin = (superClass) =>
301
210
  }
302
211
  }
303
212
 
304
- /**
305
- * Override method from `ComboBoxBaseMixin` to handle item label path.
306
- * @protected
307
- * @override
308
- */
309
- _getItemLabel(item) {
310
- let label = item && this.itemLabelPath ? get(this.itemLabelPath, item) : undefined;
311
- if (label === undefined || label === null) {
312
- label = item ? item.toString() : '';
313
- }
314
- return label;
315
- }
316
-
317
- /** @private */
318
- _getItemValue(item) {
319
- let value = item && this.itemValuePath ? get(this.itemValuePath, item) : undefined;
320
- if (value === undefined) {
321
- value = item ? item.toString() : '';
322
- }
323
- return value;
324
- }
325
-
326
213
  /**
327
214
  * Override method from `ComboBoxBaseMixin` to handle loading.
328
215
  * @protected
@@ -488,37 +375,6 @@ export const ComboBoxMixin = (superClass) =>
488
375
  this._clearFilter();
489
376
  }
490
377
 
491
- /**
492
- * Override an event listener from `ComboBoxBaseMixin` to handle
493
- * batched setting of both `opened` and `filter` properties.
494
- * @param {!Event} event
495
- * @protected
496
- * @override
497
- */
498
- _onInput(event) {
499
- const filter = this._inputElementValue;
500
-
501
- // When opening dropdown on user input, both `opened` and `filter` properties are set.
502
- // Perform a batched property update instead of relying on sync property observers.
503
- // This is necessary to avoid an extra data-provider request for loading first page.
504
- const props = {};
505
-
506
- if (this.filter === filter) {
507
- // Filter and input value might get out of sync, while keyboard navigating for example.
508
- // Afterwards, input value might be changed to the same value as used in filtering.
509
- // In situation like these, we need to make sure all the filter changes handlers are run.
510
- this._filterChanged(this.filter);
511
- } else {
512
- props.filter = filter;
513
- }
514
-
515
- if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) {
516
- props.opened = true;
517
- }
518
-
519
- this.setProperties(props);
520
- }
521
-
522
378
  /**
523
379
  * Override an event listener from `InputMixin`.
524
380
  * @param {!Event} event
@@ -531,30 +387,6 @@ export const ComboBoxMixin = (superClass) =>
531
387
  event.stopPropagation();
532
388
  }
533
389
 
534
- /** @private */
535
- _itemLabelPathChanged(itemLabelPath) {
536
- if (typeof itemLabelPath !== 'string') {
537
- console.error('You should set itemLabelPath to a valid string');
538
- }
539
- }
540
-
541
- /** @private */
542
- _filterChanged(filter) {
543
- // Scroll to the top of the list whenever the filter changes.
544
- this._scrollIntoView(0);
545
-
546
- this._focusedIndex = -1;
547
-
548
- if (this.items) {
549
- this.filteredItems = this._filterItems(this.items, filter);
550
- } else {
551
- // With certain use cases (e. g., external filtering), `items` are
552
- // undefined. Filtering is unnecessary per se, but the filteredItems
553
- // observer should still be invoked to update focused item.
554
- this._filteredItemsChanged(this.filteredItems);
555
- }
556
- }
557
-
558
390
  /**
559
391
  * Override method from `ComboBoxBaseMixin` to handle reverting value.
560
392
  * @protected
@@ -651,40 +483,6 @@ export const ComboBoxMixin = (superClass) =>
651
483
  }
652
484
  }
653
485
 
654
- /** @private */
655
- _itemsChanged(items, oldItems) {
656
- this._ensureItemsOrDataProvider(() => {
657
- this.items = oldItems;
658
- });
659
-
660
- if (items) {
661
- this.filteredItems = items.slice(0);
662
- } else if (oldItems) {
663
- // Only clear filteredItems if the component had items previously but got cleared
664
- this.filteredItems = null;
665
- }
666
- }
667
-
668
- /** @private */
669
- _filteredItemsChanged(filteredItems) {
670
- this._setDropdownItems(filteredItems);
671
- }
672
-
673
- /** @private */
674
- _filterItems(arr, filter) {
675
- if (!arr) {
676
- return arr;
677
- }
678
-
679
- const filteredItems = arr.filter((item) => {
680
- filter = filter ? filter.toString().toLowerCase() : '';
681
- // Check if item contains input value.
682
- return this._getItemLabel(item).toString().toLowerCase().indexOf(filter) > -1;
683
- });
684
-
685
- return filteredItems;
686
- }
687
-
688
486
  /** @private */
689
487
  _selectItemForValue(value) {
690
488
  const valueIndex = this.__getItemIndexByValue(this.filteredItems, value);
@@ -708,6 +506,7 @@ export const ComboBoxMixin = (superClass) =>
708
506
  * Override this method to show custom items.
709
507
  *
710
508
  * @protected
509
+ * @override
711
510
  */
712
511
  _setDropdownItems(newItems) {
713
512
  const oldItems = this._dropdownItems;
@@ -740,37 +539,6 @@ export const ComboBoxMixin = (superClass) =>
740
539
  }
741
540
  }
742
541
 
743
- /**
744
- * Returns the first item that matches the provided value.
745
- *
746
- * @private
747
- */
748
- __getItemIndexByValue(items, value) {
749
- if (!items || !isValidValue(value)) {
750
- return -1;
751
- }
752
-
753
- return findItemIndex(items, (item) => {
754
- return this._getItemValue(item) === value;
755
- });
756
- }
757
-
758
- /**
759
- * Returns the first item that matches the provided label.
760
- * Labels are matched against each other case insensitively.
761
- *
762
- * @private
763
- */
764
- __getItemIndexByLabel(items, label) {
765
- if (!items || !label) {
766
- return -1;
767
- }
768
-
769
- return findItemIndex(items, (item) => {
770
- return this._getItemLabel(item).toString().toLowerCase() === label.toString().toLowerCase();
771
- });
772
- }
773
-
774
542
  /**
775
543
  * Override method from `ComboBoxBaseMixin`.
776
544
  * @protected
@@ -22,21 +22,6 @@ export const ComboBoxOverlayMixin = (superClass) =>
22
22
  this.requiredVerticalSpace = 200;
23
23
  }
24
24
 
25
- /** @protected */
26
- connectedCallback() {
27
- super.connectedCallback();
28
-
29
- const hostDir = this._getHostDir();
30
- if (hostDir) {
31
- this.setAttribute('dir', hostDir);
32
- }
33
- }
34
-
35
- /** @protected */
36
- _getHostDir() {
37
- return this.owner && this.owner.getAttribute('dir');
38
- }
39
-
40
25
  /**
41
26
  * Override method inherited from `Overlay`
42
27
  * to not close on position target click.
@@ -66,16 +51,7 @@ export const ComboBoxOverlayMixin = (superClass) =>
66
51
 
67
52
  /** @protected */
68
53
  _updateOverlayWidth() {
69
- const propPrefix = this.localName;
70
- this.style.setProperty(`--_${propPrefix}-default-width`, `${this.positionTarget.offsetWidth}px`);
71
-
72
- const customWidth = getComputedStyle(this.owner).getPropertyValue(`--${propPrefix}-width`);
73
-
74
- if (customWidth === '') {
75
- this.style.removeProperty(`--${propPrefix}-width`);
76
- } else {
77
- this.style.setProperty(`--${propPrefix}-width`, customWidth);
78
- }
54
+ this.style.setProperty(`--_${this.localName}-default-width`, `${this.positionTarget.offsetWidth}px`);
79
55
  }
80
56
 
81
57
  /** @private */
@@ -22,6 +22,7 @@ import type { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-th
22
22
  import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
23
23
  import type { ComboBoxBaseMixinClass } from './vaadin-combo-box-base-mixin.js';
24
24
  import type { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js';
25
+ import type { ComboBoxItemsMixinClass } from './vaadin-combo-box-items-mixin.js';
25
26
  import type { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js';
26
27
  import type { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js';
27
28
 
@@ -180,9 +181,12 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
180
181
  *
181
182
  * In addition to `<vaadin-text-field>` parts, the following parts are available for theming:
182
183
  *
183
- * Part name | Description
184
- * ----------------|----------------
185
- * `toggle-button` | The toggle button
184
+ * Part name | Description
185
+ * -----------------|------------------
186
+ * `toggle-button` | The toggle button
187
+ * `overlay` | The overlay container
188
+ * `content` | The overlay content
189
+ * `loader` | The loading indicator shown while loading items
186
190
  *
187
191
  * In addition to `<vaadin-text-field>` state attributes, the following state attributes are available for theming:
188
192
  *
@@ -196,12 +200,7 @@ export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap {
196
200
  * In addition to `<vaadin-combo-box>` itself, the following internal
197
201
  * components are themable:
198
202
  *
199
- * - `<vaadin-combo-box-overlay>` - has the same API as [`<vaadin-overlay>`](#/elements/vaadin-overlay).
200
203
  * - `<vaadin-combo-box-item>` - has the same API as [`<vaadin-item>`](#/elements/vaadin-item).
201
- * - [`<vaadin-input-container>`](#/elements/vaadin-input-container) - an internal element wrapping the input.
202
- *
203
- * Note: the `theme` attribute value set on `<vaadin-combo-box>` is
204
- * propagated to the internal components listed above.
205
204
  *
206
205
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
207
206
  *
@@ -230,6 +229,7 @@ declare class ComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement {
230
229
 
231
230
  interface ComboBox<TItem = ComboBoxDefaultItem>
232
231
  extends ComboBoxDataProviderMixinClass<TItem>,
232
+ ComboBoxItemsMixinClass<TItem>,
233
233
  ComboBoxMixinClass<TItem>,
234
234
  ComboBoxBaseMixinClass,
235
235
  ValidateMixinClass,
@@ -112,9 +112,12 @@ import { ComboBoxMixin } from './vaadin-combo-box-mixin.js';
112
112
  *
113
113
  * In addition to `<vaadin-text-field>` parts, the following parts are available for theming:
114
114
  *
115
- * Part name | Description
116
- * ----------------|----------------
117
- * `toggle-button` | The toggle button
115
+ * Part name | Description
116
+ * -----------------|------------------
117
+ * `toggle-button` | The toggle button
118
+ * `overlay` | The overlay container
119
+ * `content` | The overlay content
120
+ * `loader` | The loading indicator shown while loading items
118
121
  *
119
122
  * In addition to `<vaadin-text-field>` state attributes, the following state attributes are available for theming:
120
123
  *
@@ -128,12 +131,7 @@ import { ComboBoxMixin } from './vaadin-combo-box-mixin.js';
128
131
  * In addition to `<vaadin-combo-box>` itself, the following internal
129
132
  * components are themable:
130
133
  *
131
- * - `<vaadin-combo-box-overlay>` - has the same API as [`<vaadin-overlay>`](#/elements/vaadin-overlay).
132
134
  * - `<vaadin-combo-box-item>` - has the same API as [`<vaadin-item>`](#/elements/vaadin-item).
133
- * - [`<vaadin-input-container>`](#/elements/vaadin-input-container) - an internal element wrapping the input.
134
- *
135
- * Note: the `theme` attribute value set on `<vaadin-combo-box>` is
136
- * propagated to the internal components listed above.
137
135
  *
138
136
  * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
139
137
  *
@@ -221,13 +219,17 @@ class ComboBox extends ComboBoxDataProviderMixin(
221
219
 
222
220
  <vaadin-combo-box-overlay
223
221
  id="overlay"
222
+ exportparts="overlay, content, loader"
224
223
  .owner="${this}"
224
+ .dir="${this.dir}"
225
225
  .opened="${this._overlayOpened}"
226
226
  ?loading="${this.loading}"
227
227
  theme="${ifDefined(this._theme)}"
228
228
  .positionTarget="${this._positionTarget}"
229
229
  no-vertical-overlap
230
- ></vaadin-combo-box-overlay>
230
+ >
231
+ <slot name="overlay"></slot>
232
+ </vaadin-combo-box-overlay>
231
233
 
232
234
  <slot name="tooltip"></slot>
233
235
  `;
@@ -257,6 +259,15 @@ class ComboBox extends ComboBoxDataProviderMixin(
257
259
  this._toggleElement = this.$.toggleButton;
258
260
  }
259
261
 
262
+ /** @protected */
263
+ updated(props) {
264
+ super.updated(props);
265
+
266
+ if (props.has('dataProvider') || props.has('value')) {
267
+ this._warnDataProviderValue(this.dataProvider, this.value);
268
+ }
269
+ }
270
+
260
271
  /**
261
272
  * Override the method from `InputControlMixin`
262
273
  * to stop event propagation to prevent `ComboBoxMixin`
@@ -283,6 +294,22 @@ class ComboBox extends ComboBoxDataProviderMixin(
283
294
  super._onHostClick(event);
284
295
  }
285
296
  }
297
+
298
+ /** @private */
299
+ _warnDataProviderValue(dataProvider, value) {
300
+ if (dataProvider && value !== '' && (this.selectedItem === undefined || this.selectedItem === null)) {
301
+ const valueIndex = this.__getItemIndexByValue(this.filteredItems, value);
302
+ if (valueIndex < 0 || !this._getItemLabel(this.filteredItems[valueIndex])) {
303
+ console.warn(
304
+ 'Warning: unable to determine the label for the provided `value`. ' +
305
+ 'Nothing to display in the text field. This usually happens when ' +
306
+ 'setting an initial `value` before any items are returned from ' +
307
+ 'the `dataProvider` callback. Consider setting `selectedItem` ' +
308
+ 'instead of `value`',
309
+ );
310
+ }
311
+ }
312
+ }
286
313
  }
287
314
 
288
315
  defineCustomElement(ComboBox);