@melodicdev/components 1.1.1 → 1.2.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.
@@ -0,0 +1,127 @@
1
+ import type { IElementRef, OnCreate, OnDestroy } from '@melodicdev/core';
2
+ import type { Size } from '../../../types/index.js';
3
+ import type { AutocompleteOption, AutocompleteSearchFn } from './autocomplete.types.js';
4
+ /**
5
+ * ml-autocomplete - Typeahead/autocomplete form component
6
+ *
7
+ * Provides a text input with dropdown suggestions. Supports static option lists
8
+ * or async search via a promise-returning function.
9
+ *
10
+ * @example
11
+ * ```html
12
+ * <ml-autocomplete
13
+ * label="Search users"
14
+ * placeholder="Type to search..."
15
+ * .options=${userOptions}
16
+ * ></ml-autocomplete>
17
+ *
18
+ * <ml-autocomplete
19
+ * label="Search"
20
+ * .searchFn=${async (query) => fetch(`/api/search?q=${query}`).then(r => r.json())}
21
+ * .debounce=${300}
22
+ * ></ml-autocomplete>
23
+ * ```
24
+ *
25
+ * @fires ml:change - Emitted when selection changes
26
+ * @fires ml:search - Emitted when search query changes
27
+ * @fires ml:open - Emitted when dropdown opens
28
+ * @fires ml:close - Emitted when dropdown closes
29
+ */
30
+ export declare class AutocompleteComponent implements IElementRef, OnCreate, OnDestroy {
31
+ elementRef: HTMLElement;
32
+ /** Label text */
33
+ label: string;
34
+ /** Placeholder text */
35
+ placeholder: string;
36
+ /** Hint text shown below input */
37
+ hint: string;
38
+ /** Error message (shows error state when set) */
39
+ error: string;
40
+ /** Component size */
41
+ size: Size;
42
+ /** Disable the component */
43
+ disabled: boolean;
44
+ /** Mark as required */
45
+ required: boolean;
46
+ /** Enable multi-select mode */
47
+ multiple: boolean;
48
+ /** Currently selected value (single mode) */
49
+ value: string;
50
+ /** Currently selected values (multi mode) */
51
+ values: string[];
52
+ /** Static options list */
53
+ options: AutocompleteOption[];
54
+ /** Async search function */
55
+ searchFn: AutocompleteSearchFn | null;
56
+ /** Debounce ms for async search */
57
+ debounce: number;
58
+ /** Min chars before searching (0 = show on focus) */
59
+ minChars: number;
60
+ /** Show search icon */
61
+ showIcon: boolean;
62
+ /** Current search query */
63
+ search: string;
64
+ /** Whether dropdown is open */
65
+ isOpen: boolean;
66
+ /** Currently focused option index */
67
+ focusedIndex: number;
68
+ /** Loading state for async search */
69
+ _loading: boolean;
70
+ /** Resolved async results */
71
+ _asyncOptions: AutocompleteOption[];
72
+ private readonly _handleKeyDown;
73
+ private readonly _handleDocumentClick;
74
+ private _handleScroll;
75
+ private _debounceTimer;
76
+ private _syncingValues;
77
+ onCreate(): void;
78
+ onDestroy(): void;
79
+ onPropertyChange(name: string): void;
80
+ /** Get the selected option (single mode) */
81
+ get selectedOption(): AutocompleteOption | undefined;
82
+ /** Get selected options (multi mode) */
83
+ get selectedOptions(): AutocompleteOption[];
84
+ /** All available options (static or async) */
85
+ get allOptions(): AutocompleteOption[];
86
+ /** Filtered options for display */
87
+ get filteredOptions(): AutocompleteOption[];
88
+ get hasValue(): boolean;
89
+ /** Display text for the input in single mode */
90
+ get displayText(): string;
91
+ /** Open the dropdown */
92
+ open: () => void;
93
+ /** Close the dropdown */
94
+ close: () => void;
95
+ /** Select an option */
96
+ selectOption: (option: AutocompleteOption) => void;
97
+ /** Handle click on option */
98
+ handleOptionClick: (event: Event, option: AutocompleteOption) => void;
99
+ /** Handle input changes */
100
+ handleInput: (event: Event) => void;
101
+ /** Handle input focus */
102
+ handleFocus: () => void;
103
+ /** Handle input click */
104
+ handleInputClick: (event: Event) => void;
105
+ /** Handle tag remove in multi mode */
106
+ handleTagRemove: (event: Event, value: string) => void;
107
+ /** Clear the current value (single mode) */
108
+ handleClear: (event: Event) => void;
109
+ private toggleOption;
110
+ private debouncedSearch;
111
+ private executeSearch;
112
+ /** Close dropdown on clicks outside the component */
113
+ private onDocumentClick;
114
+ private addDocumentClickListener;
115
+ private removeDocumentClickListener;
116
+ private positionDropdown;
117
+ private addScrollListener;
118
+ private removeScrollListener;
119
+ private getDropdownEl;
120
+ private onKeyDown;
121
+ private focusNextOption;
122
+ private focusPreviousOption;
123
+ private findFirstEnabledIndex;
124
+ private updateValues;
125
+ private areValuesEqual;
126
+ }
127
+ //# sourceMappingURL=autocomplete.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete.component.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/autocomplete/autocomplete.component.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,491 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { MelodicComponent } from '@melodicdev/core';
8
+ import { computePosition, offset, flip, shift } from '../../../utils/positioning/index.js';
9
+ import { autocompleteTemplate } from './autocomplete.template.js';
10
+ import { autocompleteStyles } from './autocomplete.styles.js';
11
+ /**
12
+ * ml-autocomplete - Typeahead/autocomplete form component
13
+ *
14
+ * Provides a text input with dropdown suggestions. Supports static option lists
15
+ * or async search via a promise-returning function.
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <ml-autocomplete
20
+ * label="Search users"
21
+ * placeholder="Type to search..."
22
+ * .options=${userOptions}
23
+ * ></ml-autocomplete>
24
+ *
25
+ * <ml-autocomplete
26
+ * label="Search"
27
+ * .searchFn=${async (query) => fetch(`/api/search?q=${query}`).then(r => r.json())}
28
+ * .debounce=${300}
29
+ * ></ml-autocomplete>
30
+ * ```
31
+ *
32
+ * @fires ml:change - Emitted when selection changes
33
+ * @fires ml:search - Emitted when search query changes
34
+ * @fires ml:open - Emitted when dropdown opens
35
+ * @fires ml:close - Emitted when dropdown closes
36
+ */
37
+ let AutocompleteComponent = class AutocompleteComponent {
38
+ constructor() {
39
+ /** Label text */
40
+ this.label = '';
41
+ /** Placeholder text */
42
+ this.placeholder = 'Search';
43
+ /** Hint text shown below input */
44
+ this.hint = '';
45
+ /** Error message (shows error state when set) */
46
+ this.error = '';
47
+ /** Component size */
48
+ this.size = 'md';
49
+ /** Disable the component */
50
+ this.disabled = false;
51
+ /** Mark as required */
52
+ this.required = false;
53
+ /** Enable multi-select mode */
54
+ this.multiple = false;
55
+ /** Currently selected value (single mode) */
56
+ this.value = '';
57
+ /** Currently selected values (multi mode) */
58
+ this.values = [];
59
+ /** Static options list */
60
+ this.options = [];
61
+ /** Async search function */
62
+ this.searchFn = null;
63
+ /** Debounce ms for async search */
64
+ this.debounce = 300;
65
+ /** Min chars before searching (0 = show on focus) */
66
+ this.minChars = 0;
67
+ /** Show search icon */
68
+ this.showIcon = true;
69
+ /** Current search query */
70
+ this.search = '';
71
+ /** Whether dropdown is open */
72
+ this.isOpen = false;
73
+ /** Currently focused option index */
74
+ this.focusedIndex = -1;
75
+ /** Loading state for async search */
76
+ this._loading = false;
77
+ /** Resolved async results */
78
+ this._asyncOptions = [];
79
+ this._handleKeyDown = this.onKeyDown.bind(this);
80
+ this._handleDocumentClick = this.onDocumentClick.bind(this);
81
+ this._handleScroll = null;
82
+ this._debounceTimer = null;
83
+ this._syncingValues = false;
84
+ /** Open the dropdown */
85
+ this.open = () => {
86
+ if (this.disabled || this.isOpen)
87
+ return;
88
+ if (this.minChars > 0 && this.search.length < this.minChars)
89
+ return;
90
+ const dropdownEl = this.getDropdownEl();
91
+ if (!dropdownEl)
92
+ return;
93
+ dropdownEl.showPopover();
94
+ this.isOpen = true;
95
+ this.focusedIndex = this.findFirstEnabledIndex();
96
+ this.positionDropdown();
97
+ this.addScrollListener();
98
+ this.addDocumentClickListener();
99
+ this.elementRef.dispatchEvent(new CustomEvent('ml:open', { bubbles: true, composed: true }));
100
+ };
101
+ /** Close the dropdown */
102
+ this.close = () => {
103
+ if (!this.isOpen)
104
+ return;
105
+ this.getDropdownEl()?.hidePopover();
106
+ this.isOpen = false;
107
+ this.focusedIndex = -1;
108
+ this.removeScrollListener();
109
+ this.removeDocumentClickListener();
110
+ this.elementRef.dispatchEvent(new CustomEvent('ml:close', { bubbles: true, composed: true }));
111
+ };
112
+ /** Select an option */
113
+ this.selectOption = (option) => {
114
+ if (option.disabled)
115
+ return;
116
+ if (this.multiple) {
117
+ this.toggleOption(option);
118
+ return;
119
+ }
120
+ this.value = option.value;
121
+ this.search = '';
122
+ this.close();
123
+ this.elementRef.dispatchEvent(new CustomEvent('ml:change', {
124
+ bubbles: true,
125
+ composed: true,
126
+ detail: { value: this.value, option }
127
+ }));
128
+ };
129
+ /** Handle click on option */
130
+ this.handleOptionClick = (event, option) => {
131
+ event.stopPropagation();
132
+ this.selectOption(option);
133
+ };
134
+ /** Handle input changes */
135
+ this.handleInput = (event) => {
136
+ if (this.disabled)
137
+ return;
138
+ const target = event.target;
139
+ this.search = target.value;
140
+ // In single mode, clear the current value when the user types
141
+ if (!this.multiple && this.value) {
142
+ this.value = '';
143
+ this.elementRef.dispatchEvent(new CustomEvent('ml:change', {
144
+ bubbles: true,
145
+ composed: true,
146
+ detail: { value: '', option: null }
147
+ }));
148
+ }
149
+ this.elementRef.dispatchEvent(new CustomEvent('ml:search', {
150
+ bubbles: true,
151
+ composed: true,
152
+ detail: { query: this.search }
153
+ }));
154
+ if (this.minChars > 0 && this.search.length < this.minChars) {
155
+ this.close();
156
+ return;
157
+ }
158
+ this.focusedIndex = this.findFirstEnabledIndex();
159
+ if (!this.isOpen) {
160
+ this.open();
161
+ }
162
+ if (this.searchFn) {
163
+ this.debouncedSearch(this.search);
164
+ }
165
+ };
166
+ /** Handle input focus */
167
+ this.handleFocus = () => {
168
+ if (this.disabled)
169
+ return;
170
+ if (this.minChars === 0) {
171
+ if (this.searchFn && this._asyncOptions.length === 0) {
172
+ this.debouncedSearch('');
173
+ }
174
+ if (!this.isOpen) {
175
+ this.open();
176
+ }
177
+ }
178
+ };
179
+ /** Handle input click */
180
+ this.handleInputClick = (event) => {
181
+ event.stopPropagation();
182
+ if (!this.isOpen && this.minChars === 0) {
183
+ if (this.searchFn && this._asyncOptions.length === 0) {
184
+ this.debouncedSearch('');
185
+ }
186
+ this.open();
187
+ }
188
+ };
189
+ /** Handle tag remove in multi mode */
190
+ this.handleTagRemove = (event, value) => {
191
+ event.stopPropagation();
192
+ if (this.disabled)
193
+ return;
194
+ this.values = this.values.filter((item) => item !== value);
195
+ this.elementRef.dispatchEvent(new CustomEvent('ml:change', {
196
+ bubbles: true,
197
+ composed: true,
198
+ detail: { values: [...this.values], options: this.selectedOptions }
199
+ }));
200
+ };
201
+ /** Clear the current value (single mode) */
202
+ this.handleClear = (event) => {
203
+ event.stopPropagation();
204
+ if (this.disabled)
205
+ return;
206
+ this.value = '';
207
+ this.search = '';
208
+ this.elementRef.dispatchEvent(new CustomEvent('ml:change', {
209
+ bubbles: true,
210
+ composed: true,
211
+ detail: { value: '', option: null }
212
+ }));
213
+ // Focus the input after clearing
214
+ const input = this.elementRef.shadowRoot?.querySelector('.ml-autocomplete__input');
215
+ input?.focus();
216
+ };
217
+ }
218
+ onCreate() {
219
+ this.elementRef.addEventListener('keydown', this._handleKeyDown);
220
+ }
221
+ onDestroy() {
222
+ this.elementRef.removeEventListener('keydown', this._handleKeyDown);
223
+ this.removeScrollListener();
224
+ this.removeDocumentClickListener();
225
+ if (this._debounceTimer) {
226
+ clearTimeout(this._debounceTimer);
227
+ }
228
+ }
229
+ onPropertyChange(name) {
230
+ if (this._syncingValues)
231
+ return;
232
+ if (name === 'multiple') {
233
+ if (this.multiple) {
234
+ if (!this.values.length && this.value) {
235
+ this.updateValues([this.value]);
236
+ }
237
+ return;
238
+ }
239
+ if (this.values.length) {
240
+ this.value = this.values[0] ?? '';
241
+ }
242
+ this.updateValues([]);
243
+ this.search = '';
244
+ return;
245
+ }
246
+ if (name === 'values') {
247
+ const rawValues = this.values;
248
+ let normalized = [];
249
+ if (typeof rawValues === 'string') {
250
+ normalized = rawValues
251
+ .split(',')
252
+ .map((v) => v.trim())
253
+ .filter((v) => v.length > 0);
254
+ }
255
+ else if (Array.isArray(rawValues)) {
256
+ normalized = rawValues.filter((v) => typeof v === 'string');
257
+ }
258
+ normalized = Array.from(new Set(normalized));
259
+ if (!this.areValuesEqual(this.values, normalized)) {
260
+ this.updateValues(normalized);
261
+ }
262
+ if (!this.multiple) {
263
+ this.value = normalized[0] ?? '';
264
+ this.updateValues([]);
265
+ }
266
+ return;
267
+ }
268
+ if (name === 'value' && this.multiple) {
269
+ if (this.value) {
270
+ const nextValues = Array.from(new Set([...this.values, this.value]));
271
+ if (!this.areValuesEqual(this.values, nextValues)) {
272
+ this.updateValues(nextValues);
273
+ }
274
+ }
275
+ }
276
+ }
277
+ /** Get the selected option (single mode) */
278
+ get selectedOption() {
279
+ return this.allOptions.find((opt) => opt.value === this.value);
280
+ }
281
+ /** Get selected options (multi mode) */
282
+ get selectedOptions() {
283
+ if (!this.multiple) {
284
+ return this.selectedOption ? [this.selectedOption] : [];
285
+ }
286
+ return this.allOptions.filter((opt) => this.values.includes(opt.value));
287
+ }
288
+ /** All available options (static or async) */
289
+ get allOptions() {
290
+ return this.searchFn ? this._asyncOptions : this.options;
291
+ }
292
+ /** Filtered options for display */
293
+ get filteredOptions() {
294
+ if (this.searchFn) {
295
+ return this._asyncOptions;
296
+ }
297
+ const query = this.search.trim().toLowerCase();
298
+ if (!query)
299
+ return this.options;
300
+ return this.options.filter((option) => {
301
+ const labelMatch = option.label.toLowerCase().includes(query);
302
+ const valueMatch = option.value.toLowerCase().includes(query);
303
+ const subtitleMatch = option.subtitle?.toLowerCase().includes(query) ?? false;
304
+ return labelMatch || valueMatch || subtitleMatch;
305
+ });
306
+ }
307
+ get hasValue() {
308
+ return this.multiple ? this.values.length > 0 : !!this.value;
309
+ }
310
+ /** Display text for the input in single mode */
311
+ get displayText() {
312
+ if (this.multiple)
313
+ return '';
314
+ return this.selectedOption?.label || '';
315
+ }
316
+ toggleOption(option) {
317
+ const exists = this.values.includes(option.value);
318
+ this.values = exists ? this.values.filter((v) => v !== option.value) : [...this.values, option.value];
319
+ this.elementRef.dispatchEvent(new CustomEvent('ml:change', {
320
+ bubbles: true,
321
+ composed: true,
322
+ detail: { values: [...this.values], options: this.selectedOptions, option }
323
+ }));
324
+ // Clear search and refocus input
325
+ this.search = '';
326
+ const input = this.elementRef.shadowRoot?.querySelector('.ml-autocomplete__input');
327
+ input?.focus();
328
+ }
329
+ debouncedSearch(query) {
330
+ if (this._debounceTimer) {
331
+ clearTimeout(this._debounceTimer);
332
+ }
333
+ this._debounceTimer = setTimeout(() => {
334
+ this.executeSearch(query);
335
+ }, this.debounce);
336
+ }
337
+ async executeSearch(query) {
338
+ if (!this.searchFn)
339
+ return;
340
+ this._loading = true;
341
+ try {
342
+ this._asyncOptions = await this.searchFn(query);
343
+ this.focusedIndex = this.findFirstEnabledIndex();
344
+ }
345
+ finally {
346
+ this._loading = false;
347
+ }
348
+ }
349
+ /** Close dropdown on clicks outside the component */
350
+ onDocumentClick(event) {
351
+ const path = event.composedPath();
352
+ if (!path.includes(this.elementRef)) {
353
+ this.close();
354
+ }
355
+ }
356
+ addDocumentClickListener() {
357
+ document.addEventListener('click', this._handleDocumentClick, true);
358
+ }
359
+ removeDocumentClickListener() {
360
+ document.removeEventListener('click', this._handleDocumentClick, true);
361
+ }
362
+ positionDropdown() {
363
+ const triggerEl = this.elementRef.shadowRoot?.querySelector('.ml-autocomplete__trigger');
364
+ const dropdownEl = this.getDropdownEl();
365
+ if (!triggerEl || !dropdownEl)
366
+ return;
367
+ dropdownEl.style.width = `${triggerEl.offsetWidth}px`;
368
+ const { x, y } = computePosition(triggerEl, dropdownEl, {
369
+ placement: 'bottom-start',
370
+ middleware: [offset(4), flip(), shift({ padding: 8 })]
371
+ });
372
+ dropdownEl.style.left = `${x}px`;
373
+ dropdownEl.style.top = `${y}px`;
374
+ }
375
+ addScrollListener() {
376
+ this._handleScroll = (event) => {
377
+ const dropdownEl = this.getDropdownEl();
378
+ if (dropdownEl?.contains(event.target))
379
+ return;
380
+ this.close();
381
+ };
382
+ window.addEventListener('scroll', this._handleScroll, true);
383
+ }
384
+ removeScrollListener() {
385
+ if (this._handleScroll) {
386
+ window.removeEventListener('scroll', this._handleScroll, true);
387
+ this._handleScroll = null;
388
+ }
389
+ }
390
+ getDropdownEl() {
391
+ return this.elementRef.shadowRoot?.querySelector('.ml-autocomplete__dropdown');
392
+ }
393
+ onKeyDown(event) {
394
+ if (this.disabled)
395
+ return;
396
+ switch (event.key) {
397
+ case 'Enter':
398
+ if (this.isOpen && this.focusedIndex >= 0) {
399
+ event.preventDefault();
400
+ const option = this.filteredOptions[this.focusedIndex];
401
+ if (option && !option.disabled) {
402
+ this.selectOption(option);
403
+ }
404
+ }
405
+ break;
406
+ case 'Escape':
407
+ event.preventDefault();
408
+ this.close();
409
+ break;
410
+ case 'ArrowDown':
411
+ event.preventDefault();
412
+ if (!this.isOpen) {
413
+ this.open();
414
+ }
415
+ else {
416
+ this.focusNextOption();
417
+ }
418
+ break;
419
+ case 'ArrowUp':
420
+ event.preventDefault();
421
+ if (this.isOpen) {
422
+ this.focusPreviousOption();
423
+ }
424
+ break;
425
+ case 'Backspace':
426
+ if (this.multiple && this.search === '' && this.values.length > 0) {
427
+ const lastValue = this.values[this.values.length - 1];
428
+ this.values = this.values.slice(0, -1);
429
+ this.elementRef.dispatchEvent(new CustomEvent('ml:change', {
430
+ bubbles: true,
431
+ composed: true,
432
+ detail: { values: [...this.values], options: this.selectedOptions, removedValue: lastValue }
433
+ }));
434
+ }
435
+ break;
436
+ case 'Tab':
437
+ this.close();
438
+ break;
439
+ default:
440
+ break;
441
+ }
442
+ }
443
+ focusNextOption() {
444
+ const options = this.filteredOptions;
445
+ let index = this.focusedIndex + 1;
446
+ while (index < options.length) {
447
+ if (!options[index].disabled) {
448
+ this.focusedIndex = index;
449
+ return;
450
+ }
451
+ index++;
452
+ }
453
+ }
454
+ focusPreviousOption() {
455
+ const options = this.filteredOptions;
456
+ let index = this.focusedIndex - 1;
457
+ while (index >= 0) {
458
+ if (!options[index].disabled) {
459
+ this.focusedIndex = index;
460
+ return;
461
+ }
462
+ index--;
463
+ }
464
+ }
465
+ findFirstEnabledIndex() {
466
+ return this.filteredOptions.findIndex((opt) => !opt.disabled);
467
+ }
468
+ updateValues(values) {
469
+ this._syncingValues = true;
470
+ this.values = values;
471
+ this._syncingValues = false;
472
+ }
473
+ areValuesEqual(left, right) {
474
+ if (left.length !== right.length)
475
+ return false;
476
+ for (let i = 0; i < left.length; i++) {
477
+ if (left[i] !== right[i])
478
+ return false;
479
+ }
480
+ return true;
481
+ }
482
+ };
483
+ AutocompleteComponent = __decorate([
484
+ MelodicComponent({
485
+ selector: 'ml-autocomplete',
486
+ template: autocompleteTemplate,
487
+ styles: autocompleteStyles,
488
+ attributes: ['label', 'placeholder', 'hint', 'error', 'size', 'disabled', 'required', 'value', 'multiple']
489
+ })
490
+ ], AutocompleteComponent);
491
+ export { AutocompleteComponent };
@@ -0,0 +1,2 @@
1
+ export declare const autocompleteStyles: () => import("@melodicdev/core").TemplateResult;
2
+ //# sourceMappingURL=autocomplete.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete.styles.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/autocomplete/autocomplete.styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,iDAgW9B,CAAC"}
@@ -0,0 +1,354 @@
1
+ import { css } from '@melodicdev/core';
2
+ export const autocompleteStyles = () => css `
3
+ :host {
4
+ display: block;
5
+ width: 100%;
6
+ min-width: 0;
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ *,
11
+ *::before,
12
+ *::after {
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ .ml-autocomplete {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: var(--ml-space-1-5);
20
+ max-width: 100%;
21
+ }
22
+
23
+ .ml-autocomplete__control {
24
+ position: relative;
25
+ max-width: 100%;
26
+ }
27
+
28
+ .ml-autocomplete__label {
29
+ font-size: var(--ml-text-sm);
30
+ font-weight: var(--ml-font-medium);
31
+ color: var(--ml-color-text-secondary);
32
+ line-height: var(--ml-leading-tight);
33
+ }
34
+
35
+ .ml-autocomplete__required {
36
+ color: var(--ml-color-danger);
37
+ margin-left: var(--ml-space-0-5);
38
+ }
39
+
40
+ .ml-autocomplete__trigger {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: var(--ml-space-2);
44
+ width: 100%;
45
+ max-width: 100%;
46
+ overflow: hidden;
47
+ background-color: var(--ml-color-surface);
48
+ border: var(--ml-border) solid var(--ml-color-border);
49
+ border-radius: var(--ml-radius);
50
+ box-shadow: none;
51
+ color: var(--ml-color-text);
52
+ font-family: var(--ml-font-sans);
53
+ cursor: text;
54
+ text-align: left;
55
+ transition:
56
+ border-color var(--ml-duration-150) var(--ml-ease-in-out),
57
+ box-shadow var(--ml-duration-150) var(--ml-ease-in-out);
58
+ }
59
+
60
+ .ml-autocomplete:not(.ml-autocomplete--disabled) .ml-autocomplete__trigger:hover {
61
+ border-color: var(--ml-color-border-strong);
62
+ }
63
+
64
+ .ml-autocomplete__trigger:focus-within {
65
+ outline: none;
66
+ border-color: var(--ml-color-primary);
67
+ box-shadow: var(--ml-shadow-focus-ring);
68
+ }
69
+
70
+ .ml-autocomplete--disabled .ml-autocomplete__trigger {
71
+ background-color: var(--ml-color-input-disabled-bg);
72
+ cursor: not-allowed;
73
+ color: var(--ml-color-text-muted);
74
+ }
75
+
76
+ .ml-autocomplete__search-icon {
77
+ color: var(--ml-color-text-muted);
78
+ flex-shrink: 0;
79
+ height: 20px;
80
+ display: flex;
81
+ align-items: center;
82
+ }
83
+
84
+ .ml-autocomplete__input {
85
+ flex: 1 1 20px;
86
+ min-width: 20px;
87
+ height: 20px;
88
+ border: none;
89
+ outline: none;
90
+ background: transparent;
91
+ font: inherit;
92
+ color: var(--ml-color-text);
93
+ padding: 0;
94
+ line-height: 20px;
95
+ }
96
+
97
+ .ml-autocomplete__input::placeholder {
98
+ color: var(--ml-color-text-muted);
99
+ }
100
+
101
+ .ml-autocomplete__input:disabled {
102
+ color: var(--ml-color-text-muted);
103
+ cursor: not-allowed;
104
+ }
105
+
106
+ .ml-autocomplete__clear {
107
+ border: none;
108
+ background: transparent;
109
+ padding: 2px;
110
+ color: var(--ml-color-text-muted);
111
+ cursor: pointer;
112
+ display: inline-flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ flex-shrink: 0;
116
+ border-radius: var(--ml-radius-full);
117
+ transition: color var(--ml-duration-100) var(--ml-ease-in-out),
118
+ background-color var(--ml-duration-100) var(--ml-ease-in-out);
119
+ }
120
+
121
+ .ml-autocomplete__clear:hover {
122
+ color: var(--ml-color-text);
123
+ background-color: var(--ml-color-surface-raised);
124
+ }
125
+
126
+ /* Multi-mode styles */
127
+ .ml-autocomplete--multiple .ml-autocomplete__trigger {
128
+ flex-wrap: wrap;
129
+ padding-top: var(--ml-space-2);
130
+ padding-bottom: var(--ml-space-2);
131
+ }
132
+
133
+ .ml-autocomplete__tags {
134
+ display: contents;
135
+ }
136
+
137
+ .ml-autocomplete__tag {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ gap: var(--ml-space-1);
141
+ padding: 0 var(--ml-space-1-5);
142
+ height: 20px;
143
+ border-radius: var(--ml-radius-full);
144
+ border: var(--ml-border) solid var(--ml-color-border);
145
+ background-color: var(--ml-color-surface);
146
+ font-size: var(--ml-text-xs);
147
+ font-weight: var(--ml-font-medium);
148
+ color: var(--ml-color-text);
149
+ line-height: 1;
150
+ white-space: nowrap;
151
+ max-width: 100%;
152
+ overflow: hidden;
153
+ box-sizing: border-box;
154
+ }
155
+
156
+ .ml-autocomplete__tag-label {
157
+ overflow: hidden;
158
+ text-overflow: ellipsis;
159
+ line-height: inherit;
160
+ }
161
+
162
+ .ml-autocomplete__tag-avatar {
163
+ width: 14px;
164
+ height: 14px;
165
+ border-radius: var(--ml-radius-full);
166
+ object-fit: cover;
167
+ margin-left: -2px;
168
+ }
169
+
170
+ .ml-autocomplete__tag-remove {
171
+ border: none;
172
+ background: transparent;
173
+ padding: 2px;
174
+ margin-left: var(--ml-space-0-5);
175
+ margin-right: -4px;
176
+ color: var(--ml-color-text-secondary);
177
+ cursor: pointer;
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ flex-shrink: 0;
182
+ border-radius: var(--ml-radius-full);
183
+ transition: color var(--ml-duration-100) var(--ml-ease-in-out),
184
+ background-color var(--ml-duration-100) var(--ml-ease-in-out);
185
+ }
186
+
187
+ .ml-autocomplete__tag-remove:hover {
188
+ color: var(--ml-color-text);
189
+ background-color: var(--ml-color-surface-raised);
190
+ }
191
+
192
+ /* Dropdown */
193
+ .ml-autocomplete__dropdown {
194
+ position: fixed;
195
+ inset: unset;
196
+ margin: 0;
197
+ z-index: 50;
198
+ background-color: var(--ml-color-surface);
199
+ border: var(--ml-border) solid var(--ml-color-border);
200
+ border-radius: var(--ml-radius);
201
+ box-shadow: var(--ml-shadow-lg);
202
+ max-height: 280px;
203
+ overflow-y: auto;
204
+ padding: var(--ml-space-1-5);
205
+ display: flex;
206
+ flex-direction: column;
207
+ gap: var(--ml-space-1);
208
+ }
209
+
210
+ .ml-autocomplete__dropdown:not(:popover-open) {
211
+ display: none;
212
+ }
213
+
214
+ .ml-autocomplete__empty {
215
+ padding: var(--ml-space-2) var(--ml-space-3);
216
+ font-size: var(--ml-text-sm);
217
+ color: var(--ml-color-text-muted);
218
+ }
219
+
220
+ .ml-autocomplete__loading {
221
+ padding: var(--ml-space-3) var(--ml-space-3);
222
+ font-size: var(--ml-text-sm);
223
+ color: var(--ml-color-text-muted);
224
+ display: flex;
225
+ align-items: center;
226
+ gap: var(--ml-space-2);
227
+ justify-content: center;
228
+ }
229
+
230
+ /* Options */
231
+ .ml-autocomplete__option {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: var(--ml-space-2);
235
+ padding: var(--ml-space-2) var(--ml-space-3);
236
+ border-radius: var(--ml-radius-sm);
237
+ cursor: pointer;
238
+ font-size: var(--ml-text-sm);
239
+ font-weight: var(--ml-font-medium);
240
+ color: var(--ml-color-text);
241
+ transition: background-color var(--ml-duration-100) var(--ml-ease-in-out);
242
+ }
243
+
244
+ .ml-autocomplete__option:hover:not(.ml-autocomplete__option--disabled) {
245
+ background-color: var(--ml-color-surface-raised);
246
+ }
247
+
248
+ .ml-autocomplete__option--focused {
249
+ background-color: var(--ml-color-surface-raised);
250
+ }
251
+
252
+ .ml-autocomplete__option--selected {
253
+ background-color: var(--ml-color-primary-subtle);
254
+ }
255
+
256
+ .ml-autocomplete__option--selected:hover:not(.ml-autocomplete__option--disabled) {
257
+ background-color: var(--ml-color-primary-subtle);
258
+ }
259
+
260
+ .ml-autocomplete__option--disabled {
261
+ color: var(--ml-color-text-muted);
262
+ cursor: not-allowed;
263
+ }
264
+
265
+ .ml-autocomplete__option-icon {
266
+ flex-shrink: 0;
267
+ color: var(--ml-color-text-muted);
268
+ }
269
+
270
+ .ml-autocomplete__option-avatar {
271
+ width: 24px;
272
+ height: 24px;
273
+ border-radius: var(--ml-radius-full);
274
+ object-fit: cover;
275
+ flex-shrink: 0;
276
+ }
277
+
278
+ .ml-autocomplete__option--selected .ml-autocomplete__option-icon {
279
+ color: var(--ml-color-primary);
280
+ }
281
+
282
+ .ml-autocomplete__option-content {
283
+ flex: 1;
284
+ min-width: 0;
285
+ display: flex;
286
+ flex-direction: column;
287
+ gap: 1px;
288
+ }
289
+
290
+ .ml-autocomplete__option-label {
291
+ overflow: hidden;
292
+ text-overflow: ellipsis;
293
+ white-space: nowrap;
294
+ }
295
+
296
+ .ml-autocomplete__option-subtitle {
297
+ font-size: var(--ml-text-xs);
298
+ font-weight: var(--ml-font-normal);
299
+ color: var(--ml-color-text-muted);
300
+ overflow: hidden;
301
+ text-overflow: ellipsis;
302
+ white-space: nowrap;
303
+ }
304
+
305
+ .ml-autocomplete__option-check {
306
+ flex-shrink: 0;
307
+ color: var(--ml-color-primary);
308
+ margin-left: auto;
309
+ }
310
+
311
+ /* Hint / Error */
312
+ .ml-autocomplete__hint,
313
+ .ml-autocomplete__error {
314
+ font-size: var(--ml-text-sm);
315
+ line-height: var(--ml-leading-tight);
316
+ }
317
+
318
+ .ml-autocomplete__hint {
319
+ color: var(--ml-color-text-muted);
320
+ }
321
+
322
+ .ml-autocomplete__error {
323
+ color: var(--ml-color-danger);
324
+ }
325
+
326
+ .ml-autocomplete--error .ml-autocomplete__trigger {
327
+ border-color: var(--ml-color-danger);
328
+ }
329
+
330
+ .ml-autocomplete--error .ml-autocomplete__trigger:focus-within {
331
+ box-shadow: var(--ml-shadow-ring-error);
332
+ }
333
+
334
+ /* Size variants */
335
+ .ml-autocomplete--sm .ml-autocomplete__trigger {
336
+ padding: var(--ml-space-2) var(--ml-space-3);
337
+ font-size: var(--ml-text-sm);
338
+ }
339
+
340
+ .ml-autocomplete--md .ml-autocomplete__trigger {
341
+ padding: var(--ml-space-2-5) var(--ml-space-3-5);
342
+ font-size: var(--ml-text-sm);
343
+ }
344
+
345
+ .ml-autocomplete--lg .ml-autocomplete__trigger {
346
+ padding: var(--ml-space-3) var(--ml-space-3-5);
347
+ font-size: var(--ml-text-base);
348
+ }
349
+
350
+ /* Disabled state */
351
+ .ml-autocomplete--disabled {
352
+ pointer-events: none;
353
+ }
354
+ `;
@@ -0,0 +1,3 @@
1
+ import type { AutocompleteComponent } from './autocomplete.component.js';
2
+ export declare function autocompleteTemplate(c: AutocompleteComponent): import("@melodicdev/core").TemplateResult;
3
+ //# sourceMappingURL=autocomplete.template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete.template.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/autocomplete/autocomplete.template.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAGzE,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,qBAAqB,6CA8D5D"}
@@ -0,0 +1,128 @@
1
+ import { html, classMap, when, repeat } from '@melodicdev/core';
2
+ export function autocompleteTemplate(c) {
3
+ return html `
4
+ <div
5
+ class=${classMap({
6
+ 'ml-autocomplete': true,
7
+ [`ml-autocomplete--${c.size}`]: true,
8
+ 'ml-autocomplete--open': c.isOpen,
9
+ 'ml-autocomplete--disabled': c.disabled,
10
+ 'ml-autocomplete--error': !!c.error,
11
+ 'ml-autocomplete--has-value': c.hasValue,
12
+ 'ml-autocomplete--multiple': c.multiple
13
+ })}
14
+ >
15
+ ${when(!!c.label, () => html `
16
+ <label class="ml-autocomplete__label">
17
+ ${c.label}
18
+ ${when(c.required, () => html `<span class="ml-autocomplete__required">*</span>`)}
19
+ </label>
20
+ `)}
21
+
22
+ <div class="ml-autocomplete__control">
23
+ <div
24
+ class="ml-autocomplete__trigger"
25
+ @click=${c.handleInputClick}
26
+ >
27
+ ${when(c.multiple, () => renderMultiValue(c), () => renderSingleValue(c))}
28
+ </div>
29
+
30
+ <div
31
+ class="ml-autocomplete__dropdown"
32
+ role="listbox"
33
+ popover="manual"
34
+ >
35
+ ${c._loading
36
+ ? html `<div class="ml-autocomplete__loading">
37
+ <ml-spinner size="sm"></ml-spinner>
38
+ <span>Searching...</span>
39
+ </div>`
40
+ : c.filteredOptions.length
41
+ ? repeat(c.filteredOptions, (option) => `${option.value}-${c.multiple ? c.values.includes(option.value) : c.value === option.value}`, (option, index) => renderOption(c, option, index))
42
+ : html `<div class="ml-autocomplete__empty">No results found</div>`}
43
+ </div>
44
+ </div>
45
+
46
+ ${when(!!c.error, () => html `<span class="ml-autocomplete__error">${c.error}</span>`, () => html `${when(!!c.hint, () => html `<span class="ml-autocomplete__hint">${c.hint}</span>`)}`)}
47
+ </div>
48
+ `;
49
+ }
50
+ function renderSingleValue(c) {
51
+ const showClear = !c.multiple && c.hasValue && !c.disabled;
52
+ return html `
53
+ ${when(c.showIcon, () => html `<ml-icon icon="magnifying-glass" size="sm" format="regular" class="ml-autocomplete__search-icon"></ml-icon>`)}
54
+ <input
55
+ class="ml-autocomplete__input"
56
+ type="text"
57
+ placeholder=${c.hasValue ? c.displayText : c.placeholder}
58
+ aria-label=${c.label || c.placeholder || 'Search'}
59
+ .value=${c.hasValue && !c.search ? c.displayText : c.search}
60
+ @input=${c.handleInput}
61
+ @focus=${c.handleFocus}
62
+ @click=${c.handleInputClick}
63
+ ?disabled=${c.disabled}
64
+ autocomplete="off"
65
+ />
66
+ ${when(showClear, () => html `
67
+ <button type="button" class="ml-autocomplete__clear" aria-label="Clear selection" @click=${c.handleClear}>
68
+ <ml-icon icon="x" size="sm" format="bold"></ml-icon>
69
+ </button>
70
+ `)}
71
+ `;
72
+ }
73
+ function renderMultiValue(c) {
74
+ return html `
75
+ ${when(c.showIcon, () => html `<ml-icon icon="magnifying-glass" size="sm" format="regular" class="ml-autocomplete__search-icon"></ml-icon>`)}
76
+ <span class="ml-autocomplete__tags">
77
+ ${repeat(c.selectedOptions, (option) => option.value, (option) => html `
78
+ <span class="ml-autocomplete__tag">
79
+ ${option.avatarUrl
80
+ ? html `<img class="ml-autocomplete__tag-avatar" src="${option.avatarUrl}" alt="${option.avatarAlt || option.label}" />`
81
+ : html ``}
82
+ <span class="ml-autocomplete__tag-label">${option.label}</span>
83
+ <button type="button" class="ml-autocomplete__tag-remove" aria-label="Remove ${option.label}" @click=${(event) => c.handleTagRemove(event, option.value)}>
84
+ <ml-icon icon="x" size="sm" format="bold"></ml-icon>
85
+ </button>
86
+ </span>
87
+ `)}
88
+ </span>
89
+ <input
90
+ class="ml-autocomplete__input"
91
+ type="text"
92
+ placeholder=${c.values.length ? '' : c.placeholder}
93
+ aria-label=${c.label || c.placeholder || 'Search'}
94
+ .value=${c.search}
95
+ @input=${c.handleInput}
96
+ @focus=${c.handleFocus}
97
+ @click=${c.handleInputClick}
98
+ ?disabled=${c.disabled}
99
+ autocomplete="off"
100
+ />
101
+ `;
102
+ }
103
+ function renderOption(c, option, index) {
104
+ const isSelected = c.multiple ? c.values.includes(option.value) : c.value === option.value;
105
+ const isFocused = c.focusedIndex === index;
106
+ return html `
107
+ <div
108
+ class=${classMap({
109
+ 'ml-autocomplete__option': true,
110
+ 'ml-autocomplete__option--selected': isSelected,
111
+ 'ml-autocomplete__option--focused': isFocused,
112
+ 'ml-autocomplete__option--disabled': !!option.disabled
113
+ })}
114
+ role="option"
115
+ aria-selected=${isSelected}
116
+ aria-disabled=${option.disabled || false}
117
+ @click=${(e) => c.handleOptionClick(e, option)}
118
+ >
119
+ ${when(!!option.avatarUrl, () => html `<img class="ml-autocomplete__option-avatar" src="${option.avatarUrl}" alt="${option.avatarAlt || option.label}" />`)}
120
+ ${when(!option.avatarUrl && !!option.icon, () => html `<ml-icon icon="${option.icon}" size="sm" class="ml-autocomplete__option-icon"></ml-icon>`)}
121
+ <span class="ml-autocomplete__option-content">
122
+ <span class="ml-autocomplete__option-label">${option.label}</span>
123
+ ${when(!!option.subtitle, () => html `<span class="ml-autocomplete__option-subtitle">${option.subtitle}</span>`)}
124
+ </span>
125
+ ${when(isSelected, () => html `<ml-icon icon="check" size="sm" format="regular" class="ml-autocomplete__option-check"></ml-icon>`)}
126
+ </div>
127
+ `;
128
+ }
@@ -0,0 +1,18 @@
1
+ export interface AutocompleteOption {
2
+ /** Unique value for the option */
3
+ value: string;
4
+ /** Display label */
5
+ label: string;
6
+ /** Optional secondary text */
7
+ subtitle?: string;
8
+ /** Optional avatar image */
9
+ avatarUrl?: string;
10
+ /** Optional avatar alt text */
11
+ avatarAlt?: string;
12
+ /** Optional icon name (Phosphor icon) */
13
+ icon?: string;
14
+ /** Whether the option is disabled */
15
+ disabled?: boolean;
16
+ }
17
+ export type AutocompleteSearchFn = (query: string) => Promise<AutocompleteOption[]>;
18
+ //# sourceMappingURL=autocomplete.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete.types.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/autocomplete/autocomplete.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IAClC,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IAEd,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IAEd,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { AutocompleteComponent } from './autocomplete.component.js';
2
+ export { autocompleteTemplate } from './autocomplete.template.js';
3
+ export { autocompleteStyles } from './autocomplete.styles.js';
4
+ export type { AutocompleteOption, AutocompleteSearchFn } from './autocomplete.types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/autocomplete/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { AutocompleteComponent } from './autocomplete.component.js';
2
+ export { autocompleteTemplate } from './autocomplete.template.js';
3
+ export { autocompleteStyles } from './autocomplete.styles.js';
package/lib/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './components/forms/checkbox/index.js';
8
8
  export * from './components/forms/radio/index.js';
9
9
  export * from './components/forms/toggle/index.js';
10
10
  export * from './components/forms/select/index.js';
11
+ export * from './components/forms/autocomplete/index.js';
11
12
  export * from './components/forms/slider/index.js';
12
13
  export * from './components/forms/form-field/index.js';
13
14
  export * from './components/feedback/spinner/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AAGvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AAGxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAG3D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,0CAA0C,CAAC;AAGzD,cAAc,oCAAoC,CAAC;AAGnD,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,oCAAoC,CAAC;AACnD,cAAc,wCAAwC,CAAC;AAGvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AAGxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAG3D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,0CAA0C,CAAC;AAGzD,cAAc,oCAAoC,CAAC;AAGnD,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
package/lib/index.js CHANGED
@@ -11,6 +11,7 @@ export * from './components/forms/checkbox/index.js';
11
11
  export * from './components/forms/radio/index.js';
12
12
  export * from './components/forms/toggle/index.js';
13
13
  export * from './components/forms/select/index.js';
14
+ export * from './components/forms/autocomplete/index.js';
14
15
  export * from './components/forms/slider/index.js';
15
16
  export * from './components/forms/form-field/index.js';
16
17
  // Components - Feedback
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melodicdev/components",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Themeable UI component library built on the Melodic Framework",
5
5
  "license": "MIT",
6
6
  "author": "Melodic Development",
@@ -193,6 +193,10 @@
193
193
  "types": "./lib/components/forms/select/index.d.ts",
194
194
  "default": "./lib/components/forms/select/index.js"
195
195
  },
196
+ "./autocomplete": {
197
+ "types": "./lib/components/forms/autocomplete/index.d.ts",
198
+ "default": "./lib/components/forms/autocomplete/index.js"
199
+ },
196
200
  "./sidebar": {
197
201
  "types": "./lib/components/navigation/sidebar/index.d.ts",
198
202
  "default": "./lib/components/navigation/sidebar/index.js"