@neumorphism-ui/elements 1.0.0-next.1 → 1.0.0-next.3
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 +12 -1
- package/cdn/neu.min.js +176 -51
- package/custom-elements.json +2555 -358
- package/dist/all.d.ts +8 -0
- package/dist/all.js +8 -0
- package/dist/checkbox.d.ts +3 -0
- package/dist/checkbox.js +4 -0
- package/dist/components/avatar/avatar.d.ts +0 -3
- package/dist/components/avatar/avatar.def.d.ts +0 -3
- package/dist/components/avatar/avatar.def.js +4 -18
- package/dist/components/avatar/avatar.docs.d.ts +2 -0
- package/dist/components/avatar/avatar.docs.js +22 -0
- package/dist/components/avatar/avatar.js +7 -1
- package/dist/components/badge/badge.d.ts +0 -2
- package/dist/components/badge/badge.def.d.ts +0 -2
- package/dist/components/badge/badge.def.js +1 -14
- package/dist/components/badge/badge.docs.d.ts +2 -0
- package/dist/components/badge/badge.docs.js +17 -0
- package/dist/components/button/button.d.ts +0 -2
- package/dist/components/button/button.def.d.ts +0 -5
- package/dist/components/button/button.def.js +3 -25
- package/dist/components/button/button.docs.d.ts +2 -0
- package/dist/components/button/button.docs.js +29 -0
- package/dist/components/button/button.js +3 -36
- package/dist/components/button-group/button-group.d.ts +0 -2
- package/dist/components/button-group/button-group.def.d.ts +0 -2
- package/dist/components/button-group/button-group.def.js +1 -10
- package/dist/components/button-group/button-group.docs.d.ts +2 -0
- package/dist/components/button-group/button-group.docs.js +13 -0
- package/dist/components/checkbox/checkbox.d.ts +40 -0
- package/dist/components/checkbox/checkbox.def.d.ts +27 -0
- package/dist/components/checkbox/checkbox.def.js +21 -0
- package/dist/components/checkbox/checkbox.docs.d.ts +2 -0
- package/dist/components/checkbox/checkbox.docs.js +27 -0
- package/dist/components/checkbox/checkbox.js +40 -0
- package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
- package/dist/components/checkbox/checkbox.styles.js +28 -0
- package/dist/components/divider/divider.d.ts +0 -1
- package/dist/components/divider/divider.def.d.ts +0 -1
- package/dist/components/divider/divider.def.js +0 -9
- package/dist/components/divider/divider.docs.d.ts +2 -0
- package/dist/components/divider/divider.docs.js +12 -0
- package/dist/components/icon/icon.d.ts +0 -3
- package/dist/components/icon/icon.def.d.ts +0 -3
- package/dist/components/icon/icon.def.js +3 -10
- package/dist/components/icon/icon.docs.d.ts +2 -0
- package/dist/components/icon/icon.docs.js +13 -0
- package/dist/components/icon/system-icons.d.ts +10 -0
- package/dist/components/icon/system-icons.js +22 -11
- package/dist/components/icon-button/icon-button.d.ts +0 -5
- package/dist/components/icon-button/icon-button.def.d.ts +0 -5
- package/dist/components/icon-button/icon-button.def.js +3 -17
- package/dist/components/icon-button/icon-button.docs.d.ts +2 -0
- package/dist/components/icon-button/icon-button.docs.js +26 -0
- package/dist/components/number-field/number-field.d.ts +79 -0
- package/dist/components/number-field/number-field.def.d.ts +115 -0
- package/dist/components/number-field/number-field.def.js +40 -0
- package/dist/components/number-field/number-field.docs.d.ts +2 -0
- package/dist/components/number-field/number-field.docs.js +45 -0
- package/dist/components/number-field/number-field.js +121 -0
- package/dist/components/number-field/number-field.styles.d.ts +1 -0
- package/dist/components/number-field/number-field.styles.js +15 -0
- package/dist/components/option/option-data.d.ts +8 -0
- package/dist/components/option/option-data.js +12 -0
- package/dist/components/option/option.d.ts +17 -0
- package/dist/components/option/option.def.d.ts +8 -0
- package/dist/components/option/option.def.js +12 -0
- package/dist/components/option/option.docs.d.ts +2 -0
- package/dist/components/option/option.docs.js +12 -0
- package/dist/components/option/option.js +17 -0
- package/dist/components/option/option.styles.d.ts +2 -0
- package/dist/components/option/option.styles.js +5 -0
- package/dist/components/radio/radio-link.d.ts +11 -0
- package/dist/components/radio/radio-link.js +5 -0
- package/dist/components/radio/radio.d.ts +27 -0
- package/dist/components/radio/radio.def.d.ts +9 -0
- package/dist/components/radio/radio.def.js +12 -0
- package/dist/components/radio/radio.docs.d.ts +2 -0
- package/dist/components/radio/radio.docs.js +18 -0
- package/dist/components/radio/radio.js +43 -0
- package/dist/components/radio/radio.styles.d.ts +1 -0
- package/dist/components/radio/radio.styles.js +21 -0
- package/dist/components/radio-group/radio-group.d.ts +76 -0
- package/dist/components/radio-group/radio-group.def.d.ts +36 -0
- package/dist/components/radio-group/radio-group.def.js +40 -0
- package/dist/components/radio-group/radio-group.docs.d.ts +2 -0
- package/dist/components/radio-group/radio-group.docs.js +37 -0
- package/dist/components/radio-group/radio-group.js +208 -0
- package/dist/components/radio-group/radio-group.styles.d.ts +1 -0
- package/dist/components/radio-group/radio-group.styles.js +13 -0
- package/dist/components/select/select.d.ts +115 -0
- package/dist/components/select/select.def.d.ts +34 -0
- package/dist/components/select/select.def.js +35 -0
- package/dist/components/select/select.docs.d.ts +2 -0
- package/dist/components/select/select.docs.js +65 -0
- package/dist/components/select/select.js +391 -0
- package/dist/components/select/select.styles.d.ts +1 -0
- package/dist/components/select/select.styles.js +35 -0
- package/dist/components/shared/checkable.d.ts +45 -0
- package/dist/components/shared/checkable.js +108 -0
- package/dist/components/shared/field.styles.d.ts +1 -0
- package/dist/components/shared/field.styles.js +34 -0
- package/dist/components/shared/press.d.ts +7 -0
- package/dist/components/shared/press.js +42 -0
- package/dist/components/shared/step.d.ts +2 -0
- package/dist/components/shared/step.js +2 -0
- package/dist/components/shared/text-control.d.ts +72 -0
- package/dist/components/shared/text-control.js +191 -0
- package/dist/components/slider/slider.d.ts +67 -0
- package/dist/components/slider/slider.def.d.ts +37 -0
- package/dist/components/slider/slider.def.js +54 -0
- package/dist/components/slider/slider.docs.d.ts +2 -0
- package/dist/components/slider/slider.docs.js +45 -0
- package/dist/components/slider/slider.js +170 -0
- package/dist/components/slider/slider.styles.d.ts +1 -0
- package/dist/components/slider/slider.styles.js +16 -0
- package/dist/components/spinner/spinner.d.ts +0 -1
- package/dist/components/spinner/spinner.def.d.ts +0 -1
- package/dist/components/spinner/spinner.def.js +1 -13
- package/dist/components/spinner/spinner.docs.d.ts +2 -0
- package/dist/components/spinner/spinner.docs.js +17 -0
- package/dist/components/surface/surface.d.ts +0 -4
- package/dist/components/surface/surface.def.d.ts +0 -4
- package/dist/components/surface/surface.def.js +3 -12
- package/dist/components/surface/surface.docs.d.ts +2 -0
- package/dist/components/surface/surface.docs.js +15 -0
- package/dist/components/switch/switch.d.ts +4 -35
- package/dist/components/switch/switch.def.d.ts +0 -5
- package/dist/components/switch/switch.def.js +5 -21
- package/dist/components/switch/switch.docs.d.ts +2 -0
- package/dist/components/switch/switch.docs.js +26 -0
- package/dist/components/switch/switch.js +5 -117
- package/dist/components/text-field/implicit-submit.d.ts +5 -0
- package/dist/components/text-field/implicit-submit.js +8 -3
- package/dist/components/text-field/text-field.d.ts +9 -42
- package/dist/components/text-field/text-field.def.d.ts +0 -12
- package/dist/components/text-field/text-field.def.js +5 -30
- package/dist/components/text-field/text-field.docs.d.ts +2 -0
- package/dist/components/text-field/text-field.docs.js +45 -0
- package/dist/components/text-field/text-field.js +21 -178
- package/dist/components/text-field/text-field.styles.d.ts +1 -1
- package/dist/components/text-field/text-field.styles.js +2 -35
- package/dist/components/textarea/textarea.d.ts +92 -0
- package/dist/components/textarea/textarea.def.d.ts +127 -0
- package/dist/components/textarea/textarea.def.js +44 -0
- package/dist/components/textarea/textarea.docs.d.ts +2 -0
- package/dist/components/textarea/textarea.docs.js +40 -0
- package/dist/components/textarea/textarea.js +101 -0
- package/dist/components/textarea/textarea.styles.d.ts +1 -0
- package/dist/components/textarea/textarea.styles.js +9 -0
- package/dist/components/theme-docs.d.ts +3 -0
- package/dist/components/theme-docs.js +8 -0
- package/dist/core/define.d.ts +20 -2
- package/dist/core/dom.d.ts +4 -0
- package/dist/core/dom.js +7 -0
- package/dist/core/form-control.js +6 -1
- package/dist/core/neu-element.d.ts +2 -2
- package/dist/core/neu-element.js +10 -7
- package/dist/core/props.d.ts +13 -1
- package/dist/core/props.js +13 -3
- package/dist/core/styles.d.ts +0 -2
- package/dist/core/styles.js +0 -7
- package/dist/core/validation.d.ts +6 -2
- package/dist/core/validation.js +15 -8
- package/dist/docs.d.ts +3 -0
- package/dist/docs.js +41 -0
- package/dist/number-field.d.ts +3 -0
- package/dist/number-field.js +4 -0
- package/dist/option.d.ts +3 -0
- package/dist/option.js +4 -0
- package/dist/radio-group.d.ts +4 -0
- package/dist/radio-group.js +6 -0
- package/dist/radio.d.ts +3 -0
- package/dist/radio.js +4 -0
- package/dist/select.d.ts +3 -0
- package/dist/select.js +4 -0
- package/dist/slider.d.ts +3 -0
- package/dist/slider.js +4 -0
- package/dist/ssr.d.ts +10 -6
- package/dist/ssr.js +26 -6
- package/dist/textarea.d.ts +3 -0
- package/dist/textarea.js +4 -0
- package/package.json +45 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { TextControlElement } from '../shared/text-control.js';
|
|
2
|
+
export declare class NeuTextarea extends TextControlElement {
|
|
3
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
4
|
+
readonly value: {
|
|
5
|
+
readonly type: "string";
|
|
6
|
+
};
|
|
7
|
+
readonly name: {
|
|
8
|
+
readonly type: "string";
|
|
9
|
+
};
|
|
10
|
+
readonly label: {
|
|
11
|
+
readonly type: "string";
|
|
12
|
+
};
|
|
13
|
+
readonly placeholder: {
|
|
14
|
+
readonly type: "string";
|
|
15
|
+
};
|
|
16
|
+
readonly helpText: {
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
};
|
|
19
|
+
readonly errorText: {
|
|
20
|
+
readonly type: "string";
|
|
21
|
+
};
|
|
22
|
+
readonly required: {
|
|
23
|
+
readonly type: "boolean";
|
|
24
|
+
};
|
|
25
|
+
readonly disabled: {
|
|
26
|
+
readonly type: "boolean";
|
|
27
|
+
};
|
|
28
|
+
readonly readOnly: {
|
|
29
|
+
readonly type: "boolean";
|
|
30
|
+
readonly attribute: "readonly";
|
|
31
|
+
};
|
|
32
|
+
readonly minLength: {
|
|
33
|
+
readonly type: "number";
|
|
34
|
+
readonly default: -1;
|
|
35
|
+
readonly attribute: "minlength";
|
|
36
|
+
};
|
|
37
|
+
readonly maxLength: {
|
|
38
|
+
readonly type: "number";
|
|
39
|
+
readonly default: -1;
|
|
40
|
+
readonly attribute: "maxlength";
|
|
41
|
+
};
|
|
42
|
+
readonly autocomplete: {
|
|
43
|
+
readonly type: "string";
|
|
44
|
+
};
|
|
45
|
+
readonly enterKeyHint: {
|
|
46
|
+
readonly type: "string";
|
|
47
|
+
readonly attribute: "enterkeyhint";
|
|
48
|
+
};
|
|
49
|
+
readonly rows: {
|
|
50
|
+
readonly type: "number";
|
|
51
|
+
readonly default: 3;
|
|
52
|
+
};
|
|
53
|
+
readonly resize: {
|
|
54
|
+
readonly type: "string";
|
|
55
|
+
readonly default: "vertical";
|
|
56
|
+
readonly values: readonly ["none", "vertical", "auto"];
|
|
57
|
+
};
|
|
58
|
+
readonly size: {
|
|
59
|
+
readonly type: "string";
|
|
60
|
+
readonly default: "md";
|
|
61
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
64
|
+
static extraProperties: string[];
|
|
65
|
+
minLength: number;
|
|
66
|
+
maxLength: number;
|
|
67
|
+
rows: number;
|
|
68
|
+
resize: 'none' | 'vertical' | 'auto';
|
|
69
|
+
size: 'sm' | 'md' | 'lg';
|
|
70
|
+
protected readonly commitProps: readonly ["value", "required", "disabled", "readOnly", "minLength", "maxLength", "errorText", "autocomplete"];
|
|
71
|
+
private get tp();
|
|
72
|
+
get value(): string;
|
|
73
|
+
set value(next: string);
|
|
74
|
+
protected probe(value: string): HTMLTextAreaElement;
|
|
75
|
+
private sizeWatch;
|
|
76
|
+
private sizeFrame;
|
|
77
|
+
private lastWidth;
|
|
78
|
+
/**
|
|
79
|
+
* Re-fits when the width changes or the box first gets a layout (it was hidden, or server
|
|
80
|
+
* rendered); its own height changes keep the width, so they do not loop.
|
|
81
|
+
*/
|
|
82
|
+
private watchSize;
|
|
83
|
+
private stopWatch;
|
|
84
|
+
/** resize="auto": fit the height to the content, never below the rows height. */
|
|
85
|
+
private grow;
|
|
86
|
+
protected connected(): void;
|
|
87
|
+
protected disconnected(): void;
|
|
88
|
+
protected rendered(): void;
|
|
89
|
+
protected update(changed: ReadonlySet<string>): void;
|
|
90
|
+
protected resetToDefault(): void;
|
|
91
|
+
protected restoreState(state: string): void;
|
|
92
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { InferProps } from '../../core/props.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
readonly value: {
|
|
4
|
+
readonly type: "string";
|
|
5
|
+
};
|
|
6
|
+
readonly name: {
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
};
|
|
9
|
+
readonly label: {
|
|
10
|
+
readonly type: "string";
|
|
11
|
+
};
|
|
12
|
+
readonly placeholder: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
};
|
|
15
|
+
readonly helpText: {
|
|
16
|
+
readonly type: "string";
|
|
17
|
+
};
|
|
18
|
+
readonly errorText: {
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
};
|
|
21
|
+
readonly required: {
|
|
22
|
+
readonly type: "boolean";
|
|
23
|
+
};
|
|
24
|
+
readonly disabled: {
|
|
25
|
+
readonly type: "boolean";
|
|
26
|
+
};
|
|
27
|
+
readonly readOnly: {
|
|
28
|
+
readonly type: "boolean";
|
|
29
|
+
readonly attribute: "readonly";
|
|
30
|
+
};
|
|
31
|
+
readonly minLength: {
|
|
32
|
+
readonly type: "number";
|
|
33
|
+
readonly default: -1;
|
|
34
|
+
readonly attribute: "minlength";
|
|
35
|
+
};
|
|
36
|
+
readonly maxLength: {
|
|
37
|
+
readonly type: "number";
|
|
38
|
+
readonly default: -1;
|
|
39
|
+
readonly attribute: "maxlength";
|
|
40
|
+
};
|
|
41
|
+
readonly autocomplete: {
|
|
42
|
+
readonly type: "string";
|
|
43
|
+
};
|
|
44
|
+
readonly enterKeyHint: {
|
|
45
|
+
readonly type: "string";
|
|
46
|
+
readonly attribute: "enterkeyhint";
|
|
47
|
+
};
|
|
48
|
+
readonly rows: {
|
|
49
|
+
readonly type: "number";
|
|
50
|
+
readonly default: 3;
|
|
51
|
+
};
|
|
52
|
+
readonly resize: {
|
|
53
|
+
readonly type: "string";
|
|
54
|
+
readonly default: "vertical";
|
|
55
|
+
readonly values: readonly ["none", "vertical", "auto"];
|
|
56
|
+
};
|
|
57
|
+
readonly size: {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly default: "md";
|
|
60
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export type TextareaProps = InferProps<typeof props>;
|
|
64
|
+
/** A row count the native textarea would also accept; anything else means the default of 3. */
|
|
65
|
+
export declare function rowsOf(rows: number): number;
|
|
66
|
+
export declare const textareaDef: import("../../core/define.js").ComponentDef<{
|
|
67
|
+
readonly value: {
|
|
68
|
+
readonly type: "string";
|
|
69
|
+
};
|
|
70
|
+
readonly name: {
|
|
71
|
+
readonly type: "string";
|
|
72
|
+
};
|
|
73
|
+
readonly label: {
|
|
74
|
+
readonly type: "string";
|
|
75
|
+
};
|
|
76
|
+
readonly placeholder: {
|
|
77
|
+
readonly type: "string";
|
|
78
|
+
};
|
|
79
|
+
readonly helpText: {
|
|
80
|
+
readonly type: "string";
|
|
81
|
+
};
|
|
82
|
+
readonly errorText: {
|
|
83
|
+
readonly type: "string";
|
|
84
|
+
};
|
|
85
|
+
readonly required: {
|
|
86
|
+
readonly type: "boolean";
|
|
87
|
+
};
|
|
88
|
+
readonly disabled: {
|
|
89
|
+
readonly type: "boolean";
|
|
90
|
+
};
|
|
91
|
+
readonly readOnly: {
|
|
92
|
+
readonly type: "boolean";
|
|
93
|
+
readonly attribute: "readonly";
|
|
94
|
+
};
|
|
95
|
+
readonly minLength: {
|
|
96
|
+
readonly type: "number";
|
|
97
|
+
readonly default: -1;
|
|
98
|
+
readonly attribute: "minlength";
|
|
99
|
+
};
|
|
100
|
+
readonly maxLength: {
|
|
101
|
+
readonly type: "number";
|
|
102
|
+
readonly default: -1;
|
|
103
|
+
readonly attribute: "maxlength";
|
|
104
|
+
};
|
|
105
|
+
readonly autocomplete: {
|
|
106
|
+
readonly type: "string";
|
|
107
|
+
};
|
|
108
|
+
readonly enterKeyHint: {
|
|
109
|
+
readonly type: "string";
|
|
110
|
+
readonly attribute: "enterkeyhint";
|
|
111
|
+
};
|
|
112
|
+
readonly rows: {
|
|
113
|
+
readonly type: "number";
|
|
114
|
+
readonly default: 3;
|
|
115
|
+
};
|
|
116
|
+
readonly resize: {
|
|
117
|
+
readonly type: "string";
|
|
118
|
+
readonly default: "vertical";
|
|
119
|
+
readonly values: readonly ["none", "vertical", "auto"];
|
|
120
|
+
};
|
|
121
|
+
readonly size: {
|
|
122
|
+
readonly type: "string";
|
|
123
|
+
readonly default: "md";
|
|
124
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
125
|
+
};
|
|
126
|
+
}>;
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html } from '../../core/html.js';
|
|
3
|
+
import { textareaStyles } from './textarea.styles.js';
|
|
4
|
+
const props = {
|
|
5
|
+
value: { type: 'string' },
|
|
6
|
+
name: { type: 'string' },
|
|
7
|
+
label: { type: 'string' },
|
|
8
|
+
placeholder: { type: 'string' },
|
|
9
|
+
helpText: { type: 'string' },
|
|
10
|
+
errorText: { type: 'string' },
|
|
11
|
+
required: { type: 'boolean' },
|
|
12
|
+
disabled: { type: 'boolean' },
|
|
13
|
+
readOnly: { type: 'boolean', attribute: 'readonly' },
|
|
14
|
+
minLength: { type: 'number', default: -1, attribute: 'minlength' },
|
|
15
|
+
maxLength: { type: 'number', default: -1, attribute: 'maxlength' },
|
|
16
|
+
autocomplete: { type: 'string' },
|
|
17
|
+
enterKeyHint: { type: 'string', attribute: 'enterkeyhint' },
|
|
18
|
+
rows: { type: 'number', default: 3 },
|
|
19
|
+
resize: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
default: 'vertical',
|
|
22
|
+
values: ['none', 'vertical', 'auto'],
|
|
23
|
+
},
|
|
24
|
+
size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'] },
|
|
25
|
+
};
|
|
26
|
+
/** A row count the native textarea would also accept; anything else means the default of 3. */
|
|
27
|
+
export function rowsOf(rows) {
|
|
28
|
+
return Number.isInteger(rows) && rows >= 1 ? rows : 3;
|
|
29
|
+
}
|
|
30
|
+
/** Attributes of the inner <textarea> that only appear when set. */
|
|
31
|
+
function areaAttributes(p) {
|
|
32
|
+
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.autocomplete ? html ` autocomplete="${p.autocomplete}"` : ''}${p.enterKeyHint ? html ` enterkeyhint="${p.enterKeyHint}"` : ''}`;
|
|
33
|
+
}
|
|
34
|
+
export const textareaDef = defineComponent({
|
|
35
|
+
tag: 'neu-textarea',
|
|
36
|
+
props,
|
|
37
|
+
delegatesFocus: true,
|
|
38
|
+
form: { valueProp: 'value' },
|
|
39
|
+
styles: textareaStyles,
|
|
40
|
+
// The newline after the opening tag is dropped by the HTML parser, so a value that itself starts
|
|
41
|
+
// with a newline keeps it.
|
|
42
|
+
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"><textarea part="textarea" class="input" id="input" data-ref="input" rows="${rowsOf(p.rows)}" aria-describedby="help"${areaAttributes(p)}>
|
|
43
|
+
${p.value}</textarea></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>`,
|
|
44
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { themeCssProperties } from '../theme-docs.js';
|
|
2
|
+
export const textareaDocs = {
|
|
3
|
+
summary: 'A multi-line text input with an inset field. The value attribute is the default value; resize="auto" grows the field with its content.',
|
|
4
|
+
descriptions: {
|
|
5
|
+
value: 'The current text as typed or set by script.',
|
|
6
|
+
name: 'The name under which the text is submitted with a form.',
|
|
7
|
+
label: 'Visible label text; the label slot replaces it.',
|
|
8
|
+
placeholder: 'Hint shown while the field is empty.',
|
|
9
|
+
helpText: 'Guidance shown below the field; the help-text slot replaces it.',
|
|
10
|
+
errorText: "Shown in place of the browser's validation message once the field is invalid after user interaction; does not itself make the field invalid.",
|
|
11
|
+
required: 'Makes the form invalid while the field is empty.',
|
|
12
|
+
disabled: 'Prevents interaction and leaves the field out of form submission.',
|
|
13
|
+
readOnly: 'Keeps the text visible and submitted but not editable.',
|
|
14
|
+
minLength: 'The fewest characters the text must have; checked after user edits, like native, not on script-set values.',
|
|
15
|
+
maxLength: 'The most characters the text may have; checked after user edits, like native, not a hard limit on script-set values.',
|
|
16
|
+
autocomplete: "Hint for the browser's autofill, as for the native attribute.",
|
|
17
|
+
enterKeyHint: "Hint for the label of the virtual keyboard's Enter key.",
|
|
18
|
+
rows: 'The number of visible text lines; falls back to 3 for anything but a positive integer.',
|
|
19
|
+
resize: "How the user or the content can change the field's height.",
|
|
20
|
+
size: 'The field size.',
|
|
21
|
+
defaultValue: 'The text the field starts with, which a form reset restores; set by the value attribute.',
|
|
22
|
+
},
|
|
23
|
+
events: [
|
|
24
|
+
{ name: 'input', description: 'Fired on every edit (the native input event, composed).' },
|
|
25
|
+
{ name: 'change', description: 'Fired when an edit is committed, usually on blur.' },
|
|
26
|
+
],
|
|
27
|
+
slots: [
|
|
28
|
+
{ name: 'label', description: 'Rich label; replaces the label attribute.' },
|
|
29
|
+
{ name: 'help-text', description: 'Rich help text; replaces the help-text attribute.' },
|
|
30
|
+
],
|
|
31
|
+
parts: [
|
|
32
|
+
{ name: 'base', description: 'Outer wrapper.' },
|
|
33
|
+
{ name: 'label', description: 'The <label> element.' },
|
|
34
|
+
{ name: 'control', description: 'The inset field around the textarea.' },
|
|
35
|
+
{ name: 'textarea', description: 'The native <textarea>.' },
|
|
36
|
+
{ name: 'help-text', description: 'Help or error text area.' },
|
|
37
|
+
],
|
|
38
|
+
cssProperties: themeCssProperties,
|
|
39
|
+
keyboard: [],
|
|
40
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { baseCommitProps, TextControlElement } from '../shared/text-control.js';
|
|
2
|
+
import { rowsOf, textareaDef } from './textarea.def.js';
|
|
3
|
+
export class NeuTextarea extends TextControlElement {
|
|
4
|
+
static def = textareaDef;
|
|
5
|
+
static extraProperties = ['defaultValue'];
|
|
6
|
+
// enterKeyHint is inherited from HTMLElement with the right type.
|
|
7
|
+
commitProps = baseCommitProps;
|
|
8
|
+
get tp() {
|
|
9
|
+
return this.props;
|
|
10
|
+
}
|
|
11
|
+
// An accessor override must redefine both halves; a script-set value needs a fresh height.
|
|
12
|
+
get value() {
|
|
13
|
+
return super.value;
|
|
14
|
+
}
|
|
15
|
+
set value(next) {
|
|
16
|
+
super.value = next;
|
|
17
|
+
this.grow();
|
|
18
|
+
}
|
|
19
|
+
probe(value) {
|
|
20
|
+
const probe = document.createElement('textarea');
|
|
21
|
+
probe.value = value;
|
|
22
|
+
return probe;
|
|
23
|
+
}
|
|
24
|
+
sizeWatch;
|
|
25
|
+
sizeFrame = 0;
|
|
26
|
+
lastWidth = -1;
|
|
27
|
+
/**
|
|
28
|
+
* Re-fits when the width changes or the box first gets a layout (it was hidden, or server
|
|
29
|
+
* rendered); its own height changes keep the width, so they do not loop.
|
|
30
|
+
*/
|
|
31
|
+
watchSize(area, on) {
|
|
32
|
+
if (!on) {
|
|
33
|
+
this.stopWatch();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (this.sizeWatch)
|
|
37
|
+
return;
|
|
38
|
+
this.lastWidth = -1;
|
|
39
|
+
this.sizeWatch = new ResizeObserver(([entry]) => {
|
|
40
|
+
const width = entry?.contentRect.width ?? 0;
|
|
41
|
+
if (width === this.lastWidth)
|
|
42
|
+
return;
|
|
43
|
+
this.lastWidth = width;
|
|
44
|
+
// Next frame: resizing an observed box inside its own callback reports a
|
|
45
|
+
// "ResizeObserver loop" error on window.
|
|
46
|
+
cancelAnimationFrame(this.sizeFrame);
|
|
47
|
+
this.sizeFrame = requestAnimationFrame(() => this.grow());
|
|
48
|
+
});
|
|
49
|
+
this.sizeWatch.observe(area);
|
|
50
|
+
}
|
|
51
|
+
stopWatch() {
|
|
52
|
+
cancelAnimationFrame(this.sizeFrame);
|
|
53
|
+
this.sizeWatch?.disconnect();
|
|
54
|
+
this.sizeWatch = undefined;
|
|
55
|
+
}
|
|
56
|
+
/** resize="auto": fit the height to the content, never below the rows height. */
|
|
57
|
+
grow() {
|
|
58
|
+
const area = this.input;
|
|
59
|
+
if (!area)
|
|
60
|
+
return;
|
|
61
|
+
this.watchSize(area, this.isConnected && this.tp.resize === 'auto');
|
|
62
|
+
if (this.tp.resize !== 'auto') {
|
|
63
|
+
area.style.blockSize = '';
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Back to the rows height first: scrollHeight is never smaller than the box it sits in.
|
|
67
|
+
area.style.blockSize = 'auto';
|
|
68
|
+
const fit = area.scrollHeight;
|
|
69
|
+
// Not laid out (disconnected or hidden): keep the rows height.
|
|
70
|
+
if (fit)
|
|
71
|
+
area.style.blockSize = `${fit}px`;
|
|
72
|
+
}
|
|
73
|
+
connected() {
|
|
74
|
+
super.connected();
|
|
75
|
+
this.grow();
|
|
76
|
+
}
|
|
77
|
+
disconnected() {
|
|
78
|
+
super.disconnected();
|
|
79
|
+
this.stopWatch();
|
|
80
|
+
}
|
|
81
|
+
rendered() {
|
|
82
|
+
super.rendered();
|
|
83
|
+
this.input?.addEventListener('input', () => this.grow());
|
|
84
|
+
}
|
|
85
|
+
update(changed) {
|
|
86
|
+
const area = this.input;
|
|
87
|
+
if (area && changed.has('rows'))
|
|
88
|
+
area.rows = rowsOf(this.tp.rows);
|
|
89
|
+
super.update(changed);
|
|
90
|
+
if (['value', 'rows', 'resize', 'size'].some((key) => changed.has(key)))
|
|
91
|
+
this.grow();
|
|
92
|
+
}
|
|
93
|
+
resetToDefault() {
|
|
94
|
+
super.resetToDefault();
|
|
95
|
+
this.grow();
|
|
96
|
+
}
|
|
97
|
+
restoreState(state) {
|
|
98
|
+
super.restoreState(state);
|
|
99
|
+
this.grow();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const textareaStyles = "\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 { font-size: 0.8125rem; font-weight: 600; line-height: 1rem; color: var(--_neu-fg-muted); }\n.label-text, ::slotted([slot='label']) { display: block; margin-block-end: 0.375rem; }\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.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\n.control { align-items: stretch; }\n.input {\n display: block; box-sizing: border-box; padding-block: 0.625rem; line-height: 1.4; resize: vertical;\n}\n:host([resize='none']) .input { resize: none; }\n:host([resize='auto']) .input { resize: none; overflow: hidden; }\n";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fieldStyles } from '../shared/field.styles.js';
|
|
2
|
+
export const textareaStyles = `${fieldStyles}
|
|
3
|
+
.control { align-items: stretch; }
|
|
4
|
+
.input {
|
|
5
|
+
display: block; box-sizing: border-box; padding-block: 0.625rem; line-height: 1.4; resize: vertical;
|
|
6
|
+
}
|
|
7
|
+
:host([resize='none']) .input { resize: none; }
|
|
8
|
+
:host([resize='auto']) .input { resize: none; overflow: hidden; }
|
|
9
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The theme tokens every component documents; docs only, so it stays out of the core bundle. */
|
|
2
|
+
export const themeCssProperties = [
|
|
3
|
+
{ name: '--neu-bg', description: 'Surface colour; shadows and text are derived from it.' },
|
|
4
|
+
{ name: '--neu-accent', description: 'Accent colour.' },
|
|
5
|
+
{ name: '--neu-distance', description: 'Shadow distance for the md size.' },
|
|
6
|
+
{ name: '--neu-intensity', description: 'Shadow strength multiplier.' },
|
|
7
|
+
{ name: '--neu-radius', description: 'Corner radius for the md size.' },
|
|
8
|
+
];
|
package/dist/core/define.d.ts
CHANGED
|
@@ -6,24 +6,42 @@ export interface DocEntry {
|
|
|
6
6
|
}
|
|
7
7
|
export interface ComponentDocs {
|
|
8
8
|
readonly summary: string;
|
|
9
|
+
/** One line per prop of the def, per key of def.properties, and per defaultValue/defaultChecked. */
|
|
10
|
+
readonly descriptions: Readonly<Record<string, string>>;
|
|
9
11
|
readonly events: readonly DocEntry[];
|
|
10
12
|
readonly slots: readonly DocEntry[];
|
|
11
13
|
readonly parts: readonly DocEntry[];
|
|
12
14
|
readonly cssProperties: readonly DocEntry[];
|
|
15
|
+
/** name: the keys as spec §7 writes them; description: what they do. */
|
|
16
|
+
readonly keyboard: readonly DocEntry[];
|
|
13
17
|
}
|
|
14
18
|
export interface FormDef {
|
|
15
19
|
/** The prop whose attribute holds the default value (spec §6.1). */
|
|
16
20
|
readonly valueProp: 'value' | 'checked';
|
|
17
21
|
}
|
|
22
|
+
/** One option child as the server reads it (spec §8.2): its value, label text and disabled state. */
|
|
23
|
+
export interface OptionData {
|
|
24
|
+
readonly value: string;
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** What a server renderer knows beyond the attributes: the option children of a select (§8.3). */
|
|
29
|
+
export interface TemplateContext {
|
|
30
|
+
readonly options?: readonly OptionData[];
|
|
31
|
+
}
|
|
18
32
|
/** The DOM-free half of a component, shared by the element, SSR, React wrappers and docs. */
|
|
19
33
|
export interface ComponentDef<S extends PropsSchema = PropsSchema> {
|
|
20
34
|
readonly tag: string;
|
|
21
35
|
readonly props: S;
|
|
22
36
|
readonly styles: string;
|
|
23
|
-
|
|
37
|
+
/** The client always renders without `ctx`; only a server renderer passes one. */
|
|
38
|
+
template(props: InferProps<S>, ctx?: TemplateContext): RawHtml;
|
|
24
39
|
readonly delegatesFocus?: boolean;
|
|
25
40
|
readonly form?: FormDef;
|
|
26
|
-
|
|
41
|
+
/** Properties set only from JS (no attribute), name to TypeScript type, for wrappers and the manifest. Type-only. */
|
|
42
|
+
readonly properties?: Readonly<Record<string, string>>;
|
|
43
|
+
/** Tag of the children a server renderer reads into `TemplateContext.options`. */
|
|
44
|
+
readonly optionsFrom?: string;
|
|
27
45
|
}
|
|
28
46
|
export declare function defineComponent<const S extends PropsSchema>(def: ComponentDef<S>): ComponentDef<S>;
|
|
29
47
|
/**
|
package/dist/core/dom.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export declare function setOptionalAttribute(el: Element | null, name: string, v
|
|
|
7
7
|
* loses focus, which also fires blur on the element; it is false once focus moved elsewhere.
|
|
8
8
|
*/
|
|
9
9
|
export declare function isActiveInRoot(el: Element): boolean;
|
|
10
|
+
/** True when `node` sits in a child of `host` assigned to a named slot (a rich label, help text). */
|
|
11
|
+
export declare function inSlotted(host: Element, node: Node): boolean;
|
|
12
|
+
/** An IME keystroke: WebKit ends the composition first, so the Enter that commits has keyCode 229. */
|
|
13
|
+
export declare const composing: (event: KeyboardEvent) => boolean;
|
|
10
14
|
/** Dispatches a bubbling, composed event from a host (spec §6.4). */
|
|
11
15
|
export declare function dispatch(target: EventTarget, type: string): void;
|
|
12
16
|
/**
|
package/dist/core/dom.js
CHANGED
|
@@ -19,6 +19,13 @@ export function setOptionalAttribute(el, name, value) {
|
|
|
19
19
|
export function isActiveInRoot(el) {
|
|
20
20
|
return el.getRootNode().activeElement === el;
|
|
21
21
|
}
|
|
22
|
+
/** True when `node` sits in a child of `host` assigned to a named slot (a rich label, help text). */
|
|
23
|
+
export function inSlotted(host, node) {
|
|
24
|
+
const slotted = (node instanceof Element ? node : node.parentElement)?.closest('[slot]');
|
|
25
|
+
return !!slotted && slotted !== host && host.contains(slotted);
|
|
26
|
+
}
|
|
27
|
+
/** An IME keystroke: WebKit ends the composition first, so the Enter that commits has keyCode 229. */
|
|
28
|
+
export const composing = (event) => event.isComposing || event.keyCode === 229;
|
|
22
29
|
/** Dispatches a bubbling, composed event from a host (spec §6.4). */
|
|
23
30
|
export function dispatch(target, type) {
|
|
24
31
|
target.dispatchEvent(new Event(type, { bubbles: true, composed: true }));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { composing } from './dom.js';
|
|
1
2
|
import { NeuElement } from './neu-element.js';
|
|
2
3
|
/** Forms the user is trying to submit during the current task (click on a submitter, or Enter). */
|
|
3
4
|
const submitAttempts = new WeakSet();
|
|
@@ -47,7 +48,11 @@ function watchSubmitAttempts() {
|
|
|
47
48
|
}
|
|
48
49
|
}, { capture: true });
|
|
49
50
|
document.addEventListener('keydown', (event) => {
|
|
50
|
-
|
|
51
|
+
// Only a native input submits on Enter (neu text/number fields use their inner input); Enter
|
|
52
|
+
// in a textarea, select or switch is no attempt and must not reveal untouched fields.
|
|
53
|
+
if (event.key !== 'Enter' ||
|
|
54
|
+
composing(event) ||
|
|
55
|
+
!(event.composedPath()[0] instanceof HTMLInputElement))
|
|
51
56
|
return;
|
|
52
57
|
for (const node of event.composedPath()) {
|
|
53
58
|
// Not document or window: their named properties make `.form` a <form id|name="form">.
|
|
@@ -71,8 +71,8 @@ export declare abstract class NeuElement extends ServerSafeHTMLElement {
|
|
|
71
71
|
* While the document loads, the parser may still be writing this element's declarative shadow
|
|
72
72
|
* root (spec §8.1). Before the root exists, that is only possible while nothing has been parsed
|
|
73
73
|
* inside or after the element; one created by script in the middle of the page renders at once.
|
|
74
|
-
* Once it exists, its contents may still be streaming in: it is unfinished while
|
|
75
|
-
*
|
|
74
|
+
* Once it exists, its contents may still be streaming in: it is unfinished while one of the
|
|
75
|
+
* `refs` data-ref nodes is missing, and adopting it then would run rendered() without them.
|
|
76
76
|
*/
|
|
77
77
|
private parsing;
|
|
78
78
|
/**
|
package/dist/core/neu-element.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { attributeName, coerceProperty, parseAttribute, parseProps, serializeAttribute, } from './props.js';
|
|
1
|
+
import { attributeName, coerceProperty, parseAttribute, parseProps, reflects, serializeAttribute, } from './props.js';
|
|
2
2
|
/**
|
|
3
3
|
* HTMLElement does not exist in Node (SSR, React server rendering). Falling back to an empty
|
|
4
4
|
* class lets every module be imported there; nothing is ever constructed on the server.
|
|
@@ -139,7 +139,7 @@ export class NeuElement extends ServerSafeHTMLElement {
|
|
|
139
139
|
if (!decl || Object.is(this.props[key], value))
|
|
140
140
|
return;
|
|
141
141
|
this.props[key] = value;
|
|
142
|
-
if (!fromAttribute && decl.
|
|
142
|
+
if (!fromAttribute && reflects(key, decl, this.def))
|
|
143
143
|
this.reflect(key, decl, value);
|
|
144
144
|
this.propChanged(key);
|
|
145
145
|
}
|
|
@@ -221,8 +221,11 @@ export class NeuElement extends ServerSafeHTMLElement {
|
|
|
221
221
|
if (this.root)
|
|
222
222
|
return;
|
|
223
223
|
let root = this.internals.shadowRoot;
|
|
224
|
-
//
|
|
225
|
-
|
|
224
|
+
// The data-ref names the template renders. A root that lacks one is unfinished, or was rendered
|
|
225
|
+
// by another version of this component (a page cached before an upgrade).
|
|
226
|
+
const refs = root
|
|
227
|
+
? [...this.def.template(this.props).value.matchAll(/data-ref="([^"]+)"/g)].map((m) => m[1])
|
|
228
|
+
: [];
|
|
226
229
|
if (!loaded && document.readyState === 'loading' && this.parsing(root, refs)) {
|
|
227
230
|
this.waitForParser(root, refs);
|
|
228
231
|
return;
|
|
@@ -242,12 +245,12 @@ export class NeuElement extends ServerSafeHTMLElement {
|
|
|
242
245
|
* While the document loads, the parser may still be writing this element's declarative shadow
|
|
243
246
|
* root (spec §8.1). Before the root exists, that is only possible while nothing has been parsed
|
|
244
247
|
* inside or after the element; one created by script in the middle of the page renders at once.
|
|
245
|
-
* Once it exists, its contents may still be streaming in: it is unfinished while
|
|
246
|
-
*
|
|
248
|
+
* Once it exists, its contents may still be streaming in: it is unfinished while one of the
|
|
249
|
+
* `refs` data-ref nodes is missing, and adopting it then would run rendered() without them.
|
|
247
250
|
*/
|
|
248
251
|
parsing(root, refs) {
|
|
249
252
|
if (root)
|
|
250
|
-
return root.
|
|
253
|
+
return refs.some((name) => !root.querySelector(`[data-ref="${name}"]`));
|
|
251
254
|
if (this.firstChild !== null || this.nextSibling !== null)
|
|
252
255
|
return false;
|
|
253
256
|
for (let node = this.parentNode; node !== null; node = node.parentNode) {
|
package/dist/core/props.d.ts
CHANGED
|
@@ -34,11 +34,23 @@ export type InferProps<S extends PropsSchema> = {
|
|
|
34
34
|
};
|
|
35
35
|
/** `helpText` → `help-text`, unless the declaration names the attribute explicitly. */
|
|
36
36
|
export declare function attributeName(prop: string, decl: PropDecl): string;
|
|
37
|
+
/**
|
|
38
|
+
* Every prop reflects to its attribute, except the live value of a form control (the def's
|
|
39
|
+
* form.valueProp, whose attribute is only the default) and props that declare `reflect: false`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reflects(key: string, decl: PropDecl, def: {
|
|
42
|
+
readonly form?: {
|
|
43
|
+
readonly valueProp: string;
|
|
44
|
+
};
|
|
45
|
+
}): boolean;
|
|
37
46
|
/** Attribute text (null when absent) → prop value. Invalid input silently becomes the default. */
|
|
38
47
|
export declare function parseAttribute(decl: PropDecl, raw: string | null): PropValue;
|
|
39
48
|
/** Value assigned through a JS property → prop value, with the same fallback rules. */
|
|
40
49
|
export declare function coerceProperty(decl: PropDecl, value: unknown): PropValue;
|
|
41
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Prop value → attribute text for reflection and SSR; null means "remove the attribute". The empty
|
|
52
|
+
* string is removed only when that parses back to it, i.e. when the default is empty too.
|
|
53
|
+
*/
|
|
42
54
|
export declare function serializeAttribute(decl: PropDecl, value: PropValue): string | null;
|
|
43
55
|
export declare function parseProps<S extends PropsSchema>(schema: S, read: (attribute: string) => string | null): InferProps<S>;
|
|
44
56
|
export {};
|
package/dist/core/props.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
export function attributeName(prop, decl) {
|
|
3
3
|
return decl.attribute ?? prop.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Every prop reflects to its attribute, except the live value of a form control (the def's
|
|
7
|
+
* form.valueProp, whose attribute is only the default) and props that declare `reflect: false`.
|
|
8
|
+
*/
|
|
9
|
+
export function reflects(key, decl, def) {
|
|
10
|
+
return decl.reflect ?? key !== def.form?.valueProp;
|
|
11
|
+
}
|
|
5
12
|
/** Attribute text (null when absent) → prop value. Invalid input silently becomes the default. */
|
|
6
13
|
export function parseAttribute(decl, raw) {
|
|
7
14
|
switch (decl.type) {
|
|
@@ -32,15 +39,18 @@ export function coerceProperty(decl, value) {
|
|
|
32
39
|
}
|
|
33
40
|
return parseAttribute(decl, value === null || value === undefined ? null : String(value));
|
|
34
41
|
}
|
|
35
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Prop value → attribute text for reflection and SSR; null means "remove the attribute". The empty
|
|
44
|
+
* string is removed only when that parses back to it, i.e. when the default is empty too.
|
|
45
|
+
*/
|
|
36
46
|
export function serializeAttribute(decl, value) {
|
|
37
47
|
switch (decl.type) {
|
|
38
48
|
case 'boolean':
|
|
39
49
|
return value ? '' : null;
|
|
40
50
|
case 'number':
|
|
41
|
-
return String(value);
|
|
51
|
+
return Number.isFinite(value) ? String(value) : null;
|
|
42
52
|
case 'string':
|
|
43
|
-
return value === '' ? null : String(value);
|
|
53
|
+
return value === '' && !decl.default ? null : String(value);
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
export function parseProps(schema, read) {
|