fancy-ui-ts 1.2.0 → 1.3.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.
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,8 @@
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-dropdown-icon-width: calc(var(--fc-font-size-md) + 4px);
138
+ --fc-select-dropdown-icon-height: calc(var(--fc-font-size-md) + 4px);
135
139
  }
136
140
 
137
141
  /* src/styles/themes/fc-theme-light.css */
@@ -152,11 +156,9 @@
152
156
  --fc-option-bg: var(--fc-white);
153
157
  --fc-option-bg-disabled: var(--fc-gray-50);
154
158
  --fc-option-bg-hover: var(--fc-gray-100);
155
- --fc-option-bg-selected: var(--fc-primary-200);
156
159
  --fc-option-bg-active: var(--fc-primary-100);
157
160
  --fc-option-fg: var(--fc-gray-900);
158
161
  --fc-option-fg-disabled: var(--fc-gray-300);
159
- --fc-option-fg-selected: var(--fc-primary-700);
160
162
  --fc-input-bg: var(--fc-white);
161
163
  --fc-input-bg-disabled: var(--fc-gray-50);
162
164
  --fc-input-fg: var(--fc-gray-900);
@@ -207,10 +209,8 @@
207
209
  --fc-option-bg-disabled: var(--fc-gray-900);
208
210
  --fc-option-bg-hover: var(--fc-gray-700);
209
211
  --fc-option-bg-active: var(--fc-primary-500);
210
- --fc-option-bg-selected: var(--fc-primary-600);
211
212
  --fc-option-fg: var(--fc-gray-200);
212
213
  --fc-option-fg-disabled: var(--fc-gray-700);
213
- --fc-option-fg-selected: var(--fc-primary-50);
214
214
  --fc-input-bg: var(--fc-gray-800);
215
215
  --fc-input-bg-disabled: var(--fc-gray-900);
216
216
  --fc-input-fg: var(--fc-gray-200);
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,69 @@ 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
+ constructor();
195
+ get validity(): ValidityState;
196
+ get validationMessage(): string;
197
+ get willValidate(): boolean;
198
+ checkValidity(): boolean;
199
+ reportValidity(): boolean;
200
+ get placeholder(): string;
201
+ set placeholder(val: string);
202
+ get name(): string;
203
+ set name(val: string);
204
+ get disabled(): boolean;
205
+ set disabled(val: boolean);
206
+ get value(): string;
207
+ set value(newValue: string);
208
+ get label(): string;
209
+ get options(): {
210
+ label: string;
211
+ value: string;
212
+ disabled?: boolean;
213
+ }[];
214
+ set options(data: {
215
+ label: string;
216
+ value: string;
217
+ disabled?: boolean;
218
+ }[]);
219
+ get required(): boolean;
220
+ set required(val: boolean);
221
+ get readonly(): boolean;
222
+ connectedCallback(): void;
223
+ attributeChangedCallback(name: string, _old: string, newVal: string): void;
224
+ disconnectedCallback(): void;
225
+ formResetCallback(): void;
226
+ formStateRestoreCallback(state: string | File | FormData | null, mode: 'restore' | 'autocomplete'): void;
227
+ private onClick;
228
+ private onChange;
229
+ private onOptionSelect;
230
+ private onOutsideClick;
231
+ private onFocusOut;
232
+ private onSlotChange;
233
+ private onKeyDown;
234
+ private handleTypeAhead;
235
+ private onBlur;
236
+ private onInvalid;
237
+ private setActiveOption;
238
+ private getVisibleOptions;
239
+ private selectOption;
240
+ private toggleDropdown;
241
+ private syncValidity;
242
+ setProps(props: Record<string, any>): void;
243
+ }
244
+
245
+ declare const defineSelect: () => typeof FcSelect;
246
+
185
247
  declare const defineAll: () => void;
186
248
 
187
- export { FcCombobox, FcError, FcInput, FcLabel, FcOption, defineAll, defineCombobox, defineError, defineInput, defineLabel, defineOption };
249
+ 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,516 @@ 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-combobox-max-width);\n cursor: pointer; \n }\n\n \n .fc-input {\n width: 100%;\n box-sizing: border-box;\n padding: var(--fc-combobox-padding);\n padding-right: 36px;\n \n border-radius: var(--fc-combobox-radius);\n background: var(--fc-combobox-bg);\n color: var(--fc-combobox-fg);\n border: var(--fc-combobox-border-width) solid var(--fc-combobox-border);\n font-size: var(--fc-font-size-md);\n box-shadow: var(--fc-combobox-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-combobox-bg-error);\n border-color: var(--fc-combobox-border-error);\n }\n\n :host([touched]:invalid) .fc-input:focus {\n box-shadow: 0 0 0 2px var(--fc-combobox-focus-ring-error);\n }\n\n .fc-input::placeholder {\n color: var(--fc-combobox-placeholder);\n }\n\n .fc-input:hover {\n border-color: var(--fc-combobox-border-hover);\n }\n\n .fc-input:focus {\n border-color: var(--fc-combobox-border-focus);\n outline: none;\n box-shadow: var(--fc-combobox-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-combobox-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-combobox-bg-disabled);\n cursor: not-allowed;\n box-shadow: none;\n color: var(--fc-combobox-placeholder-disabled);\n }\n\n :host([disabled]) .fc-chevron {\n color: var(--fc-combobox-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-combobox-dropdown-bg, var(--fc-combobox-bg));\n border: var(--fc-combobox-border-width) solid var(--fc-combobox-border);\n border-radius: var(--fc-combobox-dropdown-radius, var(--fc-combobox-radius));\n padding: var(--fc-combobox-dropdown-padding, calc(var(--fc-combobox-padding) - 5px));\n box-shadow: var(--fc-combobox-dropdown-shadow);\n max-height: var(--fc-combobox-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
+ const shadow = this.attachShadow({
1360
+ mode: "open",
1361
+ delegatesFocus: true
1362
+ });
1363
+ shadow.appendChild(template6.content.cloneNode(true));
1364
+ this.internals = this.attachInternals();
1365
+ this.inputEl = shadow.querySelector(".fc-input");
1366
+ this.dropdownEl = shadow.querySelector(".fc-options");
1367
+ const randomId = Math.random().toString(36).substring(2, 9);
1368
+ const inputId = `fc-input-${randomId}`;
1369
+ const dropdownId = `fc-options-${randomId}`;
1370
+ this.inputEl.id = inputId;
1371
+ this.dropdownEl.id = dropdownId;
1372
+ this.inputEl.setAttribute("aria-controls", dropdownId);
1373
+ this.onChange = this.onChange.bind(this);
1374
+ this.onOptionSelect = this.onOptionSelect.bind(this);
1375
+ this.onOutsideClick = this.onOutsideClick.bind(this);
1376
+ this.onFocusOut = this.onFocusOut.bind(this);
1377
+ this.onSlotChange = this.onSlotChange.bind(this);
1378
+ this.onKeyDown = this.onKeyDown.bind(this);
1379
+ this.onBlur = this.onBlur.bind(this);
1380
+ this.onInvalid = this.onInvalid.bind(this);
1381
+ this.onClick = this.onClick.bind(this);
1382
+ }
1383
+ static get observedAttributes() {
1384
+ return [ "placeholder", "name", "value", "disabled", "required" ];
1385
+ }
1386
+ get validity() {
1387
+ return this.internals.validity;
1388
+ }
1389
+ get validationMessage() {
1390
+ return this.internals.validationMessage;
1391
+ }
1392
+ get willValidate() {
1393
+ return this.internals.willValidate;
1394
+ }
1395
+ checkValidity() {
1396
+ return this.internals.checkValidity();
1397
+ }
1398
+ reportValidity() {
1399
+ return this.internals.reportValidity();
1400
+ }
1401
+ get placeholder() {
1402
+ var _a;
1403
+ return (_a = this.getAttribute("placeholder")) != null ? _a : "";
1404
+ }
1405
+ set placeholder(val) {
1406
+ this.setAttribute("placeholder", val);
1407
+ }
1408
+ get name() {
1409
+ var _a;
1410
+ return (_a = this.getAttribute("name")) != null ? _a : "";
1411
+ }
1412
+ set name(val) {
1413
+ this.setAttribute("name", val);
1414
+ }
1415
+ get disabled() {
1416
+ return this.hasAttribute("disabled");
1417
+ }
1418
+ set disabled(val) {
1419
+ if (val) {
1420
+ this.setAttribute("disabled", "true");
1421
+ return;
1422
+ }
1423
+ this.removeAttribute("disabled");
1424
+ }
1425
+ get value() {
1426
+ return this._value;
1427
+ }
1428
+ set value(newValue) {
1429
+ if (this._value === newValue) return;
1430
+ this._value = newValue;
1431
+ this.internals.setFormValue(newValue);
1432
+ if (!this.inputEl) return;
1433
+ this.syncValidity();
1434
+ }
1435
+ get label() {
1436
+ var _a, _b;
1437
+ return (_b = (_a = this.inputEl) == null ? void 0 : _a.value) != null ? _b : "";
1438
+ }
1439
+ get options() {
1440
+ const slot = this.shadowRoot.querySelector("slot");
1441
+ const optionElements = slot.assignedElements().filter(el => el.tagName === "FC-OPTION");
1442
+ return optionElements.map(opt => ({
1443
+ label: opt.label,
1444
+ value: opt.value
1445
+ }));
1446
+ }
1447
+ set options(data) {
1448
+ const oldOptions = this.querySelectorAll("fc-option");
1449
+ oldOptions.forEach(opt => opt.remove());
1450
+ data.forEach(element => {
1451
+ const optEl = document.createElement("fc-option");
1452
+ optEl.setAttribute("value", element.value);
1453
+ optEl.setAttribute("label", element.label);
1454
+ optEl.textContent = element.label;
1455
+ if (element.disabled) optEl.setAttribute("disabled", "");
1456
+ this.appendChild(optEl);
1457
+ });
1458
+ this.syncValidity();
1459
+ }
1460
+ get required() {
1461
+ return this.hasAttribute("required");
1462
+ }
1463
+ set required(val) {
1464
+ if (val) {
1465
+ this.setAttribute("required", "true");
1466
+ return;
1467
+ }
1468
+ this.removeAttribute("required");
1469
+ }
1470
+ get readonly() {
1471
+ return this.hasAttribute("readonly");
1472
+ }
1473
+ connectedCallback() {
1474
+ this.internals.setFormValue(this.value);
1475
+ if (this.hasAttribute("placeholder")) {
1476
+ this.inputEl.placeholder = this.getAttribute("placeholder");
1477
+ }
1478
+ if (this.hasAttribute("disabled")) {
1479
+ this.inputEl.disabled = true;
1480
+ this.internals.ariaDisabled = "true";
1481
+ }
1482
+ if (this.hasAttribute("required")) {
1483
+ this.internals.ariaRequired = "true";
1484
+ this.inputEl.required = true;
1485
+ }
1486
+ this.inputEl.addEventListener("click", this.onClick);
1487
+ this.inputEl.addEventListener("change", this.onChange);
1488
+ this.addEventListener("fc-option-select", this.onOptionSelect);
1489
+ this.addEventListener("focusout", this.onFocusOut);
1490
+ this.inputEl.addEventListener("blur", this.onBlur);
1491
+ this.addEventListener("invalid", this.onInvalid);
1492
+ const slot = this.shadowRoot.querySelector("slot");
1493
+ slot == null ? void 0 : slot.addEventListener("slotchange", this.onSlotChange);
1494
+ this.inputEl.addEventListener("keydown", this.onKeyDown);
1495
+ this.syncValidity();
1496
+ }
1497
+ attributeChangedCallback(name, _old, newVal) {
1498
+ if (name === "placeholder" && this.inputEl) {
1499
+ this.inputEl.placeholder = newVal;
1500
+ }
1501
+ if (name === "name") {
1502
+ this.internals.setFormValue(this.value);
1503
+ }
1504
+ if (name === "value") {
1505
+ this.value = newVal;
1506
+ }
1507
+ if (name === "disabled" && this.inputEl) {
1508
+ const isDisabled = this.hasAttribute("disabled");
1509
+ this.inputEl.disabled = isDisabled;
1510
+ this.internals.ariaDisabled = isDisabled ? "true" : "false";
1511
+ if (isDisabled) {
1512
+ this.toggleDropdown(false);
1513
+ }
1514
+ }
1515
+ if (name === "required" && this.inputEl) {
1516
+ const isRequired = this.hasAttribute("required");
1517
+ this.internals.ariaRequired = isRequired ? "true" : "false";
1518
+ this.inputEl.required = isRequired;
1519
+ this.syncValidity();
1520
+ }
1521
+ }
1522
+ disconnectedCallback() {
1523
+ document.removeEventListener("click", this.onOutsideClick);
1524
+ }
1525
+ formResetCallback() {
1526
+ this._value = "";
1527
+ if (this.inputEl) {
1528
+ this.inputEl.value = "";
1529
+ }
1530
+ this.internals.setFormValue("");
1531
+ const options = this.querySelectorAll("fc-option");
1532
+ options.forEach(option => {
1533
+ option.selected = false;
1534
+ });
1535
+ this.toggleDropdown(false);
1536
+ this.removeAttribute("touched");
1537
+ this.syncValidity();
1538
+ this.dispatchEvent(new CustomEvent("fc-reset", {
1539
+ bubbles: true,
1540
+ composed: true
1541
+ }));
1542
+ }
1543
+ formStateRestoreCallback(state, mode) {
1544
+ const restoredValue = state;
1545
+ if (restoredValue) {
1546
+ this._value = restoredValue;
1547
+ this.internals.setFormValue(restoredValue);
1548
+ const options = this.querySelectorAll("fc-option");
1549
+ let foundMatch = false;
1550
+ options.forEach(option => {
1551
+ const selected = option.value === this._value;
1552
+ option.selected = selected;
1553
+ if (selected) {
1554
+ this.inputEl.value = option.label;
1555
+ foundMatch = true;
1556
+ }
1557
+ });
1558
+ if (!foundMatch && this.inputEl.value === "") {
1559
+ this.inputEl.value = this._value;
1560
+ }
1561
+ this.syncValidity();
1562
+ }
1563
+ }
1564
+ onClick(e) {
1565
+ if (this.disabled) {
1566
+ return;
1567
+ }
1568
+ this.toggleDropdown(this.dropdownEl.hidden);
1569
+ }
1570
+ onChange(e) {
1571
+ e.stopPropagation();
1572
+ this.dispatchEvent(new CustomEvent("fc-change", {
1573
+ detail: {
1574
+ value: this._value,
1575
+ label: this.inputEl.value
1576
+ },
1577
+ bubbles: true,
1578
+ composed: true
1579
+ }));
1580
+ }
1581
+ onOptionSelect(e) {
1582
+ const {value: value, label: label} = e.detail;
1583
+ if (this.disabled) {
1584
+ return;
1585
+ }
1586
+ this.inputEl.value = label;
1587
+ this._value = value;
1588
+ this.internals.setFormValue(value);
1589
+ const options = this.querySelectorAll("fc-option");
1590
+ options.forEach(option => {
1591
+ const selected = option.value === value;
1592
+ option.selected = selected;
1593
+ option.active = false;
1594
+ });
1595
+ this.toggleDropdown(false);
1596
+ this.syncValidity();
1597
+ this.dispatchEvent(new CustomEvent("fc-change", {
1598
+ detail: {
1599
+ value: value,
1600
+ label: label
1601
+ },
1602
+ bubbles: true,
1603
+ composed: true
1604
+ }));
1605
+ }
1606
+ onOutsideClick(e) {
1607
+ if (!this.contains(e.target)) {
1608
+ this.toggleDropdown(false);
1609
+ }
1610
+ }
1611
+ onFocusOut(e) {
1612
+ const target = e.relatedTarget;
1613
+ if (!target || !this.contains(target)) {
1614
+ this.toggleDropdown(false);
1615
+ }
1616
+ }
1617
+ onSlotChange() {
1618
+ if (!this._value) return;
1619
+ const options = this.querySelectorAll("fc-option");
1620
+ let foundMatch = false;
1621
+ options.forEach(option => {
1622
+ const selected = option.value === this._value;
1623
+ option.selected = selected;
1624
+ if (selected) {
1625
+ this.inputEl.value = option.label;
1626
+ foundMatch = true;
1627
+ }
1628
+ });
1629
+ if (!foundMatch && this.inputEl.value === "") {
1630
+ this.inputEl.value = this._value;
1631
+ }
1632
+ this.syncValidity();
1633
+ }
1634
+ onKeyDown(e) {
1635
+ if (this.disabled) {
1636
+ return;
1637
+ }
1638
+ const options = this.getVisibleOptions();
1639
+ if (options.length === 0) return;
1640
+ if (e.key === "ArrowDown") {
1641
+ e.preventDefault();
1642
+ if (this.dropdownEl.hidden) {
1643
+ this.toggleDropdown(true);
1644
+ return;
1645
+ }
1646
+ const nextIndex = this.activeIndex >= options.length - 1 ? 0 : this.activeIndex + 1;
1647
+ this.setActiveOption(nextIndex, options);
1648
+ } else if (e.key === "ArrowUp") {
1649
+ e.preventDefault();
1650
+ if (this.dropdownEl.hidden) {
1651
+ this.toggleDropdown(true);
1652
+ return;
1653
+ }
1654
+ const prevIndex = this.activeIndex <= 0 ? options.length - 1 : this.activeIndex - 1;
1655
+ this.setActiveOption(prevIndex, options);
1656
+ } else if (e.key === "Enter" || e.key === " ") {
1657
+ e.preventDefault();
1658
+ if (this.dropdownEl.hidden) {
1659
+ this.toggleDropdown(true);
1660
+ } else {
1661
+ if (this.activeIndex > -1 && options[this.activeIndex]) {
1662
+ const target = options[this.activeIndex];
1663
+ this.selectOption(target);
1664
+ } else {
1665
+ this.toggleDropdown(false);
1666
+ }
1667
+ }
1668
+ } else if (e.key === "Escape") {
1669
+ e.preventDefault();
1670
+ this.toggleDropdown(false);
1671
+ } else if (e.key === "Tab") {
1672
+ this.toggleDropdown(false);
1673
+ } else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
1674
+ e.preventDefault();
1675
+ this.handleTypeAhead(e.key);
1676
+ }
1677
+ }
1678
+ handleTypeAhead(char) {
1679
+ if (this.searchTimeout) clearTimeout(this.searchTimeout);
1680
+ this.searchBuffer += char.toLowerCase();
1681
+ this.searchTimeout = setTimeout(() => {
1682
+ this.searchBuffer = "";
1683
+ }, 500);
1684
+ const options = this.getVisibleOptions();
1685
+ const matchIndex = options.findIndex(opt => {
1686
+ const label = (opt.getAttribute("label") || opt.textContent || "").toLowerCase();
1687
+ return label.startsWith(this.searchBuffer);
1688
+ });
1689
+ if (matchIndex !== -1) {
1690
+ const match = options[matchIndex];
1691
+ if (!this.dropdownEl.hidden) {
1692
+ this.setActiveOption(matchIndex, options);
1693
+ } else {
1694
+ this.selectOption(match);
1695
+ }
1696
+ }
1697
+ }
1698
+ onBlur() {
1699
+ this.setAttribute("touched", "");
1700
+ }
1701
+ onInvalid(e) {
1702
+ this.setAttribute("touched", "");
1703
+ this.dispatchEvent(new CustomEvent("fc-invalid", {
1704
+ bubbles: true,
1705
+ composed: true,
1706
+ detail: {
1707
+ originalEvent: e
1708
+ }
1709
+ }));
1710
+ }
1711
+ setActiveOption(index, visibleOptions) {
1712
+ this.querySelectorAll("fc-option").forEach(opt => opt.active = false);
1713
+ this.activeIndex = index;
1714
+ const target = visibleOptions[index];
1715
+ if (target) {
1716
+ target.active = true;
1717
+ this.inputEl.setAttribute("aria-activedescendant", target.id);
1718
+ target.scrollIntoView({
1719
+ block: "nearest",
1720
+ behavior: "smooth"
1721
+ });
1722
+ return;
1723
+ }
1724
+ this.inputEl.removeAttribute("aria-activedescendant");
1725
+ }
1726
+ getVisibleOptions() {
1727
+ return Array.from(this.querySelectorAll("fc-option")).filter(opt => !opt.disabled);
1728
+ }
1729
+ selectOption(option) {
1730
+ const value = option.value;
1731
+ const label = option.label;
1732
+ this.inputEl.value = label;
1733
+ this._value = value;
1734
+ this.internals.setFormValue(value);
1735
+ const allOptions = this.querySelectorAll("fc-option");
1736
+ allOptions.forEach(opt => {
1737
+ const selected = opt.value === value;
1738
+ opt.selected = selected;
1739
+ opt.active = false;
1740
+ });
1741
+ this.toggleDropdown(false);
1742
+ this.syncValidity();
1743
+ this.dispatchEvent(new CustomEvent("fc-change", {
1744
+ detail: {
1745
+ value: value,
1746
+ label: label
1747
+ },
1748
+ bubbles: true,
1749
+ composed: true
1750
+ }));
1751
+ }
1752
+ toggleDropdown(show) {
1753
+ if (!this.dropdownEl) {
1754
+ return;
1755
+ }
1756
+ if (this.disabled && show) {
1757
+ return;
1758
+ }
1759
+ const dropdown = this.dropdownEl;
1760
+ if (show) {
1761
+ const showEvent = new CustomEvent("fc-show", {
1762
+ bubbles: true,
1763
+ composed: true,
1764
+ cancelable: true
1765
+ });
1766
+ this.dispatchEvent(showEvent);
1767
+ if (showEvent.defaultPrevented) {
1768
+ return;
1769
+ }
1770
+ const spaceBelow = calculateBottomAvaliableSpace(this.inputEl);
1771
+ const spaceAbove = calculateTopAvaliableSpace(this.inputEl);
1772
+ dropdown.hidden = false;
1773
+ this.setAttribute("open", "true");
1774
+ this.inputEl.setAttribute("aria-expanded", "true");
1775
+ const shouldOpenUp = spaceBelow < dropdown.clientHeight && spaceAbove > spaceBelow;
1776
+ dropdown.classList.toggle("opens-up", shouldOpenUp);
1777
+ const options = this.getVisibleOptions();
1778
+ const selectedIndex = options.findIndex(opt => opt.value === this._value);
1779
+ if (selectedIndex >= 0) {
1780
+ this.setActiveOption(selectedIndex, options);
1781
+ } else {
1782
+ this.activeIndex = -1;
1783
+ }
1784
+ setTimeout(() => {
1785
+ document.addEventListener("click", this.onOutsideClick);
1786
+ }, 0);
1787
+ return;
1788
+ }
1789
+ if (!this.dropdownEl.hidden) {
1790
+ this.dispatchEvent(new CustomEvent("fc-hide", {
1791
+ bubbles: true,
1792
+ composed: true
1793
+ }));
1794
+ }
1795
+ document.removeEventListener("click", this.onOutsideClick);
1796
+ this.dropdownEl.hidden = true;
1797
+ this.removeAttribute("open");
1798
+ this.inputEl.setAttribute("aria-expanded", "false");
1799
+ this.activeIndex = -1;
1800
+ this.querySelectorAll("fc-option").forEach(opt => opt.active = false);
1801
+ this.inputEl.removeAttribute("aria-activedescendant");
1802
+ }
1803
+ syncValidity() {
1804
+ if (!this.inputEl) {
1805
+ return;
1806
+ }
1807
+ if (!this.inputEl.validity.valid) {
1808
+ this.internals.setValidity(this.inputEl.validity, this.inputEl.validationMessage, this.inputEl);
1809
+ return;
1810
+ }
1811
+ const options = this.querySelectorAll("fc-option");
1812
+ let match = false;
1813
+ options.forEach(option => {
1814
+ if (option.value === this._value) {
1815
+ match = true;
1816
+ }
1817
+ });
1818
+ if (!match) {
1819
+ this.internals.setValidity({
1820
+ customError: true
1821
+ }, "Please select a valid option from the list.", this.inputEl);
1822
+ return;
1823
+ }
1824
+ this.internals.setValidity({});
1825
+ }
1826
+ setProps(props) {
1827
+ for (const property in props) {
1828
+ const value = props[property];
1829
+ if (property in this) {
1830
+ this[property] = value;
1831
+ continue;
1832
+ }
1833
+ if ([ "string", "number", "boolean" ].includes(typeof value)) {
1834
+ this.setAttribute(property, String(value));
1835
+ }
1836
+ }
1837
+ }
1838
+ };
1839
+
1840
+ FcSelect.formAssociated = true;
1841
+
1842
+ var defineSelect = () => {
1843
+ if (!customElements.get("fc-select")) {
1844
+ customElements.define("fc-select", FcSelect);
1845
+ }
1846
+ return FcSelect;
1847
+ };
1848
+
1321
1849
  var defineAll = () => {
1322
1850
  defineCombobox();
1323
1851
  defineOption();
1324
1852
  defineInput();
1325
1853
  defineError();
1326
1854
  defineLabel();
1855
+ defineSelect();
1327
1856
  };
1328
1857
 
1329
- export { FcCombobox, FcError, FcInput, FcLabel, FcOption, defineAll, defineCombobox, defineError, defineInput, defineLabel, defineOption };
1858
+ 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.0",
4
4
  "type": "module",
5
5
  "description": "A library to easily create cool and customizable webcomponents.",
6
6
  "main": "dist/index.js",