@aquera/nile-elements 0.1.75-beta-1.4 → 0.1.75-beta-1.5

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 (67) hide show
  1. package/demo/index.html +0 -25
  2. package/dist/index.js +357 -331
  3. package/dist/nile-button/nile-button.cjs.js +1 -1
  4. package/dist/nile-button/nile-button.cjs.js.map +1 -1
  5. package/dist/nile-button/nile-button.esm.js +1 -1
  6. package/dist/nile-calendar/nile-calendar.test.cjs.js +1 -1
  7. package/dist/nile-calendar/nile-calendar.test.cjs.js.map +1 -1
  8. package/dist/nile-calendar/nile-calendar.test.esm.js +1 -1
  9. package/dist/nile-select/index.cjs.js +1 -1
  10. package/dist/nile-select/index.esm.js +1 -1
  11. package/dist/nile-select/nile-select.cjs.js +1 -5
  12. package/dist/nile-select/nile-select.cjs.js.map +1 -1
  13. package/dist/nile-select/nile-select.css.cjs.js +1 -1
  14. package/dist/nile-select/nile-select.css.cjs.js.map +1 -1
  15. package/dist/nile-select/nile-select.css.esm.js +1 -1
  16. package/dist/nile-select/nile-select.esm.js +2 -7
  17. package/dist/nile-select/nile-select.test.cjs.js +1 -1
  18. package/dist/nile-select/nile-select.test.cjs.js.map +1 -1
  19. package/dist/nile-select/nile-select.test.esm.js +1 -1
  20. package/dist/nile-select/virtual-scroll-helper.cjs.js +1 -1
  21. package/dist/nile-select/virtual-scroll-helper.cjs.js.map +1 -1
  22. package/dist/nile-select/virtual-scroll-helper.esm.js +2 -0
  23. package/dist/nile-virtual-select/index.cjs.js +1 -1
  24. package/dist/nile-virtual-select/index.esm.js +1 -1
  25. package/dist/nile-virtual-select/nile-virtual-select.cjs.js +5 -1
  26. package/dist/nile-virtual-select/nile-virtual-select.cjs.js.map +1 -1
  27. package/dist/nile-virtual-select/nile-virtual-select.css.cjs.js +1 -1
  28. package/dist/nile-virtual-select/nile-virtual-select.css.cjs.js.map +1 -1
  29. package/dist/nile-virtual-select/nile-virtual-select.css.esm.js +20 -1
  30. package/dist/nile-virtual-select/nile-virtual-select.esm.js +21 -12
  31. package/dist/nile-virtual-select/renderer.cjs.js +1 -1
  32. package/dist/nile-virtual-select/renderer.cjs.js.map +1 -1
  33. package/dist/nile-virtual-select/renderer.esm.js +11 -10
  34. package/dist/nile-virtual-select/selection-manager.cjs.js +1 -1
  35. package/dist/nile-virtual-select/selection-manager.cjs.js.map +1 -1
  36. package/dist/nile-virtual-select/selection-manager.esm.js +1 -1
  37. package/dist/src/nile-button/nile-button.d.ts +1 -1
  38. package/dist/src/nile-button/nile-button.js +1 -1
  39. package/dist/src/nile-button/nile-button.js.map +1 -1
  40. package/dist/src/nile-select/nile-select.css.js +1 -1
  41. package/dist/src/nile-select/nile-select.css.js.map +1 -1
  42. package/dist/src/nile-select/nile-select.d.ts +5 -1
  43. package/dist/src/nile-select/nile-select.js +31 -15
  44. package/dist/src/nile-select/nile-select.js.map +1 -1
  45. package/dist/src/nile-select/virtual-scroll-helper.js +2 -0
  46. package/dist/src/nile-select/virtual-scroll-helper.js.map +1 -1
  47. package/dist/src/nile-virtual-select/nile-virtual-select.css.js +20 -1
  48. package/dist/src/nile-virtual-select/nile-virtual-select.css.js.map +1 -1
  49. package/dist/src/nile-virtual-select/nile-virtual-select.d.ts +22 -1
  50. package/dist/src/nile-virtual-select/nile-virtual-select.js +148 -22
  51. package/dist/src/nile-virtual-select/nile-virtual-select.js.map +1 -1
  52. package/dist/src/nile-virtual-select/renderer.d.ts +2 -2
  53. package/dist/src/nile-virtual-select/renderer.js +9 -8
  54. package/dist/src/nile-virtual-select/renderer.js.map +1 -1
  55. package/dist/src/nile-virtual-select/selection-manager.js +1 -1
  56. package/dist/src/nile-virtual-select/selection-manager.js.map +1 -1
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +15 -15
  59. package/src/nile-button/nile-button.ts +1 -1
  60. package/src/nile-select/nile-select.css.ts +1 -1
  61. package/src/nile-select/nile-select.ts +31 -17
  62. package/src/nile-select/virtual-scroll-helper.ts +2 -0
  63. package/src/nile-virtual-select/nile-virtual-select.css.ts +20 -1
  64. package/src/nile-virtual-select/nile-virtual-select.ts +158 -26
  65. package/src/nile-virtual-select/renderer.ts +10 -8
  66. package/src/nile-virtual-select/selection-manager.ts +1 -1
  67. package/vscode-html-custom-data.json +61 -34
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Webcomponent nile-elements following open-wc recommendations",
4
4
  "license": "MIT",
5
5
  "author": "nile-elements",
6
- "version": "0.1.75-beta-1.4",
6
+ "version": "0.1.75-beta-1.5",
7
7
  "main": "dist/src/index.js",
8
8
  "type": "module",
9
9
  "module": "dist/src/index.js",
@@ -108,23 +108,23 @@
108
108
  },
109
109
  "dependencies": {
110
110
  "@aquera/nile": "latest",
111
- "@codemirror/lang-html": "6.4.9",
112
- "@codemirror/lang-javascript": "6.2.1",
113
- "@codemirror/lang-json": "^6.0.1",
114
- "@codemirror/lang-sql": "6.7.0",
115
- "@floating-ui/dom": "^1.2.1",
116
- "@lit-labs/observers": "^2.0.6",
117
- "@lit-labs/virtualizer": "^2.0.15",
118
111
  "@open-wc/form-control": "^0.5.0",
119
112
  "@open-wc/form-helpers": "^0.2.1",
120
- "@rollup/plugin-commonjs": "^25.0.3",
121
113
  "@rollup/plugin-node-resolve": "^15.0.1",
122
- "chalk": "5.3.0",
123
- "codemirror": "6.0.1",
124
- "composed-offset-position": "^0.0.4",
114
+ "@rollup/plugin-commonjs": "^25.0.3",
125
115
  "element-internals-polyfill": "^1.1.20",
116
+ "lit": "^3.0.0",
117
+ "@floating-ui/dom": "^1.2.1",
118
+ "composed-offset-position": "^0.0.4",
119
+ "@codemirror/lang-javascript": "6.2.1",
120
+ "@codemirror/lang-sql": "6.7.0",
121
+ "@codemirror/lang-json": "^6.0.1",
122
+ "@codemirror/lang-html": "6.4.9",
123
+ "@lit-labs/virtualizer": "^2.0.15",
124
+ "codemirror": "6.0.1",
125
+ "chalk": "5.3.0",
126
126
  "figlet": "1.7.0",
127
- "lit": "^3.0.0"
127
+ "@lit-labs/observers": "^2.0.6"
128
128
  },
129
129
  "devDependencies": {
130
130
  "@custom-elements-manifest/analyzer": "^0.4.17",
@@ -139,7 +139,6 @@
139
139
  "@web/dev-server": "^0.1.34",
140
140
  "@web/dev-server-storybook": "^0.5.4",
141
141
  "@web/test-runner": "^0.14.0",
142
- "@web/test-runner-puppeteer": "0.16.0",
143
142
  "concurrently": "^5.3.0",
144
143
  "eslint": "^7.32.0",
145
144
  "eslint-config-prettier": "^8.3.0",
@@ -156,7 +155,8 @@
156
155
  "rollup-plugin-terser": "^7.0.2",
157
156
  "tslib": "^2.3.1",
158
157
  "typescript": "^5.6.2",
159
- "x": "^0.1.2"
158
+ "x": "^0.1.2",
159
+ "@web/test-runner-puppeteer": "0.16.0"
160
160
  },
161
161
  "customElements": "custom-elements.json",
162
162
  "eslintConfig": {
@@ -78,7 +78,7 @@ export class NileButton extends NileElement implements NileFormControl {
78
78
  | 'secondary-blue' = 'primary';
79
79
 
80
80
  /** The button's size. */
81
- @property({ reflect: true }) size: 'medium' = 'medium';
81
+ @state() size = 'medium';
82
82
 
83
83
  /** Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior. */
84
84
  @property({ type: Boolean, reflect: true }) caret = false;
@@ -398,7 +398,7 @@ export const styles = css`
398
398
  position: sticky;
399
399
  bottom: 0px;
400
400
  background: var(--nile-colors-neutral-100);
401
- border: 1px solid var(--nile-colors-neutral-400);
401
+ border-top: 1px solid var(--nile-colors-neutral-400);
402
402
  display: flex;
403
403
  height: 15px;
404
404
  /* Auto layout */
@@ -42,7 +42,6 @@ import NileOptionGroup from '../nile-option-group/nile-option-group';
42
42
  import { GroupAttributes } from './nile-select.interface';
43
43
  import { ResizeController } from '@lit-labs/observers/resize-controller.js';
44
44
 
45
-
46
45
  type NileRemoveEvent = CustomEvent<Record<PropertyKey, never>>;
47
46
 
48
47
  /**
@@ -122,16 +121,7 @@ export class NileSelect extends NileElement implements NileFormControl{
122
121
  private scrollTimeout: number | undefined;
123
122
  private scrolling = false;
124
123
  private options: NileOption[] = [];
125
- private resizeController = new ResizeController(this, {
126
- callback: (entries: ResizeObserverEntry[]) => {
127
- for (const entry of entries) {
128
- if (entry.target.classList.contains('select__tags')) {
129
- this.calculateTotalWidthOfTags();
130
- }
131
- }
132
- }
133
- });
134
-
124
+ private resizeController?: ResizeController;
135
125
 
136
126
  @query('.select') popup: NilePopup;
137
127
  @query('.select__combobox') combobox: HTMLSlotElement;
@@ -202,6 +192,7 @@ export class NileSelect extends NileElement implements NileFormControl{
202
192
 
203
193
 
204
194
  @property({ attribute: 'help-text', reflect: true }) helpText = '';
195
+ @property({ type: Boolean, attribute: true, reflect: true }) autoResize = false;
205
196
 
206
197
  @property({ attribute: 'error-message', reflect: true }) errorMessage = '';
207
198
 
@@ -285,6 +276,9 @@ export class NileSelect extends NileElement implements NileFormControl{
285
276
  /** To auto focus the search input when the select is opened */
286
277
  @property({ type: Boolean, reflect: true, attribute: true }) autoFocusSearch = false;
287
278
 
279
+ /** loading indicator for virtual select */
280
+ @property({ type: Boolean, reflect: true, attribute: true }) loading = false;
281
+
288
282
  /** Gets the validity state object */
289
283
  get validity() {
290
284
  return this.valueInput?.validity;
@@ -326,24 +320,43 @@ export class NileSelect extends NileElement implements NileFormControl{
326
320
  this.emit('nile-destroy');
327
321
  }
328
322
 
323
+ private setupResizeObserver() {
324
+ if (this.autoResize) {
325
+ const tagsContainer = this.shadowRoot?.querySelector('.select__tags');
326
+ if (tagsContainer) {
327
+ this.resizeController = new ResizeController(this, {
328
+ callback: () => this.calculateTotalWidthOfTags()
329
+ });
330
+ this.resizeController.observe(tagsContainer);
331
+ }
332
+ } else {
333
+ this.resizeController?.unobserve?.(
334
+ this.shadowRoot?.querySelector('.select__tags') as Element
335
+ );
336
+ this.resizeController = undefined;
337
+ }
338
+ }
339
+
329
340
  protected updated(_changedProperties: PropertyValues): void {
330
341
  if(_changedProperties.has('multiple')) {
331
342
  this.setCheckBoxInOption(this.multiple as boolean);
332
343
  }
344
+ if (_changedProperties.has('autoResize')) {
345
+ this.setupResizeObserver();
346
+ }
333
347
  }
334
348
 
335
349
  protected firstUpdated(_changedProperties: PropertyValues): void {
336
350
  if(this.enableGroupHeader) {
337
351
  this.handleGroupSearchChange();
338
352
  }
339
- const tagsContainer = this.shadowRoot?.querySelector('.select__tags');
340
- if (tagsContainer) {
341
- this.resizeController.observe(tagsContainer);
342
- }
353
+
354
+ this.setupResizeObserver();
343
355
  if(_changedProperties.has('multiple')) {
344
356
  this.setCheckBoxInOption(this.multiple as boolean);
345
357
  }
346
358
  }
359
+
347
360
  setCheckBoxInOption(checked: boolean): void {
348
361
  if(!this.options.length) {
349
362
  this.options = this.getAllOptions();
@@ -958,8 +971,9 @@ export class NileSelect extends NileElement implements NileFormControl{
958
971
  const allOptions = this.getAllOptions();
959
972
  const value = Array.isArray(this.value) ? this.value : [this.value];
960
973
 
961
- // Select only the options that match the new value
962
- this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
974
+ if(!this.enableVirtualScroll) {
975
+ this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
976
+ }
963
977
  }
964
978
 
965
979
  @watch('open', { waitUntilFirstUpdate: true })
@@ -19,6 +19,7 @@ export class VirtualScrollHelper {
19
19
  <nile-virtual-select
20
20
  .name=${component.name}
21
21
  .value=${component.value}
22
+ .autoResize=${component.autoResize}
22
23
  .placeholder=${component.placeholder}
23
24
  .size=${component.size}
24
25
  .searchEnabled=${component.searchEnabled}
@@ -48,6 +49,7 @@ export class VirtualScrollHelper {
48
49
  .maxOptionsVisible=${component.maxOptionsVisible}
49
50
  .data=${component.data}
50
51
  .open=${component.open}
52
+ .loading=${component.loading}
51
53
  exportparts="
52
54
  select-options,
53
55
  select-no-results,
@@ -32,6 +32,11 @@ export const styles = css`
32
32
  flex-direction: column;
33
33
  }
34
34
 
35
+ .virtualized.no-scroll {
36
+ overflow-y: hidden !important;
37
+ max-height: none !important;
38
+ }
39
+
35
40
  .virtualized:not(:has(lit-virtualizer)) nile-option {
36
41
  flex-shrink: 0;
37
42
  }
@@ -421,7 +426,7 @@ export const styles = css`
421
426
  position: sticky;
422
427
  bottom: 0px;
423
428
  background: var(--nile-colors-neutral-100);
424
- border: 1px solid var(--nile-colors-neutral-400);
429
+ border-top: 1px solid var(--nile-colors-neutral-400);
425
430
  display: flex;
426
431
  height: 15px;
427
432
  /* Auto layout */
@@ -486,6 +491,20 @@ export const styles = css`
486
491
  .virtualized nile-option[selected]::part(base) {
487
492
  color: var(--nile-colors-primary-600);
488
493
  }
494
+
495
+ .virtual-select-loader {
496
+ position: absolute;
497
+ display: flex;
498
+ justify-content: center;
499
+ align-items: center;
500
+ width: 100%;
501
+ height: 75%;
502
+ }
503
+
504
+ .select__footer.loading, .select__options.loading {
505
+ opacity: 0.5;
506
+ pointer-events: none;
507
+ }
489
508
  `;
490
509
 
491
510
  export default [styles];
@@ -16,6 +16,7 @@ import '../nile-icon';
16
16
  import '../nile-popup/nile-popup';
17
17
  import '../nile-tag/nile-tag';
18
18
  import '../nile-checkbox/nile-checkbox';
19
+ import '../nile-loader/nile-loader';
19
20
  import { animateTo, stopAnimations } from '../internal/animate';
20
21
  import { classMap } from 'lit/directives/class-map.js';
21
22
  import { query, state } from 'lit/decorators.js';
@@ -29,7 +30,7 @@ import { HasSlotController } from '../internal/slot';
29
30
  import { waitForEvent } from '../internal/event';
30
31
  import { watch } from '../internal/watch';
31
32
  import NileElement from '../internal/nile-element';
32
- import type { CSSResultGroup, TemplateResult } from 'lit';
33
+ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
33
34
  import type { NileFormControl } from '../internal/nile-element';
34
35
  import type NileOption from '../nile-option/nile-option';
35
36
  import type NilePopup from '../nile-popup/nile-popup';
@@ -39,6 +40,7 @@ import type { VirtualOption, NileRemoveEvent, RenderItemConfig } from './types.j
39
40
  import { VirtualSelectSelectionManager } from './selection-manager.js';
40
41
  import { VirtualSelectSearchManager } from './search-manager.js';
41
42
  import { VirtualSelectRenderer } from './renderer.js';
43
+ import { ResizeController } from '@lit-labs/observers/resize-controller.js';
42
44
 
43
45
  /**
44
46
  * Nile Virtual Select component.
@@ -73,6 +75,10 @@ import { VirtualSelectRenderer } from './renderer.js';
73
75
  * @event nile-hide - Emitted when the select's menu closes.
74
76
  * @event nile-after-hide - Emitted after the select's menu closes and all animations are complete.
75
77
  * @event nile-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
78
+ * @event nile-search - Emitted when the user types in the search input. The event payload includes the search query for backend search functionality.
79
+ * @event nile-scroll - Emitted when the user scrolls within the virtualized container. The event payload includes scroll position information.
80
+ * @event nile-scroll-start - Emitted when the user starts scrolling within the virtualized container.
81
+ * @event nile-scroll-end - Emitted when the user stops scrolling and reaches the bottom of the virtualized container (debounced).
76
82
  *
77
83
  * @csspart form-control - The form control that wraps the label, input, and help text.
78
84
  * @csspart form-control-label - The label's wrapper.
@@ -111,6 +117,9 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
111
117
  @state() displayLabel = '';
112
118
  @state() selectedOptions: VirtualOption[] = [];
113
119
  @state() oldValue: string | string[] = '';
120
+
121
+ private scrollTimeout: number | undefined;
122
+ private scrolling = false;
114
123
 
115
124
  /** The name of the select, submitted as a name/value pair with form data. */
116
125
  @property() name = '';
@@ -141,6 +150,9 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
141
150
  /** Placeholder text to show as a hint when the select is empty. */
142
151
  @property() placeholder = 'Select...';
143
152
 
153
+ /** Enable automatic resizing of tags area */
154
+ @property({ type: Boolean }) autoResize = false;
155
+
144
156
  /** Current search value */
145
157
  @state() searchValue: string = '';
146
158
 
@@ -156,6 +168,9 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
156
168
  /** Show loading state */
157
169
  @property({ type: Boolean, reflect: true }) optionsLoading = false;
158
170
 
171
+ /** Show loading state using nile-loader */
172
+ @property({ type: Boolean, reflect: true, attribute: true }) loading = false;
173
+
159
174
  /** Allows more than one option to be selected. */
160
175
  @property({ type: Boolean, reflect: true }) multiple = false;
161
176
 
@@ -240,6 +255,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
240
255
  maxOptionsVisible = 3;
241
256
 
242
257
  @state() oldMaxOptionsVisible: number = 1;
258
+ @state() showListbox: boolean = false;
243
259
 
244
260
  /** Gets the validity state object */
245
261
  get validity() {
@@ -265,6 +281,25 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
265
281
 
266
282
  disconnectedCallback() {
267
283
  this.removeOpenListeners();
284
+ // Clear any pending scroll timeout to prevent memory leaks
285
+ if (this.scrollTimeout) {
286
+ clearTimeout(this.scrollTimeout);
287
+ this.scrollTimeout = undefined;
288
+ }
289
+ }
290
+
291
+ protected updated(changedProperties: PropertyValues): void {
292
+ if(changedProperties.has('value')) {
293
+ this.selectionChanged();
294
+ }
295
+ if (changedProperties.has('autoResize')) {
296
+ const tagsDiv = this.shadowRoot?.querySelector('.select__tags') as HTMLElement;
297
+ if (this.autoResize && tagsDiv) {
298
+ this.resizeController.observe(tagsDiv);
299
+ } else if (tagsDiv) {
300
+ this.resizeController.unobserve(tagsDiv);
301
+ }
302
+ }
268
303
  }
269
304
 
270
305
  private initializeComponent(): void {
@@ -307,18 +342,21 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
307
342
  this.handleDocumentFocusIn = this.handleDocumentFocusIn.bind(this);
308
343
  this.handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
309
344
  this.handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
345
+ this.handleWindowError = this.handleWindowError.bind(this);
310
346
  }
311
347
 
312
348
  private addOpenListeners(): void {
313
349
  document.addEventListener('focusin', this.handleDocumentFocusIn);
314
350
  document.addEventListener('keydown', this.handleDocumentKeyDown);
315
351
  document.addEventListener('mousedown', this.handleDocumentMouseDown);
352
+ window.addEventListener('error', this.handleWindowError);
316
353
  }
317
354
 
318
355
  private removeOpenListeners(): void {
319
356
  document.removeEventListener('focusin', this.handleDocumentFocusIn);
320
357
  document.removeEventListener('keydown', this.handleDocumentKeyDown);
321
358
  document.removeEventListener('mousedown', this.handleDocumentMouseDown);
359
+ window.removeEventListener('error', this.handleWindowError);
322
360
  }
323
361
 
324
362
  private handleFocus(): void {
@@ -405,6 +443,19 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
405
443
  }
406
444
  };
407
445
 
446
+ /**
447
+ * This is a workaround for an error in the Lit Labs virtualizer.
448
+ * Since there are no specific guidelines available to fix the issue,
449
+ * we are catching only the error message related to the virtualizer.
450
+ */
451
+ private handleWindowError = (event: ErrorEvent): void => {
452
+ const errorMessage = event.error?.message || event.message || '';
453
+ if (errorMessage.includes('Cannot read properties of null (reading \'insertBefore\')')) {
454
+ event.preventDefault();
455
+ return;
456
+ }
457
+ };
458
+
408
459
  private handleFooterClick(event: MouseEvent): void {
409
460
  event.stopPropagation();
410
461
  event.preventDefault();
@@ -420,9 +471,10 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
420
471
 
421
472
  if (this.showSelected) {
422
473
  const selectedValues = Array.isArray(this.value) ? this.value : [this.value];
423
- this.data = this.originalOptionItems.filter((item: any) =>
424
- selectedValues.includes(item.value)
425
- );
474
+ this.data = this.originalOptionItems.filter((item: any) => {
475
+ const itemValue = this.getItemValue(item);
476
+ return selectedValues.some(val => String(val) === String(itemValue));
477
+ });
426
478
  } else {
427
479
  this.data = [...this.originalOptionItems];
428
480
  }
@@ -456,6 +508,16 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
456
508
  this.open = !this.open;
457
509
  }
458
510
 
511
+ private resizeController = new ResizeController(this, {
512
+ callback: (entries: ResizeObserverEntry[]) => {
513
+ for (const entry of entries) {
514
+ if (entry.target.classList.contains('select__tags')) {
515
+ this.calculateTotalWidthOfTags();
516
+ }
517
+ }
518
+ }
519
+ });
520
+
459
521
  private shouldIgnoreComboboxClick(event: MouseEvent): boolean {
460
522
  const path = event.composedPath();
461
523
  const isIconButton = path.some(
@@ -617,6 +679,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
617
679
 
618
680
  this.calculateTotalWidthOfTags();
619
681
  }
682
+
620
683
 
621
684
  handleSearchFocus(): void {
622
685
  document.removeEventListener('keydown', this.handleDocumentKeyDown);
@@ -628,9 +691,65 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
628
691
 
629
692
  handleSearchChange(e: any): void {
630
693
  this.searchValue = e.detail.value;
694
+
695
+ this.emit('nile-search', {
696
+ query: this.searchValue,
697
+ name: this.name
698
+ });
699
+
631
700
  if (!this.disableLocalSearch) {
632
701
  this.filterVirtualOptions(this.searchValue);
633
702
  this.resetScrollPosition();
703
+ this.updateComplete.then(() => {
704
+ const virtualized = this.shadowRoot?.querySelector('.virtualized') as HTMLElement;
705
+ if (virtualized) {
706
+ if (this.data.length <= 5) {
707
+ virtualized.classList.add('no-scroll');
708
+ } else {
709
+ virtualized.classList.remove('no-scroll');
710
+ }
711
+ }
712
+ });
713
+ }
714
+ }
715
+
716
+ handleScroll(e: Event): void {
717
+ if(this.showSelected) {
718
+ return;
719
+ }
720
+
721
+ const target = e.target as HTMLElement;
722
+
723
+ this.emit('nile-scroll', {
724
+ scrollTop: target.scrollTop,
725
+ scrollLeft: target.scrollLeft,
726
+ name: this.name
727
+ });
728
+
729
+ if (!this.scrolling) {
730
+ this.scrolling = true;
731
+ this.emit('nile-scroll-start', {
732
+ scrollTop: target.scrollTop,
733
+ scrollLeft: target.scrollLeft,
734
+ name: this.name
735
+ });
736
+ }
737
+
738
+ clearTimeout(this.scrollTimeout);
739
+ this.scrollTimeout = window.setTimeout(() => {
740
+ if (this.scrolling) {
741
+ this.scrolling = false;
742
+ }
743
+ }, 300);
744
+
745
+ const isAtBottom = Math.ceil(target.scrollTop) >= Math.floor(target.scrollHeight - target.offsetHeight);
746
+ if (isAtBottom && !this.searchValue) {
747
+ this.emit('nile-scroll-end', {
748
+ scrollTop: target.scrollTop,
749
+ scrollLeft: target.scrollLeft,
750
+ name: this.name,
751
+ isAtBottom: true
752
+ });
634
753
  }
635
754
  }
636
755
 
@@ -670,18 +789,18 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
670
789
 
671
790
  @watch('data', { waitUntilFirstUpdate: true })
672
791
  handleDataChange(): void {
792
+ if (this.data.length > 0 && this.open && !this.showSelected && !this.searchValue) {
793
+ this.originalOptionItems = [...this.data];
794
+ }
795
+
673
796
  this.selectionChanged();
674
797
  // Show no results message when data is empty and not loading
675
- if (!this.optionsLoading && this.data.length === 0) {
798
+ if (!this.optionsLoading && !this.loading && this.data.length === 0) {
676
799
  this.showNoResults = true;
677
800
  } else if (this.data.length > 0) {
678
801
  this.showNoResults = false;
679
802
  }
680
803
  this.requestUpdate();
681
-
682
- if (this.open) {
683
- this.resetScrollPosition();
684
- }
685
804
  }
686
805
 
687
806
  @watch('renderItemConfig', { waitUntilFirstUpdate: true })
@@ -705,8 +824,10 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
705
824
  async handleOpenChange(): Promise<void> {
706
825
  if (this.open && !this.disabled) {
707
826
  await this.handleOpen();
827
+ this.showListbox = true;
708
828
  } else {
709
829
  await this.handleClose();
830
+ this.showListbox = false;
710
831
  }
711
832
  }
712
833
 
@@ -745,7 +866,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
745
866
  }
746
867
 
747
868
  private initializeOriginalItems(): void {
748
- if (this.originalOptionItems.length === 0 && this.data.length > 0) {
869
+ if (this.data.length > 0) {
749
870
  this.originalOptionItems = [...this.data];
750
871
  }
751
872
  }
@@ -893,7 +1014,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
893
1014
  >
894
1015
  ${this.renderCustomSelect(hasCustomSelect)}
895
1016
  ${this.renderCombobox(hasCustomSelect, hasClearIcon)}
896
- ${this.renderListbox()}
1017
+ ${this.showListbox ? this.renderListbox() : html``}
897
1018
  </nile-popup>
898
1019
  `;
899
1020
  }
@@ -1125,16 +1246,26 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
1125
1246
  }
1126
1247
 
1127
1248
  private renderLoader(): TemplateResult {
1128
- return this.optionsLoading
1129
- ? html`
1130
- <span part="loader" class="select__loader">
1131
- <nile-icon
1132
- class="select__loader--icon"
1133
- name="button-loading-blue"
1134
- ></nile-icon>
1135
- </span>
1136
- `
1137
- : html``;
1249
+ if (this.loading) {
1250
+ return html`
1251
+ <span part="loader" class="virtual-select-loader">
1252
+ <nile-loader size="sm"></nile-loader>
1253
+ </span>
1254
+ `;
1255
+ }
1256
+
1257
+ if (this.optionsLoading) {
1258
+ return html`
1259
+ <span part="loader" class="select__loader">
1260
+ <nile-icon
1261
+ class="select__loader--icon"
1262
+ name="button-loading-blue"
1263
+ ></nile-icon>
1264
+ </span>
1265
+ `;
1266
+ }
1267
+
1268
+ return html``;
1138
1269
  }
1139
1270
 
1140
1271
  private renderFooter(): TemplateResult {
@@ -1142,7 +1273,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
1142
1273
  ? html`
1143
1274
  <div
1144
1275
  part="footer"
1145
- class="select__footer"
1276
+ class="select__footer ${this.loading ? 'loading' : ''}"
1146
1277
  @click="${this.handleFooterClick}"
1147
1278
  >
1148
1279
  <span
@@ -1197,7 +1328,8 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
1197
1328
  this.renderItemConfig?.getValue,
1198
1329
  this.showNoResults,
1199
1330
  this.noResultsMessage,
1200
- this.optionsLoading
1331
+ this.optionsLoading || this.loading,
1332
+ this.handleScroll.bind(this)
1201
1333
  );
1202
1334
  }
1203
1335
 
@@ -1272,16 +1404,16 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
1272
1404
 
1273
1405
  private resetScrollPosition(): void {
1274
1406
  this.updateComplete.then(() => {
1275
- if (this.virtualizedContainer) {
1407
+ if (this.virtualizedContainer && this.virtualizedContainer.isConnected) {
1276
1408
  this.virtualizedContainer.scrollTop = 0;
1277
1409
 
1278
1410
  const listbox = this.shadowRoot?.querySelector('.select__listbox') as HTMLElement;
1279
- if (listbox) {
1411
+ if (listbox && listbox.isConnected) {
1280
1412
  listbox.scrollTop = 0;
1281
1413
  }
1282
1414
 
1283
1415
  const virtualizer = this.virtualizedContainer.querySelector('lit-virtualizer') as HTMLElement;
1284
- if (virtualizer) {
1416
+ if (virtualizer && virtualizer.isConnected) {
1285
1417
  virtualizer.scrollTop = 0;
1286
1418
  }
1287
1419
  }
@@ -21,13 +21,14 @@ export class VirtualSelectRenderer {
21
21
  getItemValue?: (item: any) => string,
22
22
  showNoResults?: boolean,
23
23
  noResultsMessage?: string,
24
- optionsLoading?: boolean
24
+ loading?: boolean,
25
+ onScroll?: (e: Event) => void
25
26
  ): TemplateResult {
26
27
  return html`
27
28
  <div part="select-options" class="select__options ${
28
29
  searchEnabled ? 'select__options__search-enabled' : ``
29
- }">
30
- ${showNoResults && !optionsLoading
30
+ } ${loading ? 'loading' : ''}">
31
+ ${showNoResults && !loading
31
32
  ? html`
32
33
  <div part="select-no-results" class="select__no-results">
33
34
  ${noResultsMessage || 'No results found'}
@@ -37,6 +38,7 @@ export class VirtualSelectRenderer {
37
38
  <div
38
39
  class="virtualized"
39
40
  part="virtualized"
41
+ @scroll=${onScroll}
40
42
  >
41
43
  ${VirtualSelectRenderer.shouldUseVirtualizer(data)
42
44
  ? html`
@@ -70,12 +72,12 @@ export class VirtualSelectRenderer {
70
72
 
71
73
  const optionValue = valueFn(item);
72
74
  const displayText = displayTextFn(item);
73
- const isDisabled = item.disabled || false;
74
- const className = item.className;
75
+ const isDisabled = item?.disabled || false;
76
+ const className = item?.className;
75
77
 
76
78
  let isSelected = false;
77
79
  if (multiple) {
78
- isSelected = Array.isArray(value) && value.includes(optionValue);
80
+ isSelected = Array.isArray(value) && value.some(v => String(v) === String(optionValue));
79
81
  } else {
80
82
  isSelected = (Array.isArray(value) ? value[0] : value) === optionValue;
81
83
  }
@@ -95,9 +97,9 @@ export class VirtualSelectRenderer {
95
97
 
96
98
  /**
97
99
  * Determines whether to use virtualizer based on dataset size
98
- * For small datasets (less than 50 items), use regular rendering for better sizing
100
+ * For small datasets (less than 5 items), use regular rendering for better sizing
99
101
  */
100
102
  private static shouldUseVirtualizer(data: any[]): boolean {
101
- return data.length >= 50;
103
+ return data.length >= 5;
102
104
  }
103
105
  }
@@ -28,7 +28,7 @@ export class VirtualSelectSelectionManager {
28
28
  const item = data.find((item: any) => {
29
29
  const itemValue = valueFn(item);
30
30
  const itemDisplayText = displayTextFn(item);
31
- return itemValue === valueItem || itemDisplayText === valueItem;
31
+ return String(itemValue) === String(valueItem) || String(itemDisplayText) === String(valueItem);
32
32
  });
33
33
 
34
34
  const displayText = item ? displayTextFn(item) : valueItem;