tyrell-components 1.0.0-TC56 → 1.0.0-TC58
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/css/tyrell.css +3 -3
- package/dist/tyrell.js +1 -1
- package/dist/tyrell.js.map +1 -0
- package/lib/base/ty-component.d.ts +15 -3
- package/lib/base/ty-component.js +26 -18
- package/lib/components/button.d.ts +4 -0
- package/lib/components/button.js +33 -6
- package/lib/components/calendar-month.js +3 -10
- package/lib/components/calendar.d.ts +5 -1
- package/lib/components/calendar.js +28 -34
- package/lib/components/checkbox.d.ts +3 -1
- package/lib/components/checkbox.js +9 -1
- package/lib/components/date-picker.d.ts +61 -7
- package/lib/components/date-picker.js +274 -133
- package/lib/components/file-upload.d.ts +4 -1
- package/lib/components/file-upload.js +17 -3
- package/lib/components/input.d.ts +82 -3
- package/lib/components/input.js +178 -20
- package/lib/components/modal.d.ts +2 -0
- package/lib/components/modal.js +49 -10
- package/lib/components/option.js +7 -5
- package/lib/components/popup.js +59 -10
- package/lib/components/radio.d.ts +4 -1
- package/lib/components/radio.js +10 -1
- package/lib/components/resize-observer.js +2 -0
- package/lib/components/scroll-container.js +2 -0
- package/lib/components/select.d.ts +34 -3
- package/lib/components/select.js +135 -11
- package/lib/components/selected-tags.d.ts +7 -1
- package/lib/components/selected-tags.js +10 -1
- package/lib/components/switch.d.ts +5 -1
- package/lib/components/switch.js +9 -1
- package/lib/components/textarea.d.ts +46 -2
- package/lib/components/textarea.js +60 -9
- package/lib/styles/button.js +20 -19
- package/lib/styles/date-picker.js +18 -1
- package/lib/styles/file-upload.d.ts +1 -1
- package/lib/styles/file-upload.js +3 -3
- package/lib/styles/icon.d.ts +1 -1
- package/lib/styles/icon.js +2 -2
- package/lib/styles/input.js +1 -1
- package/lib/styles/radio.js +2 -2
- package/lib/styles/select-base.js +26 -0
- package/lib/styles/textarea.d.ts +1 -1
- package/lib/styles/textarea.js +7 -0
- package/lib/utils/error-flavor.d.ts +8 -0
- package/lib/utils/error-flavor.js +1 -0
- package/lib/utils/property-manager.d.ts +6 -0
- package/lib/utils/property-manager.js +15 -5
- package/lib/utils/upgrade-properties.d.ts +21 -0
- package/lib/utils/upgrade-properties.js +47 -0
- package/lib/utils/validity.d.ts +72 -2
- package/lib/utils/validity.js +192 -8
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -2
- package/lib/base/ty-component.d.ts.map +0 -1
- package/lib/base/ty-component.js.map +0 -1
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js.map +0 -1
- package/lib/components/calendar-month.d.ts.map +0 -1
- package/lib/components/calendar-month.js.map +0 -1
- package/lib/components/calendar-navigation.d.ts.map +0 -1
- package/lib/components/calendar-navigation.js.map +0 -1
- package/lib/components/calendar.d.ts.map +0 -1
- package/lib/components/calendar.js.map +0 -1
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js.map +0 -1
- package/lib/components/copy.d.ts.map +0 -1
- package/lib/components/copy.js.map +0 -1
- package/lib/components/date-picker.d.ts.map +0 -1
- package/lib/components/date-picker.js.map +0 -1
- package/lib/components/dropdown.d.ts +0 -403
- package/lib/components/dropdown.d.ts.map +0 -1
- package/lib/components/dropdown.js +0 -1315
- package/lib/components/dropdown.js.map +0 -1
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js.map +0 -1
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js.map +0 -1
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js.map +0 -1
- package/lib/components/multiselect.d.ts +0 -389
- package/lib/components/multiselect.d.ts.map +0 -1
- package/lib/components/multiselect.js +0 -1301
- package/lib/components/multiselect.js.map +0 -1
- package/lib/components/option.d.ts.map +0 -1
- package/lib/components/option.js.map +0 -1
- package/lib/components/popup.d.ts.map +0 -1
- package/lib/components/popup.js.map +0 -1
- package/lib/components/resize-observer.d.ts.map +0 -1
- package/lib/components/resize-observer.js.map +0 -1
- package/lib/components/scroll-container.d.ts.map +0 -1
- package/lib/components/scroll-container.js.map +0 -1
- package/lib/components/step.d.ts.map +0 -1
- package/lib/components/step.js.map +0 -1
- package/lib/components/tab.d.ts.map +0 -1
- package/lib/components/tab.js.map +0 -1
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js.map +0 -1
- package/lib/components/tag.d.ts.map +0 -1
- package/lib/components/tag.js.map +0 -1
- package/lib/components/textarea.d.ts.map +0 -1
- package/lib/components/textarea.js.map +0 -1
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js.map +0 -1
- package/lib/components/wizard.d.ts.map +0 -1
- package/lib/components/wizard.js.map +0 -1
- package/lib/icons/fontawesome/brands.d.ts.map +0 -1
- package/lib/icons/fontawesome/brands.js.map +0 -1
- package/lib/icons/fontawesome/regular.d.ts.map +0 -1
- package/lib/icons/fontawesome/regular.js.map +0 -1
- package/lib/icons/fontawesome/solid.d.ts.map +0 -1
- package/lib/icons/fontawesome/solid.js.map +0 -1
- package/lib/icons/heroicons/micro.d.ts.map +0 -1
- package/lib/icons/heroicons/micro.js.map +0 -1
- package/lib/icons/heroicons/mini.d.ts.map +0 -1
- package/lib/icons/heroicons/mini.js.map +0 -1
- package/lib/icons/heroicons/outline.d.ts.map +0 -1
- package/lib/icons/heroicons/outline.js.map +0 -1
- package/lib/icons/heroicons/solid.d.ts.map +0 -1
- package/lib/icons/heroicons/solid.js.map +0 -1
- package/lib/icons/lucide.d.ts.map +0 -1
- package/lib/icons/lucide.js.map +0 -1
- package/lib/icons/material/filled.d.ts.map +0 -1
- package/lib/icons/material/filled.js.map +0 -1
- package/lib/icons/material/outlined.d.ts.map +0 -1
- package/lib/icons/material/outlined.js.map +0 -1
- package/lib/icons/material/round.d.ts.map +0 -1
- package/lib/icons/material/round.js.map +0 -1
- package/lib/icons/material/sharp.d.ts.map +0 -1
- package/lib/icons/material/sharp.js.map +0 -1
- package/lib/icons/material/two-tone.d.ts.map +0 -1
- package/lib/icons/material/two-tone.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/styles/button.d.ts.map +0 -1
- package/lib/styles/button.js.map +0 -1
- package/lib/styles/calendar-month.d.ts.map +0 -1
- package/lib/styles/calendar-month.js.map +0 -1
- package/lib/styles/calendar-navigation.d.ts.map +0 -1
- package/lib/styles/calendar-navigation.js.map +0 -1
- package/lib/styles/calendar.d.ts.map +0 -1
- package/lib/styles/calendar.js.map +0 -1
- package/lib/styles/copy.d.ts.map +0 -1
- package/lib/styles/copy.js.map +0 -1
- package/lib/styles/date-picker.d.ts.map +0 -1
- package/lib/styles/date-picker.js.map +0 -1
- package/lib/styles/dropdown.d.ts +0 -12
- package/lib/styles/dropdown.d.ts.map +0 -1
- package/lib/styles/dropdown.js +0 -725
- package/lib/styles/dropdown.js.map +0 -1
- package/lib/styles/icon.d.ts.map +0 -1
- package/lib/styles/icon.js.map +0 -1
- package/lib/styles/input.d.ts.map +0 -1
- package/lib/styles/input.js.map +0 -1
- package/lib/styles/modal.d.ts.map +0 -1
- package/lib/styles/modal.js.map +0 -1
- package/lib/styles/multiselect.d.ts +0 -6
- package/lib/styles/multiselect.d.ts.map +0 -1
- package/lib/styles/multiselect.js +0 -568
- package/lib/styles/multiselect.js.map +0 -1
- package/lib/styles/option.d.ts.map +0 -1
- package/lib/styles/option.js.map +0 -1
- package/lib/styles/popup.d.ts.map +0 -1
- package/lib/styles/popup.js.map +0 -1
- package/lib/styles/resize-observer.d.ts.map +0 -1
- package/lib/styles/resize-observer.js.map +0 -1
- package/lib/styles/scroll-container.d.ts.map +0 -1
- package/lib/styles/scroll-container.js.map +0 -1
- package/lib/styles/step.d.ts.map +0 -1
- package/lib/styles/step.js.map +0 -1
- package/lib/styles/tab.d.ts.map +0 -1
- package/lib/styles/tab.js.map +0 -1
- package/lib/styles/tabs.d.ts.map +0 -1
- package/lib/styles/tabs.js.map +0 -1
- package/lib/styles/tag.d.ts.map +0 -1
- package/lib/styles/tag.js.map +0 -1
- package/lib/styles/textarea.d.ts.map +0 -1
- package/lib/styles/textarea.js.map +0 -1
- package/lib/styles/tooltip.d.ts.map +0 -1
- package/lib/styles/tooltip.js.map +0 -1
- package/lib/styles/wizard.d.ts.map +0 -1
- package/lib/styles/wizard.js.map +0 -1
- package/lib/types/common.d.ts.map +0 -1
- package/lib/types/common.js.map +0 -1
- package/lib/utils/calendar-utils.d.ts.map +0 -1
- package/lib/utils/calendar-utils.js.map +0 -1
- package/lib/utils/icon-registry.d.ts.map +0 -1
- package/lib/utils/icon-registry.js.map +0 -1
- package/lib/utils/locale.d.ts.map +0 -1
- package/lib/utils/locale.js.map +0 -1
- package/lib/utils/number-format.d.ts.map +0 -1
- package/lib/utils/number-format.js.map +0 -1
- package/lib/utils/parse-boolean.d.ts +0 -39
- package/lib/utils/parse-boolean.d.ts.map +0 -1
- package/lib/utils/parse-boolean.js +0 -23
- package/lib/utils/parse-boolean.js.map +0 -1
- package/lib/utils/positioning.d.ts.map +0 -1
- package/lib/utils/positioning.js.map +0 -1
- package/lib/utils/property-capture.d.ts.map +0 -1
- package/lib/utils/property-capture.js.map +0 -1
- package/lib/utils/property-manager.d.ts.map +0 -1
- package/lib/utils/property-manager.js.map +0 -1
- package/lib/utils/resize-observer.d.ts.map +0 -1
- package/lib/utils/resize-observer.js.map +0 -1
- package/lib/utils/scroll-lock.d.ts.map +0 -1
- package/lib/utils/scroll-lock.js.map +0 -1
- package/lib/utils/styles.d.ts.map +0 -1
- package/lib/utils/styles.js.map +0 -1
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js.map +0 -1
|
@@ -81,9 +81,12 @@ export declare class TyFileUpload extends TyComponent<FileUploadState> implement
|
|
|
81
81
|
protected onFormReset(): void;
|
|
82
82
|
protected updateFormValue(): void;
|
|
83
83
|
/** Report `required` to the owning form, like ty-input and ty-textarea
|
|
84
|
-
do — without this the asterisk showed but submission went through.
|
|
84
|
+
do — without this the asterisk showed but submission went through.
|
|
85
|
+
The message is a native file input's. */
|
|
85
86
|
private updateValidity;
|
|
86
87
|
protected getFormValue(): FormData | string | null;
|
|
88
|
+
/** Focus the drop zone: the host itself isn't focusable. */
|
|
89
|
+
focus(options?: FocusOptions): void;
|
|
87
90
|
/**
|
|
88
91
|
* Match a file against the `accept` attribute the way native inputs do:
|
|
89
92
|
* comma-separated list of `.ext`, `type/subtype` or `type/*` entries.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TyComponent } from "../base/ty-component.js";
|
|
2
2
|
import { ensureStyles } from "../utils/styles.js";
|
|
3
3
|
import { fileUploadStyles } from "../styles/file-upload.js";
|
|
4
|
+
import { nativeMessage } from "../utils/validity.js";
|
|
4
5
|
const UPLOAD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>`;
|
|
5
6
|
const FILE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`;
|
|
6
7
|
const X_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
@@ -29,7 +30,9 @@ this._dropHandler = null;
|
|
|
29
30
|
this._inputChangeHandler = null;
|
|
30
31
|
ensureStyles(this.shadowRoot, { css: fileUploadStyles, id: "ty-file-upload" });
|
|
31
32
|
}
|
|
32
|
-
onConnect() {
|
|
33
|
+
onConnect() {
|
|
34
|
+
this.updateFormValue();
|
|
35
|
+
}
|
|
33
36
|
onDisconnect() {
|
|
34
37
|
this.removeEventListeners();
|
|
35
38
|
}
|
|
@@ -50,21 +53,31 @@ this.updateValidity();
|
|
|
50
53
|
updateValidity() {
|
|
51
54
|
if (this.required && !this.disabled && this._files.length === 0) {
|
|
52
55
|
const anchor = this.shadowRoot?.querySelector(".drop-zone");
|
|
53
|
-
this.applyValidity({ valueMissing: true }, "
|
|
56
|
+
this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "file", required: true, multiple: this.multiple }), anchor ?? undefined);
|
|
54
57
|
}
|
|
55
58
|
else {
|
|
56
59
|
this.applyValidity({});
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
getFormValue() {
|
|
60
|
-
if (
|
|
63
|
+
if (!this.name)
|
|
61
64
|
return null;
|
|
62
65
|
const fd = new FormData();
|
|
66
|
+
if (this._files.length === 0) {
|
|
67
|
+
fd.append(this.name, new File([], '', { type: 'application/octet-stream' }));
|
|
68
|
+
}
|
|
63
69
|
for (const file of this._files) {
|
|
64
70
|
fd.append(this.name, file);
|
|
65
71
|
}
|
|
66
72
|
return fd;
|
|
67
73
|
}
|
|
74
|
+
focus(options) {
|
|
75
|
+
const zone = this.shadowRoot?.querySelector('.drop-zone');
|
|
76
|
+
if (zone)
|
|
77
|
+
zone.focus(options);
|
|
78
|
+
else
|
|
79
|
+
super.focus(options);
|
|
80
|
+
}
|
|
68
81
|
matchesAccept(file) {
|
|
69
82
|
const accept = this.accept?.trim();
|
|
70
83
|
if (!accept)
|
|
@@ -146,6 +159,7 @@ names: this._files.map((f) => f.name),
|
|
|
146
159
|
};
|
|
147
160
|
setTimeout(() => {
|
|
148
161
|
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true, composed: true }));
|
|
162
|
+
this.revealValidity();
|
|
149
163
|
}, 0);
|
|
150
164
|
}
|
|
151
165
|
removeFile(index) {
|
|
@@ -59,6 +59,41 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
59
59
|
visual: boolean;
|
|
60
60
|
default: boolean;
|
|
61
61
|
};
|
|
62
|
+
pattern: {
|
|
63
|
+
type: "string";
|
|
64
|
+
visual: boolean;
|
|
65
|
+
default: string;
|
|
66
|
+
};
|
|
67
|
+
minlength: {
|
|
68
|
+
type: "string";
|
|
69
|
+
visual: boolean;
|
|
70
|
+
default: string;
|
|
71
|
+
};
|
|
72
|
+
maxlength: {
|
|
73
|
+
type: "string";
|
|
74
|
+
visual: boolean;
|
|
75
|
+
default: string;
|
|
76
|
+
};
|
|
77
|
+
min: {
|
|
78
|
+
type: "string";
|
|
79
|
+
visual: boolean;
|
|
80
|
+
default: string;
|
|
81
|
+
};
|
|
82
|
+
max: {
|
|
83
|
+
type: "string";
|
|
84
|
+
visual: boolean;
|
|
85
|
+
default: string;
|
|
86
|
+
};
|
|
87
|
+
tooShortMessage: {
|
|
88
|
+
type: "string";
|
|
89
|
+
visual: boolean;
|
|
90
|
+
default: string;
|
|
91
|
+
};
|
|
92
|
+
tooLongMessage: {
|
|
93
|
+
type: "string";
|
|
94
|
+
visual: boolean;
|
|
95
|
+
default: string;
|
|
96
|
+
};
|
|
62
97
|
error: {
|
|
63
98
|
type: "string";
|
|
64
99
|
visual: boolean;
|
|
@@ -103,7 +138,6 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
103
138
|
private _shadowValue;
|
|
104
139
|
private _isFocused;
|
|
105
140
|
private _listenersSetup;
|
|
106
|
-
private _errorAutoDanger;
|
|
107
141
|
private _customFlavorSheet;
|
|
108
142
|
private _inputHandler;
|
|
109
143
|
private _changeHandler;
|
|
@@ -117,6 +151,14 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
117
151
|
protected onConnect(): void;
|
|
118
152
|
/** Custom (non-built-in) flavors — see utils/flavor-sheet.ts. */
|
|
119
153
|
private _syncCustomFlavor;
|
|
154
|
+
/**
|
|
155
|
+
* `error` turns a neutral field danger; clearing it (the author, or
|
|
156
|
+
* validation dropping the message it showed) puts back exactly the flavor
|
|
157
|
+
* attribute the author had, or none, kept meanwhile in ERROR_FLAVOR (same
|
|
158
|
+
* as ty-select and ty-date-picker). Runs once connected: while the element
|
|
159
|
+
* upgrades, `error` may come before the `flavor` written next to it.
|
|
160
|
+
*/
|
|
161
|
+
private syncErrorFlavor;
|
|
120
162
|
protected onDisconnect(): void;
|
|
121
163
|
/** Handle property changes — called BEFORE render. */
|
|
122
164
|
protected onPropertiesChanged(changes: PropertyChange[]): void;
|
|
@@ -143,14 +185,31 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
143
185
|
set label(v: string);
|
|
144
186
|
get disabled(): boolean;
|
|
145
187
|
set disabled(v: boolean);
|
|
188
|
+
/** Focus the inner <input>: the host itself isn't focusable. */
|
|
189
|
+
focus(options?: FocusOptions): void;
|
|
146
190
|
get required(): boolean;
|
|
147
191
|
set required(v: boolean);
|
|
192
|
+
get pattern(): string;
|
|
193
|
+
set pattern(v: string);
|
|
194
|
+
get minLength(): number;
|
|
195
|
+
set minLength(v: number);
|
|
196
|
+
get maxLength(): number;
|
|
197
|
+
set maxLength(v: number);
|
|
198
|
+
get min(): string;
|
|
199
|
+
set min(v: string);
|
|
200
|
+
get max(): string;
|
|
201
|
+
set max(v: string);
|
|
202
|
+
get tooShortMessage(): string;
|
|
203
|
+
set tooShortMessage(v: string);
|
|
204
|
+
get tooLongMessage(): string;
|
|
205
|
+
set tooLongMessage(v: string);
|
|
148
206
|
get error(): string;
|
|
149
207
|
set error(v: string);
|
|
150
208
|
get size(): Size;
|
|
151
209
|
set size(v: Size);
|
|
152
210
|
get flavor(): Flavor;
|
|
153
211
|
set flavor(v: Flavor);
|
|
212
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
154
213
|
get currency(): string;
|
|
155
214
|
set currency(v: string);
|
|
156
215
|
get locale(): string;
|
|
@@ -165,6 +224,13 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
165
224
|
private dispatchChangeEvent;
|
|
166
225
|
private removeEventListeners;
|
|
167
226
|
/** IMPORTANT: Only called ONCE, not on every render. */
|
|
227
|
+
/**
|
|
228
|
+
* Enter submits the owning form, like a native input (HTML implicit
|
|
229
|
+
* submission): through the form's default button when it has one (nothing
|
|
230
|
+
* if that button is disabled), otherwise only when this is the form's only
|
|
231
|
+
* text field.
|
|
232
|
+
*/
|
|
233
|
+
private _implicitSubmit;
|
|
168
234
|
private setupEventListeners;
|
|
169
235
|
/** Render the input component with wrapper and slots. */
|
|
170
236
|
render(): void;
|
|
@@ -174,10 +240,23 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
174
240
|
* `type` property stays "password" (form value and API unaffected).
|
|
175
241
|
*/
|
|
176
242
|
private setupPasswordToggle;
|
|
243
|
+
/**
|
|
244
|
+
* pattern, minlength and maxlength go on the inner <input>: pattern for the
|
|
245
|
+
* browser's own patternMismatch check, minlength for its tooShort message
|
|
246
|
+
* after the user types, maxlength so typing stops at the limit like a
|
|
247
|
+
* native field. Formatted (numeric) types show a display string, so they
|
|
248
|
+
* get none.
|
|
249
|
+
*/
|
|
250
|
+
private syncConstraintAttributes;
|
|
177
251
|
/**
|
|
178
252
|
* Reflect constraint validation to the owning <form> via ElementInternals.
|
|
179
|
-
* Without this
|
|
180
|
-
*
|
|
253
|
+
* Without this the inner shadow <input> can't block the outer form.
|
|
254
|
+
* Rules, first failing one supplies the message:
|
|
255
|
+
* - required: empty => valueMissing
|
|
256
|
+
* - email/url: the inner input's own typeMismatch
|
|
257
|
+
* - text types: pattern, minlength, maxlength
|
|
258
|
+
* - number/currency/percent/compact: min, max against the numeric value
|
|
259
|
+
* Empty values only fail `required`, like native inputs.
|
|
181
260
|
* Disabled fields are barred from constraint validation.
|
|
182
261
|
*/
|
|
183
262
|
private updateValidity;
|
package/lib/components/input.js
CHANGED
|
@@ -2,10 +2,12 @@ import { DEFAULT_SIZE } from '../types/common.js';
|
|
|
2
2
|
import { TyComponent } from '../base/ty-component.js';
|
|
3
3
|
import { ensureStyles } from '../utils/styles.js';
|
|
4
4
|
import { syncCustomFlavorSheet } from '../utils/flavor-sheet.js';
|
|
5
|
+
import { ERROR_FLAVOR } from '../utils/error-flavor.js';
|
|
5
6
|
import { inputStyles, inputCustomFlavorCss } from '../styles/input.js';
|
|
6
7
|
import { formatNumber, parseNumericValue, shouldFormat as shouldFormatType } from '../utils/number-format.js';
|
|
7
8
|
import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
|
|
8
9
|
import { REQUIRED_ICON_SVG } from '../utils/icons.js';
|
|
10
|
+
import { nativeMessage, nativeValidity, lengthValidity, toLimit } from '../utils/validity.js';
|
|
9
11
|
const EYE_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>`;
|
|
10
12
|
const EYE_OFF_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg>`;
|
|
11
13
|
export class TyInput extends TyComponent {
|
|
@@ -14,7 +16,6 @@ super();
|
|
|
14
16
|
this._shadowValue = null;
|
|
15
17
|
this._isFocused = false;
|
|
16
18
|
this._listenersSetup = false;
|
|
17
|
-
this._errorAutoDanger = false;
|
|
18
19
|
this._customFlavorSheet = null;
|
|
19
20
|
this._inputHandler = null;
|
|
20
21
|
this._changeHandler = null;
|
|
@@ -22,6 +23,34 @@ this._focusHandler = null;
|
|
|
22
23
|
this._blurHandler = null;
|
|
23
24
|
this._inputDebounceTimer = null;
|
|
24
25
|
this._changeDebounceTimer = null;
|
|
26
|
+
this._implicitSubmit = (e) => {
|
|
27
|
+
if (e.key !== 'Enter' || e.isComposing || e.defaultPrevented)
|
|
28
|
+
return;
|
|
29
|
+
const form = this._internals.form;
|
|
30
|
+
if (!form)
|
|
31
|
+
return;
|
|
32
|
+
const controls = Array.from(form.elements);
|
|
33
|
+
const isSubmitter = (el) => (el instanceof HTMLButtonElement && el.type === 'submit') ||
|
|
34
|
+
(el instanceof HTMLInputElement && (el.type === 'submit' || el.type === 'image')) ||
|
|
35
|
+
(el.tagName === 'TY-BUTTON' && !['button', 'reset'].includes(el.getAttribute('type') ?? ''));
|
|
36
|
+
const submitter = controls.find(isSubmitter);
|
|
37
|
+
if (submitter) {
|
|
38
|
+
if (submitter.disabled || submitter.hasAttribute('disabled'))
|
|
39
|
+
return;
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
const target = submitter.tagName === 'TY-BUTTON'
|
|
42
|
+
? submitter.shadowRoot?.querySelector('button') ?? submitter
|
|
43
|
+
: submitter;
|
|
44
|
+
target.click();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const textFields = controls.filter((el) => el.tagName === 'TY-INPUT' ||
|
|
48
|
+
(el instanceof HTMLInputElement && !['button', 'checkbox', 'radio', 'file', 'hidden', 'image', 'reset', 'submit', 'range', 'color'].includes(el.type)));
|
|
49
|
+
if (textFields.length === 1) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
form.requestSubmit();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
25
54
|
const shadow = this.shadowRoot;
|
|
26
55
|
ensureStyles(shadow, { css: inputStyles, id: 'ty-input' });
|
|
27
56
|
}
|
|
@@ -31,10 +60,32 @@ this._localeObserver = observeLocaleChanges(this, () => {
|
|
|
31
60
|
this.render();
|
|
32
61
|
});
|
|
33
62
|
this._syncCustomFlavor();
|
|
63
|
+
this.syncErrorFlavor();
|
|
34
64
|
}
|
|
35
65
|
_syncCustomFlavor() {
|
|
36
66
|
this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.flavor, ({ base }) => inputCustomFlavorCss(base));
|
|
37
67
|
}
|
|
68
|
+
syncErrorFlavor() {
|
|
69
|
+
if (!this._connected)
|
|
70
|
+
return;
|
|
71
|
+
const flavor = this.getProperty('flavor');
|
|
72
|
+
const replaced = this.getAttribute(ERROR_FLAVOR);
|
|
73
|
+
if (this.getProperty('error')) {
|
|
74
|
+
if (replaced === null && flavor === 'neutral') {
|
|
75
|
+
this.setAttribute(ERROR_FLAVOR, this.getAttribute('flavor') ?? '');
|
|
76
|
+
this.setProperty('flavor', 'danger');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (replaced !== null) {
|
|
80
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
81
|
+
if (flavor !== 'danger')
|
|
82
|
+
return;
|
|
83
|
+
if (!replaced)
|
|
84
|
+
this.removeAttribute('flavor');
|
|
85
|
+
else
|
|
86
|
+
this.setProperty('flavor', replaced);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
38
89
|
onDisconnect() {
|
|
39
90
|
this.removeEventListeners();
|
|
40
91
|
if (this._localeObserver) {
|
|
@@ -54,6 +105,10 @@ onPropertiesChanged(changes) {
|
|
|
54
105
|
for (const { name, newValue } of changes) {
|
|
55
106
|
switch (name) {
|
|
56
107
|
case 'flavor':
|
|
108
|
+
if (newValue !== 'danger') {
|
|
109
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
110
|
+
this.syncErrorFlavor();
|
|
111
|
+
}
|
|
57
112
|
this._syncCustomFlavor();
|
|
58
113
|
break;
|
|
59
114
|
case 'value':
|
|
@@ -64,18 +119,7 @@ const currentValue = this.getProperty('value') || '';
|
|
|
64
119
|
this._shadowValue = this.parseShadowValue(currentValue);
|
|
65
120
|
break;
|
|
66
121
|
case 'error':
|
|
67
|
-
|
|
68
|
-
if (this.getProperty('flavor') === 'neutral') {
|
|
69
|
-
this.setProperty('flavor', 'danger');
|
|
70
|
-
this._errorAutoDanger = true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
else if (this._errorAutoDanger) {
|
|
74
|
-
if (this.getProperty('flavor') === 'danger') {
|
|
75
|
-
this.setProperty('flavor', 'neutral');
|
|
76
|
-
}
|
|
77
|
-
this._errorAutoDanger = false;
|
|
78
|
-
}
|
|
122
|
+
this.syncErrorFlavor();
|
|
79
123
|
break;
|
|
80
124
|
}
|
|
81
125
|
}
|
|
@@ -95,14 +139,17 @@ this._changeDebounceTimer = null;
|
|
|
95
139
|
this.render();
|
|
96
140
|
}
|
|
97
141
|
getFormValue() {
|
|
98
|
-
const formValue = this._shadowValue !== null ? String(this._shadowValue) :
|
|
142
|
+
const formValue = this._shadowValue !== null ? String(this._shadowValue) : '';
|
|
99
143
|
return formValue;
|
|
100
144
|
}
|
|
101
145
|
initializeShadowValue() {
|
|
102
146
|
const currentValue = this.getProperty('value');
|
|
103
147
|
if (currentValue) {
|
|
104
148
|
this._shadowValue = this.parseShadowValue(currentValue);
|
|
105
|
-
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) :
|
|
149
|
+
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : '');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
this._internals.setFormValue('');
|
|
106
153
|
}
|
|
107
154
|
}
|
|
108
155
|
parseShadowValue(value) {
|
|
@@ -155,14 +202,39 @@ get label() { return this.getProperty('label'); }
|
|
|
155
202
|
set label(v) { this.setProperty('label', v); }
|
|
156
203
|
get disabled() { return this.getProperty('disabled'); }
|
|
157
204
|
set disabled(v) { this.setProperty('disabled', v); }
|
|
205
|
+
focus(options) {
|
|
206
|
+
this.shadowRoot?.querySelector('input')?.focus(options);
|
|
207
|
+
}
|
|
158
208
|
get required() { return this.getProperty('required'); }
|
|
159
209
|
set required(v) { this.setProperty('required', v); }
|
|
210
|
+
get pattern() { return this.getProperty('pattern'); }
|
|
211
|
+
set pattern(v) { this.setProperty('pattern', v ?? ''); }
|
|
212
|
+
get minLength() { return toLimit(this.getProperty('minlength')) ?? -1; }
|
|
213
|
+
set minLength(v) { this.setProperty('minlength', v == null || v < 0 ? '' : String(v)); }
|
|
214
|
+
get maxLength() { return toLimit(this.getProperty('maxlength')) ?? -1; }
|
|
215
|
+
set maxLength(v) { this.setProperty('maxlength', v == null || v < 0 ? '' : String(v)); }
|
|
216
|
+
get min() { return this.getProperty('min'); }
|
|
217
|
+
set min(v) { this.setProperty('min', v == null ? '' : String(v)); }
|
|
218
|
+
get max() { return this.getProperty('max'); }
|
|
219
|
+
set max(v) { this.setProperty('max', v == null ? '' : String(v)); }
|
|
220
|
+
get tooShortMessage() { return this.getProperty('tooShortMessage'); }
|
|
221
|
+
set tooShortMessage(v) { this.setProperty('tooShortMessage', v ?? ''); }
|
|
222
|
+
get tooLongMessage() { return this.getProperty('tooLongMessage'); }
|
|
223
|
+
set tooLongMessage(v) { this.setProperty('tooLongMessage', v ?? ''); }
|
|
160
224
|
get error() { return this.getProperty('error'); }
|
|
161
225
|
set error(v) { this.setProperty('error', v); }
|
|
162
226
|
get size() { return this.getProperty('size'); }
|
|
163
227
|
set size(v) { this.setProperty('size', v); }
|
|
164
228
|
get flavor() { return this.getProperty('flavor'); }
|
|
165
|
-
set flavor(v) {
|
|
229
|
+
set flavor(v) {
|
|
230
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
231
|
+
this.setProperty('flavor', v);
|
|
232
|
+
}
|
|
233
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
234
|
+
if (name === 'flavor' && newValue === 'danger' && oldValue === newValue)
|
|
235
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
236
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
237
|
+
}
|
|
166
238
|
get currency() { return this.getProperty('currency'); }
|
|
167
239
|
set currency(v) { this.setProperty('currency', v); }
|
|
168
240
|
get locale() {
|
|
@@ -235,6 +307,7 @@ if (this._blurHandler) {
|
|
|
235
307
|
inputEl.removeEventListener('blur', this._blurHandler);
|
|
236
308
|
this._blurHandler = null;
|
|
237
309
|
}
|
|
310
|
+
inputEl.removeEventListener('keydown', this._implicitSubmit);
|
|
238
311
|
this._listenersSetup = false;
|
|
239
312
|
}
|
|
240
313
|
setupEventListeners() {
|
|
@@ -249,12 +322,13 @@ console.warn('[TyInput.setupEventListeners] ⚠️ Input element not found!');
|
|
|
249
322
|
return;
|
|
250
323
|
}
|
|
251
324
|
this._inputHandler = (e) => {
|
|
325
|
+
this._userEdited = true;
|
|
252
326
|
e.stopPropagation();
|
|
253
327
|
e.stopImmediatePropagation();
|
|
254
328
|
const target = e.target;
|
|
255
329
|
const rawValue = target.value;
|
|
256
330
|
this._shadowValue = this.parseShadowValue(rawValue);
|
|
257
|
-
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) :
|
|
331
|
+
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : '');
|
|
258
332
|
this.updateValidity();
|
|
259
333
|
if (this._inputDebounceTimer !== null) {
|
|
260
334
|
clearTimeout(this._inputDebounceTimer);
|
|
@@ -275,7 +349,7 @@ e.stopImmediatePropagation();
|
|
|
275
349
|
const target = e.target;
|
|
276
350
|
const rawValue = target.value;
|
|
277
351
|
this._shadowValue = this.parseShadowValue(rawValue);
|
|
278
|
-
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) :
|
|
352
|
+
this._internals.setFormValue(this._shadowValue !== null ? String(this._shadowValue) : '');
|
|
279
353
|
this.updateValidity();
|
|
280
354
|
if (this._changeDebounceTimer !== null) {
|
|
281
355
|
clearTimeout(this._changeDebounceTimer);
|
|
@@ -317,6 +391,8 @@ this.dispatchChangeEvent(rawValue, e);
|
|
|
317
391
|
}
|
|
318
392
|
this._isFocused = false;
|
|
319
393
|
wrapperEl.classList.remove('focused');
|
|
394
|
+
if (this._userEdited)
|
|
395
|
+
this.revealValidity();
|
|
320
396
|
if (this.shouldFormat()) {
|
|
321
397
|
inputEl.value = this.getDisplayValue();
|
|
322
398
|
}
|
|
@@ -330,6 +406,7 @@ inputEl.addEventListener('input', this._inputHandler);
|
|
|
330
406
|
inputEl.addEventListener('change', this._changeHandler);
|
|
331
407
|
inputEl.addEventListener('focus', this._focusHandler);
|
|
332
408
|
inputEl.addEventListener('blur', this._blurHandler);
|
|
409
|
+
inputEl.addEventListener('keydown', this._implicitSubmit);
|
|
333
410
|
this._listenersSetup = true;
|
|
334
411
|
}
|
|
335
412
|
render() {
|
|
@@ -350,6 +427,7 @@ const inputMode = shouldFormatType(this.type) ? 'decimal'
|
|
|
350
427
|
const displayValue = this.getDisplayValue();
|
|
351
428
|
if (existingInput && existingWrapper) {
|
|
352
429
|
existingInput.type = inputType;
|
|
430
|
+
if (existingInput.value !== displayValue)
|
|
353
431
|
existingInput.value = displayValue;
|
|
354
432
|
existingInput.placeholder = this.placeholder;
|
|
355
433
|
existingInput.name = this.name;
|
|
@@ -372,6 +450,7 @@ existingInput.setAttribute('required', '');
|
|
|
372
450
|
else {
|
|
373
451
|
existingInput.removeAttribute('required');
|
|
374
452
|
}
|
|
453
|
+
this.syncConstraintAttributes(existingInput);
|
|
375
454
|
if (this.label)
|
|
376
455
|
existingInput.setAttribute('aria-label', this.label);
|
|
377
456
|
else
|
|
@@ -445,6 +524,7 @@ ${errorHtml}
|
|
|
445
524
|
const inputEl = shadow.querySelector('input');
|
|
446
525
|
inputEl.disabled = this.disabled;
|
|
447
526
|
inputEl.required = this.required;
|
|
527
|
+
this.syncConstraintAttributes(inputEl);
|
|
448
528
|
if (this.label)
|
|
449
529
|
inputEl.setAttribute('aria-label', this.label);
|
|
450
530
|
this.setupPasswordToggle();
|
|
@@ -467,6 +547,13 @@ toggle.setAttribute('aria-label', reveal ? 'Hide password' : 'Show password');
|
|
|
467
547
|
toggle.setAttribute('aria-pressed', String(reveal));
|
|
468
548
|
});
|
|
469
549
|
}
|
|
550
|
+
syncConstraintAttributes(inputEl) {
|
|
551
|
+
const text = !shouldFormatType(this.type);
|
|
552
|
+
const set = (name, v) => text && v !== '' ? inputEl.setAttribute(name, v) : inputEl.removeAttribute(name);
|
|
553
|
+
set('pattern', this.pattern);
|
|
554
|
+
set('minlength', toLimit(this.getProperty('minlength')) != null ? String(this.minLength) : '');
|
|
555
|
+
set('maxlength', toLimit(this.getProperty('maxlength')) != null ? String(this.maxLength) : '');
|
|
556
|
+
}
|
|
470
557
|
updateValidity() {
|
|
471
558
|
const inputEl = this.shadowRoot?.querySelector('input');
|
|
472
559
|
if (this.disabled) {
|
|
@@ -474,12 +561,48 @@ this.applyValidity({});
|
|
|
474
561
|
return;
|
|
475
562
|
}
|
|
476
563
|
const current = inputEl ? inputEl.value : String(this.getProperty('value') ?? '');
|
|
477
|
-
if (
|
|
478
|
-
this.
|
|
564
|
+
if (current.trim() === '') {
|
|
565
|
+
if (this.required) {
|
|
566
|
+
this.applyValidity({ valueMissing: true }, nativeMessage('input', { type: 'text', required: true }), inputEl ?? undefined);
|
|
479
567
|
}
|
|
480
568
|
else {
|
|
481
569
|
this.applyValidity({});
|
|
482
570
|
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const flags = {};
|
|
574
|
+
const messages = [];
|
|
575
|
+
const fail = (flag, message) => {
|
|
576
|
+
flags[flag] = true;
|
|
577
|
+
messages.push(message);
|
|
578
|
+
};
|
|
579
|
+
if (shouldFormatType(this.type)) {
|
|
580
|
+
const n = typeof this._shadowValue === 'number' ? this._shadowValue : null;
|
|
581
|
+
if (n !== null && (this.min || this.max)) {
|
|
582
|
+
const { validity, message } = nativeValidity('input', { type: 'number', step: 'any', min: this.min, max: this.max }, String(n));
|
|
583
|
+
if (validity.rangeUnderflow)
|
|
584
|
+
fail('rangeUnderflow', message);
|
|
585
|
+
if (validity.rangeOverflow)
|
|
586
|
+
fail('rangeOverflow', message);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
if (inputEl?.validity.typeMismatch)
|
|
591
|
+
fail('typeMismatch', inputEl.validationMessage);
|
|
592
|
+
if (inputEl?.validity.patternMismatch)
|
|
593
|
+
fail('patternMismatch', inputEl.validationMessage);
|
|
594
|
+
const { tooShort, tooLong } = lengthValidity(current, {
|
|
595
|
+
minlength: this.getProperty('minlength'),
|
|
596
|
+
maxlength: this.getProperty('maxlength'),
|
|
597
|
+
tooShortMessage: this.tooShortMessage,
|
|
598
|
+
tooLongMessage: this.tooLongMessage,
|
|
599
|
+
}, inputEl);
|
|
600
|
+
if (tooShort)
|
|
601
|
+
fail('tooShort', tooShort);
|
|
602
|
+
if (tooLong)
|
|
603
|
+
fail('tooLong', tooLong);
|
|
604
|
+
}
|
|
605
|
+
this.applyValidity(flags, messages[0], inputEl ?? undefined);
|
|
483
606
|
}
|
|
484
607
|
}
|
|
485
608
|
TyInput.formAssociated = true;
|
|
@@ -538,6 +661,41 @@ type: 'boolean',
|
|
|
538
661
|
visual: true,
|
|
539
662
|
default: false
|
|
540
663
|
},
|
|
664
|
+
pattern: {
|
|
665
|
+
type: 'string',
|
|
666
|
+
visual: true,
|
|
667
|
+
default: ''
|
|
668
|
+
},
|
|
669
|
+
minlength: {
|
|
670
|
+
type: 'string',
|
|
671
|
+
visual: true,
|
|
672
|
+
default: ''
|
|
673
|
+
},
|
|
674
|
+
maxlength: {
|
|
675
|
+
type: 'string',
|
|
676
|
+
visual: true,
|
|
677
|
+
default: ''
|
|
678
|
+
},
|
|
679
|
+
min: {
|
|
680
|
+
type: 'string',
|
|
681
|
+
visual: true,
|
|
682
|
+
default: ''
|
|
683
|
+
},
|
|
684
|
+
max: {
|
|
685
|
+
type: 'string',
|
|
686
|
+
visual: true,
|
|
687
|
+
default: ''
|
|
688
|
+
},
|
|
689
|
+
tooShortMessage: {
|
|
690
|
+
type: 'string',
|
|
691
|
+
visual: true,
|
|
692
|
+
default: ''
|
|
693
|
+
},
|
|
694
|
+
tooLongMessage: {
|
|
695
|
+
type: 'string',
|
|
696
|
+
visual: true,
|
|
697
|
+
default: ''
|
|
698
|
+
},
|
|
541
699
|
error: {
|
|
542
700
|
type: 'string',
|
|
543
701
|
visual: true,
|
|
@@ -52,6 +52,8 @@ export declare class TyModal extends HTMLElement {
|
|
|
52
52
|
set open(v: boolean);
|
|
53
53
|
get noBackdrop(): boolean;
|
|
54
54
|
set noBackdrop(v: boolean);
|
|
55
|
+
get noZoom(): boolean;
|
|
56
|
+
set noZoom(v: boolean);
|
|
55
57
|
get preventOutsideClick(): boolean;
|
|
56
58
|
set preventOutsideClick(v: boolean);
|
|
57
59
|
get preventEscape(): boolean;
|