fancy-ui-ts 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,8 +3,234 @@
3
3
  A lightweight, cool, customizable Web Component library built with TypeScript.
4
4
  Works with **React**, **Vue**, **Angular**, **Svelte**, or plain HTML/JS.
5
5
 
6
- ## 📦 Installation
6
+ ## Installation
7
7
 
8
8
  ```bash
9
9
  npm install fancy-ui-ts
10
- ```
10
+ ```
11
+
12
+ -----
13
+
14
+ ## 1\. Quick Start
15
+
16
+ ### Registration
17
+
18
+ Before using a component, you must register it with the browser.
19
+
20
+ ```typescript
21
+ import { defineInput } from './index';
22
+
23
+ defineInput();
24
+ ```
25
+
26
+ You can also register every component at once:
27
+
28
+ ```typescript
29
+ import { defineAll } from './index';
30
+
31
+ defineAll();
32
+ ```
33
+
34
+ ### Basic HTML Usage
35
+
36
+ ```html
37
+ <form>
38
+ <fc-input
39
+ name="username"
40
+ placeholder="Enter username"
41
+ required
42
+ minlength="3"
43
+ >
44
+ </fc-input>
45
+
46
+ <fc-input
47
+ type="password"
48
+ name="password"
49
+ required
50
+ >
51
+ </fc-input>
52
+ </form>
53
+ ```
54
+
55
+ -----
56
+
57
+ ## 2\. Setting Properties & Attributes
58
+
59
+ Most fc-components have properties that can be set as custom attributes, they also accepts default attributes. You can configure the component using HTML Attributes or JavaScript Properties.
60
+
61
+ ### Via HTML Attributes
62
+
63
+ Use this for static configuration or simple strings.
64
+
65
+ ```html
66
+ <fc-input
67
+ label="Email"
68
+ type="email"
69
+ disabled
70
+ placeholder="john@doe.com"
71
+ ></fc-input>
72
+ ```
73
+
74
+ ### Via JavaScript Properties
75
+
76
+ In some cases, you'll need to pass dynamic data, complex objects, arrays or event listeners. You can't do that as attributes, you must
77
+ pass them with Javascript:
78
+
79
+ ```javascript
80
+ const input = document.querySelector('fc-input');
81
+
82
+ input.value = "New Value";
83
+ input.disabled = true;
84
+ input.required = false;
85
+
86
+ // The setProps() helper: useful for setting multiple properties at once using a configuration object.
87
+ input.setProps({
88
+ value: "Dynamic Value",
89
+ placeholder: "Updated Placeholder",
90
+ readOnly: true
91
+ });
92
+ ```
93
+
94
+ -----
95
+
96
+ ## 3\. Styling & Theming
97
+
98
+ The component uses **Shadow DOM**, which means internal elements (like the actual `<input>` tag) are isolated from global CSS. You cannot style internal classes like `.fc-input-field` directly. Instead, you use **CSS Variables** or style the **Host Component**.
99
+
100
+ ### Strategy A: CSS Variables (Theming)
101
+
102
+ Variables pierce the Shadow DOM and are the primary way to theme the component.
103
+
104
+ ```css
105
+ /* Apply globally to all inputs */
106
+ :root {
107
+ --fc-input-bg: #ffffff;
108
+ --fc-input-border: #ccc;
109
+ --fc-input-focus-ring: 0 0 0 3px rgba(0, 123, 255, 0.25);
110
+ --fc-input-radius: 4px;
111
+ }
112
+
113
+ /* Apply only on a theme */
114
+ :root[fc-theme="dark"] {
115
+ --fc-input-bg: #333;
116
+ --fc-input-fg: #fff;
117
+ }
118
+
119
+ /* Apply to a specific class */
120
+ .username-input {
121
+ --fc-input-border: #555;
122
+ }
123
+ ```
124
+
125
+ ### Strategy B: Host Styling
126
+
127
+ You can style the element tag itself (dimensions, margins, display).
128
+
129
+ ```css
130
+ /* Target by tag name */
131
+ fc-input {
132
+ display: block;
133
+ margin-bottom: 1rem;
134
+ width: 100%;
135
+ max-width: 400px;
136
+ }
137
+
138
+ /* Target by class */
139
+ .my-custom-input {
140
+ width: 50%;
141
+ }
142
+
143
+ /* Target specific states provided by the component */
144
+ fc-input[disabled] {
145
+ opacity: 0.7;
146
+ }
147
+
148
+ fc-input[touched]:invalid {
149
+ /* You can style the host wrapper when error exists */
150
+ animation: shake 0.2s ease-in-out;
151
+ }
152
+ ```
153
+
154
+ -----
155
+
156
+ ## 4\. Events
157
+
158
+ Each element has its own unique events that are emitted from it. Even though the elements still standart events like `input`, `click`, `blur`, because of the nature of the Shadow DOM, it is highly recommended to listen to only custom Events.
159
+
160
+ ```javascript
161
+ const el = document.querySelector('fc-input');
162
+
163
+ el.addEventListener('fc-input', (e) => {
164
+ console.log('Typing:', e.detail.value);
165
+ });
166
+
167
+ el.addEventListener('fc-change', (e) => {
168
+ console.log('Committed:', e.detail.value);
169
+ });
170
+ ```
171
+
172
+ -----
173
+
174
+ ## 5\. Validation Logic
175
+
176
+ Some elements has validation logic, most are form field elements, link inputs, select, combobox, etc. Visual errors (red borders) are **suppressed** until the user interacts with the field (blur) or submits the form. Validation works in two layers:
177
+
178
+ ### Layer 1: Native Attributes
179
+
180
+ Standard HTML5 attributes work automatically.
181
+
182
+ * `required`, `min`, `max`, `step`
183
+ * `minlength`, `maxlength`, `pattern`
184
+ * `type="email"`, `type="url"`
185
+
186
+
187
+ ### Layer 2: Custom Validator Function
188
+
189
+ You can inject custom business logic using the `.validator` property.
190
+
191
+ ```javascript
192
+ const usernameInput = document.getElementById('user-field');
193
+
194
+ // Function receives value -> returns Error String or Null
195
+ usernameInput.validator = (value) => {
196
+ if (value.toLowerCase() === 'admin') {
197
+ return 'This username is reserved.'; // Error message
198
+ }
199
+ return null; // Valid
200
+ };
201
+ ```
202
+
203
+ On both ways, you can also use our custom `<fc-error>` component to display the error message as a paragraph:
204
+
205
+ ```html
206
+ <fc-input
207
+ id="email-field"
208
+ label="Email"
209
+ type="email"
210
+ disabled
211
+ placeholder="john@doe.com"
212
+ >
213
+ </fc-input>
214
+ <fc-error
215
+ for="email-field"
216
+ >
217
+ </fc-error>
218
+ ```
219
+
220
+ -----
221
+
222
+ ## 6\. Accessibility (A11y)
223
+
224
+ * **Delegates Focus:** Most elements has delegate focus, which means clicking anywhere on the component focuses the internal input.
225
+ * **ARIA Labels:** `aria-label`, `aria-labelledby`, `aria-describedby` and others aria's can be set directly to the fc-element. The browser automatically maps them to the respective internal element.
226
+ * **Auto-States:** `aria-disabled`, `aria-required`, and `aria-invalid` are automatically managed by `ElementInternals`. Do not set these manually.
227
+ * **Other:** Other aria properties are added directly to the element template because they do not need to be custom and does not change. Example: `<fc-combobox>` has keyboard mapping arias added to its template, `<fc-input>` has 'aria-live' on its internal input element, etc.
228
+
229
+ -----
230
+
231
+ ## 7\. Attribute Reflection
232
+
233
+ For all properties (`disabled`, `placeholder`, `min`, etc.), the component uses **Reflection**.
234
+
235
+ * **JS to HTML:** Setting `el.disabled = true` adds the `disabled` attribute to the HTML tag.
236
+ * **HTML to JS:** Changing the HTML attribute triggers `attributeChangedCallback`, which updates the internal logic.
package/dist/index.css CHANGED
@@ -75,34 +75,34 @@
75
75
  :root {
76
76
  font-family: var(--fc-font-family);
77
77
  --fc-combobox-bg: var(--fc-white);
78
- --fc-combobox-disabled-bg: var(--fc-gray-50);
78
+ --fc-combobox-bg-disabled: var(--fc-gray-50);
79
79
  --fc-combobox-fg: var(--fc-gray-900);
80
80
  --fc-combobox-border: var(--fc-gray-300);
81
81
  --fc-combobox-border-hover: var(--fc-gray-400);
82
82
  --fc-combobox-border-focus: var(--fc-primary-400);
83
83
  --fc-combobox-placeholder: var(--fc-gray-400);
84
- --fc-combobox-disabled-placeholder: var(--fc-gray-300);
84
+ --fc-combobox-placeholder-disabled: var(--fc-gray-300);
85
85
  --fc-combobox-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-primary);
86
- --fc-combobox-error-bg: var(--fc-danger-50);
87
- --fc-combobox-error-color: var(--fc-danger-300);
88
- --fc-combobox-error-focus-ring: var(--fc-focus-ring-danger);
86
+ --fc-combobox-bg-error: var(--fc-danger-50);
87
+ --fc-combobox-border-error: var(--fc-danger-300);
88
+ --fc-combobox-focus-ring-error: var(--fc-focus-ring-danger);
89
89
  --fc-combobox-max-width: 412px;
90
90
  --fc-combobox-padding: var(--fc-space-3);
91
91
  --fc-combobox-border-width: var(--fc-border-width-xs);
92
92
  --fc-combobox-radius: var(--fc-radius-md);
93
93
  --fc-combobox-shadow: var(--fc-shadow-none);
94
94
  --fc-option-bg: var(--fc-white);
95
- --fc-option-disabled-bg: var(--fc-gray-50);
95
+ --fc-option-bg-disabled: var(--fc-gray-50);
96
96
  --fc-option-bg-hover: var(--fc-gray-100);
97
97
  --fc-option-bg-selected: var(--fc-primary-200);
98
98
  --fc-option-bg-active: var(--fc-primary-100);
99
99
  --fc-option-fg: var(--fc-gray-900);
100
- --fc-option-disabled-fg: var(--fc-gray-300);
100
+ --fc-option-fg-disabled: var(--fc-gray-300);
101
101
  --fc-option-fg-selected: var(--fc-primary-700);
102
102
  --fc-option-padding: var(--fc-space-3);
103
103
  --fc-option-radius: var(--fc-radius-md);
104
104
  --fc-input-bg: var(--fc-white);
105
- --fc-input-disabled-bg: var(--fc-gray-50);
105
+ --fc-input-bg-disabled: var(--fc-gray-50);
106
106
  --fc-input-fg: var(--fc-gray-900);
107
107
  --fc-input-border: var(--fc-gray-400);
108
108
  --fc-input-border-hover: var(--fc-gray-400);
@@ -110,9 +110,9 @@
110
110
  --fc-input-placeholder: var(--fc-gray-400);
111
111
  --fc-input-disabled-placeholder: var(--fc-gray-300);
112
112
  --fc-input-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-primary);
113
- --fc-input-error-bg: var(--fc-danger-50);
114
- --fc-input-error-color: var(--fc-danger-300);
115
- --fc-input-error-focus-ring: var(--fc-focus-ring-danger);
113
+ --fc-input-bg-error: var(--fc-danger-50);
114
+ --fc-input-border-error: var(--fc-danger-300);
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
118
  --fc-input-file-border: var(--fc-gray-400);
@@ -126,45 +126,46 @@
126
126
  --fc-input-border-width: var(--fc-border-width-xs);
127
127
  --fc-input-radius: var(--fc-radius-md);
128
128
  --fc-input-shadow: var(--fc-shadow-none);
129
- --fc-error-color: var(--fc-danger-300);
129
+ --fc-error-fg: var(--fc-danger-300);
130
130
  --fc-error-max-width: fit-content;
131
131
  --fc-error-font-size: var(--fc-font-size-sm);
132
132
  }
133
133
 
134
134
  /* src/styles/themes/fc-theme-light.css */
135
135
  :root[fc-theme=light] {
136
+ --fc-color-scheme: light !important;
136
137
  --fc-combobox-bg: var(--fc-white);
137
- --fc-combobox-disabled-bg: var(--fc-gray-50);
138
+ --fc-combobox-bg-disabled: var(--fc-gray-50);
138
139
  --fc-combobox-fg: var(--fc-gray-900);
139
140
  --fc-combobox-border: var(--fc-gray-300);
140
141
  --fc-combobox-border-hover: var(--fc-gray-400);
141
142
  --fc-combobox-border-focus: var(--fc-primary-400);
142
143
  --fc-combobox-placeholder: var(--fc-gray-400);
143
- --fc-combobox-disabled-placeholder: var(--fc-gray-300);
144
+ --fc-combobox-placeholder-disabled: var(--fc-gray-300);
144
145
  --fc-combobox-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-primary);
145
- --fc-combobox-error-bg: var(--fc-danger-50);
146
- --fc-combobox-error-color: var(--fc-danger-300);
147
- --fc-combobox-error-focus-ring: var(--fc-focus-ring-danger);
146
+ --fc-combobox-bg-error: var(--fc-danger-50);
147
+ --fc-combobox-border-error: var(--fc-danger-300);
148
+ --fc-combobox-focus-ring-error: var(--fc-focus-ring-danger);
148
149
  --fc-option-bg: var(--fc-white);
149
- --fc-option-disabled-bg: var(--fc-gray-50);
150
+ --fc-option-bg-disabled: var(--fc-gray-50);
150
151
  --fc-option-bg-hover: var(--fc-gray-100);
151
152
  --fc-option-bg-selected: var(--fc-primary-200);
152
153
  --fc-option-bg-active: var(--fc-primary-100);
153
154
  --fc-option-fg: var(--fc-gray-900);
154
- --fc-option-disabled-fg: var(--fc-gray-300);
155
+ --fc-option-fg-disabled: var(--fc-gray-300);
155
156
  --fc-option-fg-selected: var(--fc-primary-700);
156
157
  --fc-input-bg: var(--fc-white);
157
- --fc-input-disabled-bg: var(--fc-gray-50);
158
+ --fc-input-bg-disabled: var(--fc-gray-50);
158
159
  --fc-input-fg: var(--fc-gray-900);
159
160
  --fc-input-border: var(--fc-gray-300);
160
161
  --fc-input-border-hover: var(--fc-gray-400);
161
162
  --fc-input-border-focus: var(--fc-primary-400);
162
163
  --fc-input-placeholder: var(--fc-gray-400);
163
- --fc-input-disabled-placeholder: var(--fc-gray-300);
164
+ --fc-input-placeholder-disabled: var(--fc-gray-300);
164
165
  --fc-input-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-primary);
165
- --fc-input-error-bg: var(--fc-danger-50);
166
- --fc-input-error-color: var(--fc-danger-300);
167
- --fc-input-error-focus-ring: var(--fc-focus-ring-danger);
166
+ --fc-input-bg-error: var(--fc-danger-50);
167
+ --fc-input-border-error: var(--fc-danger-300);
168
+ --fc-input-focus-ring-error: var(--fc-focus-ring-danger);
168
169
  --fc-input-password-icon-color: var(--fc-gray-400);
169
170
  --fc-input-password-icon-color-hover: var(--fc-gray-500);
170
171
  --fc-input-file-border: var(--fc-gray-300);
@@ -178,43 +179,44 @@
178
179
  --fc-input-border-width: var(--fc-border-width-xs);
179
180
  --fc-input-radius: var(--fc-radius-md);
180
181
  --fc-input-shadow: var(--fc-shadow-none);
181
- --fc-error-color: var(--fc-danger-300);
182
+ --fc-error-fg: var(--fc-danger-300);
182
183
  }
183
184
 
184
185
  /* src/styles/themes/fc-theme-dark.css */
185
186
  :root[fc-theme=dark] {
187
+ --fc-color-scheme: dark !important;
186
188
  --fc-combobox-bg: var(--fc-gray-800);
187
- --fc-combobox-disabled-bg: var(--fc-gray-900);
189
+ --fc-combobox-bg-disabled: var(--fc-gray-900);
188
190
  --fc-combobox-fg: var(--fc-gray-200);
189
191
  --fc-combobox-border: var(--fc-gray-700);
190
192
  --fc-combobox-border-hover: var(--fc-gray-500);
191
193
  --fc-combobox-border-focus: var(--fc-primary-400);
192
194
  --fc-combobox-placeholder: var(--fc-gray-500);
193
- --fc-combobox-disabled-placeholder: var(--fc-gray-700);
195
+ --fc-combobox-placeholder-disabled: var(--fc-gray-700);
194
196
  --fc-combobox-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-secondary);
195
- --fc-combobox-error-bg: var(--fc-danger-900);
196
- --fc-combobox-error-color: var(--fc-danger-700);
197
- --fc-combobox-error-focus-ring: var(--fc-focus-ring-danger-secondary);
197
+ --fc-combobox-bg-error: var(--fc-danger-900);
198
+ --fc-combobox-border-error: var(--fc-danger-700);
199
+ --fc-combobox-focus-ring-error: var(--fc-focus-ring-danger-secondary);
198
200
  --fc-option-bg: var(--fc-gray-800);
199
- --fc-option-disabled-bg: var(--fc-gray-900);
201
+ --fc-option-bg-disabled: var(--fc-gray-900);
200
202
  --fc-option-bg-hover: var(--fc-gray-700);
201
203
  --fc-option-bg-active: var(--fc-primary-500);
202
204
  --fc-option-bg-selected: var(--fc-primary-600);
203
205
  --fc-option-fg: var(--fc-gray-200);
204
- --fc-option-disabled-fg: var(--fc-gray-700);
206
+ --fc-option-fg-disabled: var(--fc-gray-700);
205
207
  --fc-option-fg-selected: var(--fc-primary-50);
206
208
  --fc-input-bg: var(--fc-gray-800);
207
- --fc-input-disabled-bg: var(--fc-gray-900);
209
+ --fc-input-bg-disabled: var(--fc-gray-900);
208
210
  --fc-input-fg: var(--fc-gray-200);
209
211
  --fc-input-border: var(--fc-gray-700);
210
212
  --fc-input-border-hover: var(--fc-gray-500);
211
213
  --fc-input-border-focus: var(--fc-primary-400);
212
214
  --fc-input-placeholder: var(--fc-gray-500);
213
- --fc-input-disabled-placeholder: var(--fc-gray-700);
215
+ --fc-input-placeholder-disabled: var(--fc-gray-700);
214
216
  --fc-input-focus-ring: var(--fc-focus-ring-sm) var(--fc-focus-ring-secondary);
215
- --fc-input-error-bg: var(--fc-danger-900);
216
- --fc-input-error-color: var(--fc-danger-700);
217
- --fc-input-error-focus-ring: var(--fc-focus-ring-danger-secondary);
217
+ --fc-input-bg-error: var(--fc-danger-900);
218
+ --fc-input-border-error: var(--fc-danger-700);
219
+ --fc-input-focus-ring-error: var(--fc-focus-ring-danger-secondary);
218
220
  --fc-input-password-icon-color: var(--fc-gray-500);
219
221
  --fc-input-password-icon-color-hover: var(--fc-gray-400);
220
222
  --fc-input-file-border: var(--fc-gray-700);
@@ -228,5 +230,5 @@
228
230
  --fc-input-border-width: var(--fc-border-width-xs);
229
231
  --fc-input-radius: var(--fc-radius-md);
230
232
  --fc-input-shadow: var(--fc-shadow-none);
231
- --fc-error-color: var(--fc-danger-700);
233
+ --fc-error-fg: var(--fc-danger-700);
232
234
  }
package/dist/index.d.ts CHANGED
@@ -51,7 +51,6 @@ declare class FcCombobox extends HTMLElement {
51
51
  private onOptionSelect;
52
52
  private onOutsideClick;
53
53
  private onFocusOut;
54
- private onDropdownClick;
55
54
  private onFocus;
56
55
  private onSlotChange;
57
56
  private onKeyDown;
package/dist/index.js CHANGED
@@ -1,8 +1,19 @@
1
- var styles = `\n\t:host {\n\t\tdisplay: block;\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n \tfont-family: var(--fc-font-family);\n\t\tmax-width: var(--fc-combobox-max-width);\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 {\n background-color: var(--fc-combobox-error-bg);\n border-color: var(--fc-combobox-error-color);\n }\n\n :host([touched]:invalid) .fc-input:focus {\n box-shadow: 0 0 0 2px var(--fc-combobox-error-focus-ring);\n }\n\n\t.fc-input {\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n\n\t\tpadding: var(--fc-combobox-padding);\n\t\tborder-radius: var(--fc-combobox-radius);\n\t\tbackground: var(--fc-combobox-bg);\n\t\tcolor: var(--fc-combobox-fg);\n\n\t\tborder: var(--fc-combobox-border-width) solid var(--fc-combobox-border);\n\t\tfont-size: var(--fc-font-size-md);\n\n\t\tbox-shadow: var(--fc-combobox-shadow);\n\t\ttransition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n\t}\n\n\t.fc-input::placeholder {\n\t\tcolor: var(--fc-combobox-placeholder);\n\t}\n\n\t.fc-input:hover {\n\t\tborder-color: var(--fc-combobox-border-hover);\n\t}\n\n\t.fc-input:focus {\n\t\tborder-color: var(--fc-combobox-border-focus);\n\t\toutline: none;\n\t\tbox-shadow: var(--fc-combobox-focus-ring);\n\t}\n\n\t.fc-options {\n\t\tposition: absolute;\n\t\ttop: calc(100% + 6px);\n\t\tleft: 0;\n\t\tright: 0;\n\t\tz-index: 1000;\n\t\tbackground: var(--fc-combobox-dropdown-bg, var(--fc-combobox-bg));\n\t\tborder: var(--fc-combobox-border-width) solid var(--fc-combobox-border);\n\t\tborder-radius: var(--fc-combobox-dropdown-radius, var(--fc-combobox-radius));\n\t\tpadding: var(--fc-combobox-dropdown-padding, calc(var(--fc-combobox-padding) - 5px));\n\t\tbox-shadow: var(--fc-combobox-dropdown-shadow);\n\t\tmax-height: var(--fc-combobox-dropdown-max-height, 240px);\n\t\toverflow-y: auto;\n\t\tbox-sizing: border-box;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 4px;\n\t}\n\t\n\t.fc-options[hidden] {\n display: none !important;\n }\n\n\t:host([disabled]) {\n cursor: not-allowed;\n }\n\n\t.fc-input:disabled {\n\t\tbackground: var(--fc-combobox-disabled-bg);\n cursor: not-allowed;\n\t\tbox-shadow: none;\n }\n\n\t.fc-input:disabled::placeholder {\n\t\tcolor: var(--fc-combobox-disabled-placeholder);\n }\n\n\t.fc-input:disabled:hover {\n\t\tborder-color: var(--fc-combobox-border);\n\t}\n\n\t.fc-input:disabled:focus {\n\t\tborder-color: var(--fc-combobox-border);\n\t}\n`;
1
+ var styles = `\n\t:host {\n\t\tdisplay: block;\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n \tfont-family: var(--fc-font-family);\n\t\tmax-width: var(--fc-combobox-max-width);\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 {\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\t.fc-input {\n\t\twidth: 100%;\n\t\tbox-sizing: border-box;\n\n\t\tpadding: var(--fc-combobox-padding);\n\t\tborder-radius: var(--fc-combobox-radius);\n\t\tbackground: var(--fc-combobox-bg);\n\t\tcolor: var(--fc-combobox-fg);\n\n\t\tborder: var(--fc-combobox-border-width) solid var(--fc-combobox-border);\n\t\tfont-size: var(--fc-font-size-md);\n\n\t\tbox-shadow: var(--fc-combobox-shadow);\n\t\ttransition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n\t}\n\n\t.fc-input::placeholder {\n\t\tcolor: var(--fc-combobox-placeholder);\n\t}\n\n\t.fc-input:hover {\n\t\tborder-color: var(--fc-combobox-border-hover);\n\t}\n\n\t.fc-input:focus {\n\t\tborder-color: var(--fc-combobox-border-focus);\n\t\toutline: none;\n\t\tbox-shadow: var(--fc-combobox-focus-ring);\n\t}\n\n\t.fc-options {\n\t\tposition: absolute;\n\t\ttop: calc(100% + 6px);\n\t\tleft: 0;\n\t\tright: 0;\n\t\tz-index: 1000;\n\t\tbackground: var(--fc-combobox-dropdown-bg, var(--fc-combobox-bg));\n\t\tborder: var(--fc-combobox-border-width) solid var(--fc-combobox-border);\n\t\tborder-radius: var(--fc-combobox-dropdown-radius, var(--fc-combobox-radius));\n\t\tpadding: var(--fc-combobox-dropdown-padding, calc(var(--fc-combobox-padding) - 5px));\n\t\tbox-shadow: var(--fc-combobox-dropdown-shadow);\n\t\tmax-height: var(--fc-combobox-dropdown-max-height, 240px);\n\t\toverflow-y: auto;\n\t\tbox-sizing: border-box;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 4px;\n\t}\n\n\t.fc-options.opens-up {\n top: auto;\n bottom: calc(100% + 6px);\n }\n\t\n\t.fc-options[hidden] {\n display: none !important;\n }\n\n\t:host([disabled]) {\n cursor: not-allowed;\n }\n\n\t.fc-input:disabled {\n\t\tbackground: var(--fc-combobox-bg-disabled);\n cursor: not-allowed;\n\t\tbox-shadow: none;\n }\n\n\t.fc-input:disabled::placeholder {\n\t\tcolor: var(--fc-combobox-placeholder-disabled);\n }\n\n\t.fc-input:disabled:hover {\n\t\tborder-color: var(--fc-combobox-border);\n\t}\n\n\t.fc-input:disabled:focus {\n\t\tborder-color: var(--fc-combobox-border);\n\t}\n`;
2
2
 
3
3
  var template = document.createElement("template");
4
4
 
5
- template.innerHTML = `\n\t<style>${styles}</style>\n\n\t<input \n\t\tid="fc-input" \n\t\tclass="fc-input"\n\t\ttype="text" \n\t\trole="combobox"\n\t\taria-autocomplete="list"\n\t\taria-expanded="false"\n\t\taria-haspopup="listbox"\n\t\taria-controls="fc-options"\n\t\tpart="input"\n\t\tautocomplete="off"\n\t/>\n\n\t<div \n\t\tid="fc-options" \n\t\tpart="options" \n\t\tclass="fc-options" \n\t\trole="listbox"\n\t\thidden\n\t>\n\t\t<slot></slot>\n\t</div>\n\t\n`;
5
+ template.innerHTML = `\n\t<style>${styles}</style>\n\n\t<input \n\t\tclass="fc-input"\n\t\ttype="text" \n\t\trole="combobox"\n\t\taria-autocomplete="list"\n\t\taria-expanded="false"\n\t\taria-haspopup="listbox"\n\t\taria-controls="fc-options"\n\t\tpart="input"\n\t\tautocomplete="off"\n\t/>\n\n\t<div \n\t\tpart="options" \n\t\tclass="fc-options" \n\t\trole="listbox"\n\t\thidden\n\t>\n\t\t<slot></slot>\n\t</div>\n\t\n`;
6
+
7
+ var calculateBottomAvaliableSpace = element => {
8
+ const rect = element.getBoundingClientRect();
9
+ const viewportHeight = window.innerHeight;
10
+ return viewportHeight - rect.bottom;
11
+ };
12
+
13
+ var calculateTopAvaliableSpace = element => {
14
+ const rect = element.getBoundingClientRect();
15
+ return rect.top;
16
+ };
6
17
 
7
18
  var FcCombobox = class extends HTMLElement {
8
19
  constructor() {
@@ -16,13 +27,20 @@ var FcCombobox = class extends HTMLElement {
16
27
  });
17
28
  shadow.appendChild(template.content.cloneNode(true));
18
29
  this.internals = this.attachInternals();
30
+ this.inputEl = shadow.querySelector(".fc-input");
31
+ this.dropdownEl = shadow.querySelector(".fc-options");
32
+ const randomId = Math.random().toString(36).substring(2, 9);
33
+ const inputId = `fc-input-${randomId}`;
34
+ const dropdownId = `fc-options-${randomId}`;
35
+ this.inputEl.id = inputId;
36
+ this.dropdownEl.id = dropdownId;
37
+ this.inputEl.setAttribute("aria-controls", dropdownId);
19
38
  this.onInput = this.onInput.bind(this);
20
39
  this.onChange = this.onChange.bind(this);
21
40
  this.onOptionSelect = this.onOptionSelect.bind(this);
22
41
  this.onOutsideClick = this.onOutsideClick.bind(this);
23
42
  this.onFocusOut = this.onFocusOut.bind(this);
24
43
  this.onFocus = this.onFocus.bind(this);
25
- this.onDropdownClick = this.onDropdownClick.bind(this);
26
44
  this.onSlotChange = this.onSlotChange.bind(this);
27
45
  this.onKeyDown = this.onKeyDown.bind(this);
28
46
  this.onBlur = this.onBlur.bind(this);
@@ -82,19 +100,6 @@ var FcCombobox = class extends HTMLElement {
82
100
  if (!this.inputEl) {
83
101
  return;
84
102
  }
85
- const options = Array.from(this.querySelectorAll("fc-option"));
86
- options.forEach(option => {
87
- const selected = option.value === newValue;
88
- option.selected = selected;
89
- option.hidden = !selected;
90
- option.active = false;
91
- if (selected) {
92
- this.inputEl.value = option.label;
93
- }
94
- });
95
- if (this.inputEl.value === "") {
96
- this.inputEl.value = newValue;
97
- }
98
103
  this.syncValidity();
99
104
  }
100
105
  get label() {
@@ -162,12 +167,10 @@ var FcCombobox = class extends HTMLElement {
162
167
  this.syncValidity();
163
168
  }
164
169
  connectedCallback() {
165
- this.inputEl = this.shadowRoot.getElementById("fc-input");
166
- this.dropdownEl = this.shadowRoot.getElementById("fc-options");
170
+ this.internals.setFormValue(this.value);
167
171
  if (this.hasAttribute("placeholder")) {
168
172
  this.inputEl.placeholder = this.getAttribute("placeholder");
169
173
  }
170
- this.internals.setFormValue(this.value);
171
174
  if (this.hasAttribute("disabled")) {
172
175
  this.inputEl.disabled = true;
173
176
  this.internals.ariaDisabled = "true";
@@ -185,7 +188,6 @@ var FcCombobox = class extends HTMLElement {
185
188
  document.addEventListener("click", this.onOutsideClick);
186
189
  this.addEventListener("focusout", this.onFocusOut);
187
190
  this.inputEl.addEventListener("focus", this.onFocus);
188
- this.dropdownEl.addEventListener("mousedown", this.onDropdownClick);
189
191
  this.inputEl.addEventListener("blur", this.onBlur);
190
192
  this.addEventListener("invalid", this.onInvalid);
191
193
  const slot = this.shadowRoot.querySelector("slot");
@@ -233,7 +235,7 @@ var FcCombobox = class extends HTMLElement {
233
235
  this.inputEl.value = "";
234
236
  }
235
237
  this.internals.setFormValue("");
236
- const options = Array.from(this.querySelectorAll("fc-option"));
238
+ const options = this.querySelectorAll("fc-option");
237
239
  options.forEach(option => {
238
240
  option.selected = false;
239
241
  option.hidden = false;
@@ -251,11 +253,23 @@ var FcCombobox = class extends HTMLElement {
251
253
  if (restoredValue) {
252
254
  this._value = restoredValue;
253
255
  this.internals.setFormValue(restoredValue);
254
- const options = Array.from(this.querySelectorAll("fc-option"));
255
- const match = options.find(opt => opt.value === restoredValue);
256
- if (match && this.inputEl) {
257
- this.inputEl.value = match.label;
258
- match.selected = true;
256
+ const options = this.querySelectorAll("fc-option");
257
+ let foundMatch = false;
258
+ options.forEach(option => {
259
+ const selected = option.value === this._value;
260
+ option.selected = selected;
261
+ option.hidden = !selected;
262
+ if (selected) {
263
+ this.inputEl.value = option.label;
264
+ foundMatch = true;
265
+ }
266
+ });
267
+ if (!foundMatch && this.inputEl.value === "") {
268
+ this.inputEl.value = this._value;
269
+ options.forEach(option => {
270
+ const match = option.label.includes(this._value);
271
+ option.hidden = !match;
272
+ });
259
273
  }
260
274
  this.syncValidity();
261
275
  }
@@ -263,7 +277,7 @@ var FcCombobox = class extends HTMLElement {
263
277
  onInput(e) {
264
278
  const rawQuery = e.target.value;
265
279
  const query = rawQuery.toLowerCase().trim();
266
- const options = Array.from(this.querySelectorAll("fc-option"));
280
+ const options = this.querySelectorAll("fc-option");
267
281
  if (query.length === 0) {
268
282
  options.forEach(opt => {
269
283
  opt.hidden = false;
@@ -338,7 +352,7 @@ var FcCombobox = class extends HTMLElement {
338
352
  this.inputEl.value = label;
339
353
  this._value = value;
340
354
  this.internals.setFormValue(value);
341
- const options = Array.from(this.querySelectorAll("fc-option"));
355
+ const options = this.querySelectorAll("fc-option");
342
356
  options.forEach(option => {
343
357
  const selected = option.value === value;
344
358
  option.selected = selected;
@@ -366,9 +380,6 @@ var FcCombobox = class extends HTMLElement {
366
380
  this.toggleDropdown(false);
367
381
  }
368
382
  }
369
- onDropdownClick(e) {
370
- e.preventDefault();
371
- }
372
383
  onFocus(e) {
373
384
  if (this.disabled) {
374
385
  return;
@@ -385,13 +396,12 @@ var FcCombobox = class extends HTMLElement {
385
396
  if (!this._value) {
386
397
  return;
387
398
  }
388
- const options = Array.from(this.querySelectorAll("fc-option"));
399
+ const options = this.querySelectorAll("fc-option");
389
400
  let foundMatch = false;
390
401
  options.forEach(option => {
391
402
  const selected = option.value === this._value;
392
403
  option.selected = selected;
393
404
  option.hidden = !selected;
394
- option.active = false;
395
405
  if (selected) {
396
406
  this.inputEl.value = option.label;
397
407
  foundMatch = true;
@@ -399,6 +409,10 @@ var FcCombobox = class extends HTMLElement {
399
409
  });
400
410
  if (!foundMatch && this.inputEl.value === "") {
401
411
  this.inputEl.value = this._value;
412
+ options.forEach(option => {
413
+ const match = option.label.includes(this._value);
414
+ option.hidden = !match;
415
+ });
402
416
  }
403
417
  this.syncValidity();
404
418
  }
@@ -463,7 +477,7 @@ var FcCombobox = class extends HTMLElement {
463
477
  this.inputEl.value = label;
464
478
  this._value = value;
465
479
  this.internals.setFormValue(value);
466
- const allOptions = Array.from(this.querySelectorAll("fc-option"));
480
+ const allOptions = this.querySelectorAll("fc-option");
467
481
  allOptions.forEach(opt => {
468
482
  const selected = opt.value === value;
469
483
  opt.selected = selected;
@@ -490,9 +504,13 @@ var FcCombobox = class extends HTMLElement {
490
504
  }
491
505
  const dropdown = this.dropdownEl;
492
506
  if (show) {
507
+ const spaceBelow = calculateBottomAvaliableSpace(this.inputEl);
508
+ const spaceAbove = calculateTopAvaliableSpace(this.inputEl);
493
509
  dropdown.hidden = false;
494
510
  this.setAttribute("open", "true");
495
511
  this.inputEl.setAttribute("aria-expanded", "true");
512
+ const shouldOpenUp = spaceBelow < dropdown.clientHeight && spaceAbove > spaceBelow;
513
+ dropdown.classList.toggle("opens-up", shouldOpenUp);
496
514
  return;
497
515
  }
498
516
  this.dropdownEl.hidden = true;
@@ -511,8 +529,13 @@ var FcCombobox = class extends HTMLElement {
511
529
  return;
512
530
  }
513
531
  if (this.strict && this._value) {
514
- const options = Array.from(this.querySelectorAll("fc-option"));
515
- const match = options.some(opt => opt.value === this._value);
532
+ const options = this.querySelectorAll("fc-option");
533
+ let match = false;
534
+ options.forEach(option => {
535
+ if (option.value === this._value) {
536
+ match = true;
537
+ }
538
+ });
516
539
  if (!match) {
517
540
  this.internals.setValidity({
518
541
  customError: true
@@ -554,7 +577,7 @@ var defineCombobox = () => {
554
577
  return FcCombobox;
555
578
  };
556
579
 
557
- 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-disabled-fg);\n\t\tbackground: var(--fc-option-disabled-bg);\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-disabled-bg);\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`;
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`;
558
581
 
559
582
  var template2 = document.createElement("template");
560
583
 
@@ -694,11 +717,11 @@ var defineOption = () => {
694
717
  return FcOption;
695
718
  };
696
719
 
697
- 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-color: var(--fc-input-error-bg);\n\t\tborder-color: var(--fc-input-error-color);\n\t}\n\n\t:host([touched]:invalid) .fc-input-field:focus {\n\t\tbox-shadow: 0 0 0 2px var(--fc-input-error-focus-ring);\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-disabled-bg);\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-disabled-placeholder);\n }\n\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-color: 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-color: 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-color: var(--fc-input-file-btn-bg-hover); \n\t}\n\n\n\t.fc-input-field::-webkit-file-upload-button:hover {\n\t\tbackground-color: 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`;
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`;
698
721
 
699
722
  var template3 = document.createElement("template");
700
723
 
701
- template3.innerHTML = `\n\t<style>${styles3}</style>\n\n\t<div class="fc-input-wrapper">\n\n\t\t\x3c!-- prefix slot here --\x3e\n\t\t\n\t\t<input \n\t\t\tid="fc-field" \n\t\t\tclass="fc-input-field" \n\t\t\tpart="input"\n\t\t\ttype="text"\n\t\t/>\n\n\t\t\x3c!-- password toggle button --\x3e\n\t\t<button \n\t\t\tid="btn-show-pass" \n\t\t\tclass="fc-password-toggle" \n\t\t\tpart="password-toggle" \n\t\t\ttype="button" \n\t\t\thidden \n\t\t\taria-label="Toggle password visibility"\n\t\t\taria-pressed="false"\n\t\t>\n\t\t\t<svg class="icon-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n\t\t\t\t<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>\n\t\t\t\t<circle cx="12" cy="12" r="3"></circle>\n\t\t\t</svg>\n\t\t\t\n\t\t\t<svg class="icon-eye-off" hidden viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n\t\t\t\t<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>\n\t\t\t\t<line x1="1" y1="1" x2="23" y2="23"></line>\n\t\t\t</svg>\n\t\t</button>\n\n\t\t\x3c!-- suffix slot here --\x3e\n\t</div>\n`;
724
+ template3.innerHTML = `\n\t<style>${styles3}</style>\n\n\t<div class="fc-input-wrapper">\n\n\t\t\x3c!-- prefix slot here --\x3e\n\t\t\n\t\t<input \n\t\t\tclass="fc-input-field" \n\t\t\tpart="input"\n\t\t\ttype="text"\n\t\t/>\n\n\t\t\x3c!-- password toggle button --\x3e\n\t\t<button \n\t\t\tclass="fc-password-toggle" \n\t\t\tpart="password-toggle" \n\t\t\ttype="button" \n\t\t\thidden \n\t\t\taria-label="Toggle password visibility"\n\t\t\taria-pressed="false"\n\t\t>\n\t\t\t<svg class="icon-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n\t\t\t\t<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>\n\t\t\t\t<circle cx="12" cy="12" r="3"></circle>\n\t\t\t</svg>\n\t\t\t\n\t\t\t<svg class="icon-eye-off" hidden viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n\t\t\t\t<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>\n\t\t\t\t<line x1="1" y1="1" x2="23" y2="23"></line>\n\t\t\t</svg>\n\t\t</button>\n\n\t\t\x3c!-- suffix slot here --\x3e\n\t</div>\n`;
702
725
 
703
726
  var FcInput = class extends HTMLElement {
704
727
  constructor() {
@@ -712,6 +735,16 @@ var FcInput = class extends HTMLElement {
712
735
  });
713
736
  shadow.appendChild(template3.content.cloneNode(true));
714
737
  this.internals = this.attachInternals();
738
+ this.inputEl = this.shadowRoot.querySelector(".fc-input-field");
739
+ this.passwordBtnEl = this.shadowRoot.querySelector(".fc-password-toggle");
740
+ this.fcPassEnableIcon = this.shadowRoot.querySelector(".icon-eye");
741
+ this.fcPassDisableIcon = this.shadowRoot.querySelector(".icon-eye-off");
742
+ const randomId = Math.random().toString(36).substring(2, 9);
743
+ const inputId = `fc-input-${randomId}`;
744
+ const passwordBtnId = `fc-pass-btn-${randomId}`;
745
+ this.inputEl.id = inputId;
746
+ this.passwordBtnEl.id = passwordBtnId;
747
+ this.passwordBtnEl.setAttribute("aria-controls", inputId);
715
748
  this.onInput = this.onInput.bind(this);
716
749
  this.onChange = this.onChange.bind(this);
717
750
  this.onBlur = this.onBlur.bind(this);
@@ -857,10 +890,9 @@ var FcInput = class extends HTMLElement {
857
890
  this.setAttribute("pattern", val);
858
891
  }
859
892
  connectedCallback() {
860
- this.inputEl = this.shadowRoot.getElementById("fc-field");
861
- this.passwordBtnEl = this.shadowRoot.getElementById("btn-show-pass");
862
- this.fcPassEnableIcon = this.shadowRoot.querySelector(".icon-eye");
863
- this.fcPassDisableIcon = this.shadowRoot.querySelector(".icon-eye-off");
893
+ if (this.type !== "file") {
894
+ this.internals.setFormValue(this._value);
895
+ }
864
896
  if (this.hasAttribute("type")) {
865
897
  const type = this.getAttribute("type");
866
898
  this.ALLOWED_TYPES.includes(type) ? this.inputEl.type = type : this.inputEl.type = "text";
@@ -907,9 +939,6 @@ var FcInput = class extends HTMLElement {
907
939
  if (this.type !== "file") {
908
940
  this.inputEl.value = this._value;
909
941
  }
910
- if (this.type !== "file") {
911
- this.internals.setFormValue(this._value);
912
- }
913
942
  this.inputEl.addEventListener("input", this.onInput);
914
943
  this.inputEl.addEventListener("change", this.onChange);
915
944
  this.inputEl.addEventListener("blur", this.onBlur);
@@ -1138,7 +1167,7 @@ var defineInput = () => {
1138
1167
  return FcInput;
1139
1168
  };
1140
1169
 
1141
- var styles4 = `\n :host {\n display: block;\n width: 100%;\n\t\tbox-sizing: border-box;\n contain: content;\n max-width: var(--fc-error-max-width);\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .fc-error-text {\n color: var(--fc-error-color);\n font-family: var(--fc-font-family, inherit);\n font-size: var(--fc-error-font-size);\n display: flex;\n align-items: center;\n gap: 4px;\n }\n`;
1170
+ var styles4 = `\n :host {\n display: block;\n width: 100%;\n\t\tbox-sizing: border-box;\n contain: content;\n max-width: var(--fc-error-max-width);\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .fc-error-text {\n color: var(--fc-error-fg);\n font-family: var(--fc-font-family, inherit);\n font-size: var(--fc-error-font-size);\n display: flex;\n align-items: center;\n gap: 4px;\n }\n`;
1142
1171
 
1143
1172
  var template4 = document.createElement("template");
1144
1173
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fancy-ui-ts",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "description": "A library to easily create cool and customizable webcomponents.",
6
6
  "main": "dist/index.js",