fancy-ui-ts 1.1.1 → 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);
@@ -129,6 +131,11 @@
129
131
  --fc-error-fg: var(--fc-danger-300);
130
132
  --fc-error-max-width: fit-content;
131
133
  --fc-error-font-size: var(--fc-font-size-sm);
134
+ --fc-label-font-size: var(--fc-font-size-md);
135
+ --fc-label-font-weight: var(--fc-font-weight-bold);
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);
132
139
  }
133
140
 
134
141
  /* src/styles/themes/fc-theme-light.css */
@@ -149,11 +156,9 @@
149
156
  --fc-option-bg: var(--fc-white);
150
157
  --fc-option-bg-disabled: var(--fc-gray-50);
151
158
  --fc-option-bg-hover: var(--fc-gray-100);
152
- --fc-option-bg-selected: var(--fc-primary-200);
153
159
  --fc-option-bg-active: var(--fc-primary-100);
154
160
  --fc-option-fg: var(--fc-gray-900);
155
161
  --fc-option-fg-disabled: var(--fc-gray-300);
156
- --fc-option-fg-selected: var(--fc-primary-700);
157
162
  --fc-input-bg: var(--fc-white);
158
163
  --fc-input-bg-disabled: var(--fc-gray-50);
159
164
  --fc-input-fg: var(--fc-gray-900);
@@ -180,6 +185,9 @@
180
185
  --fc-input-radius: var(--fc-radius-md);
181
186
  --fc-input-shadow: var(--fc-shadow-none);
182
187
  --fc-error-fg: var(--fc-danger-300);
188
+ --fc-label-font-size: var(--fc-font-size-md);
189
+ --fc-label-font-weight: var(--fc-font-weight-bold);
190
+ --fc-label-fg: var(--fc-gray-900);
183
191
  }
184
192
 
185
193
  /* src/styles/themes/fc-theme-dark.css */
@@ -201,10 +209,8 @@
201
209
  --fc-option-bg-disabled: var(--fc-gray-900);
202
210
  --fc-option-bg-hover: var(--fc-gray-700);
203
211
  --fc-option-bg-active: var(--fc-primary-500);
204
- --fc-option-bg-selected: var(--fc-primary-600);
205
212
  --fc-option-fg: var(--fc-gray-200);
206
213
  --fc-option-fg-disabled: var(--fc-gray-700);
207
- --fc-option-fg-selected: var(--fc-primary-50);
208
214
  --fc-input-bg: var(--fc-gray-800);
209
215
  --fc-input-bg-disabled: var(--fc-gray-900);
210
216
  --fc-input-fg: var(--fc-gray-200);
@@ -231,4 +237,7 @@
231
237
  --fc-input-radius: var(--fc-radius-md);
232
238
  --fc-input-shadow: var(--fc-shadow-none);
233
239
  --fc-error-fg: var(--fc-danger-700);
240
+ --fc-label-font-size: var(--fc-font-size-md);
241
+ --fc-label-font-weight: var(--fc-font-weight-bold);
242
+ --fc-label-fg: var(--fc-gray-200);
234
243
  }
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
  }
@@ -168,6 +167,83 @@ declare class FcError extends HTMLElement {
168
167
 
169
168
  declare const defineError: () => typeof FcError;
170
169
 
170
+ declare class FcLabel extends HTMLElement {
171
+ static get observedAttributes(): string[];
172
+ constructor();
173
+ connectedCallback(): void;
174
+ disconnectedCallback(): void;
175
+ attributeChangedCallback(name: string, _oldVal: string, newVal: string): void;
176
+ get htmlFor(): string;
177
+ set htmlFor(val: string);
178
+ private onClick;
179
+ setProps(props: Record<string, any>): void;
180
+ }
181
+
182
+ declare const defineLabel: () => typeof FcLabel;
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
+
171
247
  declare const defineAll: () => void;
172
248
 
173
- export { FcCombobox, FcError, FcInput, FcOption, defineAll, defineCombobox, defineError, defineInput, 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,7 @@ 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
- const match = option.label.includes(this._value);
413
+ const match = option.label.toLowerCase().includes(this._value.toLowerCase());
414
414
  option.hidden = !match;
415
415
  });
416
416
  }
@@ -420,31 +420,50 @@ var FcCombobox = class extends HTMLElement {
420
420
  if (this.disabled) {
421
421
  return;
422
422
  }
423
- const options = this.getVisibleOptions();
424
- 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) {
425
426
  return;
426
427
  }
427
428
  if (e.key === "ArrowDown") {
428
429
  e.preventDefault();
429
- this.toggleDropdown(true);
430
- 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;
431
432
  this.setActiveOption(nextIndex, options);
432
433
  } else if (e.key === "ArrowUp") {
433
434
  e.preventDefault();
434
- this.toggleDropdown(true);
435
- 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;
436
437
  this.setActiveOption(prevIndex, options);
437
438
  } else if (e.key === "Enter") {
438
439
  e.preventDefault();
439
- if (this.activeIndex > -1 && options[this.activeIndex]) {
440
- const target = options[this.activeIndex];
441
- 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
+ }));
442
461
  }
443
462
  } else if (e.key === "Escape") {
444
463
  e.preventDefault();
445
- this.toggleDropdown(false);
464
+ this.hideDropdown();
446
465
  } else if (e.key === "Tab") {
447
- this.toggleDropdown(false);
466
+ this.hideDropdown();
448
467
  }
449
468
  }
450
469
  onBlur() {
@@ -452,10 +471,18 @@ var FcCombobox = class extends HTMLElement {
452
471
  }
453
472
  onInvalid(e) {
454
473
  this.setAttribute("touched", "");
474
+ this.dispatchEvent(new CustomEvent("fc-invalid", {
475
+ bubbles: true,
476
+ composed: true,
477
+ detail: {
478
+ originalEvent: e
479
+ }
480
+ }));
455
481
  }
456
- setActiveOption(index, visibleOptions) {
457
- this.querySelectorAll("fc-option").forEach(opt => opt.active = false);
482
+ setActiveOption(index, allOptions) {
483
+ allOptions.forEach(opt => opt.active = false);
458
484
  this.activeIndex = index;
485
+ const visibleOptions = Array.from(allOptions).filter(opt => !opt.hidden && !opt.disabled);
459
486
  const target = visibleOptions[index];
460
487
  if (target) {
461
488
  target.active = true;
@@ -468,51 +495,43 @@ var FcCombobox = class extends HTMLElement {
468
495
  }
469
496
  this.inputEl.removeAttribute("aria-activedescendant");
470
497
  }
471
- getVisibleOptions() {
472
- return Array.from(this.querySelectorAll("fc-option")).filter(opt => !opt.hidden && !opt.disabled);
473
- }
474
- selectOption(option) {
475
- const value = option.value;
476
- const label = option.label;
477
- this.inputEl.value = label;
478
- this._value = value;
479
- this.internals.setFormValue(value);
480
- const allOptions = this.querySelectorAll("fc-option");
481
- allOptions.forEach(opt => {
482
- const selected = opt.value === value;
483
- opt.selected = selected;
484
- opt.hidden = !selected;
485
- opt.active = false;
486
- });
487
- this.toggleDropdown(false);
488
- this.syncValidity();
489
- this.dispatchEvent(new CustomEvent("fc-change", {
490
- detail: {
491
- value: value,
492
- label: label
493
- },
494
- bubbles: true,
495
- composed: true
496
- }));
497
- }
498
- toggleDropdown(show) {
499
- if (!this.dropdownEl) {
498
+ showDropdown() {
499
+ if (!this.dropdownEl || this.disabled) {
500
500
  return;
501
501
  }
502
- 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) {
503
510
  return;
504
511
  }
505
- const dropdown = this.dropdownEl;
506
- if (show) {
507
- const spaceBelow = calculateBottomAvaliableSpace(this.inputEl);
508
- const spaceAbove = calculateTopAvaliableSpace(this.inputEl);
509
- dropdown.hidden = false;
510
- this.setAttribute("open", "true");
511
- this.inputEl.setAttribute("aria-expanded", "true");
512
- const shouldOpenUp = spaceBelow < dropdown.clientHeight && spaceAbove > spaceBelow;
513
- 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) {
514
526
  return;
515
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);
516
535
  this.dropdownEl.hidden = true;
517
536
  this.removeAttribute("open");
518
537
  this.inputEl.setAttribute("aria-expanded", "false");
@@ -577,11 +596,11 @@ var defineCombobox = () => {
577
596
  return FcCombobox;
578
597
  };
579
598
 
580
- 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`;
581
600
 
582
601
  var template2 = document.createElement("template");
583
602
 
584
- 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`;
585
604
 
586
605
  var FcOption = class extends HTMLElement {
587
606
  static get observedAttributes() {
@@ -717,7 +736,7 @@ var defineOption = () => {
717
736
  return FcOption;
718
737
  };
719
738
 
720
- 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`;
721
740
 
722
741
  var template3 = document.createElement("template");
723
742
 
@@ -1098,6 +1117,13 @@ var FcInput = class extends HTMLElement {
1098
1117
  }
1099
1118
  onInvalid(e) {
1100
1119
  this.setAttribute("touched", "");
1120
+ this.dispatchEvent(new CustomEvent("fc-invalid", {
1121
+ bubbles: true,
1122
+ composed: true,
1123
+ detail: {
1124
+ originalEvent: e
1125
+ }
1126
+ }));
1101
1127
  }
1102
1128
  onTogglePassword(e) {
1103
1129
  e.preventDefault();
@@ -1252,11 +1278,581 @@ var defineError = () => {
1252
1278
  return FcError;
1253
1279
  };
1254
1280
 
1281
+ var styles5 = `\n :host {\n display: block;\n width: 100%;\n box-sizing: border-box;\n font-family: var(--fc-font-family, inherit);\n font-size: var(--fc-label-font-size);\n font-weight: var(--fc-label-font-weight);\n color: var(--fc-label-fg);\n cursor: pointer;\n line-height: 1.5;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .fc-label-text {\n display: flex;\n align-items: center;\n }\n`;
1282
+
1283
+ var template5 = document.createElement("template");
1284
+
1285
+ template5.innerHTML = `\n <style>${styles5}</style>\n <div class="fc-label-text" part="text">\n <slot></slot>\n </div>\n`;
1286
+
1287
+ var FcLabel = class extends HTMLElement {
1288
+ static get observedAttributes() {
1289
+ return [ "for" ];
1290
+ }
1291
+ constructor() {
1292
+ super();
1293
+ const shadow = this.attachShadow({
1294
+ mode: "open"
1295
+ });
1296
+ shadow.appendChild(template5.content.cloneNode(true));
1297
+ this.onClick = this.onClick.bind(this);
1298
+ }
1299
+ connectedCallback() {
1300
+ this.addEventListener("click", this.onClick);
1301
+ }
1302
+ disconnectedCallback() {
1303
+ this.removeEventListener("click", this.onClick);
1304
+ }
1305
+ attributeChangedCallback(name, _oldVal, newVal) {}
1306
+ get htmlFor() {
1307
+ var _a;
1308
+ return (_a = this.getAttribute("for")) != null ? _a : "";
1309
+ }
1310
+ set htmlFor(val) {
1311
+ this.setAttribute("for", val);
1312
+ }
1313
+ onClick(e) {
1314
+ e.preventDefault();
1315
+ const targetId = this.getAttribute("for");
1316
+ if (!targetId) {
1317
+ return;
1318
+ }
1319
+ const targetEl = document.getElementById(targetId);
1320
+ if (targetEl) {
1321
+ targetEl.focus();
1322
+ targetEl.click();
1323
+ }
1324
+ }
1325
+ setProps(props) {
1326
+ for (const property in props) {
1327
+ const value = props[property];
1328
+ if (property in this) {
1329
+ this[property] = value;
1330
+ continue;
1331
+ }
1332
+ if ([ "string", "number", "boolean" ].includes(typeof value)) {
1333
+ this.setAttribute(property, String(value));
1334
+ }
1335
+ }
1336
+ }
1337
+ };
1338
+
1339
+ var defineLabel = () => {
1340
+ if (!customElements.get("fc-label")) {
1341
+ customElements.define("fc-label", FcLabel);
1342
+ }
1343
+ return FcLabel;
1344
+ };
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
+
1255
1849
  var defineAll = () => {
1256
1850
  defineCombobox();
1257
1851
  defineOption();
1258
1852
  defineInput();
1259
1853
  defineError();
1854
+ defineLabel();
1855
+ defineSelect();
1260
1856
  };
1261
1857
 
1262
- export { FcCombobox, FcError, FcInput, FcOption, defineAll, defineCombobox, defineError, defineInput, 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.1.1",
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",
@@ -26,6 +26,20 @@
26
26
  "build": "tsup",
27
27
  "build:watch": "tsup --watch"
28
28
  },
29
+ "keywords": [
30
+ "web-components",
31
+ "components",
32
+ "custom-elements",
33
+ "typescript",
34
+ "ui-library",
35
+ "design-system",
36
+ "combobox",
37
+ "input",
38
+ "label",
39
+ "html",
40
+ "react",
41
+ "vue"
42
+ ],
29
43
  "author": "Luan Peixoto",
30
44
  "license": "MIT",
31
45
  "devDependencies": {