fancy-ui-ts 1.2.0 → 1.3.1

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.
package/dist/index.css CHANGED
@@ -94,13 +94,13 @@
94
94
  --fc-option-bg: var(--fc-white);
95
95
  --fc-option-bg-disabled: var(--fc-gray-50);
96
96
  --fc-option-bg-hover: var(--fc-gray-100);
97
- --fc-option-bg-selected: var(--fc-primary-200);
98
97
  --fc-option-bg-active: var(--fc-primary-100);
99
98
  --fc-option-fg: var(--fc-gray-900);
100
99
  --fc-option-fg-disabled: var(--fc-gray-300);
101
- --fc-option-fg-selected: var(--fc-primary-700);
102
100
  --fc-option-padding: var(--fc-space-3);
103
101
  --fc-option-radius: var(--fc-radius-md);
102
+ --fc-option-icon-width: calc(var(--fc-font-size-md) + 1px);
103
+ --fc-option-icon-height: calc(var(--fc-font-size-md) + 1px);
104
104
  --fc-input-bg: var(--fc-white);
105
105
  --fc-input-bg-disabled: var(--fc-gray-50);
106
106
  --fc-input-fg: var(--fc-gray-900);
@@ -115,6 +115,8 @@
115
115
  --fc-input-focus-ring-error: var(--fc-focus-ring-danger);
116
116
  --fc-input-password-icon-color: var(--fc-gray-400);
117
117
  --fc-input-password-icon-color-hover: var(--fc-gray-500);
118
+ --fc-input-password-icon-width: calc(var(--fc-font-size-md) + 2px);
119
+ --fc-input-password-icon-height: calc(var(--fc-font-size-md) + 2px);
118
120
  --fc-input-file-border: var(--fc-gray-400);
119
121
  --fc-input-file-border-hover: var(--fc-gray-400);
120
122
  --fc-input-file-btn-bg: var(--fc-gray-400);
@@ -132,6 +134,25 @@
132
134
  --fc-label-font-size: var(--fc-font-size-md);
133
135
  --fc-label-font-weight: var(--fc-font-weight-bold);
134
136
  --fc-label-fg: var(--fc-gray-900);
137
+ --fc-select-bg: var(--fc-white);
138
+ --fc-select-bg-disabled: var(--fc-gray-50);
139
+ --fc-select-fg: var(--fc-gray-900);
140
+ --fc-select-border: var(--fc-gray-300);
141
+ --fc-select-border-hover: var(--fc-gray-400);
142
+ --fc-select-border-focus: var(--fc-primary-400);
143
+ --fc-select-placeholder: var(--fc-gray-400);
144
+ --fc-select-placeholder-disabled: var(--fc-gray-300);
145
+ --fc-select-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-primary);
146
+ --fc-select-bg-error: var(--fc-danger-50);
147
+ --fc-select-border-error: var(--fc-danger-300);
148
+ --fc-select-focus-ring-error: var(--fc-focus-ring-danger);
149
+ --fc-select-dropdown-icon-width: calc(var(--fc-font-size-md) + 4px);
150
+ --fc-select-dropdown-icon-height: calc(var(--fc-font-size-md) + 4px);
151
+ --fc-select-max-width: 412px;
152
+ --fc-select-padding: var(--fc-space-3);
153
+ --fc-select-border-width: var(--fc-border-width-xs);
154
+ --fc-select-radius: var(--fc-radius-md);
155
+ --fc-select-shadow: var(--fc-shadow-none);
135
156
  }
136
157
 
137
158
  /* src/styles/themes/fc-theme-light.css */
@@ -152,11 +173,9 @@
152
173
  --fc-option-bg: var(--fc-white);
153
174
  --fc-option-bg-disabled: var(--fc-gray-50);
154
175
  --fc-option-bg-hover: var(--fc-gray-100);
155
- --fc-option-bg-selected: var(--fc-primary-200);
156
176
  --fc-option-bg-active: var(--fc-primary-100);
157
177
  --fc-option-fg: var(--fc-gray-900);
158
178
  --fc-option-fg-disabled: var(--fc-gray-300);
159
- --fc-option-fg-selected: var(--fc-primary-700);
160
179
  --fc-input-bg: var(--fc-white);
161
180
  --fc-input-bg-disabled: var(--fc-gray-50);
162
181
  --fc-input-fg: var(--fc-gray-900);
@@ -186,6 +205,18 @@
186
205
  --fc-label-font-size: var(--fc-font-size-md);
187
206
  --fc-label-font-weight: var(--fc-font-weight-bold);
188
207
  --fc-label-fg: var(--fc-gray-900);
208
+ --fc-select-bg: var(--fc-white);
209
+ --fc-select-bg-disabled: var(--fc-gray-50);
210
+ --fc-select-fg: var(--fc-gray-900);
211
+ --fc-select-border: var(--fc-gray-300);
212
+ --fc-select-border-hover: var(--fc-gray-400);
213
+ --fc-select-border-focus: var(--fc-primary-400);
214
+ --fc-select-placeholder: var(--fc-gray-400);
215
+ --fc-select-placeholder-disabled: var(--fc-gray-300);
216
+ --fc-select-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-primary);
217
+ --fc-select-bg-error: var(--fc-danger-50);
218
+ --fc-select-border-error: var(--fc-danger-300);
219
+ --fc-select-focus-ring-error: var(--fc-focus-ring-danger);
189
220
  }
190
221
 
191
222
  /* src/styles/themes/fc-theme-dark.css */
@@ -207,10 +238,8 @@
207
238
  --fc-option-bg-disabled: var(--fc-gray-900);
208
239
  --fc-option-bg-hover: var(--fc-gray-700);
209
240
  --fc-option-bg-active: var(--fc-primary-500);
210
- --fc-option-bg-selected: var(--fc-primary-600);
211
241
  --fc-option-fg: var(--fc-gray-200);
212
242
  --fc-option-fg-disabled: var(--fc-gray-700);
213
- --fc-option-fg-selected: var(--fc-primary-50);
214
243
  --fc-input-bg: var(--fc-gray-800);
215
244
  --fc-input-bg-disabled: var(--fc-gray-900);
216
245
  --fc-input-fg: var(--fc-gray-200);
@@ -240,4 +269,13 @@
240
269
  --fc-label-font-size: var(--fc-font-size-md);
241
270
  --fc-label-font-weight: var(--fc-font-weight-bold);
242
271
  --fc-label-fg: var(--fc-gray-200);
272
+ --fc-select-bg: var(--fc-gray-800);
273
+ --fc-select-bg-disabled: var(--fc-gray-900);
274
+ --fc-select-fg: var(--fc-gray-200);
275
+ --fc-select-border: var(--fc-gray-700);
276
+ --fc-select-border-hover: var(--fc-gray-500);
277
+ --fc-select-border-focus: var(--fc-primary-400);
278
+ --fc-select-placeholder: var(--fc-gray-500);
279
+ --fc-select-placeholder-disabled: var(--fc-gray-700);
280
+ --fc-select-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-secondary);
243
281
  }
package/dist/index.d.ts CHANGED
@@ -57,9 +57,8 @@ declare class FcCombobox extends HTMLElement {
57
57
  private onBlur;
58
58
  private onInvalid;
59
59
  private setActiveOption;
60
- private getVisibleOptions;
61
- private selectOption;
62
- private toggleDropdown;
60
+ private showDropdown;
61
+ private hideDropdown;
63
62
  private syncValidity;
64
63
  setProps(props: Record<string, any>): void;
65
64
  }
@@ -182,6 +181,71 @@ declare class FcLabel extends HTMLElement {
182
181
 
183
182
  declare const defineLabel: () => typeof FcLabel;
184
183
 
184
+ declare class FcSelect extends HTMLElement {
185
+ static get observedAttributes(): string[];
186
+ private inputEl;
187
+ private dropdownEl;
188
+ static formAssociated: boolean;
189
+ private internals;
190
+ private _value;
191
+ private activeIndex;
192
+ private searchBuffer;
193
+ private searchTimeout;
194
+ private _validatorFunction;
195
+ constructor();
196
+ get validity(): ValidityState;
197
+ get validationMessage(): string;
198
+ get willValidate(): boolean;
199
+ checkValidity(): boolean;
200
+ reportValidity(): boolean;
201
+ get placeholder(): string;
202
+ set placeholder(val: string);
203
+ get name(): string;
204
+ set name(val: string);
205
+ get disabled(): boolean;
206
+ set disabled(val: boolean);
207
+ get value(): string;
208
+ set value(newValue: string);
209
+ get label(): string;
210
+ get options(): {
211
+ label: string;
212
+ value: string;
213
+ disabled?: boolean;
214
+ }[];
215
+ set options(data: {
216
+ label: string;
217
+ value: string;
218
+ disabled?: boolean;
219
+ }[]);
220
+ get required(): boolean;
221
+ set required(val: boolean);
222
+ get readonly(): boolean;
223
+ connectedCallback(): void;
224
+ attributeChangedCallback(name: string, _old: string, newVal: string): void;
225
+ disconnectedCallback(): void;
226
+ formResetCallback(): void;
227
+ formStateRestoreCallback(state: string | File | FormData | null, mode: 'restore' | 'autocomplete'): void;
228
+ private onClick;
229
+ private onChange;
230
+ private onOptionSelect;
231
+ private onOutsideClick;
232
+ private onFocusOut;
233
+ private onSlotChange;
234
+ private onKeyDown;
235
+ private handleTypeAhead;
236
+ private onBlur;
237
+ private onInvalid;
238
+ private setActiveOption;
239
+ private getVisibleOptions;
240
+ private selectOption;
241
+ private showDropdown;
242
+ private hideDropdown;
243
+ private syncValidity;
244
+ setProps(props: Record<string, any>): void;
245
+ }
246
+
247
+ declare const defineSelect: () => typeof FcSelect;
248
+
185
249
  declare const defineAll: () => void;
186
250
 
187
- export { FcCombobox, FcError, FcInput, FcLabel, FcOption, defineAll, defineCombobox, defineError, defineInput, defineLabel, defineOption };
251
+ export { FcCombobox, FcError, FcInput, FcLabel, FcOption, FcSelect, defineAll, defineCombobox, defineError, defineInput, defineLabel, defineOption, defineSelect };
package/dist/index.js CHANGED
@@ -185,7 +185,6 @@ var FcCombobox = class extends HTMLElement {
185
185
  this.inputEl.addEventListener("input", this.onInput);
186
186
  this.inputEl.addEventListener("change", this.onChange);
187
187
  this.addEventListener("fc-option-select", this.onOptionSelect);
188
- document.addEventListener("click", this.onOutsideClick);
189
188
  this.addEventListener("focusout", this.onFocusOut);
190
189
  this.inputEl.addEventListener("focus", this.onFocus);
191
190
  this.inputEl.addEventListener("blur", this.onBlur);
@@ -210,7 +209,7 @@ var FcCombobox = class extends HTMLElement {
210
209
  this.inputEl.disabled = isDisabled;
211
210
  this.internals.ariaDisabled = isDisabled ? "true" : "false";
212
211
  if (isDisabled) {
213
- this.toggleDropdown(false);
212
+ this.hideDropdown();
214
213
  }
215
214
  }
216
215
  if (name === "required" && this.inputEl) {
@@ -240,7 +239,7 @@ var FcCombobox = class extends HTMLElement {
240
239
  option.selected = false;
241
240
  option.hidden = false;
242
241
  });
243
- this.toggleDropdown(false);
242
+ this.hideDropdown();
244
243
  this.removeAttribute("touched");
245
244
  this.syncValidity();
246
245
  this.dispatchEvent(new CustomEvent("fc-reset", {
@@ -287,7 +286,7 @@ var FcCombobox = class extends HTMLElement {
287
286
  this._value = "";
288
287
  this.internals.setFormValue("");
289
288
  this.inputEl.removeAttribute("aria-activedescendant");
290
- this.toggleDropdown(true);
289
+ this.showDropdown();
291
290
  this.syncValidity();
292
291
  this.dispatchEvent(new CustomEvent("fc-input", {
293
292
  detail: {
@@ -331,7 +330,7 @@ var FcCombobox = class extends HTMLElement {
331
330
  bubbles: true,
332
331
  composed: true
333
332
  }));
334
- this.toggleDropdown(hasMatch);
333
+ hasMatch ? this.showDropdown() : this.hideDropdown();
335
334
  }
336
335
  onChange(e) {
337
336
  e.stopPropagation();
@@ -359,7 +358,7 @@ var FcCombobox = class extends HTMLElement {
359
358
  option.hidden = !selected;
360
359
  option.active = false;
361
360
  });
362
- this.toggleDropdown(false);
361
+ this.hideDropdown();
363
362
  this.syncValidity();
364
363
  this.dispatchEvent(new CustomEvent("fc-change", {
365
364
  detail: {
@@ -372,12 +371,13 @@ var FcCombobox = class extends HTMLElement {
372
371
  }
373
372
  onOutsideClick(e) {
374
373
  if (!this.contains(e.target)) {
375
- this.toggleDropdown(false);
374
+ this.hideDropdown();
376
375
  }
377
376
  }
378
377
  onFocusOut(e) {
379
- if (!this.contains(e.relatedTarget)) {
380
- this.toggleDropdown(false);
378
+ const target = e.relatedTarget;
379
+ if (!target || !this.contains(target)) {
380
+ this.hideDropdown();
381
381
  }
382
382
  }
383
383
  onFocus(e) {
@@ -390,7 +390,7 @@ var FcCombobox = class extends HTMLElement {
390
390
  const label = (option.getAttribute("label") || option.textContent || "").toLowerCase();
391
391
  return label.includes(query);
392
392
  });
393
- this.toggleDropdown(match);
393
+ match ? this.showDropdown() : this.hideDropdown();
394
394
  }
395
395
  onSlotChange() {
396
396
  if (!this._value) {
@@ -410,7 +410,6 @@ var FcCombobox = class extends HTMLElement {
410
410
  if (!foundMatch && this.inputEl.value === "") {
411
411
  this.inputEl.value = this._value;
412
412
  options.forEach(option => {
413
- console.log(option.value);
414
413
  const match = option.label.toLowerCase().includes(this._value.toLowerCase());
415
414
  option.hidden = !match;
416
415
  });
@@ -421,31 +420,50 @@ var FcCombobox = class extends HTMLElement {
421
420
  if (this.disabled) {
422
421
  return;
423
422
  }
424
- const options = this.getVisibleOptions();
425
- if (options.length === 0) {
423
+ const options = this.querySelectorAll("fc-option");
424
+ const visibleOptions = Array.from(options).filter(opt => !opt.hidden && !opt.disabled);
425
+ if (visibleOptions.length === 0) {
426
426
  return;
427
427
  }
428
428
  if (e.key === "ArrowDown") {
429
429
  e.preventDefault();
430
- this.toggleDropdown(true);
431
- const nextIndex = this.activeIndex >= options.length - 1 ? 0 : this.activeIndex + 1;
430
+ this.showDropdown();
431
+ const nextIndex = this.activeIndex >= visibleOptions.length - 1 ? 0 : this.activeIndex + 1;
432
432
  this.setActiveOption(nextIndex, options);
433
433
  } else if (e.key === "ArrowUp") {
434
434
  e.preventDefault();
435
- this.toggleDropdown(true);
436
- const prevIndex = this.activeIndex <= 0 ? options.length - 1 : this.activeIndex - 1;
435
+ this.showDropdown();
436
+ const prevIndex = this.activeIndex <= 0 ? visibleOptions.length - 1 : this.activeIndex - 1;
437
437
  this.setActiveOption(prevIndex, options);
438
438
  } else if (e.key === "Enter") {
439
439
  e.preventDefault();
440
- if (this.activeIndex > -1 && options[this.activeIndex]) {
441
- const target = options[this.activeIndex];
442
- this.selectOption(target);
440
+ if (this.activeIndex > -1 && visibleOptions[this.activeIndex]) {
441
+ const target = visibleOptions[this.activeIndex];
442
+ this.inputEl.value = target.label;
443
+ this._value = target.value;
444
+ this.internals.setFormValue(target.value);
445
+ options.forEach(option => {
446
+ const selected = option.value === target.value;
447
+ option.selected = selected;
448
+ option.hidden = !selected;
449
+ option.active = false;
450
+ });
451
+ this.hideDropdown();
452
+ this.syncValidity();
453
+ this.dispatchEvent(new CustomEvent("fc-change", {
454
+ detail: {
455
+ value: target.value,
456
+ label: target.label
457
+ },
458
+ bubbles: true,
459
+ composed: true
460
+ }));
443
461
  }
444
462
  } else if (e.key === "Escape") {
445
463
  e.preventDefault();
446
- this.toggleDropdown(false);
464
+ this.hideDropdown();
447
465
  } else if (e.key === "Tab") {
448
- this.toggleDropdown(false);
466
+ this.hideDropdown();
449
467
  }
450
468
  }
451
469
  onBlur() {
@@ -453,10 +471,18 @@ var FcCombobox = class extends HTMLElement {
453
471
  }
454
472
  onInvalid(e) {
455
473
  this.setAttribute("touched", "");
474
+ this.dispatchEvent(new CustomEvent("fc-invalid", {
475
+ bubbles: true,
476
+ composed: true,
477
+ detail: {
478
+ originalEvent: e
479
+ }
480
+ }));
456
481
  }
457
- setActiveOption(index, visibleOptions) {
458
- this.querySelectorAll("fc-option").forEach(opt => opt.active = false);
482
+ setActiveOption(index, allOptions) {
483
+ allOptions.forEach(opt => opt.active = false);
459
484
  this.activeIndex = index;
485
+ const visibleOptions = Array.from(allOptions).filter(opt => !opt.hidden && !opt.disabled);
460
486
  const target = visibleOptions[index];
461
487
  if (target) {
462
488
  target.active = true;
@@ -469,51 +495,43 @@ var FcCombobox = class extends HTMLElement {
469
495
  }
470
496
  this.inputEl.removeAttribute("aria-activedescendant");
471
497
  }
472
- getVisibleOptions() {
473
- return Array.from(this.querySelectorAll("fc-option")).filter(opt => !opt.hidden && !opt.disabled);
474
- }
475
- selectOption(option) {
476
- const value = option.value;
477
- const label = option.label;
478
- this.inputEl.value = label;
479
- this._value = value;
480
- this.internals.setFormValue(value);
481
- const allOptions = this.querySelectorAll("fc-option");
482
- allOptions.forEach(opt => {
483
- const selected = opt.value === value;
484
- opt.selected = selected;
485
- opt.hidden = !selected;
486
- opt.active = false;
487
- });
488
- this.toggleDropdown(false);
489
- this.syncValidity();
490
- this.dispatchEvent(new CustomEvent("fc-change", {
491
- detail: {
492
- value: value,
493
- label: label
494
- },
495
- bubbles: true,
496
- composed: true
497
- }));
498
- }
499
- toggleDropdown(show) {
500
- if (!this.dropdownEl) {
498
+ showDropdown() {
499
+ if (!this.dropdownEl || this.disabled) {
501
500
  return;
502
501
  }
503
- if (this.disabled && show) {
502
+ const dropdown = this.dropdownEl;
503
+ const showEvent = new CustomEvent("fc-show", {
504
+ bubbles: true,
505
+ composed: true,
506
+ cancelable: true
507
+ });
508
+ this.dispatchEvent(showEvent);
509
+ if (showEvent.defaultPrevented) {
504
510
  return;
505
511
  }
506
- const dropdown = this.dropdownEl;
507
- if (show) {
508
- const spaceBelow = calculateBottomAvaliableSpace(this.inputEl);
509
- const spaceAbove = calculateTopAvaliableSpace(this.inputEl);
510
- dropdown.hidden = false;
511
- this.setAttribute("open", "true");
512
- this.inputEl.setAttribute("aria-expanded", "true");
513
- const shouldOpenUp = spaceBelow < dropdown.clientHeight && spaceAbove > spaceBelow;
514
- dropdown.classList.toggle("opens-up", shouldOpenUp);
512
+ const spaceBelow = calculateBottomAvaliableSpace(this.inputEl);
513
+ const spaceAbove = calculateTopAvaliableSpace(this.inputEl);
514
+ dropdown.hidden = false;
515
+ this.setAttribute("open", "true");
516
+ this.inputEl.setAttribute("aria-expanded", "true");
517
+ const shouldOpenUp = spaceBelow < dropdown.clientHeight && spaceAbove > spaceBelow;
518
+ dropdown.classList.toggle("opens-up", shouldOpenUp);
519
+ setTimeout(() => {
520
+ document.addEventListener("click", this.onOutsideClick);
521
+ }, 0);
522
+ return;
523
+ }
524
+ hideDropdown() {
525
+ if (!this.dropdownEl) {
515
526
  return;
516
527
  }
528
+ if (!this.dropdownEl.hidden) {
529
+ this.dispatchEvent(new CustomEvent("fc-hide", {
530
+ bubbles: true,
531
+ composed: true
532
+ }));
533
+ }
534
+ document.removeEventListener("click", this.onOutsideClick);
517
535
  this.dropdownEl.hidden = true;
518
536
  this.removeAttribute("open");
519
537
  this.inputEl.setAttribute("aria-expanded", "false");
@@ -578,11 +596,11 @@ var defineCombobox = () => {
578
596
  return FcCombobox;
579
597
  };
580
598
 
581
- var styles2 = `\n\t:host {\n\t\tdisplay: block;\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n \tfont-family: var(--fc-font-family);\n\t}\n\n\t:host([hidden]) {\n display: none !important;\n }\n\n\t:host([disabled]) {\n cursor: not-allowed;\n }\n\t\t\n\t\t\n\tbutton.fc-option {\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n\t\ttext-align: left;\n\t\tbackground: var(--fc-option-bg);\n\t\tcolor: var(--fc-option-fg);\n\t\tpadding: var(--fc-option-padding);\n\t\tborder-radius: var(--fc-option-radius);\n\t\tborder: none;\n\t\tfont: inherit;\n\t\tcursor: pointer;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\tbutton.fc-option:hover {\n\t\tbackground: var(--fc-option-bg-hover);\n\t\ttransition: background 0.15s ease-in-out, color 0.15s ease-in-out;\n\t}\n\n\tbutton.fc-option[data-active="true"] { \n background: var(--fc-option-bg-active);\n }\n\n\tbutton.fc-option:disabled {\n\t\tcolor: var(--fc-option-fg-disabled);\n\t\tbackground: var(--fc-option-bg-disabled);\n\t\tpointer-events: none; // this prevents disabled attribute on button to move focus out of the fc-combobox\n\t\tbox-shadow: none;\n\t}\n\n button.fc-option:disabled:hover {\n\t\tbackground: var(--fc-option-bg-disabled);\n\t}\n\n\tbutton.fc-option[aria-selected="true"] {\n\t\tbackground: var(--fc-option-bg-selected);\n\t\tcolor: var(--fc-option-fg-selected);\n\t}\n\n`;
599
+ var styles2 = `\n\t:host {\n\t\tdisplay: block;\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n \tfont-family: var(--fc-font-family);\n\t}\n\n\t:host([hidden]) {\n display: none !important;\n }\n\n\t:host([disabled]) {\n cursor: not-allowed;\n }\n\t\t\n\tbutton.fc-option {\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n\t\ttext-align: left;\n\t\tbackground: var(--fc-option-bg);\n\t\tcolor: var(--fc-option-fg);\n\t\tpadding: var(--fc-option-padding);\n\t\tborder-radius: var(--fc-option-radius);\n\t\tborder: none;\n\t\tfont: inherit;\n\t\tcursor: pointer;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\n\t\tdisplay: flex;\n justify-content: space-between;\n align-items: center;\n\t}\n\n\t.fc-option-text {\n flex-grow: 1;\n\t\tfont-size: var(--fc-font-size-md);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-right: 12px;\n }\n\n .fc-option-icon {\n display: none;\n align-items: center;\n\t\tjustify-content: center;\n color: inherit; \n }\n\t\n\t.fc-option-icon svg {\n\t\twidth: var(--fc-option-icon-width);\n\t\theight: var(--fc-option-icon-height);\n }\n\n\tbutton.fc-option:hover {\n\t\tbackground: var(--fc-option-bg-hover);\n\t\ttransition: background 0.15s ease-in-out, color 0.15s ease-in-out;\n\t}\n\n\tbutton.fc-option[data-active="true"] { \n background: var(--fc-option-bg-active);\n }\n\n\tbutton.fc-option:disabled {\n\t\tcolor: var(--fc-option-fg-disabled);\n\t\tbackground: var(--fc-option-bg-disabled);\n\t\tpointer-events: none; // this prevents disabled attribute on button to move focus out of the fc-combobox\n\t\tbox-shadow: none;\n\t}\n\n button.fc-option:disabled:hover {\n\t\tbackground: var(--fc-option-bg-disabled);\n\t}\n\n\tbutton.fc-option[aria-selected="true"] .fc-option-icon {\n display: flex;\n }\n\n`;
582
600
 
583
601
  var template2 = document.createElement("template");
584
602
 
585
- template2.innerHTML = `\n\t<style>${styles2}</style>\n\t<button part="base" class="fc-option" role="option">\n\t\t<slot></slot>\n\t</button>\n`;
603
+ template2.innerHTML = `\n <style>${styles2}</style>\n \n <button part="container" class="fc-option" role="option">\n \n <span class="fc-option-text" part="text">\n <slot></slot>\n </span>\n\n <span class="fc-option-icon" part="icon" aria-hidden="true">\n <slot name="checked-icon">\n\t\t\t\t<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">\n <polyline points="20 6.5 9 17.5 4 12.5"></polyline>\n </svg>\n </slot>\n </span>\n\n </button>\n`;
586
604
 
587
605
  var FcOption = class extends HTMLElement {
588
606
  static get observedAttributes() {
@@ -718,7 +736,7 @@ var defineOption = () => {
718
736
  return FcOption;
719
737
  };
720
738
 
721
- var styles3 = `\n\t:host {\n\t\tdisplay: block;\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n \tfont-family: var(--fc-font-family);\n\t\tmax-width: var(--fc-input-max-width);\n\t}\n\n\t:host([disabled]) {\n\t\tcursor: not-allowed;\n\t}\n\n\t:host([hidden]) {\n\t\tdisplay: none !important;\n\t}\n\n\t/* only show invalid style if the user has touched the field (blurred). the :invalid pseudo-class comes from \n\tinternals.setValidity() logic. */\n\n\t:host([touched]:invalid) .fc-input-field {\n\t\tbackground: var(--fc-input-bg-error);\n\t\tborder-color: var(--fc-input-border-error);\n\t}\n\n\t:host([touched]:invalid) .fc-input-field:focus {\n\t\tbox-shadow: 0 0 0 2px var(--fc-input-focus-ring-error);\n\t}\n\n\t.fc-input-wrapper {\n\t\tposition: relative;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t}\n\n\t.fc-input-field {\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n\n\t\tpadding: var(--fc-input-padding);\n\t\tborder-radius: var(--fc-input-radius);\n\t\tbackground: var(--fc-input-bg);\n\t\tcolor: var(--fc-input-fg);\n\n\t\tborder: var(--fc-input-border-width) solid var(--fc-input-border);\n\n\t\tfont-size: var(--fc-font-size-md);\n\n\t\tbox-shadow: var(--fc-input-shadow);\n\t\ttransition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n\t\tfont-family: inherit;\n\n\t\t-webkit-appearance: none;\n\t\tappearance: none;\n\t}\n\n\t.fc-input-field::placeholder {\n\t\tcolor: var(--fc-input-placeholder);\n\t}\n\n\t.fc-input-field:hover {\n\t\tborder-color: var(--fc-input-border-hover);\n\t}\n\n\t.fc-input-field:focus {\n\t\tborder-color: var(--fc-input-border-focus);\n\t\toutline: none;\n\t\tbox-shadow: var(--fc-input-focus-ring);\n\t}\n\n\t.fc-input-field:disabled {\n\t\tbackground: var(--fc-input-bg-disabled);\n\t\tcursor: not-allowed;\n\t\tbox-shadow: none;\n\t}\n\n\t.fc-input-field:disabled::placeholder {\n\t\tcolor: var(--fc-input-placeholder-disabled);\n }\n\n\t.fc-input-field:disabled:hover {\n\t\tborder-color: var(--fc-input-border);\n\t}\n\n\t.fc-input-field:disabled:focus {\n\t\tborder-color: var(--fc-input-border);\n\t}\n\t\t\n\t/* FILE type specific CSS */\n\n\t.fc-input-field[type="file"] {\n\t\tpadding: calc(var(--fc-input-padding));\n\t\tcursor: pointer;\n\t\tdisplay: flex; \n \talign-items: center;\n\t\tborder-color: var(--fc-input-file-border);\n\t\ttransition: border-color 0.15s ease-in-out, color 0.15s ease-in-out;\n\t}\n\n\t.fc-input-field[type="file"]:focus {\n\t\tborder-color: var(--fc-input-border-focus);\n\t\toutline: none;\n\t\tbox-shadow: var(--fc-input-focus-ring);\n\t}\n\n\t/* target the button inside type="file" */\n\n\t.fc-input-field::file-selector-button {\n\t\tpadding: 4px 10px;\n\t\tborder-radius: var(--fc-input-radius);\n\t\tbackground: var(--fc-input-btn-bg);\n\t\tcolor: var(--fc-input-file-btn-fg);\n\t\tborder: 1px solid var(--fc-input-file-border);\n\t\tcursor: pointer;\n\t\tfont-family: inherit;\n\t\ttransition: background-color 0.15s ease;\n\t}\n\n\t/* legacy browsers */\n\t.fc-input-field::-webkit-file-upload-button {\n\t\tmargin-right: 12px;\n\t\tpadding: 4px 10px;\n\t\tborder-radius: var(--fc-input-radius);\n\t\tbackground: var(--fc-input-file-btn-bg);\n\t\tcolor: var(--fc-input-file-btn-fg);\n\t\tborder: 1px solid var(--fc-input-file-border);\n\t\tcursor: pointer;\n\t\tfont-family: inherit;\n\t\tfont-size: 0.9em;\n\t}\n\n\t/* Hover effects for the button */\n\n\t.fc-input-field::file-selector-button:hover {\n\t\tbackground: var(--fc-input-file-btn-bg-hover); \n\t}\n\n\n\t.fc-input-field::-webkit-file-upload-button:hover {\n\t\tbackground: var(--fc-input-file-btn-bg-hover);\n\t}\n\n\t/* PASSWORD type specific CSS */\n\n\t/* when password toggle is visible, add padding to input so text doesn't overlap icon */\n\t:host([type="password"]) .fc-input-field {\n\t\tpadding-right: 40px; \n\t}\n\n\t.fc-password-toggle {\n\t\tposition: absolute;\n\t\tright: 8px; /* position inside the input */\n\t\ttop: 50%;\n\t\ttransform: translateY(-50%);\n\t\tbackground: transparent;\n\t\tborder: none;\n\t\tcursor: pointer;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tcolor: var(--fc-input-password-icon-color);\n\t\ttransition: color 0.15s ease-in-out;\n\t\tz-index: 2; /* above input */\n\t}\n\t\n\t.fc-password-toggle[hidden] {\n display: none !important;\n }\n\n\t.fc-password-toggle:hover {\n\t\tcolor: var(--fc-input-password-icon-color-hover);\n\t}\n\t\n\t.fc-password-toggle svg {\n\t\twidth: 20px;\n\t\theight: 20px;\n\t}\n\n\t.fc-password-toggle svg[hidden] {\n display: none !important;\n }\n\n`;
739
+ var styles3 = `\n\t:host {\n\t\tdisplay: block;\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n \tfont-family: var(--fc-font-family);\n\t\tmax-width: var(--fc-input-max-width);\n\t}\n\n\t:host([disabled]) {\n\t\tcursor: not-allowed;\n\t}\n\n\t:host([hidden]) {\n\t\tdisplay: none !important;\n\t}\n\n\t/* only show invalid style if the user has touched the field (blurred). the :invalid pseudo-class comes from \n\tinternals.setValidity() logic. */\n\n\t:host([touched]:invalid) .fc-input-field {\n\t\tbackground: var(--fc-input-bg-error);\n\t\tborder-color: var(--fc-input-border-error);\n\t}\n\n\t:host([touched]:invalid) .fc-input-field:focus {\n\t\tbox-shadow: 0 0 0 2px var(--fc-input-focus-ring-error);\n\t}\n\n\t.fc-input-wrapper {\n\t\tposition: relative;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t}\n\n\t.fc-input-field {\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n\n\t\tpadding: var(--fc-input-padding);\n\t\tborder-radius: var(--fc-input-radius);\n\t\tbackground: var(--fc-input-bg);\n\t\tcolor: var(--fc-input-fg);\n\n\t\tborder: var(--fc-input-border-width) solid var(--fc-input-border);\n\n\t\tfont-size: var(--fc-font-size-md);\n\n\t\tbox-shadow: var(--fc-input-shadow);\n\t\ttransition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n\t\tfont-family: inherit;\n\n\t\t-webkit-appearance: none;\n\t\tappearance: none;\n\t}\n\n\t.fc-input-field::placeholder {\n\t\tcolor: var(--fc-input-placeholder);\n\t}\n\n\t.fc-input-field:hover {\n\t\tborder-color: var(--fc-input-border-hover);\n\t}\n\n\t.fc-input-field:focus {\n\t\tborder-color: var(--fc-input-border-focus);\n\t\toutline: none;\n\t\tbox-shadow: var(--fc-input-focus-ring);\n\t}\n\n\t.fc-input-field:disabled {\n\t\tbackground: var(--fc-input-bg-disabled);\n\t\tcursor: not-allowed;\n\t\tbox-shadow: none;\n\t}\n\n\t.fc-input-field:disabled::placeholder {\n\t\tcolor: var(--fc-input-placeholder-disabled);\n }\n\n\t.fc-input-field:disabled:hover {\n\t\tborder-color: var(--fc-input-border);\n\t}\n\n\t.fc-input-field:disabled:focus {\n\t\tborder-color: var(--fc-input-border);\n\t}\n\t\t\n\t/* FILE type specific CSS */\n\n\t.fc-input-field[type="file"] {\n\t\tpadding: calc(var(--fc-input-padding));\n\t\tcursor: pointer;\n\t\tdisplay: flex; \n \talign-items: center;\n\t\tborder-color: var(--fc-input-file-border);\n\t\ttransition: border-color 0.15s ease-in-out, color 0.15s ease-in-out;\n\t}\n\n\t.fc-input-field[type="file"]:focus {\n\t\tborder-color: var(--fc-input-border-focus);\n\t\toutline: none;\n\t\tbox-shadow: var(--fc-input-focus-ring);\n\t}\n\n\t/* target the button inside type="file" */\n\n\t.fc-input-field::file-selector-button {\n\t\tpadding: 4px 10px;\n\t\tborder-radius: var(--fc-input-radius);\n\t\tbackground: var(--fc-input-btn-bg);\n\t\tcolor: var(--fc-input-file-btn-fg);\n\t\tborder: 1px solid var(--fc-input-file-border);\n\t\tcursor: pointer;\n\t\tfont-family: inherit;\n\t\ttransition: background-color 0.15s ease;\n\t}\n\n\t/* legacy browsers */\n\t.fc-input-field::-webkit-file-upload-button {\n\t\tmargin-right: 12px;\n\t\tpadding: 4px 10px;\n\t\tborder-radius: var(--fc-input-radius);\n\t\tbackground: var(--fc-input-file-btn-bg);\n\t\tcolor: var(--fc-input-file-btn-fg);\n\t\tborder: 1px solid var(--fc-input-file-border);\n\t\tcursor: pointer;\n\t\tfont-family: inherit;\n\t\tfont-size: 0.9em;\n\t}\n\n\t/* Hover effects for the button */\n\n\t.fc-input-field::file-selector-button:hover {\n\t\tbackground: var(--fc-input-file-btn-bg-hover); \n\t}\n\n\n\t.fc-input-field::-webkit-file-upload-button:hover {\n\t\tbackground: var(--fc-input-file-btn-bg-hover);\n\t}\n\n\t/* PASSWORD type specific CSS */\n\n\t/* when password toggle is visible, add padding to input so text doesn't overlap icon */\n\t:host([type="password"]) .fc-input-field {\n\t\tpadding-right: 40px; \n\t}\n\n\t.fc-password-toggle {\n\t\tposition: absolute;\n\t\tright: 8px; /* position inside the input */\n\t\ttop: 50%;\n\t\ttransform: translateY(-50%);\n\t\tbackground: transparent;\n\t\tborder: none;\n\t\tcursor: pointer;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tcolor: var(--fc-input-password-icon-color);\n\t\ttransition: color 0.15s ease-in-out;\n\t\tz-index: 2; /* above input */\n\t}\n\t\n\t.fc-password-toggle[hidden] {\n display: none !important;\n }\n\n\t.fc-password-toggle:hover {\n\t\tcolor: var(--fc-input-password-icon-color-hover);\n\t}\n\t\n\t.fc-password-toggle svg {\n\t\twidth: var(--fc-input-password-icon-width);\n\t\theight: var(--fc-input-password-icon-height);\n\t}\n\n\t.fc-password-toggle svg[hidden] {\n display: none !important;\n }\n\n`;
722
740
 
723
741
  var template3 = document.createElement("template");
724
742
 
@@ -1099,6 +1117,13 @@ var FcInput = class extends HTMLElement {
1099
1117
  }
1100
1118
  onInvalid(e) {
1101
1119
  this.setAttribute("touched", "");
1120
+ this.dispatchEvent(new CustomEvent("fc-invalid", {
1121
+ bubbles: true,
1122
+ composed: true,
1123
+ detail: {
1124
+ originalEvent: e
1125
+ }
1126
+ }));
1102
1127
  }
1103
1128
  onTogglePassword(e) {
1104
1129
  e.preventDefault();
@@ -1318,12 +1343,518 @@ var defineLabel = () => {
1318
1343
  return FcLabel;
1319
1344
  };
1320
1345
 
1346
+ var styles6 = `\n :host {\n display: block;\n position: relative;\n width: 100%;\n box-sizing: border-box;\n font-family: var(--fc-font-family);\n max-width: var(--fc-select-max-width);\n cursor: pointer; \n }\n\n \n .fc-input {\n width: 100%;\n box-sizing: border-box;\n padding: var(--fc-select-padding);\n padding-right: 36px; /* space for dropdown icon */\n border-radius: var(--fc-select-radius);\n background: var(--fc-select-bg);\n color: var(--fc-select-fg);\n border: var(--fc-select-border-width) solid var(--fc-select-border);\n font-size: var(--fc-font-size-md);\n box-shadow: var(--fc-select-shadow);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n \n cursor: pointer; \n caret-color: transparent; \n user-select: none;\n }\n\n \n :host([touched]:invalid) .fc-input {\n background-color: var(--fc-select-bg-error);\n border-color: var(--fc-select-border-error);\n }\n\n :host([touched]:invalid) .fc-input:focus {\n box-shadow: 0 0 0 2px var(--fc-select-focus-ring-error);\n }\n\n .fc-input::placeholder {\n color: var(--fc-select-placeholder);\n }\n\n .fc-input:hover {\n border-color: var(--fc-select-border-hover);\n }\n\n .fc-input:focus {\n border-color: var(--fc-select-border-focus);\n outline: none;\n box-shadow: var(--fc-select-focus-ring);\n }\n\n \n .fc-chevron {\n position: absolute;\n right: 12px;\n top: 50%;\n transform: translateY(-50%);\n width: var(--fc-select-dropdown-icon-width);\n height: var(--fc-select-dropdown-icon-height);\n pointer-events: none;\n color: var(--fc-select-fg);\n transition: transform 0.2s ease;\n }\n\n \n :host([open]) .fc-chevron {\n transform: translateY(-50%) rotate(180deg);\n }\n\n :host([disabled]) {\n cursor: not-allowed;\n }\n\n .fc-input:disabled {\n background: var(--fc-select-bg-disabled);\n cursor: not-allowed;\n box-shadow: none;\n color: var(--fc-select-placeholder-disabled);\n }\n\n :host([disabled]) .fc-chevron {\n color: var(--fc-select-placeholder-disabled);\n }\n\n .fc-options {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n right: 0;\n z-index: 1000;\n background: var(--fc-select-dropdown-bg, var(--fc-select-bg));\n border: var(--fc-select-border-width) solid var(--fc-select-border);\n border-radius: var(--fc-select-dropdown-radius, var(--fc-select-radius));\n padding: var(--fc-select-dropdown-padding, calc(var(--fc-select-padding) - 5px));\n box-shadow: var(--fc-select-dropdown-shadow);\n max-height: var(--fc-select-dropdown-max-height, 240px);\n overflow-y: auto;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .fc-options.opens-up {\n top: auto;\n bottom: calc(100% + 6px);\n }\n \n .fc-options[hidden] {\n display: none !important;\n }\n`;
1347
+
1348
+ var template6 = document.createElement("template");
1349
+
1350
+ template6.innerHTML = `\n <style>${styles6}</style>\n\n <input \n class="fc-input"\n type="text" \n role="combobox"\n aria-autocomplete="none" \n aria-expanded="false"\n aria-haspopup="listbox"\n aria-controls="fc-options"\n part="input"\n autocomplete="off"\n readonly\n />\n\n <svg class="fc-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n <polyline points="6 9 12 15 18 9"></polyline>\n </svg>\n\n <div \n part="options" \n class="fc-options" \n role="listbox"\n hidden\n >\n <slot></slot>\n </div>\n`;
1351
+
1352
+ var FcSelect = class extends HTMLElement {
1353
+ constructor() {
1354
+ super();
1355
+ this._value = "";
1356
+ this.activeIndex = -1;
1357
+ this.searchBuffer = "";
1358
+ this.searchTimeout = null;
1359
+ this._validatorFunction = null;
1360
+ const shadow = this.attachShadow({
1361
+ mode: "open",
1362
+ delegatesFocus: true
1363
+ });
1364
+ shadow.appendChild(template6.content.cloneNode(true));
1365
+ this.internals = this.attachInternals();
1366
+ this.inputEl = shadow.querySelector(".fc-input");
1367
+ this.dropdownEl = shadow.querySelector(".fc-options");
1368
+ const randomId = Math.random().toString(36).substring(2, 9);
1369
+ const inputId = `fc-input-${randomId}`;
1370
+ const dropdownId = `fc-options-${randomId}`;
1371
+ this.inputEl.id = inputId;
1372
+ this.dropdownEl.id = dropdownId;
1373
+ this.inputEl.setAttribute("aria-controls", dropdownId);
1374
+ this.onChange = this.onChange.bind(this);
1375
+ this.onOptionSelect = this.onOptionSelect.bind(this);
1376
+ this.onOutsideClick = this.onOutsideClick.bind(this);
1377
+ this.onFocusOut = this.onFocusOut.bind(this);
1378
+ this.onSlotChange = this.onSlotChange.bind(this);
1379
+ this.onKeyDown = this.onKeyDown.bind(this);
1380
+ this.onBlur = this.onBlur.bind(this);
1381
+ this.onInvalid = this.onInvalid.bind(this);
1382
+ this.onClick = this.onClick.bind(this);
1383
+ }
1384
+ static get observedAttributes() {
1385
+ return [ "placeholder", "name", "value", "disabled", "required" ];
1386
+ }
1387
+ get validity() {
1388
+ return this.internals.validity;
1389
+ }
1390
+ get validationMessage() {
1391
+ return this.internals.validationMessage;
1392
+ }
1393
+ get willValidate() {
1394
+ return this.internals.willValidate;
1395
+ }
1396
+ checkValidity() {
1397
+ return this.internals.checkValidity();
1398
+ }
1399
+ reportValidity() {
1400
+ return this.internals.reportValidity();
1401
+ }
1402
+ get placeholder() {
1403
+ var _a;
1404
+ return (_a = this.getAttribute("placeholder")) != null ? _a : "";
1405
+ }
1406
+ set placeholder(val) {
1407
+ this.setAttribute("placeholder", val);
1408
+ }
1409
+ get name() {
1410
+ var _a;
1411
+ return (_a = this.getAttribute("name")) != null ? _a : "";
1412
+ }
1413
+ set name(val) {
1414
+ this.setAttribute("name", val);
1415
+ }
1416
+ get disabled() {
1417
+ return this.hasAttribute("disabled");
1418
+ }
1419
+ set disabled(val) {
1420
+ if (val) {
1421
+ this.setAttribute("disabled", "true");
1422
+ return;
1423
+ }
1424
+ this.removeAttribute("disabled");
1425
+ }
1426
+ get value() {
1427
+ return this._value;
1428
+ }
1429
+ set value(newValue) {
1430
+ if (this._value === newValue) return;
1431
+ this._value = newValue;
1432
+ this.internals.setFormValue(newValue);
1433
+ if (!this.inputEl) return;
1434
+ this.syncValidity();
1435
+ }
1436
+ get label() {
1437
+ var _a, _b;
1438
+ return (_b = (_a = this.inputEl) == null ? void 0 : _a.value) != null ? _b : "";
1439
+ }
1440
+ get options() {
1441
+ const slot = this.shadowRoot.querySelector("slot");
1442
+ const optionElements = slot.assignedElements().filter(el => el.tagName === "FC-OPTION");
1443
+ return optionElements.map(opt => ({
1444
+ label: opt.label,
1445
+ value: opt.value
1446
+ }));
1447
+ }
1448
+ set options(data) {
1449
+ const oldOptions = this.querySelectorAll("fc-option");
1450
+ oldOptions.forEach(opt => opt.remove());
1451
+ data.forEach(element => {
1452
+ const optEl = document.createElement("fc-option");
1453
+ optEl.setAttribute("value", element.value);
1454
+ optEl.setAttribute("label", element.label);
1455
+ optEl.textContent = element.label;
1456
+ if (element.disabled) optEl.setAttribute("disabled", "");
1457
+ this.appendChild(optEl);
1458
+ });
1459
+ this.syncValidity();
1460
+ }
1461
+ get required() {
1462
+ return this.hasAttribute("required");
1463
+ }
1464
+ set required(val) {
1465
+ if (val) {
1466
+ this.setAttribute("required", "true");
1467
+ return;
1468
+ }
1469
+ this.removeAttribute("required");
1470
+ }
1471
+ get readonly() {
1472
+ return this.hasAttribute("readonly");
1473
+ }
1474
+ connectedCallback() {
1475
+ this.internals.setFormValue(this.value);
1476
+ if (this.hasAttribute("placeholder")) {
1477
+ this.inputEl.placeholder = this.getAttribute("placeholder");
1478
+ }
1479
+ if (this.hasAttribute("disabled")) {
1480
+ this.inputEl.disabled = true;
1481
+ this.internals.ariaDisabled = "true";
1482
+ }
1483
+ if (this.hasAttribute("required")) {
1484
+ this.internals.ariaRequired = "true";
1485
+ this.inputEl.required = true;
1486
+ }
1487
+ this.inputEl.addEventListener("click", this.onClick);
1488
+ this.inputEl.addEventListener("change", this.onChange);
1489
+ this.addEventListener("fc-option-select", this.onOptionSelect);
1490
+ this.addEventListener("focusout", this.onFocusOut);
1491
+ this.inputEl.addEventListener("blur", this.onBlur);
1492
+ this.addEventListener("invalid", this.onInvalid);
1493
+ const slot = this.shadowRoot.querySelector("slot");
1494
+ slot == null ? void 0 : slot.addEventListener("slotchange", this.onSlotChange);
1495
+ this.inputEl.addEventListener("keydown", this.onKeyDown);
1496
+ this.syncValidity();
1497
+ }
1498
+ attributeChangedCallback(name, _old, newVal) {
1499
+ if (name === "placeholder" && this.inputEl) {
1500
+ this.inputEl.placeholder = newVal;
1501
+ }
1502
+ if (name === "name") {
1503
+ this.internals.setFormValue(this.value);
1504
+ }
1505
+ if (name === "value") {
1506
+ this.value = newVal;
1507
+ }
1508
+ if (name === "disabled" && this.inputEl) {
1509
+ const isDisabled = this.hasAttribute("disabled");
1510
+ this.inputEl.disabled = isDisabled;
1511
+ this.internals.ariaDisabled = isDisabled ? "true" : "false";
1512
+ if (isDisabled) {
1513
+ this.hideDropdown();
1514
+ }
1515
+ }
1516
+ if (name === "required" && this.inputEl) {
1517
+ const isRequired = this.hasAttribute("required");
1518
+ this.internals.ariaRequired = isRequired ? "true" : "false";
1519
+ this.inputEl.required = isRequired;
1520
+ this.syncValidity();
1521
+ }
1522
+ }
1523
+ disconnectedCallback() {
1524
+ document.removeEventListener("click", this.onOutsideClick);
1525
+ }
1526
+ formResetCallback() {
1527
+ this._value = "";
1528
+ if (this.inputEl) {
1529
+ this.inputEl.value = "";
1530
+ }
1531
+ this.internals.setFormValue("");
1532
+ const options = this.querySelectorAll("fc-option");
1533
+ options.forEach(option => {
1534
+ option.selected = false;
1535
+ });
1536
+ this.hideDropdown();
1537
+ this.removeAttribute("touched");
1538
+ this.syncValidity();
1539
+ this.dispatchEvent(new CustomEvent("fc-reset", {
1540
+ bubbles: true,
1541
+ composed: true
1542
+ }));
1543
+ }
1544
+ formStateRestoreCallback(state, mode) {
1545
+ const restoredValue = state;
1546
+ if (restoredValue) {
1547
+ this._value = restoredValue;
1548
+ this.internals.setFormValue(restoredValue);
1549
+ const options = this.querySelectorAll("fc-option");
1550
+ let foundMatch = false;
1551
+ options.forEach(option => {
1552
+ const selected = option.value === this._value;
1553
+ option.selected = selected;
1554
+ if (selected) {
1555
+ this.inputEl.value = option.label;
1556
+ foundMatch = true;
1557
+ }
1558
+ });
1559
+ if (!foundMatch && this.inputEl.value === "") {
1560
+ this.inputEl.value = this._value;
1561
+ }
1562
+ this.syncValidity();
1563
+ }
1564
+ }
1565
+ onClick(e) {
1566
+ if (this.disabled) {
1567
+ return;
1568
+ }
1569
+ if (this.dropdownEl.hidden) {
1570
+ this.showDropdown();
1571
+ } else {
1572
+ this.hideDropdown();
1573
+ }
1574
+ }
1575
+ onChange(e) {
1576
+ e.stopPropagation();
1577
+ this.dispatchEvent(new CustomEvent("fc-change", {
1578
+ detail: {
1579
+ value: this._value,
1580
+ label: this.inputEl.value
1581
+ },
1582
+ bubbles: true,
1583
+ composed: true
1584
+ }));
1585
+ }
1586
+ onOptionSelect(e) {
1587
+ const {value: value, label: label} = e.detail;
1588
+ if (this.disabled) {
1589
+ return;
1590
+ }
1591
+ this.inputEl.value = label;
1592
+ this._value = value;
1593
+ this.internals.setFormValue(value);
1594
+ const options = this.querySelectorAll("fc-option");
1595
+ options.forEach(option => {
1596
+ const selected = option.value === value;
1597
+ option.selected = selected;
1598
+ option.active = false;
1599
+ });
1600
+ this.hideDropdown();
1601
+ this.syncValidity();
1602
+ this.dispatchEvent(new CustomEvent("fc-change", {
1603
+ detail: {
1604
+ value: value,
1605
+ label: label
1606
+ },
1607
+ bubbles: true,
1608
+ composed: true
1609
+ }));
1610
+ }
1611
+ onOutsideClick(e) {
1612
+ if (!this.contains(e.target)) {
1613
+ this.hideDropdown();
1614
+ }
1615
+ }
1616
+ onFocusOut(e) {
1617
+ const target = e.relatedTarget;
1618
+ if (!target || !this.contains(target)) {
1619
+ this.hideDropdown();
1620
+ }
1621
+ }
1622
+ onSlotChange() {
1623
+ if (!this._value) return;
1624
+ const options = this.querySelectorAll("fc-option");
1625
+ let foundMatch = false;
1626
+ options.forEach(option => {
1627
+ const selected = option.value === this._value;
1628
+ option.selected = selected;
1629
+ if (selected) {
1630
+ this.inputEl.value = option.label;
1631
+ foundMatch = true;
1632
+ }
1633
+ });
1634
+ if (!foundMatch && this.inputEl.value === "") {
1635
+ this.inputEl.value = this._value;
1636
+ }
1637
+ this.syncValidity();
1638
+ }
1639
+ onKeyDown(e) {
1640
+ if (this.disabled) {
1641
+ return;
1642
+ }
1643
+ const options = this.getVisibleOptions();
1644
+ if (options.length === 0) return;
1645
+ if (e.key === "ArrowDown") {
1646
+ e.preventDefault();
1647
+ if (this.dropdownEl.hidden) {
1648
+ this.showDropdown();
1649
+ return;
1650
+ }
1651
+ const nextIndex = this.activeIndex >= options.length - 1 ? 0 : this.activeIndex + 1;
1652
+ this.setActiveOption(nextIndex, options);
1653
+ } else if (e.key === "ArrowUp") {
1654
+ e.preventDefault();
1655
+ if (this.dropdownEl.hidden) {
1656
+ this.showDropdown();
1657
+ return;
1658
+ }
1659
+ const prevIndex = this.activeIndex <= 0 ? options.length - 1 : this.activeIndex - 1;
1660
+ this.setActiveOption(prevIndex, options);
1661
+ } else if (e.key === "Enter" || e.key === " ") {
1662
+ e.preventDefault();
1663
+ if (this.dropdownEl.hidden) {
1664
+ this.showDropdown();
1665
+ } else {
1666
+ if (this.activeIndex > -1 && options[this.activeIndex]) {
1667
+ const target = options[this.activeIndex];
1668
+ this.selectOption(target);
1669
+ } else {
1670
+ this.hideDropdown();
1671
+ }
1672
+ }
1673
+ } else if (e.key === "Escape") {
1674
+ e.preventDefault();
1675
+ this.hideDropdown();
1676
+ } else if (e.key === "Tab") {
1677
+ this.hideDropdown();
1678
+ } else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
1679
+ e.preventDefault();
1680
+ this.handleTypeAhead(e.key);
1681
+ }
1682
+ }
1683
+ handleTypeAhead(char) {
1684
+ if (this.searchTimeout) clearTimeout(this.searchTimeout);
1685
+ this.searchBuffer += char.toLowerCase();
1686
+ this.searchTimeout = setTimeout(() => {
1687
+ this.searchBuffer = "";
1688
+ }, 500);
1689
+ const options = this.getVisibleOptions();
1690
+ const matchIndex = options.findIndex(opt => {
1691
+ const label = (opt.getAttribute("label") || opt.textContent || "").toLowerCase();
1692
+ return label.startsWith(this.searchBuffer);
1693
+ });
1694
+ if (matchIndex !== -1) {
1695
+ const match = options[matchIndex];
1696
+ if (!this.dropdownEl.hidden) {
1697
+ this.setActiveOption(matchIndex, options);
1698
+ } else {
1699
+ this.selectOption(match);
1700
+ }
1701
+ }
1702
+ }
1703
+ onBlur() {
1704
+ this.setAttribute("touched", "");
1705
+ }
1706
+ onInvalid(e) {
1707
+ this.setAttribute("touched", "");
1708
+ this.dispatchEvent(new CustomEvent("fc-invalid", {
1709
+ bubbles: true,
1710
+ composed: true,
1711
+ detail: {
1712
+ originalEvent: e
1713
+ }
1714
+ }));
1715
+ }
1716
+ setActiveOption(index, visibleOptions) {
1717
+ this.querySelectorAll("fc-option").forEach(opt => opt.active = false);
1718
+ this.activeIndex = index;
1719
+ const target = visibleOptions[index];
1720
+ if (target) {
1721
+ target.active = true;
1722
+ this.inputEl.setAttribute("aria-activedescendant", target.id);
1723
+ target.scrollIntoView({
1724
+ block: "nearest",
1725
+ behavior: "smooth"
1726
+ });
1727
+ return;
1728
+ }
1729
+ this.inputEl.removeAttribute("aria-activedescendant");
1730
+ }
1731
+ getVisibleOptions() {
1732
+ return Array.from(this.querySelectorAll("fc-option")).filter(opt => !opt.disabled);
1733
+ }
1734
+ selectOption(option) {
1735
+ const value = option.value;
1736
+ const label = option.label;
1737
+ this.inputEl.value = label;
1738
+ this._value = value;
1739
+ this.internals.setFormValue(value);
1740
+ const allOptions = this.querySelectorAll("fc-option");
1741
+ allOptions.forEach(opt => {
1742
+ const selected = opt.value === value;
1743
+ opt.selected = selected;
1744
+ opt.active = false;
1745
+ });
1746
+ this.hideDropdown();
1747
+ this.syncValidity();
1748
+ this.dispatchEvent(new CustomEvent("fc-change", {
1749
+ detail: {
1750
+ value: value,
1751
+ label: label
1752
+ },
1753
+ bubbles: true,
1754
+ composed: true
1755
+ }));
1756
+ }
1757
+ showDropdown() {
1758
+ if (!this.dropdownEl || this.disabled) {
1759
+ return;
1760
+ }
1761
+ const dropdown = this.dropdownEl;
1762
+ const showEvent = new CustomEvent("fc-show", {
1763
+ bubbles: true,
1764
+ composed: true,
1765
+ cancelable: true
1766
+ });
1767
+ this.dispatchEvent(showEvent);
1768
+ if (showEvent.defaultPrevented) {
1769
+ return;
1770
+ }
1771
+ const spaceBelow = calculateBottomAvaliableSpace(this.inputEl);
1772
+ const spaceAbove = calculateTopAvaliableSpace(this.inputEl);
1773
+ dropdown.hidden = false;
1774
+ this.setAttribute("open", "true");
1775
+ this.inputEl.setAttribute("aria-expanded", "true");
1776
+ const shouldOpenUp = spaceBelow < dropdown.clientHeight && spaceAbove > spaceBelow;
1777
+ dropdown.classList.toggle("opens-up", shouldOpenUp);
1778
+ const options = this.getVisibleOptions();
1779
+ const selectedIndex = options.findIndex(opt => opt.value === this._value);
1780
+ if (selectedIndex >= 0) {
1781
+ this.setActiveOption(selectedIndex, options);
1782
+ } else {
1783
+ this.activeIndex = -1;
1784
+ }
1785
+ setTimeout(() => {
1786
+ document.addEventListener("click", this.onOutsideClick);
1787
+ }, 0);
1788
+ return;
1789
+ }
1790
+ hideDropdown() {
1791
+ if (!this.dropdownEl) {
1792
+ return;
1793
+ }
1794
+ if (!this.dropdownEl.hidden) {
1795
+ this.dispatchEvent(new CustomEvent("fc-hide", {
1796
+ bubbles: true,
1797
+ composed: true
1798
+ }));
1799
+ }
1800
+ document.removeEventListener("click", this.onOutsideClick);
1801
+ this.dropdownEl.hidden = true;
1802
+ this.removeAttribute("open");
1803
+ this.inputEl.setAttribute("aria-expanded", "false");
1804
+ this.activeIndex = -1;
1805
+ this.querySelectorAll("fc-option").forEach(opt => opt.active = false);
1806
+ this.inputEl.removeAttribute("aria-activedescendant");
1807
+ }
1808
+ syncValidity() {
1809
+ if (!this.inputEl) {
1810
+ return;
1811
+ }
1812
+ if (!this.inputEl.validity.valid) {
1813
+ this.internals.setValidity(this.inputEl.validity, this.inputEl.validationMessage, this.inputEl);
1814
+ return;
1815
+ }
1816
+ this.querySelectorAll("fc-option");
1817
+ if (this._validatorFunction) {
1818
+ const customErrorMessage = this._validatorFunction(this.value);
1819
+ if (customErrorMessage) {
1820
+ this.internals.setValidity({
1821
+ customError: true
1822
+ }, customErrorMessage, this.inputEl);
1823
+ return;
1824
+ }
1825
+ }
1826
+ this.internals.setValidity({});
1827
+ }
1828
+ setProps(props) {
1829
+ for (const property in props) {
1830
+ const value = props[property];
1831
+ if (property in this) {
1832
+ this[property] = value;
1833
+ continue;
1834
+ }
1835
+ if ([ "string", "number", "boolean" ].includes(typeof value)) {
1836
+ this.setAttribute(property, String(value));
1837
+ }
1838
+ }
1839
+ }
1840
+ };
1841
+
1842
+ FcSelect.formAssociated = true;
1843
+
1844
+ var defineSelect = () => {
1845
+ if (!customElements.get("fc-select")) {
1846
+ customElements.define("fc-select", FcSelect);
1847
+ }
1848
+ return FcSelect;
1849
+ };
1850
+
1321
1851
  var defineAll = () => {
1322
1852
  defineCombobox();
1323
1853
  defineOption();
1324
1854
  defineInput();
1325
1855
  defineError();
1326
1856
  defineLabel();
1857
+ defineSelect();
1327
1858
  };
1328
1859
 
1329
- export { FcCombobox, FcError, FcInput, FcLabel, FcOption, defineAll, defineCombobox, defineError, defineInput, defineLabel, defineOption };
1860
+ export { FcCombobox, FcError, FcInput, FcLabel, FcOption, FcSelect, defineAll, defineCombobox, defineError, defineInput, defineLabel, defineOption, defineSelect };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fancy-ui-ts",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "type": "module",
5
5
  "description": "A library to easily create cool and customizable webcomponents.",
6
6
  "main": "dist/index.js",