@neumorphism-ui/elements 0.0.0-stage → 1.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +63 -2
- package/cdn/neu.min.js +205 -0
- package/custom-elements.json +1892 -0
- package/dist/all.d.ts +12 -0
- package/dist/all.js +12 -0
- package/dist/avatar.d.ts +3 -0
- package/dist/avatar.js +4 -0
- package/dist/badge.d.ts +3 -0
- package/dist/badge.js +4 -0
- package/dist/button-group.d.ts +3 -0
- package/dist/button-group.js +4 -0
- package/dist/button.d.ts +3 -0
- package/dist/button.js +4 -0
- package/dist/components/avatar/avatar.d.ts +38 -0
- package/dist/components/avatar/avatar.def.d.ts +24 -0
- package/dist/components/avatar/avatar.def.js +33 -0
- package/dist/components/avatar/avatar.js +58 -0
- package/dist/components/avatar/avatar.styles.d.ts +1 -0
- package/dist/components/avatar/avatar.styles.js +13 -0
- package/dist/components/badge/badge.d.ts +17 -0
- package/dist/components/badge/badge.def.d.ts +12 -0
- package/dist/components/badge/badge.def.js +29 -0
- package/dist/components/badge/badge.js +5 -0
- package/dist/components/badge/badge.styles.d.ts +1 -0
- package/dist/components/badge/badge.styles.js +10 -0
- package/dist/components/button/button.d.ts +25 -0
- package/dist/components/button/button.def.d.ts +28 -0
- package/dist/components/button/button.def.js +45 -0
- package/dist/components/button/button.js +87 -0
- package/dist/components/button/button.styles.d.ts +2 -0
- package/dist/components/button/button.styles.js +43 -0
- package/dist/components/button-group/button-group.d.ts +19 -0
- package/dist/components/button-group/button-group.def.d.ts +12 -0
- package/dist/components/button-group/button-group.def.js +25 -0
- package/dist/components/button-group/button-group.js +13 -0
- package/dist/components/button-group/button-group.styles.d.ts +7 -0
- package/dist/components/button-group/button-group.styles.js +19 -0
- package/dist/components/divider/divider.d.ts +14 -0
- package/dist/components/divider/divider.def.d.ts +8 -0
- package/dist/components/divider/divider.def.js +24 -0
- package/dist/components/divider/divider.js +13 -0
- package/dist/components/divider/divider.styles.d.ts +1 -0
- package/dist/components/divider/divider.styles.js +7 -0
- package/dist/components/icon/icon-drawer.d.ts +12 -0
- package/dist/components/icon/icon-drawer.js +38 -0
- package/dist/components/icon/icon-loader.d.ts +5 -0
- package/dist/components/icon/icon-loader.js +102 -0
- package/dist/components/icon/icon-registry.d.ts +11 -0
- package/dist/components/icon/icon-registry.js +26 -0
- package/dist/components/icon/icon.d.ts +25 -0
- package/dist/components/icon/icon.def.d.ts +15 -0
- package/dist/components/icon/icon.def.js +24 -0
- package/dist/components/icon/icon.js +23 -0
- package/dist/components/icon/icon.styles.d.ts +1 -0
- package/dist/components/icon/icon.styles.js +7 -0
- package/dist/components/icon/system-icons.d.ts +2 -0
- package/dist/components/icon/system-icons.js +17 -0
- package/dist/components/icon-button/icon-button.d.ts +51 -0
- package/dist/components/icon-button/icon-button.def.d.ts +40 -0
- package/dist/components/icon-button/icon-button.def.js +44 -0
- package/dist/components/icon-button/icon-button.js +21 -0
- package/dist/components/icon-button/icon-button.styles.d.ts +1 -0
- package/dist/components/icon-button/icon-button.styles.js +12 -0
- package/dist/components/spinner/spinner.d.ts +19 -0
- package/dist/components/spinner/spinner.def.d.ts +12 -0
- package/dist/components/spinner/spinner.def.js +25 -0
- package/dist/components/spinner/spinner.js +17 -0
- package/dist/components/spinner/spinner.styles.d.ts +1 -0
- package/dist/components/spinner/spinner.styles.js +13 -0
- package/dist/components/surface/surface.d.ts +33 -0
- package/dist/components/surface/surface.def.d.ts +26 -0
- package/dist/components/surface/surface.def.js +27 -0
- package/dist/components/surface/surface.js +5 -0
- package/dist/components/surface/surface.styles.d.ts +1 -0
- package/dist/components/surface/surface.styles.js +17 -0
- package/dist/components/switch/switch.d.ts +60 -0
- package/dist/components/switch/switch.def.d.ts +28 -0
- package/dist/components/switch/switch.def.js +33 -0
- package/dist/components/switch/switch.js +125 -0
- package/dist/components/switch/switch.styles.d.ts +1 -0
- package/dist/components/switch/switch.styles.js +29 -0
- package/dist/components/text-field/implicit-submit.d.ts +8 -0
- package/dist/components/text-field/implicit-submit.js +42 -0
- package/dist/components/text-field/text-field.d.ts +116 -0
- package/dist/components/text-field/text-field.def.d.ts +143 -0
- package/dist/components/text-field/text-field.def.js +63 -0
- package/dist/components/text-field/text-field.js +207 -0
- package/dist/components/text-field/text-field.styles.d.ts +1 -0
- package/dist/components/text-field/text-field.styles.js +39 -0
- package/dist/core/define.d.ts +33 -0
- package/dist/core/define.js +15 -0
- package/dist/core/dom.d.ts +21 -0
- package/dist/core/dom.js +51 -0
- package/dist/core/form-control.d.ts +35 -0
- package/dist/core/form-control.js +128 -0
- package/dist/core/html.d.ts +13 -0
- package/dist/core/html.js +35 -0
- package/dist/core/neu-element.d.ts +87 -0
- package/dist/core/neu-element.js +285 -0
- package/dist/core/props.d.ts +44 -0
- package/dist/core/props.js +52 -0
- package/dist/core/styles.d.ts +8 -0
- package/dist/core/styles.js +40 -0
- package/dist/core/validation.d.ts +4 -0
- package/dist/core/validation.js +26 -0
- package/dist/divider.d.ts +3 -0
- package/dist/divider.js +4 -0
- package/dist/icon-button.d.ts +3 -0
- package/dist/icon-button.js +4 -0
- package/dist/icon.d.ts +3 -0
- package/dist/icon.js +4 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +1 -0
- package/dist/spinner.d.ts +3 -0
- package/dist/spinner.js +4 -0
- package/dist/ssr.d.ts +11 -0
- package/dist/ssr.js +41 -0
- package/dist/surface.d.ts +3 -0
- package/dist/surface.js +4 -0
- package/dist/switch.d.ts +3 -0
- package/dist/switch.js +4 -0
- package/dist/text-field.d.ts +3 -0
- package/dist/text-field.js +4 -0
- package/package.json +104 -3
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { FormControlElement } from '../../core/form-control.js';
|
|
2
|
+
export declare class NeuTextField extends FormControlElement {
|
|
3
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
4
|
+
readonly type: {
|
|
5
|
+
readonly type: "string";
|
|
6
|
+
readonly default: "text";
|
|
7
|
+
readonly values: readonly ["text", "email", "password", "search", "tel", "url"];
|
|
8
|
+
readonly reflect: true;
|
|
9
|
+
};
|
|
10
|
+
readonly value: {
|
|
11
|
+
readonly type: "string";
|
|
12
|
+
};
|
|
13
|
+
readonly name: {
|
|
14
|
+
readonly type: "string";
|
|
15
|
+
readonly reflect: true;
|
|
16
|
+
};
|
|
17
|
+
readonly label: {
|
|
18
|
+
readonly type: "string";
|
|
19
|
+
};
|
|
20
|
+
readonly placeholder: {
|
|
21
|
+
readonly type: "string";
|
|
22
|
+
};
|
|
23
|
+
readonly helpText: {
|
|
24
|
+
readonly type: "string";
|
|
25
|
+
};
|
|
26
|
+
readonly errorText: {
|
|
27
|
+
readonly type: "string";
|
|
28
|
+
};
|
|
29
|
+
readonly required: {
|
|
30
|
+
readonly type: "boolean";
|
|
31
|
+
readonly reflect: true;
|
|
32
|
+
};
|
|
33
|
+
readonly disabled: {
|
|
34
|
+
readonly type: "boolean";
|
|
35
|
+
readonly reflect: true;
|
|
36
|
+
};
|
|
37
|
+
readonly readOnly: {
|
|
38
|
+
readonly type: "boolean";
|
|
39
|
+
readonly reflect: true;
|
|
40
|
+
readonly attribute: "readonly";
|
|
41
|
+
};
|
|
42
|
+
readonly minLength: {
|
|
43
|
+
readonly type: "number";
|
|
44
|
+
readonly default: -1;
|
|
45
|
+
readonly attribute: "minlength";
|
|
46
|
+
};
|
|
47
|
+
readonly maxLength: {
|
|
48
|
+
readonly type: "number";
|
|
49
|
+
readonly default: -1;
|
|
50
|
+
readonly attribute: "maxlength";
|
|
51
|
+
};
|
|
52
|
+
readonly pattern: {
|
|
53
|
+
readonly type: "string";
|
|
54
|
+
};
|
|
55
|
+
readonly autocomplete: {
|
|
56
|
+
readonly type: "string";
|
|
57
|
+
};
|
|
58
|
+
readonly inputMode: {
|
|
59
|
+
readonly type: "string";
|
|
60
|
+
readonly attribute: "inputmode";
|
|
61
|
+
};
|
|
62
|
+
readonly enterKeyHint: {
|
|
63
|
+
readonly type: "string";
|
|
64
|
+
readonly attribute: "enterkeyhint";
|
|
65
|
+
};
|
|
66
|
+
readonly size: {
|
|
67
|
+
readonly type: "string";
|
|
68
|
+
readonly default: "md";
|
|
69
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
70
|
+
readonly reflect: true;
|
|
71
|
+
};
|
|
72
|
+
}>;
|
|
73
|
+
static extraProperties: string[];
|
|
74
|
+
type: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url';
|
|
75
|
+
name: string;
|
|
76
|
+
label: string;
|
|
77
|
+
placeholder: string;
|
|
78
|
+
helpText: string;
|
|
79
|
+
errorText: string;
|
|
80
|
+
required: boolean;
|
|
81
|
+
disabled: boolean;
|
|
82
|
+
readOnly: boolean;
|
|
83
|
+
minLength: number;
|
|
84
|
+
maxLength: number;
|
|
85
|
+
pattern: string;
|
|
86
|
+
autocomplete: string;
|
|
87
|
+
size: 'sm' | 'md' | 'lg';
|
|
88
|
+
/** True once the value was edited or set as a property; the default then stops applying. */
|
|
89
|
+
private dirty;
|
|
90
|
+
/** A value set before the shadow root exists. */
|
|
91
|
+
private pendingValue;
|
|
92
|
+
get value(): string;
|
|
93
|
+
set value(next: string);
|
|
94
|
+
get defaultValue(): string;
|
|
95
|
+
set defaultValue(next: string);
|
|
96
|
+
/** Focuses the inner input directly, so the caret stays where it was (no select-all). */
|
|
97
|
+
focus(options?: FocusOptions): void;
|
|
98
|
+
select(): void;
|
|
99
|
+
setSelectionRange(start: number, end: number, direction?: 'forward' | 'backward' | 'none'): void;
|
|
100
|
+
private get p();
|
|
101
|
+
private get input();
|
|
102
|
+
/** A detached input of this type holding `value`, sanitized the way the browser does it. */
|
|
103
|
+
private probe;
|
|
104
|
+
/** Stands in for the inner input until it exists, so validity is right from the start. */
|
|
105
|
+
private validityProbe;
|
|
106
|
+
protected rendered(): void;
|
|
107
|
+
protected update(changed: ReadonlySet<string>): void;
|
|
108
|
+
/** update() only runs once connected; before that, keep the value and validity current here. */
|
|
109
|
+
protected willUpdate(changed: ReadonlySet<string>): void;
|
|
110
|
+
protected formValue(): string;
|
|
111
|
+
/** Like a native input, a password or autocomplete=off field is never saved for restore. */
|
|
112
|
+
protected formState(): string | null;
|
|
113
|
+
protected validate(): void;
|
|
114
|
+
protected resetToDefault(): void;
|
|
115
|
+
protected restoreState(state: string): void;
|
|
116
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { InferProps } from '../../core/props.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
readonly type: {
|
|
4
|
+
readonly type: "string";
|
|
5
|
+
readonly default: "text";
|
|
6
|
+
readonly values: readonly ["text", "email", "password", "search", "tel", "url"];
|
|
7
|
+
readonly reflect: true;
|
|
8
|
+
};
|
|
9
|
+
readonly value: {
|
|
10
|
+
readonly type: "string";
|
|
11
|
+
};
|
|
12
|
+
readonly name: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
readonly reflect: true;
|
|
15
|
+
};
|
|
16
|
+
readonly label: {
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
};
|
|
19
|
+
readonly placeholder: {
|
|
20
|
+
readonly type: "string";
|
|
21
|
+
};
|
|
22
|
+
readonly helpText: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
};
|
|
25
|
+
readonly errorText: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
};
|
|
28
|
+
readonly required: {
|
|
29
|
+
readonly type: "boolean";
|
|
30
|
+
readonly reflect: true;
|
|
31
|
+
};
|
|
32
|
+
readonly disabled: {
|
|
33
|
+
readonly type: "boolean";
|
|
34
|
+
readonly reflect: true;
|
|
35
|
+
};
|
|
36
|
+
readonly readOnly: {
|
|
37
|
+
readonly type: "boolean";
|
|
38
|
+
readonly reflect: true;
|
|
39
|
+
readonly attribute: "readonly";
|
|
40
|
+
};
|
|
41
|
+
readonly minLength: {
|
|
42
|
+
readonly type: "number";
|
|
43
|
+
readonly default: -1;
|
|
44
|
+
readonly attribute: "minlength";
|
|
45
|
+
};
|
|
46
|
+
readonly maxLength: {
|
|
47
|
+
readonly type: "number";
|
|
48
|
+
readonly default: -1;
|
|
49
|
+
readonly attribute: "maxlength";
|
|
50
|
+
};
|
|
51
|
+
readonly pattern: {
|
|
52
|
+
readonly type: "string";
|
|
53
|
+
};
|
|
54
|
+
readonly autocomplete: {
|
|
55
|
+
readonly type: "string";
|
|
56
|
+
};
|
|
57
|
+
readonly inputMode: {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly attribute: "inputmode";
|
|
60
|
+
};
|
|
61
|
+
readonly enterKeyHint: {
|
|
62
|
+
readonly type: "string";
|
|
63
|
+
readonly attribute: "enterkeyhint";
|
|
64
|
+
};
|
|
65
|
+
readonly size: {
|
|
66
|
+
readonly type: "string";
|
|
67
|
+
readonly default: "md";
|
|
68
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
69
|
+
readonly reflect: true;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export type TextFieldProps = InferProps<typeof props>;
|
|
73
|
+
export declare const textFieldDef: import("../../core/define.js").ComponentDef<{
|
|
74
|
+
readonly type: {
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
readonly default: "text";
|
|
77
|
+
readonly values: readonly ["text", "email", "password", "search", "tel", "url"];
|
|
78
|
+
readonly reflect: true;
|
|
79
|
+
};
|
|
80
|
+
readonly value: {
|
|
81
|
+
readonly type: "string";
|
|
82
|
+
};
|
|
83
|
+
readonly name: {
|
|
84
|
+
readonly type: "string";
|
|
85
|
+
readonly reflect: true;
|
|
86
|
+
};
|
|
87
|
+
readonly label: {
|
|
88
|
+
readonly type: "string";
|
|
89
|
+
};
|
|
90
|
+
readonly placeholder: {
|
|
91
|
+
readonly type: "string";
|
|
92
|
+
};
|
|
93
|
+
readonly helpText: {
|
|
94
|
+
readonly type: "string";
|
|
95
|
+
};
|
|
96
|
+
readonly errorText: {
|
|
97
|
+
readonly type: "string";
|
|
98
|
+
};
|
|
99
|
+
readonly required: {
|
|
100
|
+
readonly type: "boolean";
|
|
101
|
+
readonly reflect: true;
|
|
102
|
+
};
|
|
103
|
+
readonly disabled: {
|
|
104
|
+
readonly type: "boolean";
|
|
105
|
+
readonly reflect: true;
|
|
106
|
+
};
|
|
107
|
+
readonly readOnly: {
|
|
108
|
+
readonly type: "boolean";
|
|
109
|
+
readonly reflect: true;
|
|
110
|
+
readonly attribute: "readonly";
|
|
111
|
+
};
|
|
112
|
+
readonly minLength: {
|
|
113
|
+
readonly type: "number";
|
|
114
|
+
readonly default: -1;
|
|
115
|
+
readonly attribute: "minlength";
|
|
116
|
+
};
|
|
117
|
+
readonly maxLength: {
|
|
118
|
+
readonly type: "number";
|
|
119
|
+
readonly default: -1;
|
|
120
|
+
readonly attribute: "maxlength";
|
|
121
|
+
};
|
|
122
|
+
readonly pattern: {
|
|
123
|
+
readonly type: "string";
|
|
124
|
+
};
|
|
125
|
+
readonly autocomplete: {
|
|
126
|
+
readonly type: "string";
|
|
127
|
+
};
|
|
128
|
+
readonly inputMode: {
|
|
129
|
+
readonly type: "string";
|
|
130
|
+
readonly attribute: "inputmode";
|
|
131
|
+
};
|
|
132
|
+
readonly enterKeyHint: {
|
|
133
|
+
readonly type: "string";
|
|
134
|
+
readonly attribute: "enterkeyhint";
|
|
135
|
+
};
|
|
136
|
+
readonly size: {
|
|
137
|
+
readonly type: "string";
|
|
138
|
+
readonly default: "md";
|
|
139
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
140
|
+
readonly reflect: true;
|
|
141
|
+
};
|
|
142
|
+
}>;
|
|
143
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html } from '../../core/html.js';
|
|
3
|
+
import { themeCssProperties } from '../../core/styles.js';
|
|
4
|
+
import { textFieldStyles } from './text-field.styles.js';
|
|
5
|
+
const props = {
|
|
6
|
+
type: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
default: 'text',
|
|
9
|
+
values: ['text', 'email', 'password', 'search', 'tel', 'url'],
|
|
10
|
+
reflect: true,
|
|
11
|
+
},
|
|
12
|
+
value: { type: 'string' },
|
|
13
|
+
name: { type: 'string', reflect: true },
|
|
14
|
+
label: { type: 'string' },
|
|
15
|
+
placeholder: { type: 'string' },
|
|
16
|
+
helpText: { type: 'string' },
|
|
17
|
+
errorText: { type: 'string' },
|
|
18
|
+
required: { type: 'boolean', reflect: true },
|
|
19
|
+
disabled: { type: 'boolean', reflect: true },
|
|
20
|
+
readOnly: { type: 'boolean', reflect: true, attribute: 'readonly' },
|
|
21
|
+
minLength: { type: 'number', default: -1, attribute: 'minlength' },
|
|
22
|
+
maxLength: { type: 'number', default: -1, attribute: 'maxlength' },
|
|
23
|
+
pattern: { type: 'string' },
|
|
24
|
+
autocomplete: { type: 'string' },
|
|
25
|
+
inputMode: { type: 'string', attribute: 'inputmode' },
|
|
26
|
+
enterKeyHint: { type: 'string', attribute: 'enterkeyhint' },
|
|
27
|
+
size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
|
|
28
|
+
};
|
|
29
|
+
/** Attributes of the inner <input> that only appear when set. */
|
|
30
|
+
function inputAttributes(p) {
|
|
31
|
+
return html `${p.placeholder ? html ` placeholder="${p.placeholder}"` : ''}${p.required ? html ` required` : ''}${p.disabled ? html ` disabled` : ''}${p.readOnly ? html ` readonly` : ''}${p.minLength >= 0 ? html ` minlength="${p.minLength}"` : ''}${p.maxLength >= 0 ? html ` maxlength="${p.maxLength}"` : ''}${p.pattern ? html ` pattern="${p.pattern}"` : ''}${p.autocomplete ? html ` autocomplete="${p.autocomplete}"` : ''}${p.inputMode ? html ` inputmode="${p.inputMode}"` : ''}${p.enterKeyHint ? html ` enterkeyhint="${p.enterKeyHint}"` : ''}`;
|
|
32
|
+
}
|
|
33
|
+
export const textFieldDef = defineComponent({
|
|
34
|
+
tag: 'neu-text-field',
|
|
35
|
+
props,
|
|
36
|
+
delegatesFocus: true,
|
|
37
|
+
form: { valueProp: 'value' },
|
|
38
|
+
styles: textFieldStyles,
|
|
39
|
+
template: (p) => html `<div part="base" class="base"><label part="label" class="label" for="input"><slot name="label"><span class="label-text" data-ref="label-text">${p.label}</span></slot></label><div part="control" class="control"><span part="prefix" class="prefix"><slot name="prefix"></slot></span><input part="input" class="input" id="input" data-ref="input" type="${p.type}" value="${p.value}" aria-describedby="help"${inputAttributes(p)}><span part="suffix" class="suffix"><slot name="suffix"></slot></span></div><div part="help-text" class="help" id="help"><slot name="help-text"><span class="help-text" data-ref="help-text">${p.helpText}</span></slot><span class="error-text" data-ref="error-text">${p.errorText}</span></div></div>`,
|
|
40
|
+
docs: {
|
|
41
|
+
summary: 'A single-line text input with an inset field. The value attribute is the default value.',
|
|
42
|
+
events: [
|
|
43
|
+
{ name: 'input', description: 'Fired on every edit (the native input event, composed).' },
|
|
44
|
+
{ name: 'change', description: 'Fired when an edit is committed, usually on blur.' },
|
|
45
|
+
],
|
|
46
|
+
slots: [
|
|
47
|
+
{ name: 'label', description: 'Rich label; replaces the label attribute.' },
|
|
48
|
+
{ name: 'prefix', description: 'Content before the input, such as an icon.' },
|
|
49
|
+
{ name: 'suffix', description: 'Content after the input.' },
|
|
50
|
+
{ name: 'help-text', description: 'Rich help text; replaces the help-text attribute.' },
|
|
51
|
+
],
|
|
52
|
+
parts: [
|
|
53
|
+
{ name: 'base', description: 'Outer wrapper.' },
|
|
54
|
+
{ name: 'label', description: 'The <label> element.' },
|
|
55
|
+
{ name: 'control', description: 'The inset field around the input.' },
|
|
56
|
+
{ name: 'input', description: 'The native <input>.' },
|
|
57
|
+
{ name: 'prefix', description: 'Wrapper around the prefix slot.' },
|
|
58
|
+
{ name: 'suffix', description: 'Wrapper around the suffix slot.' },
|
|
59
|
+
{ name: 'help-text', description: 'Help or error text area.' },
|
|
60
|
+
],
|
|
61
|
+
cssProperties: themeCssProperties,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { afterDispatch, dispatch, setOptionalAttribute, setText } from '../../core/dom.js';
|
|
2
|
+
import { FormControlElement } from '../../core/form-control.js';
|
|
3
|
+
import { copyValidity } from '../../core/validation.js';
|
|
4
|
+
import { implicitSubmit } from './implicit-submit.js';
|
|
5
|
+
import { textFieldDef } from './text-field.def.js';
|
|
6
|
+
/** Props that can change the form value, its restore state or its validity. */
|
|
7
|
+
const COMMIT_PROPS = [
|
|
8
|
+
'value',
|
|
9
|
+
'type',
|
|
10
|
+
'required',
|
|
11
|
+
'disabled',
|
|
12
|
+
'readOnly',
|
|
13
|
+
'minLength',
|
|
14
|
+
'maxLength',
|
|
15
|
+
'pattern',
|
|
16
|
+
'errorText',
|
|
17
|
+
'autocomplete',
|
|
18
|
+
];
|
|
19
|
+
function needsCommit(changed) {
|
|
20
|
+
return COMMIT_PROPS.some((key) => changed.has(key));
|
|
21
|
+
}
|
|
22
|
+
export class NeuTextField extends FormControlElement {
|
|
23
|
+
static def = textFieldDef;
|
|
24
|
+
static extraProperties = ['defaultValue'];
|
|
25
|
+
// inputMode and enterKeyHint are inherited from HTMLElement with the right type.
|
|
26
|
+
/** True once the value was edited or set as a property; the default then stops applying. */
|
|
27
|
+
dirty = false;
|
|
28
|
+
/** A value set before the shadow root exists. */
|
|
29
|
+
pendingValue = null;
|
|
30
|
+
get value() {
|
|
31
|
+
return this.input?.value ?? this.pendingValue ?? this.p.value;
|
|
32
|
+
}
|
|
33
|
+
set value(next) {
|
|
34
|
+
const text = next === null || next === undefined ? '' : String(next);
|
|
35
|
+
this.dirty = true;
|
|
36
|
+
const input = this.input;
|
|
37
|
+
if (input)
|
|
38
|
+
input.value = text;
|
|
39
|
+
else
|
|
40
|
+
this.pendingValue = text;
|
|
41
|
+
this.commit();
|
|
42
|
+
}
|
|
43
|
+
get defaultValue() {
|
|
44
|
+
return this.p.value;
|
|
45
|
+
}
|
|
46
|
+
set defaultValue(next) {
|
|
47
|
+
this.setAttribute('value', String(next));
|
|
48
|
+
}
|
|
49
|
+
/** Focuses the inner input directly, so the caret stays where it was (no select-all). */
|
|
50
|
+
focus(options) {
|
|
51
|
+
const input = this.input;
|
|
52
|
+
if (input)
|
|
53
|
+
input.focus(options);
|
|
54
|
+
else
|
|
55
|
+
super.focus(options);
|
|
56
|
+
}
|
|
57
|
+
select() {
|
|
58
|
+
this.input?.select();
|
|
59
|
+
}
|
|
60
|
+
setSelectionRange(start, end, direction) {
|
|
61
|
+
this.input?.setSelectionRange(start, end, direction);
|
|
62
|
+
}
|
|
63
|
+
get p() {
|
|
64
|
+
return this.props;
|
|
65
|
+
}
|
|
66
|
+
get input() {
|
|
67
|
+
return this.ref('input');
|
|
68
|
+
}
|
|
69
|
+
/** A detached input of this type holding `value`, sanitized the way the browser does it. */
|
|
70
|
+
probe(value) {
|
|
71
|
+
const probe = document.createElement('input');
|
|
72
|
+
probe.type = this.p.type;
|
|
73
|
+
probe.value = value;
|
|
74
|
+
return probe;
|
|
75
|
+
}
|
|
76
|
+
/** Stands in for the inner input until it exists, so validity is right from the start. */
|
|
77
|
+
validityProbe() {
|
|
78
|
+
const p = this.p;
|
|
79
|
+
const probe = this.probe(this.value);
|
|
80
|
+
probe.required = p.required;
|
|
81
|
+
probe.readOnly = p.readOnly;
|
|
82
|
+
probe.disabled = this.isDisabled;
|
|
83
|
+
if (p.pattern)
|
|
84
|
+
probe.setAttribute('pattern', p.pattern);
|
|
85
|
+
if (p.minLength >= 0)
|
|
86
|
+
probe.setAttribute('minlength', String(p.minLength));
|
|
87
|
+
if (p.maxLength >= 0)
|
|
88
|
+
probe.setAttribute('maxlength', String(p.maxLength));
|
|
89
|
+
return probe;
|
|
90
|
+
}
|
|
91
|
+
rendered() {
|
|
92
|
+
const input = this.input;
|
|
93
|
+
// A declarative root adopted without its input (cut short by the parser): nothing to wire up.
|
|
94
|
+
if (!input)
|
|
95
|
+
return;
|
|
96
|
+
// An SSR'd input works before JS loads; keep whatever was typed or autofilled meanwhile.
|
|
97
|
+
// Compare with the sanitized default: the browser trims or strips some values by itself.
|
|
98
|
+
if (input.value !== this.probe(this.p.value).value)
|
|
99
|
+
this.dirty = true;
|
|
100
|
+
if (this.pendingValue !== null) {
|
|
101
|
+
input.value = this.pendingValue;
|
|
102
|
+
this.pendingValue = null;
|
|
103
|
+
}
|
|
104
|
+
input.addEventListener('input', () => {
|
|
105
|
+
this.dirty = true;
|
|
106
|
+
this.commit();
|
|
107
|
+
});
|
|
108
|
+
input.addEventListener('change', () => {
|
|
109
|
+
this.markInteracted();
|
|
110
|
+
dispatch(this, 'change');
|
|
111
|
+
});
|
|
112
|
+
input.addEventListener('keydown', (event) => {
|
|
113
|
+
if (event.key !== 'Enter' || event.isComposing)
|
|
114
|
+
return;
|
|
115
|
+
// Like a native input: submit once the keydown has finished dispatching, unless a listener
|
|
116
|
+
// (on the host or further up) cancelled it.
|
|
117
|
+
afterDispatch(event, () => {
|
|
118
|
+
if (!event.defaultPrevented)
|
|
119
|
+
implicitSubmit(this.internals.form);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
update(changed) {
|
|
124
|
+
const p = this.p;
|
|
125
|
+
const input = this.input;
|
|
126
|
+
if (input) {
|
|
127
|
+
if (changed.has('type') && input.type !== p.type)
|
|
128
|
+
input.type = p.type;
|
|
129
|
+
if (changed.has('value')) {
|
|
130
|
+
if (input.defaultValue !== p.value)
|
|
131
|
+
input.defaultValue = p.value;
|
|
132
|
+
if (!this.dirty)
|
|
133
|
+
input.value = p.value;
|
|
134
|
+
}
|
|
135
|
+
if (changed.has('placeholder'))
|
|
136
|
+
setOptionalAttribute(input, 'placeholder', p.placeholder);
|
|
137
|
+
if (changed.has('required'))
|
|
138
|
+
input.required = p.required;
|
|
139
|
+
if (changed.has('disabled'))
|
|
140
|
+
input.disabled = this.isDisabled;
|
|
141
|
+
if (changed.has('readOnly'))
|
|
142
|
+
input.readOnly = p.readOnly;
|
|
143
|
+
if (changed.has('minLength')) {
|
|
144
|
+
setOptionalAttribute(input, 'minlength', p.minLength >= 0 ? String(p.minLength) : '');
|
|
145
|
+
}
|
|
146
|
+
if (changed.has('maxLength')) {
|
|
147
|
+
setOptionalAttribute(input, 'maxlength', p.maxLength >= 0 ? String(p.maxLength) : '');
|
|
148
|
+
}
|
|
149
|
+
if (changed.has('pattern'))
|
|
150
|
+
setOptionalAttribute(input, 'pattern', p.pattern);
|
|
151
|
+
if (changed.has('autocomplete'))
|
|
152
|
+
setOptionalAttribute(input, 'autocomplete', p.autocomplete);
|
|
153
|
+
if (changed.has('inputMode'))
|
|
154
|
+
setOptionalAttribute(input, 'inputmode', p.inputMode);
|
|
155
|
+
if (changed.has('enterKeyHint'))
|
|
156
|
+
setOptionalAttribute(input, 'enterkeyhint', p.enterKeyHint);
|
|
157
|
+
}
|
|
158
|
+
if (changed.has('label'))
|
|
159
|
+
setText(this.ref('label-text'), p.label);
|
|
160
|
+
if (changed.has('helpText'))
|
|
161
|
+
setText(this.ref('help-text'), p.helpText);
|
|
162
|
+
if (needsCommit(changed))
|
|
163
|
+
this.commit();
|
|
164
|
+
}
|
|
165
|
+
/** update() only runs once connected; before that, keep the value and validity current here. */
|
|
166
|
+
willUpdate(changed) {
|
|
167
|
+
if (!this.isConnected && !this.input && needsCommit(changed))
|
|
168
|
+
this.commit();
|
|
169
|
+
}
|
|
170
|
+
formValue() {
|
|
171
|
+
return this.value;
|
|
172
|
+
}
|
|
173
|
+
/** Like a native input, a password or autocomplete=off field is never saved for restore. */
|
|
174
|
+
formState() {
|
|
175
|
+
const p = this.p;
|
|
176
|
+
if (p.type === 'password' || p.autocomplete.trim().toLowerCase() === 'off')
|
|
177
|
+
return null;
|
|
178
|
+
return this.value;
|
|
179
|
+
}
|
|
180
|
+
validate() {
|
|
181
|
+
const input = this.input;
|
|
182
|
+
const source = input ?? this.validityProbe();
|
|
183
|
+
// A disabled or readonly input still sets flags such as typeMismatch, but is barred from
|
|
184
|
+
// validation and has no message; setValidity() would throw on flags without a message.
|
|
185
|
+
if (!source.willValidate || source.validity.valid)
|
|
186
|
+
this.internals.setValidity({});
|
|
187
|
+
else {
|
|
188
|
+
this.internals.setValidity(copyValidity(source.validity), source.validationMessage, input ?? undefined);
|
|
189
|
+
}
|
|
190
|
+
setText(this.ref('error-text'), this.p.errorText || input?.validationMessage || '');
|
|
191
|
+
}
|
|
192
|
+
resetToDefault() {
|
|
193
|
+
this.dirty = false;
|
|
194
|
+
this.pendingValue = null;
|
|
195
|
+
const input = this.input;
|
|
196
|
+
if (input)
|
|
197
|
+
input.value = this.p.value;
|
|
198
|
+
}
|
|
199
|
+
restoreState(state) {
|
|
200
|
+
this.dirty = true;
|
|
201
|
+
const input = this.input;
|
|
202
|
+
if (input)
|
|
203
|
+
input.value = state;
|
|
204
|
+
else
|
|
205
|
+
this.pendingValue = state;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const textFieldStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: block; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: flex; flex-direction: column; }\n.label-text, ::slotted([slot='label']) {\n display: block; margin-block-end: 0.375rem;\n font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted);\n}\n.label-text:empty { display: none; }\n.control {\n display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }\n:host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }\n:host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }\n:host(:state(user-invalid)) .control {\n box-shadow:\n inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),\n inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n}\n:host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }\n.input {\n flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;\n background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;\n}\n.input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }\n.prefix, .suffix { display: inline-flex; }\n::slotted([slot='prefix']) { margin-inline-end: 0.5rem; }\n::slotted([slot='suffix']) { margin-inline-start: 0.5rem; }\n.help { font-size: 0.75rem; color: var(--_neu-fg-muted); }\n.help-text, ::slotted([slot='help-text']) { display: block; margin-block-start: 0.375rem; }\n.help-text:empty { display: none; }\n.error-text { display: none; margin-block-start: 0.375rem; color: var(--_neu-danger); }\n:host(:state(user-invalid)) slot[name='help-text'] { display: none; }\n:host(:state(user-invalid)) .error-text:not(:empty) { display: block; }\n";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { hostTheme } from '../../core/styles.js';
|
|
2
|
+
export const textFieldStyles = `${hostTheme}
|
|
3
|
+
:host { display: block; }
|
|
4
|
+
:host(:disabled), :host([disabled]) { cursor: not-allowed; }
|
|
5
|
+
.base { display: flex; flex-direction: column; }
|
|
6
|
+
.label-text, ::slotted([slot='label']) {
|
|
7
|
+
display: block; margin-block-end: 0.375rem;
|
|
8
|
+
font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted);
|
|
9
|
+
}
|
|
10
|
+
.label-text:empty { display: none; }
|
|
11
|
+
.control {
|
|
12
|
+
display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;
|
|
13
|
+
border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);
|
|
14
|
+
transition: box-shadow var(--_neu-dur) ease;
|
|
15
|
+
}
|
|
16
|
+
:host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }
|
|
17
|
+
:host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }
|
|
18
|
+
:host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }
|
|
19
|
+
:host(:state(user-invalid)) .control {
|
|
20
|
+
box-shadow:
|
|
21
|
+
inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),
|
|
22
|
+
inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);
|
|
23
|
+
}
|
|
24
|
+
:host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }
|
|
25
|
+
.input {
|
|
26
|
+
flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;
|
|
27
|
+
background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;
|
|
28
|
+
}
|
|
29
|
+
.input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }
|
|
30
|
+
.prefix, .suffix { display: inline-flex; }
|
|
31
|
+
::slotted([slot='prefix']) { margin-inline-end: 0.5rem; }
|
|
32
|
+
::slotted([slot='suffix']) { margin-inline-start: 0.5rem; }
|
|
33
|
+
.help { font-size: 0.75rem; color: var(--_neu-fg-muted); }
|
|
34
|
+
.help-text, ::slotted([slot='help-text']) { display: block; margin-block-start: 0.375rem; }
|
|
35
|
+
.help-text:empty { display: none; }
|
|
36
|
+
.error-text { display: none; margin-block-start: 0.375rem; color: var(--_neu-danger); }
|
|
37
|
+
:host(:state(user-invalid)) slot[name='help-text'] { display: none; }
|
|
38
|
+
:host(:state(user-invalid)) .error-text:not(:empty) { display: block; }
|
|
39
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RawHtml } from './html.js';
|
|
2
|
+
import type { InferProps, PropsSchema } from './props.js';
|
|
3
|
+
export interface DocEntry {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly description: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ComponentDocs {
|
|
8
|
+
readonly summary: string;
|
|
9
|
+
readonly events: readonly DocEntry[];
|
|
10
|
+
readonly slots: readonly DocEntry[];
|
|
11
|
+
readonly parts: readonly DocEntry[];
|
|
12
|
+
readonly cssProperties: readonly DocEntry[];
|
|
13
|
+
}
|
|
14
|
+
export interface FormDef {
|
|
15
|
+
/** The prop whose attribute holds the default value (spec §6.1). */
|
|
16
|
+
readonly valueProp: 'value' | 'checked';
|
|
17
|
+
}
|
|
18
|
+
/** The DOM-free half of a component, shared by the element, SSR, React wrappers and docs. */
|
|
19
|
+
export interface ComponentDef<S extends PropsSchema = PropsSchema> {
|
|
20
|
+
readonly tag: string;
|
|
21
|
+
readonly props: S;
|
|
22
|
+
readonly styles: string;
|
|
23
|
+
template(props: InferProps<S>): RawHtml;
|
|
24
|
+
readonly delegatesFocus?: boolean;
|
|
25
|
+
readonly form?: FormDef;
|
|
26
|
+
readonly docs: ComponentDocs;
|
|
27
|
+
}
|
|
28
|
+
export declare function defineComponent<const S extends PropsSchema>(def: ComponentDef<S>): ComponentDef<S>;
|
|
29
|
+
/**
|
|
30
|
+
* The element property that sets a form control's default (its value attribute), as wrappers and
|
|
31
|
+
* generated types expose it: `defaultChecked` for `checked`, `defaultValue` for `value`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function formDefaultProp(def: ComponentDef): 'defaultChecked' | 'defaultValue' | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function defineComponent(def) {
|
|
2
|
+
return def;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* The element property that sets a form control's default (its value attribute), as wrappers and
|
|
6
|
+
* generated types expose it: `defaultChecked` for `checked`, `defaultValue` for `value`.
|
|
7
|
+
*/
|
|
8
|
+
export function formDefaultProp(def) {
|
|
9
|
+
const valueProp = def.form?.valueProp;
|
|
10
|
+
if (valueProp === 'checked')
|
|
11
|
+
return 'defaultChecked';
|
|
12
|
+
if (valueProp === 'value')
|
|
13
|
+
return 'defaultValue';
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Writes text only when it changed, so hydrated nodes are not replaced needlessly. */
|
|
2
|
+
export declare function setText(el: Element | null, text: string): void;
|
|
3
|
+
/** Sets the attribute when value is non-empty and removes it otherwise. */
|
|
4
|
+
export declare function setOptionalAttribute(el: Element | null, name: string, value: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* True while `el` is the focused element of its tree. It stays true when only the window or frame
|
|
7
|
+
* loses focus, which also fires blur on the element; it is false once focus moved elsewhere.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isActiveInRoot(el: Element): boolean;
|
|
10
|
+
/** Dispatches a bubbling, composed event from a host (spec §6.4). */
|
|
11
|
+
export declare function dispatch(target: EventTarget, type: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Runs `run` once `event` has finished dispatching — after every listener, including those on
|
|
14
|
+
* window — or on the next task if propagation was stopped before window. Runs exactly once.
|
|
15
|
+
*
|
|
16
|
+
* A one-shot bubble listener on window catches this same event object, so `run` lands within the
|
|
17
|
+
* dispatch, after the listeners on the way up, and sees their preventDefault(). Not a microtask:
|
|
18
|
+
* for a trusted event the microtask queue drains between listeners. An event that never reaches
|
|
19
|
+
* window (stopPropagation, bubbles: false, a disconnected target) is finished by the timeout.
|
|
20
|
+
*/
|
|
21
|
+
export declare function afterDispatch(event: Event, run: () => void): void;
|