@liwe3/webcomponents 1.0.2 → 1.1.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 (52) hide show
  1. package/dist/AITextEditor.d.ts +173 -0
  2. package/dist/AITextEditor.d.ts.map +1 -0
  3. package/dist/ChunkUploader.d.ts +103 -0
  4. package/dist/ChunkUploader.d.ts.map +1 -0
  5. package/dist/ChunkUploader.js +614 -0
  6. package/dist/ChunkUploader.js.map +1 -0
  7. package/dist/ContainerBox.d.ts +112 -0
  8. package/dist/ContainerBox.d.ts.map +1 -0
  9. package/dist/ContainerBox.js +359 -0
  10. package/dist/ContainerBox.js.map +1 -0
  11. package/dist/DateSelector.d.ts +103 -0
  12. package/dist/DateSelector.d.ts.map +1 -0
  13. package/dist/DateSelector.js +372 -0
  14. package/dist/DateSelector.js.map +1 -0
  15. package/dist/Drawer.d.ts +63 -0
  16. package/dist/Drawer.d.ts.map +1 -0
  17. package/dist/Drawer.js +340 -0
  18. package/dist/Drawer.js.map +1 -0
  19. package/dist/ImageView.d.ts +42 -0
  20. package/dist/ImageView.d.ts.map +1 -0
  21. package/dist/ImageView.js +209 -0
  22. package/dist/ImageView.js.map +1 -0
  23. package/dist/PopoverMenu.d.ts +103 -0
  24. package/dist/PopoverMenu.d.ts.map +1 -0
  25. package/dist/PopoverMenu.js +312 -0
  26. package/dist/PopoverMenu.js.map +1 -0
  27. package/dist/SmartSelect.d.ts +99 -0
  28. package/dist/SmartSelect.d.ts.map +1 -0
  29. package/dist/SmartSelect.js.map +1 -1
  30. package/dist/Toast.d.ts +127 -0
  31. package/dist/Toast.d.ts.map +1 -0
  32. package/dist/Toast.js +507 -0
  33. package/dist/Toast.js.map +1 -0
  34. package/dist/TreeView.d.ts +84 -0
  35. package/dist/TreeView.d.ts.map +1 -0
  36. package/dist/TreeView.js +478 -0
  37. package/dist/TreeView.js.map +1 -0
  38. package/dist/index.d.ts +16 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +30 -6
  41. package/dist/index.js.map +1 -1
  42. package/package.json +43 -3
  43. package/src/ChunkUploader.ts +921 -0
  44. package/src/ContainerBox.ts +570 -0
  45. package/src/DateSelector.ts +550 -0
  46. package/src/Drawer.ts +435 -0
  47. package/src/ImageView.ts +265 -0
  48. package/src/PopoverMenu.ts +595 -0
  49. package/src/SmartSelect.ts +231 -231
  50. package/src/Toast.ts +834 -0
  51. package/src/TreeView.ts +673 -0
  52. package/src/index.ts +70 -3
@@ -3,10 +3,10 @@
3
3
  * A customizable select dropdown with search, multi-select, and keyboard navigation
4
4
  */
5
5
 
6
- export interface SelectOption {
6
+ export type SelectOption = {
7
7
  value: string;
8
8
  label: string;
9
- }
9
+ };
10
10
 
11
11
  export class SmartSelectElement extends HTMLElement {
12
12
  declare shadowRoot: ShadowRoot;
@@ -18,119 +18,119 @@ export class SmartSelectElement extends HTMLElement {
18
18
  private keyboardNavigating: boolean = false;
19
19
  private keyboardTimer?: number;
20
20
 
21
- constructor() {
21
+ constructor () {
22
22
  super();
23
- this.attachShadow({ mode: 'open' });
23
+ this.attachShadow( { mode: 'open' } );
24
24
 
25
25
  // Make component focusable
26
- if (!this.hasAttribute('tabindex')) {
27
- this.setAttribute('tabindex', '0');
26
+ if ( !this.hasAttribute( 'tabindex' ) ) {
27
+ this.setAttribute( 'tabindex', '0' );
28
28
  }
29
29
 
30
30
  this.render();
31
31
  this.bindEvents();
32
32
  }
33
33
 
34
- static get observedAttributes(): string[] {
35
- return ['multiple', 'searchable', 'placeholder', 'disabled', 'value', 'options'];
34
+ static get observedAttributes (): string[] {
35
+ return [ 'multiple', 'searchable', 'placeholder', 'disabled', 'value', 'options' ];
36
36
  }
37
37
 
38
- attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
39
- if (oldValue !== newValue) {
40
- if (name === 'options') {
41
- this.filteredOptions = [...this.options];
38
+ attributeChangedCallback ( name: string, oldValue: string | null, newValue: string | null ): void {
39
+ if ( oldValue !== newValue ) {
40
+ if ( name === 'options' ) {
41
+ this.filteredOptions = [ ...this.options ];
42
42
  }
43
43
  this.render();
44
44
  }
45
45
  }
46
46
 
47
- get multiple(): boolean {
48
- return this.hasAttribute('multiple');
47
+ get multiple (): boolean {
48
+ return this.hasAttribute( 'multiple' );
49
49
  }
50
50
 
51
- set multiple(value: boolean) {
52
- if (value) {
53
- this.setAttribute('multiple', '');
51
+ set multiple ( value: boolean ) {
52
+ if ( value ) {
53
+ this.setAttribute( 'multiple', '' );
54
54
  } else {
55
- this.removeAttribute('multiple');
55
+ this.removeAttribute( 'multiple' );
56
56
  }
57
57
  }
58
58
 
59
- get searchable(): boolean {
60
- return this.hasAttribute('searchable');
59
+ get searchable (): boolean {
60
+ return this.hasAttribute( 'searchable' );
61
61
  }
62
62
 
63
- set searchable(value: boolean) {
64
- if (value) {
65
- this.setAttribute('searchable', '');
63
+ set searchable ( value: boolean ) {
64
+ if ( value ) {
65
+ this.setAttribute( 'searchable', '' );
66
66
  } else {
67
- this.removeAttribute('searchable');
67
+ this.removeAttribute( 'searchable' );
68
68
  }
69
69
  }
70
70
 
71
- get placeholder(): string {
72
- return this.getAttribute('placeholder') || 'Select an option';
71
+ get placeholder (): string {
72
+ return this.getAttribute( 'placeholder' ) || 'Select an option';
73
73
  }
74
74
 
75
- set placeholder(value: string) {
76
- this.setAttribute('placeholder', value);
75
+ set placeholder ( value: string ) {
76
+ this.setAttribute( 'placeholder', value );
77
77
  }
78
78
 
79
- get disabled(): boolean {
80
- return this.hasAttribute('disabled');
79
+ get disabled (): boolean {
80
+ return this.hasAttribute( 'disabled' );
81
81
  }
82
82
 
83
- set disabled(value: boolean) {
84
- if (value) {
85
- this.setAttribute('disabled', '');
83
+ set disabled ( value: boolean ) {
84
+ if ( value ) {
85
+ this.setAttribute( 'disabled', '' );
86
86
  } else {
87
- this.removeAttribute('disabled');
87
+ this.removeAttribute( 'disabled' );
88
88
  }
89
89
  }
90
90
 
91
- get value(): string | string[] {
92
- if (this.multiple) {
93
- return this.selectedOptions.map(opt => opt.value);
91
+ get value (): string | string[] {
92
+ if ( this.multiple ) {
93
+ return this.selectedOptions.map( opt => opt.value );
94
94
  }
95
- return this.selectedOptions.length > 0 ? this.selectedOptions[0].value : '';
95
+ return this.selectedOptions.length > 0 ? this.selectedOptions[ 0 ].value : '';
96
96
  }
97
97
 
98
- set value(val: string | string[]) {
99
- if (this.multiple && Array.isArray(val)) {
100
- this.selectedOptions = this.options.filter(opt => val.includes(opt.value));
98
+ set value ( val: string | string[] ) {
99
+ if ( this.multiple && Array.isArray( val ) ) {
100
+ this.selectedOptions = this.options.filter( opt => val.includes( opt.value ) );
101
101
  } else {
102
- const option = this.options.find(opt => opt.value === val);
103
- this.selectedOptions = option ? [option] : [];
102
+ const option = this.options.find( opt => opt.value === val );
103
+ this.selectedOptions = option ? [ option ] : [];
104
104
  }
105
105
  this.render();
106
106
  }
107
107
 
108
- get options(): SelectOption[] {
109
- const optionsAttr = this.getAttribute('options');
110
- if (optionsAttr) {
108
+ get options (): SelectOption[] {
109
+ const optionsAttr = this.getAttribute( 'options' );
110
+ if ( optionsAttr ) {
111
111
  try {
112
- return JSON.parse(optionsAttr);
113
- } catch (e) {
114
- console.error('Invalid options format:', e);
112
+ return JSON.parse( optionsAttr );
113
+ } catch ( e ) {
114
+ console.error( 'Invalid options format:', e );
115
115
  return [];
116
116
  }
117
117
  }
118
118
  return [];
119
119
  }
120
120
 
121
- set options(opts: SelectOption[]) {
122
- this.setAttribute('options', JSON.stringify(opts));
121
+ set options ( opts: SelectOption[] ) {
122
+ this.setAttribute( 'options', JSON.stringify( opts ) );
123
123
  }
124
124
 
125
125
  /**
126
126
  * Opens the dropdown
127
127
  */
128
- open(): void {
129
- if (this.disabled) return;
128
+ open (): void {
129
+ if ( this.disabled ) return;
130
130
  this.isOpen = true;
131
131
  this.focusedIndex = -1;
132
- if (this.options.length > 0) {
133
- this.filteredOptions = [...this.options];
132
+ if ( this.options.length > 0 ) {
133
+ this.filteredOptions = [ ...this.options ];
134
134
  }
135
135
  this.render();
136
136
 
@@ -138,34 +138,34 @@ export class SmartSelectElement extends HTMLElement {
138
138
  this._updateDropdownPosition();
139
139
 
140
140
  // Focus search input if searchable
141
- if (this.searchable) {
142
- requestAnimationFrame(() => {
143
- const searchInput = this.shadowRoot.querySelector('.search-input') as HTMLInputElement;
144
- if (searchInput) {
141
+ if ( this.searchable ) {
142
+ requestAnimationFrame( () => {
143
+ const searchInput = this.shadowRoot.querySelector( '.search-input' ) as HTMLInputElement;
144
+ if ( searchInput ) {
145
145
  searchInput.focus();
146
146
  }
147
- });
147
+ } );
148
148
  }
149
149
 
150
- this.dispatchEvent(new CustomEvent('open'));
150
+ this.dispatchEvent( new CustomEvent( 'open' ) );
151
151
  }
152
152
 
153
153
  /**
154
154
  * Closes the dropdown
155
155
  */
156
- close(): void {
156
+ close (): void {
157
157
  this.isOpen = false;
158
158
  this.focusedIndex = -1;
159
159
  this.searchValue = '';
160
160
 
161
161
  // Reset filtered options when closing
162
- if (this.searchable && this.options.length > 0) {
163
- this.filteredOptions = [...this.options];
162
+ if ( this.searchable && this.options.length > 0 ) {
163
+ this.filteredOptions = [ ...this.options ];
164
164
  }
165
165
 
166
166
  // Clear any inline positioning styles
167
- const dropdown = this.shadowRoot.querySelector('.dropdown') as HTMLElement;
168
- if (dropdown) {
167
+ const dropdown = this.shadowRoot.querySelector( '.dropdown' ) as HTMLElement;
168
+ if ( dropdown ) {
169
169
  dropdown.style.top = '';
170
170
  dropdown.style.left = '';
171
171
  dropdown.style.width = '';
@@ -173,14 +173,14 @@ export class SmartSelectElement extends HTMLElement {
173
173
  }
174
174
 
175
175
  this.render();
176
- this.dispatchEvent(new CustomEvent('close'));
176
+ this.dispatchEvent( new CustomEvent( 'close' ) );
177
177
  }
178
178
 
179
179
  /**
180
180
  * Toggles the dropdown open/closed state
181
181
  */
182
- toggle(): void {
183
- if (this.isOpen) {
182
+ toggle (): void {
183
+ if ( this.isOpen ) {
184
184
  this.close();
185
185
  } else {
186
186
  this.open();
@@ -190,45 +190,45 @@ export class SmartSelectElement extends HTMLElement {
190
190
  /**
191
191
  * Selects an option by its value
192
192
  */
193
- selectOption(value: string): void {
194
- const option = this.options.find(opt => opt.value === value);
195
- if (!option) return;
193
+ selectOption ( value: string ): void {
194
+ const option = this.options.find( opt => opt.value === value );
195
+ if ( !option ) return;
196
196
 
197
- if (this.multiple) {
198
- if (!this.selectedOptions.find(opt => opt.value === value)) {
199
- this.selectedOptions.push(option);
197
+ if ( this.multiple ) {
198
+ if ( !this.selectedOptions.find( opt => opt.value === value ) ) {
199
+ this.selectedOptions.push( option );
200
200
  }
201
201
  } else {
202
- this.selectedOptions = [option];
202
+ this.selectedOptions = [ option ];
203
203
  this.close();
204
204
  }
205
205
 
206
206
  this.render();
207
- this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value } }));
207
+ this.dispatchEvent( new CustomEvent( 'change', { detail: { value: this.value } } ) );
208
208
  }
209
209
 
210
210
  /**
211
211
  * Deselects an option by its value
212
212
  */
213
- deselectOption(value: string): void {
214
- this.selectedOptions = this.selectedOptions.filter(opt => opt.value !== value);
213
+ deselectOption ( value: string ): void {
214
+ this.selectedOptions = this.selectedOptions.filter( opt => opt.value !== value );
215
215
  this.render();
216
- this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value } }));
216
+ this.dispatchEvent( new CustomEvent( 'change', { detail: { value: this.value } } ) );
217
217
  }
218
218
 
219
219
  /**
220
220
  * Returns an array of currently selected options
221
221
  */
222
- getSelectedOptions(): SelectOption[] {
223
- return [...this.selectedOptions];
222
+ getSelectedOptions (): SelectOption[] {
223
+ return [ ...this.selectedOptions ];
224
224
  }
225
225
 
226
226
  /**
227
227
  * Sets the options for the select component
228
228
  */
229
- setOptions(options: SelectOption[]): void {
229
+ setOptions ( options: SelectOption[] ): void {
230
230
  this.options = options;
231
- this.filteredOptions = [...options];
231
+ this.filteredOptions = [ ...options ];
232
232
  this.selectedOptions = [];
233
233
  this.render();
234
234
  }
@@ -236,28 +236,28 @@ export class SmartSelectElement extends HTMLElement {
236
236
  /**
237
237
  * Handles search functionality
238
238
  */
239
- private handleSearch(query: string): void {
239
+ private handleSearch ( query: string ): void {
240
240
  this.searchValue = query;
241
- this.filteredOptions = this.options.filter(option =>
242
- option.label.toLowerCase().includes(query.toLowerCase())
241
+ this.filteredOptions = this.options.filter( option =>
242
+ option.label.toLowerCase().includes( query.toLowerCase() )
243
243
  );
244
244
  this.focusedIndex = -1;
245
245
  this.render();
246
- this.dispatchEvent(new CustomEvent('search', { detail: { query } }));
246
+ this.dispatchEvent( new CustomEvent( 'search', { detail: { query } } ) );
247
247
  }
248
248
 
249
249
  /**
250
250
  * Updates the visual focus state without full re-render
251
251
  */
252
- private updateFocusedOption(): void {
253
- const options = this.shadowRoot.querySelectorAll('.option');
252
+ private updateFocusedOption (): void {
253
+ const options = this.shadowRoot.querySelectorAll( '.option' );
254
254
 
255
255
  // Remove focused class from all options
256
- options.forEach(option => option.classList.remove('focused'));
256
+ options.forEach( option => option.classList.remove( 'focused' ) );
257
257
 
258
258
  // Add focused class to current option
259
- if (this.focusedIndex >= 0 && this.focusedIndex < options.length) {
260
- options[this.focusedIndex].classList.add('focused');
259
+ if ( this.focusedIndex >= 0 && this.focusedIndex < options.length ) {
260
+ options[ this.focusedIndex ].classList.add( 'focused' );
261
261
  }
262
262
 
263
263
  this.scrollToFocusedOption();
@@ -266,35 +266,35 @@ export class SmartSelectElement extends HTMLElement {
266
266
  /**
267
267
  * Scrolls the focused option into view
268
268
  */
269
- private scrollToFocusedOption(): void {
270
- if (this.focusedIndex < 0) return;
269
+ private scrollToFocusedOption (): void {
270
+ if ( this.focusedIndex < 0 ) return;
271
271
 
272
- requestAnimationFrame(() => {
273
- const dropdown = this.shadowRoot.querySelector('.dropdown') as HTMLElement;
274
- const focusedOption = this.shadowRoot.querySelector('.option.focused') as HTMLElement;
272
+ requestAnimationFrame( () => {
273
+ const dropdown = this.shadowRoot.querySelector( '.dropdown' ) as HTMLElement;
274
+ const focusedOption = this.shadowRoot.querySelector( '.option.focused' ) as HTMLElement;
275
275
 
276
- if (dropdown && focusedOption) {
276
+ if ( dropdown && focusedOption ) {
277
277
  const dropdownRect = dropdown.getBoundingClientRect();
278
278
  const optionRect = focusedOption.getBoundingClientRect();
279
279
 
280
280
  // Check if option is above visible area
281
- if (optionRect.top < dropdownRect.top) {
282
- dropdown.scrollTop -= (dropdownRect.top - optionRect.top);
281
+ if ( optionRect.top < dropdownRect.top ) {
282
+ dropdown.scrollTop -= ( dropdownRect.top - optionRect.top );
283
283
  }
284
284
  // Check if option is below visible area
285
- else if (optionRect.bottom > dropdownRect.bottom) {
286
- dropdown.scrollTop += (optionRect.bottom - dropdownRect.bottom);
285
+ else if ( optionRect.bottom > dropdownRect.bottom ) {
286
+ dropdown.scrollTop += ( optionRect.bottom - dropdownRect.bottom );
287
287
  }
288
288
  }
289
- });
289
+ } );
290
290
  }
291
291
 
292
292
  /**
293
293
  * Calculates the optimal dropdown position based on viewport constraints
294
294
  */
295
- private _calculateDropdownPosition(): { top: number; left: number; width: number; maxHeight: number } | null {
296
- const trigger = this.shadowRoot.querySelector('.select-trigger') as HTMLElement;
297
- if (!trigger) return null;
295
+ private _calculateDropdownPosition (): { top: number; left: number; width: number; maxHeight: number; } | null {
296
+ const trigger = this.shadowRoot.querySelector( '.select-trigger' ) as HTMLElement;
297
+ if ( !trigger ) return null;
298
298
 
299
299
  const triggerRect = trigger.getBoundingClientRect();
300
300
  const viewportHeight = window.innerHeight;
@@ -312,73 +312,73 @@ export class SmartSelectElement extends HTMLElement {
312
312
 
313
313
  // Calculate dimensions
314
314
  const width = triggerRect.width;
315
- const left = Math.max(0, Math.min(triggerRect.left, viewportWidth - width));
315
+ const left = Math.max( 0, Math.min( triggerRect.left, viewportWidth - width ) );
316
316
 
317
317
  let top: number;
318
318
  let maxHeight: number;
319
319
 
320
- if (shouldOpenUpward) {
320
+ if ( shouldOpenUpward ) {
321
321
  // Position above the trigger
322
- maxHeight = Math.min(dropdownMaxHeight, spaceAbove - dropdownPadding);
322
+ maxHeight = Math.min( dropdownMaxHeight, spaceAbove - dropdownPadding );
323
323
  top = triggerRect.top - maxHeight - margin;
324
324
  } else {
325
325
  // Position below the trigger
326
- maxHeight = Math.min(dropdownMaxHeight, spaceBelow - dropdownPadding);
326
+ maxHeight = Math.min( dropdownMaxHeight, spaceBelow - dropdownPadding );
327
327
  top = triggerRect.bottom + margin;
328
328
  }
329
329
 
330
330
  return {
331
- top: Math.max(0, top),
331
+ top: Math.max( 0, top ),
332
332
  left,
333
333
  width,
334
- maxHeight: Math.max(100, maxHeight) // Ensure minimum height
334
+ maxHeight: Math.max( 100, maxHeight ) // Ensure minimum height
335
335
  };
336
336
  }
337
337
 
338
338
  /**
339
339
  * Updates dropdown position using fixed positioning relative to viewport
340
340
  */
341
- private _updateDropdownPosition(): void {
342
- requestAnimationFrame(() => {
343
- const dropdown = this.shadowRoot.querySelector('.dropdown') as HTMLElement;
344
- if (!dropdown) return;
341
+ private _updateDropdownPosition (): void {
342
+ requestAnimationFrame( () => {
343
+ const dropdown = this.shadowRoot.querySelector( '.dropdown' ) as HTMLElement;
344
+ if ( !dropdown ) return;
345
345
 
346
346
  const position = this._calculateDropdownPosition();
347
- if (!position) return;
347
+ if ( !position ) return;
348
348
 
349
349
  // Apply calculated position as inline styles
350
- dropdown.style.top = `${position.top}px`;
351
- dropdown.style.left = `${position.left}px`;
352
- dropdown.style.width = `${position.width}px`;
353
- dropdown.style.maxHeight = `${position.maxHeight}px`;
354
- });
350
+ dropdown.style.top = `${ position.top }px`;
351
+ dropdown.style.left = `${ position.left }px`;
352
+ dropdown.style.width = `${ position.width }px`;
353
+ dropdown.style.maxHeight = `${ position.maxHeight }px`;
354
+ } );
355
355
  }
356
356
 
357
357
  /**
358
358
  * Handles keyboard navigation
359
359
  */
360
- private handleKeydown(event: KeyboardEvent): void {
361
- if (this.disabled) return;
360
+ private handleKeydown ( event: KeyboardEvent ): void {
361
+ if ( this.disabled ) return;
362
362
 
363
363
  // Prevent double execution if event has already been handled
364
- if ((event as any)._smartSelectHandled) return;
365
- (event as any)._smartSelectHandled = true;
364
+ if ( ( event as any )._smartSelectHandled ) return;
365
+ ( event as any )._smartSelectHandled = true;
366
366
 
367
- switch (event.key) {
367
+ switch ( event.key ) {
368
368
  case 'ArrowDown':
369
369
  event.preventDefault();
370
370
  this.keyboardNavigating = true;
371
- clearTimeout(this.keyboardTimer);
372
- this.keyboardTimer = window.setTimeout(() => { this.keyboardNavigating = false; }, 100);
371
+ clearTimeout( this.keyboardTimer );
372
+ this.keyboardTimer = window.setTimeout( () => { this.keyboardNavigating = false; }, 100 );
373
373
 
374
- if (!this.isOpen) {
374
+ if ( !this.isOpen ) {
375
375
  this.open();
376
376
  } else {
377
377
  // If searchable and search input is focused, move to first option
378
- const searchInput = this.shadowRoot.querySelector('.search-input') as HTMLInputElement;
378
+ const searchInput = this.shadowRoot.querySelector( '.search-input' ) as HTMLInputElement;
379
379
  const isSearchFocused = this.searchable && searchInput === this.shadowRoot.activeElement;
380
380
 
381
- if (isSearchFocused) {
381
+ if ( isSearchFocused ) {
382
382
  this.focusedIndex = 0;
383
383
  searchInput.blur(); // Blur search input to allow normal navigation
384
384
  // Focus the component to ensure it receives keyboard events
@@ -387,7 +387,7 @@ export class SmartSelectElement extends HTMLElement {
387
387
  return;
388
388
  }
389
389
  // Navigate through options
390
- const newIndex = Math.min(this.focusedIndex + 1, this.filteredOptions.length - 1);
390
+ const newIndex = Math.min( this.focusedIndex + 1, this.filteredOptions.length - 1 );
391
391
  this.focusedIndex = newIndex;
392
392
  this.updateFocusedOption();
393
393
  }
@@ -396,32 +396,32 @@ export class SmartSelectElement extends HTMLElement {
396
396
  case 'ArrowUp':
397
397
  event.preventDefault();
398
398
  this.keyboardNavigating = true;
399
- clearTimeout(this.keyboardTimer);
400
- this.keyboardTimer = window.setTimeout(() => { this.keyboardNavigating = false; }, 100);
399
+ clearTimeout( this.keyboardTimer );
400
+ this.keyboardTimer = window.setTimeout( () => { this.keyboardNavigating = false; }, 100 );
401
401
 
402
- if (this.isOpen) {
402
+ if ( this.isOpen ) {
403
403
  // If at first option and searchable, focus search input
404
- if (this.focusedIndex === 0 && this.searchable) {
404
+ if ( this.focusedIndex === 0 && this.searchable ) {
405
405
  this.focusedIndex = -1;
406
406
  this.updateFocusedOption();
407
- requestAnimationFrame(() => {
408
- const searchInput = this.shadowRoot.querySelector('.search-input') as HTMLInputElement;
409
- if (searchInput) {
407
+ requestAnimationFrame( () => {
408
+ const searchInput = this.shadowRoot.querySelector( '.search-input' ) as HTMLInputElement;
409
+ if ( searchInput ) {
410
410
  searchInput.focus();
411
- searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
411
+ searchInput.setSelectionRange( searchInput.value.length, searchInput.value.length );
412
412
  }
413
- });
413
+ } );
414
414
  return;
415
415
  }
416
416
  // If searchable and search input is focused, do nothing
417
- const searchInput = this.shadowRoot.querySelector('.search-input') as HTMLInputElement;
417
+ const searchInput = this.shadowRoot.querySelector( '.search-input' ) as HTMLInputElement;
418
418
  const isSearchFocused = this.searchable && searchInput === this.shadowRoot.activeElement;
419
419
 
420
- if (isSearchFocused) {
420
+ if ( isSearchFocused ) {
421
421
  return;
422
422
  }
423
423
  // Navigate through options
424
- const newIndex = Math.max(this.focusedIndex - 1, -1);
424
+ const newIndex = Math.max( this.focusedIndex - 1, -1 );
425
425
  this.focusedIndex = newIndex;
426
426
  this.updateFocusedOption();
427
427
  }
@@ -429,9 +429,9 @@ export class SmartSelectElement extends HTMLElement {
429
429
 
430
430
  case 'Enter':
431
431
  event.preventDefault();
432
- if (this.isOpen && this.focusedIndex >= 0 && this.focusedIndex < this.filteredOptions.length) {
433
- this.selectOption(this.filteredOptions[this.focusedIndex].value);
434
- } else if (!this.isOpen) {
432
+ if ( this.isOpen && this.focusedIndex >= 0 && this.focusedIndex < this.filteredOptions.length ) {
433
+ this.selectOption( this.filteredOptions[ this.focusedIndex ].value );
434
+ } else if ( !this.isOpen ) {
435
435
  this.open();
436
436
  }
437
437
  break;
@@ -450,114 +450,114 @@ export class SmartSelectElement extends HTMLElement {
450
450
  /**
451
451
  * Binds all event listeners
452
452
  */
453
- private bindEvents(): void {
453
+ private bindEvents (): void {
454
454
  // Listen for keydown events on both the component and shadow root
455
- const keydownHandler = this.handleKeydown.bind(this);
456
- this.addEventListener('keydown', keydownHandler);
457
- this.shadowRoot.addEventListener('keydown', keydownHandler as EventListener);
455
+ const keydownHandler = this.handleKeydown.bind( this );
456
+ this.addEventListener( 'keydown', keydownHandler );
457
+ this.shadowRoot.addEventListener( 'keydown', keydownHandler as EventListener );
458
458
 
459
459
  // Use event delegation on the shadow root
460
- this.shadowRoot.addEventListener('click', (e) => {
460
+ this.shadowRoot.addEventListener( 'click', ( e ) => {
461
461
  e.stopPropagation();
462
462
  const target = e.target as HTMLElement;
463
463
 
464
- if (target.closest('.remove-tag')) {
465
- const value = (target.closest('.remove-tag') as HTMLElement).dataset.value;
466
- if (value) this.deselectOption(value);
467
- } else if (target.closest('.option')) {
468
- const value = (target.closest('.option') as HTMLElement).dataset.value;
469
- if (value) this.selectOption(value);
470
- } else if (target.closest('.select-trigger')) {
464
+ if ( target.closest( '.remove-tag' ) ) {
465
+ const value = ( target.closest( '.remove-tag' ) as HTMLElement ).dataset.value;
466
+ if ( value ) this.deselectOption( value );
467
+ } else if ( target.closest( '.option' ) ) {
468
+ const value = ( target.closest( '.option' ) as HTMLElement ).dataset.value;
469
+ if ( value ) this.selectOption( value );
470
+ } else if ( target.closest( '.select-trigger' ) ) {
471
471
  this.toggle();
472
472
  }
473
- });
473
+ } );
474
474
 
475
475
  // Handle mouse hover on options to update focused index
476
- this.shadowRoot.addEventListener('mouseover', (e) => {
476
+ this.shadowRoot.addEventListener( 'mouseover', ( e ) => {
477
477
  // Don't interfere with keyboard navigation
478
- if (this.keyboardNavigating) return;
478
+ if ( this.keyboardNavigating ) return;
479
479
 
480
480
  const target = e.target as HTMLElement;
481
- if (target.closest('.option')) {
482
- const option = target.closest('.option') as HTMLElement;
483
- const options = Array.from(this.shadowRoot.querySelectorAll('.option'));
484
- const newFocusedIndex = options.indexOf(option);
481
+ if ( target.closest( '.option' ) ) {
482
+ const option = target.closest( '.option' ) as HTMLElement;
483
+ const options = Array.from( this.shadowRoot.querySelectorAll( '.option' ) );
484
+ const newFocusedIndex = options.indexOf( option );
485
485
 
486
486
  // Only update if the focused index actually changed
487
- if (this.focusedIndex !== newFocusedIndex) {
487
+ if ( this.focusedIndex !== newFocusedIndex ) {
488
488
  // Remove focused class from current option
489
- const currentFocused = this.shadowRoot.querySelector('.option.focused');
490
- if (currentFocused) {
491
- currentFocused.classList.remove('focused');
489
+ const currentFocused = this.shadowRoot.querySelector( '.option.focused' );
490
+ if ( currentFocused ) {
491
+ currentFocused.classList.remove( 'focused' );
492
492
  }
493
493
 
494
494
  // Add focused class to new option
495
- option.classList.add('focused');
495
+ option.classList.add( 'focused' );
496
496
  this.focusedIndex = newFocusedIndex;
497
497
  }
498
498
  }
499
- });
499
+ } );
500
500
 
501
501
  // Handle mouse leaving dropdown to clear focus
502
- this.shadowRoot.addEventListener('mouseleave', (e) => {
502
+ this.shadowRoot.addEventListener( 'mouseleave', ( e ) => {
503
503
  // Don't interfere with keyboard navigation
504
- if (this.keyboardNavigating) return;
504
+ if ( this.keyboardNavigating ) return;
505
505
 
506
506
  const target = e.target as HTMLElement;
507
- if (target.closest('.dropdown')) {
508
- const currentFocused = this.shadowRoot.querySelector('.option.focused');
509
- if (currentFocused) {
510
- currentFocused.classList.remove('focused');
507
+ if ( target.closest( '.dropdown' ) ) {
508
+ const currentFocused = this.shadowRoot.querySelector( '.option.focused' );
509
+ if ( currentFocused ) {
510
+ currentFocused.classList.remove( 'focused' );
511
511
  }
512
512
  this.focusedIndex = -1;
513
513
  }
514
- });
514
+ } );
515
515
 
516
516
  // Handle search input
517
- this.shadowRoot.addEventListener('input', (e) => {
517
+ this.shadowRoot.addEventListener( 'input', ( e ) => {
518
518
  const target = e.target as HTMLInputElement;
519
- if (target.classList.contains('search-input')) {
520
- this.handleSearch(target.value);
519
+ if ( target.classList.contains( 'search-input' ) ) {
520
+ this.handleSearch( target.value );
521
521
  }
522
- });
522
+ } );
523
523
 
524
524
  // Close dropdown when clicking outside
525
- document.addEventListener('click', (e) => {
526
- if (!this.contains(e.target as Node)) {
525
+ document.addEventListener( 'click', ( e ) => {
526
+ if ( !this.contains( e.target as Node ) ) {
527
527
  this.close();
528
528
  }
529
- });
529
+ } );
530
530
 
531
531
  // Update dropdown position on window resize or scroll
532
- window.addEventListener('resize', () => {
533
- if (this.isOpen) {
532
+ window.addEventListener( 'resize', () => {
533
+ if ( this.isOpen ) {
534
534
  this._updateDropdownPosition();
535
535
  }
536
- });
536
+ } );
537
537
 
538
- window.addEventListener('scroll', () => {
539
- if (this.isOpen) {
538
+ window.addEventListener( 'scroll', () => {
539
+ if ( this.isOpen ) {
540
540
  this._updateDropdownPosition();
541
541
  }
542
- }, true); // Use capture to catch all scroll events
542
+ }, true ); // Use capture to catch all scroll events
543
543
  }
544
544
 
545
545
  /**
546
546
  * Renders the component
547
547
  */
548
- private render(): void {
548
+ private render (): void {
549
549
  // Initialize filteredOptions if not set
550
- if (this.filteredOptions.length === 0 && this.options.length > 0) {
551
- this.filteredOptions = [...this.options];
550
+ if ( this.filteredOptions.length === 0 && this.options.length > 0 ) {
551
+ this.filteredOptions = [ ...this.options ];
552
552
  }
553
553
 
554
554
  // Remember if search input was focused before render
555
- const wasSearchFocused = this.shadowRoot.querySelector('.search-input') === this.shadowRoot.activeElement;
555
+ const wasSearchFocused = this.shadowRoot.querySelector( '.search-input' ) === this.shadowRoot.activeElement;
556
556
 
557
557
  const displayText = this.selectedOptions.length > 0
558
- ? (this.multiple
559
- ? `${this.selectedOptions.length} selected`
560
- : this.selectedOptions[0].label)
558
+ ? ( this.multiple
559
+ ? `${ this.selectedOptions.length } selected`
560
+ : this.selectedOptions[ 0 ].label )
561
561
  : this.placeholder;
562
562
 
563
563
  this.shadowRoot.innerHTML = `
@@ -709,56 +709,56 @@ export class SmartSelectElement extends HTMLElement {
709
709
  <div class="select-container">
710
710
  <div class="select-trigger" tabindex="-1">
711
711
  <div class="selected-content">
712
- ${this.multiple && this.selectedOptions.length > 0
713
- ? this.selectedOptions.map(option => `
712
+ ${ this.multiple && this.selectedOptions.length > 0
713
+ ? this.selectedOptions.map( option => `
714
714
  <span class="tag">
715
- ${option.label}
716
- <span class="remove-tag" data-value="${option.value}">×</span>
715
+ ${ option.label }
716
+ <span class="remove-tag" data-value="${ option.value }">×</span>
717
717
  </span>
718
- `).join('')
719
- : `<span>${displayText}</span>`
720
- }
718
+ `).join( '' )
719
+ : `<span>${ displayText }</span>`
720
+ }
721
721
  </div>
722
- <div class="arrow ${this.isOpen ? 'open' : ''}"></div>
722
+ <div class="arrow ${ this.isOpen ? 'open' : '' }"></div>
723
723
  </div>
724
724
 
725
- ${this.isOpen ? `
725
+ ${ this.isOpen ? `
726
726
  <div class="dropdown">
727
- ${this.searchable ? `
727
+ ${ this.searchable ? `
728
728
  <input
729
729
  type="text"
730
730
  class="search-input"
731
731
  placeholder="Search options..."
732
- value="${this.searchValue}"
732
+ value="${ this.searchValue }"
733
733
  >
734
- ` : ''}
734
+ ` : '' }
735
735
 
736
- ${this.filteredOptions.length > 0
737
- ? this.filteredOptions.map((option, index) => `
736
+ ${ this.filteredOptions.length > 0
737
+ ? this.filteredOptions.map( ( option, index ) => `
738
738
  <div
739
- class="option ${this.selectedOptions.find(selected => selected.value === option.value) ? 'selected' : ''} ${index === this.focusedIndex ? 'focused' : ''}"
740
- data-value="${option.value}"
739
+ class="option ${ this.selectedOptions.find( selected => selected.value === option.value ) ? 'selected' : '' } ${ index === this.focusedIndex ? 'focused' : '' }"
740
+ data-value="${ option.value }"
741
741
  >
742
- ${option.label}
742
+ ${ option.label }
743
743
  </div>
744
- `).join('')
745
- : '<div class="no-options">No options available</div>'
746
- }
744
+ `).join( '' )
745
+ : '<div class="no-options">No options available</div>'
746
+ }
747
747
  </div>
748
- ` : ''}
748
+ ` : '' }
749
749
  </div>
750
750
  `;
751
751
 
752
752
  // Re-focus search input if it was previously focused
753
- if (wasSearchFocused && this.searchable && this.isOpen) {
754
- requestAnimationFrame(() => {
755
- const searchInput = this.shadowRoot.querySelector('.search-input') as HTMLInputElement;
756
- if (searchInput) {
753
+ if ( wasSearchFocused && this.searchable && this.isOpen ) {
754
+ requestAnimationFrame( () => {
755
+ const searchInput = this.shadowRoot.querySelector( '.search-input' ) as HTMLInputElement;
756
+ if ( searchInput ) {
757
757
  searchInput.focus();
758
758
  // Restore cursor position to the end
759
- searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
759
+ searchInput.setSelectionRange( searchInput.value.length, searchInput.value.length );
760
760
  }
761
- });
761
+ } );
762
762
  }
763
763
  }
764
764
  }
@@ -766,9 +766,9 @@ export class SmartSelectElement extends HTMLElement {
766
766
  /**
767
767
  * Conditionally defines the custom element if in a browser environment.
768
768
  */
769
- const defineSmartSelect = (tagName: string = 'liwe3-select'): void => {
770
- if (typeof window !== 'undefined' && !window.customElements.get(tagName)) {
771
- customElements.define(tagName, SmartSelectElement);
769
+ const defineSmartSelect = ( tagName: string = 'liwe3-select' ): void => {
770
+ if ( typeof window !== 'undefined' && !window.customElements.get( tagName ) ) {
771
+ customElements.define( tagName, SmartSelectElement );
772
772
  }
773
773
  };
774
774