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 +228 -2
- package/dist/index.css +40 -38
- package/dist/index.d.ts +0 -1
- package/dist/index.js +76 -47
- package/package.json +1 -1
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
|
-
##
|
|
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
|
|
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
|
|
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
|
|
87
|
-
--fc-combobox-error
|
|
88
|
-
--fc-combobox-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
114
|
-
--fc-input-error
|
|
115
|
-
--fc-input-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
146
|
-
--fc-combobox-error
|
|
147
|
-
--fc-combobox-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
166
|
-
--fc-input-error
|
|
167
|
-
--fc-input-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
196
|
-
--fc-combobox-error
|
|
197
|
-
--fc-combobox-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
216
|
-
--fc-input-error
|
|
217
|
-
--fc-input-
|
|
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-
|
|
233
|
+
--fc-error-fg: var(--fc-danger-700);
|
|
232
234
|
}
|
package/dist/index.d.ts
CHANGED
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
|
|
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\
|
|
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.
|
|
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 =
|
|
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 =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
515
|
-
|
|
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
|
|
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
|
|
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\
|
|
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.
|
|
861
|
-
|
|
862
|
-
|
|
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-
|
|
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
|
|