@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,19 @@
|
|
|
1
|
+
import { NeuElement } from '../../core/neu-element.js';
|
|
2
|
+
export declare class NeuSpinner extends NeuElement {
|
|
3
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
4
|
+
readonly label: {
|
|
5
|
+
readonly type: "string";
|
|
6
|
+
readonly default: "Loading";
|
|
7
|
+
};
|
|
8
|
+
readonly size: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly default: "md";
|
|
11
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
12
|
+
readonly reflect: true;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
label: string;
|
|
16
|
+
size: 'sm' | 'md' | 'lg';
|
|
17
|
+
constructor();
|
|
18
|
+
protected update(changed: ReadonlySet<string>): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const spinnerDef: import("../../core/define.js").ComponentDef<{
|
|
2
|
+
readonly label: {
|
|
3
|
+
readonly type: "string";
|
|
4
|
+
readonly default: "Loading";
|
|
5
|
+
};
|
|
6
|
+
readonly size: {
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
readonly default: "md";
|
|
9
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
10
|
+
readonly reflect: true;
|
|
11
|
+
};
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html } from '../../core/html.js';
|
|
3
|
+
import { themeCssProperties } from '../../core/styles.js';
|
|
4
|
+
import { spinnerStyles } from './spinner.styles.js';
|
|
5
|
+
export const spinnerDef = defineComponent({
|
|
6
|
+
tag: 'neu-spinner',
|
|
7
|
+
props: {
|
|
8
|
+
label: { type: 'string', default: 'Loading' },
|
|
9
|
+
size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
|
|
10
|
+
},
|
|
11
|
+
styles: spinnerStyles,
|
|
12
|
+
// The label is also visually hidden text: a live region announces content, not its name.
|
|
13
|
+
template: (p) => html `<span part="base" class="base"><span part="track" class="track"></span><span part="indicator" class="indicator"></span><span class="label" data-ref="label">${p.label}</span></span>`,
|
|
14
|
+
docs: {
|
|
15
|
+
summary: 'A rotating indicator for work in progress.',
|
|
16
|
+
events: [],
|
|
17
|
+
slots: [],
|
|
18
|
+
parts: [
|
|
19
|
+
{ name: 'base', description: 'The sized wrapper.' },
|
|
20
|
+
{ name: 'track', description: 'The inset ring behind the indicator.' },
|
|
21
|
+
{ name: 'indicator', description: 'The rotating arc.' },
|
|
22
|
+
],
|
|
23
|
+
cssProperties: themeCssProperties,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { setText } from '../../core/dom.js';
|
|
2
|
+
import { NeuElement } from '../../core/neu-element.js';
|
|
3
|
+
import { spinnerDef } from './spinner.def.js';
|
|
4
|
+
export class NeuSpinner extends NeuElement {
|
|
5
|
+
static def = spinnerDef;
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.internals.role = 'status';
|
|
9
|
+
}
|
|
10
|
+
update(changed) {
|
|
11
|
+
if (!changed.has('label'))
|
|
12
|
+
return;
|
|
13
|
+
const label = String(this.props.label);
|
|
14
|
+
this.internals.ariaLabel = label || null;
|
|
15
|
+
setText(this.ref('label'), label);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const spinnerStyles = "\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: inline-block; vertical-align: middle; inline-size: 1.5rem; block-size: 1.5rem; }\n:host([size='sm']) { inline-size: 1rem; block-size: 1rem; }\n:host([size='lg']) { inline-size: 2.25rem; block-size: 2.25rem; }\n.base { position: relative; display: block; inline-size: 100%; block-size: 100%; }\n.track, .indicator { position: absolute; inset: 0; border-radius: 50%; }\n.label { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n.track { box-shadow: var(--_neu-inset-sm); }\n.indicator { border: 3px solid transparent; border-block-start-color: var(--_neu-accent-c); animation: neu-spinner-rotate 0.9s linear infinite; }\n@keyframes neu-spinner-rotate { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) { .indicator { animation-duration: 2.7s; } }\n";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { hostTheme } from '../../core/styles.js';
|
|
2
|
+
export const spinnerStyles = `${hostTheme}
|
|
3
|
+
:host { display: inline-block; vertical-align: middle; inline-size: 1.5rem; block-size: 1.5rem; }
|
|
4
|
+
:host([size='sm']) { inline-size: 1rem; block-size: 1rem; }
|
|
5
|
+
:host([size='lg']) { inline-size: 2.25rem; block-size: 2.25rem; }
|
|
6
|
+
.base { position: relative; display: block; inline-size: 100%; block-size: 100%; }
|
|
7
|
+
.track, .indicator { position: absolute; inset: 0; border-radius: 50%; }
|
|
8
|
+
.label { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
9
|
+
.track { box-shadow: var(--_neu-inset-sm); }
|
|
10
|
+
.indicator { border: 3px solid transparent; border-block-start-color: var(--_neu-accent-c); animation: neu-spinner-rotate 0.9s linear infinite; }
|
|
11
|
+
@keyframes neu-spinner-rotate { to { transform: rotate(360deg); } }
|
|
12
|
+
@media (prefers-reduced-motion: reduce) { .indicator { animation-duration: 2.7s; } }
|
|
13
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NeuElement } from '../../core/neu-element.js';
|
|
2
|
+
export declare class NeuSurface extends NeuElement {
|
|
3
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
4
|
+
readonly shape: {
|
|
5
|
+
readonly type: "string";
|
|
6
|
+
readonly default: "raised";
|
|
7
|
+
readonly values: readonly ["raised", "inset", "flat", "convex", "concave"];
|
|
8
|
+
readonly reflect: true;
|
|
9
|
+
};
|
|
10
|
+
readonly elevation: {
|
|
11
|
+
readonly type: "string";
|
|
12
|
+
readonly default: "md";
|
|
13
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
14
|
+
readonly reflect: true;
|
|
15
|
+
};
|
|
16
|
+
readonly radius: {
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
readonly default: "md";
|
|
19
|
+
readonly values: readonly ["sm", "md", "lg", "full"];
|
|
20
|
+
readonly reflect: true;
|
|
21
|
+
};
|
|
22
|
+
readonly padding: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
readonly default: "md";
|
|
25
|
+
readonly values: readonly ["none", "sm", "md", "lg"];
|
|
26
|
+
readonly reflect: true;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
shape: 'raised' | 'inset' | 'flat' | 'convex' | 'concave';
|
|
30
|
+
elevation: 'sm' | 'md' | 'lg';
|
|
31
|
+
radius: 'sm' | 'md' | 'lg' | 'full';
|
|
32
|
+
padding: 'none' | 'sm' | 'md' | 'lg';
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const surfaceDef: import("../../core/define.js").ComponentDef<{
|
|
2
|
+
readonly shape: {
|
|
3
|
+
readonly type: "string";
|
|
4
|
+
readonly default: "raised";
|
|
5
|
+
readonly values: readonly ["raised", "inset", "flat", "convex", "concave"];
|
|
6
|
+
readonly reflect: true;
|
|
7
|
+
};
|
|
8
|
+
readonly elevation: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly default: "md";
|
|
11
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
12
|
+
readonly reflect: true;
|
|
13
|
+
};
|
|
14
|
+
readonly radius: {
|
|
15
|
+
readonly type: "string";
|
|
16
|
+
readonly default: "md";
|
|
17
|
+
readonly values: readonly ["sm", "md", "lg", "full"];
|
|
18
|
+
readonly reflect: true;
|
|
19
|
+
};
|
|
20
|
+
readonly padding: {
|
|
21
|
+
readonly type: "string";
|
|
22
|
+
readonly default: "md";
|
|
23
|
+
readonly values: readonly ["none", "sm", "md", "lg"];
|
|
24
|
+
readonly reflect: true;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html } from '../../core/html.js';
|
|
3
|
+
import { themeCssProperties } from '../../core/styles.js';
|
|
4
|
+
import { surfaceStyles } from './surface.styles.js';
|
|
5
|
+
export const surfaceDef = defineComponent({
|
|
6
|
+
tag: 'neu-surface',
|
|
7
|
+
props: {
|
|
8
|
+
shape: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
default: 'raised',
|
|
11
|
+
values: ['raised', 'inset', 'flat', 'convex', 'concave'],
|
|
12
|
+
reflect: true,
|
|
13
|
+
},
|
|
14
|
+
elevation: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
|
|
15
|
+
radius: { type: 'string', default: 'md', values: ['sm', 'md', 'lg', 'full'], reflect: true },
|
|
16
|
+
padding: { type: 'string', default: 'md', values: ['none', 'sm', 'md', 'lg'], reflect: true },
|
|
17
|
+
},
|
|
18
|
+
styles: surfaceStyles,
|
|
19
|
+
template: () => html `<div part="base" class="base"><slot></slot></div>`,
|
|
20
|
+
docs: {
|
|
21
|
+
summary: 'A neumorphic container with its own shape, elevation, radius and padding.',
|
|
22
|
+
events: [],
|
|
23
|
+
slots: [{ name: '', description: 'The content of the surface.' }],
|
|
24
|
+
parts: [{ name: 'base', description: 'The surface itself.' }],
|
|
25
|
+
cssProperties: themeCssProperties,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const surfaceStyles = "\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; --_neu-raised-lg: calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 4) var(--_neu-sd), calc(var(--_neu-d) * -2) calc(var(--_neu-d) * -2) calc(var(--_neu-d) * 4) var(--_neu-sl); --_neu-inset-lg: inset calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 4) var(--_neu-sd), inset calc(var(--_neu-d) * -2) calc(var(--_neu-d) * -2) calc(var(--_neu-d) * 4) var(--_neu-sl); --_neu-up: var(--_neu-raised); --_neu-down: var(--_neu-inset); --_neu-hi: color-mix(in oklab, var(--_neu-bg), white 8%); --_neu-lo: color-mix(in oklab, var(--_neu-bg), black 6%); }\n:host([elevation='sm']) { --_neu-up: var(--_neu-raised-sm); --_neu-down: var(--_neu-inset-sm); }\n:host([elevation='lg']) { --_neu-up: var(--_neu-raised-lg); --_neu-down: var(--_neu-inset-lg); }\n.base { background: var(--_neu-bg); color: var(--_neu-fg); box-shadow: var(--_neu-up); border-radius: var(--_neu-r); padding: 1.25rem; }\n:host([shape='inset']) .base { box-shadow: var(--_neu-down); }\n:host([shape='flat']) .base { box-shadow: none; }\n:host([shape='convex']) .base { background: linear-gradient(145deg, var(--_neu-hi), var(--_neu-lo)); }\n:host([shape='concave']) .base { background: linear-gradient(145deg, var(--_neu-lo), var(--_neu-hi)); }\n:host([radius='sm']) .base { border-radius: calc(var(--_neu-r) * 0.5); }\n:host([radius='lg']) .base { border-radius: calc(var(--_neu-r) * 2); }\n:host([radius='full']) .base { border-radius: 999px; }\n:host([padding='none']) .base { padding: 0; }\n:host([padding='sm']) .base { padding: 0.75rem; }\n:host([padding='lg']) .base { padding: 2rem; }\n";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { hostTheme } from '../../core/styles.js';
|
|
2
|
+
export const surfaceStyles = `${hostTheme}
|
|
3
|
+
:host { display: block; --_neu-raised-lg: calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 4) var(--_neu-sd), calc(var(--_neu-d) * -2) calc(var(--_neu-d) * -2) calc(var(--_neu-d) * 4) var(--_neu-sl); --_neu-inset-lg: inset calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 2) calc(var(--_neu-d) * 4) var(--_neu-sd), inset calc(var(--_neu-d) * -2) calc(var(--_neu-d) * -2) calc(var(--_neu-d) * 4) var(--_neu-sl); --_neu-up: var(--_neu-raised); --_neu-down: var(--_neu-inset); --_neu-hi: color-mix(in oklab, var(--_neu-bg), white 8%); --_neu-lo: color-mix(in oklab, var(--_neu-bg), black 6%); }
|
|
4
|
+
:host([elevation='sm']) { --_neu-up: var(--_neu-raised-sm); --_neu-down: var(--_neu-inset-sm); }
|
|
5
|
+
:host([elevation='lg']) { --_neu-up: var(--_neu-raised-lg); --_neu-down: var(--_neu-inset-lg); }
|
|
6
|
+
.base { background: var(--_neu-bg); color: var(--_neu-fg); box-shadow: var(--_neu-up); border-radius: var(--_neu-r); padding: 1.25rem; }
|
|
7
|
+
:host([shape='inset']) .base { box-shadow: var(--_neu-down); }
|
|
8
|
+
:host([shape='flat']) .base { box-shadow: none; }
|
|
9
|
+
:host([shape='convex']) .base { background: linear-gradient(145deg, var(--_neu-hi), var(--_neu-lo)); }
|
|
10
|
+
:host([shape='concave']) .base { background: linear-gradient(145deg, var(--_neu-lo), var(--_neu-hi)); }
|
|
11
|
+
:host([radius='sm']) .base { border-radius: calc(var(--_neu-r) * 0.5); }
|
|
12
|
+
:host([radius='lg']) .base { border-radius: calc(var(--_neu-r) * 2); }
|
|
13
|
+
:host([radius='full']) .base { border-radius: 999px; }
|
|
14
|
+
:host([padding='none']) .base { padding: 0; }
|
|
15
|
+
:host([padding='sm']) .base { padding: 0.75rem; }
|
|
16
|
+
:host([padding='lg']) .base { padding: 2rem; }
|
|
17
|
+
`;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { FormControlElement } from '../../core/form-control.js';
|
|
2
|
+
export declare class NeuSwitch extends FormControlElement {
|
|
3
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
4
|
+
readonly checked: {
|
|
5
|
+
readonly type: "boolean";
|
|
6
|
+
};
|
|
7
|
+
readonly name: {
|
|
8
|
+
readonly type: "string";
|
|
9
|
+
readonly reflect: true;
|
|
10
|
+
};
|
|
11
|
+
readonly value: {
|
|
12
|
+
readonly type: "string";
|
|
13
|
+
readonly default: "on";
|
|
14
|
+
readonly reflect: true;
|
|
15
|
+
};
|
|
16
|
+
readonly disabled: {
|
|
17
|
+
readonly type: "boolean";
|
|
18
|
+
readonly reflect: true;
|
|
19
|
+
};
|
|
20
|
+
readonly required: {
|
|
21
|
+
readonly type: "boolean";
|
|
22
|
+
readonly reflect: true;
|
|
23
|
+
};
|
|
24
|
+
readonly size: {
|
|
25
|
+
readonly type: "string";
|
|
26
|
+
readonly default: "md";
|
|
27
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
28
|
+
readonly reflect: true;
|
|
29
|
+
};
|
|
30
|
+
}>;
|
|
31
|
+
static extraProperties: string[];
|
|
32
|
+
name: string;
|
|
33
|
+
value: string;
|
|
34
|
+
disabled: boolean;
|
|
35
|
+
required: boolean;
|
|
36
|
+
size: 'sm' | 'md' | 'lg';
|
|
37
|
+
private current;
|
|
38
|
+
private dirty;
|
|
39
|
+
constructor();
|
|
40
|
+
get checked(): boolean;
|
|
41
|
+
set checked(value: boolean);
|
|
42
|
+
get defaultChecked(): boolean;
|
|
43
|
+
set defaultChecked(value: boolean);
|
|
44
|
+
/**
|
|
45
|
+
* Native checkbox order: flip now, let click listeners see it, then input + change once the
|
|
46
|
+
* click has finished dispatching (within it, unless propagation was stopped before window).
|
|
47
|
+
*/
|
|
48
|
+
private onClick;
|
|
49
|
+
private onKeyDown;
|
|
50
|
+
/** Like a native checkbox, Space toggles on keyup only after its own keydown. */
|
|
51
|
+
private onKeyUp;
|
|
52
|
+
private setCurrent;
|
|
53
|
+
protected willUpdate(changed: ReadonlySet<string>): void;
|
|
54
|
+
protected update(changed: ReadonlySet<string>): void;
|
|
55
|
+
protected formValue(): string | null;
|
|
56
|
+
protected formState(): string;
|
|
57
|
+
protected validate(): void;
|
|
58
|
+
protected resetToDefault(): void;
|
|
59
|
+
protected restoreState(state: string): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const switchDef: import("../../core/define.js").ComponentDef<{
|
|
2
|
+
readonly checked: {
|
|
3
|
+
readonly type: "boolean";
|
|
4
|
+
};
|
|
5
|
+
readonly name: {
|
|
6
|
+
readonly type: "string";
|
|
7
|
+
readonly reflect: true;
|
|
8
|
+
};
|
|
9
|
+
readonly value: {
|
|
10
|
+
readonly type: "string";
|
|
11
|
+
readonly default: "on";
|
|
12
|
+
readonly reflect: true;
|
|
13
|
+
};
|
|
14
|
+
readonly disabled: {
|
|
15
|
+
readonly type: "boolean";
|
|
16
|
+
readonly reflect: true;
|
|
17
|
+
};
|
|
18
|
+
readonly required: {
|
|
19
|
+
readonly type: "boolean";
|
|
20
|
+
readonly reflect: true;
|
|
21
|
+
};
|
|
22
|
+
readonly size: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
readonly default: "md";
|
|
25
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
26
|
+
readonly reflect: true;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html } from '../../core/html.js';
|
|
3
|
+
import { themeCssProperties } from '../../core/styles.js';
|
|
4
|
+
import { switchStyles } from './switch.styles.js';
|
|
5
|
+
export const switchDef = defineComponent({
|
|
6
|
+
tag: 'neu-switch',
|
|
7
|
+
props: {
|
|
8
|
+
checked: { type: 'boolean' },
|
|
9
|
+
name: { type: 'string', reflect: true },
|
|
10
|
+
value: { type: 'string', default: 'on', reflect: true },
|
|
11
|
+
disabled: { type: 'boolean', reflect: true },
|
|
12
|
+
required: { type: 'boolean', reflect: true },
|
|
13
|
+
size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
|
|
14
|
+
},
|
|
15
|
+
form: { valueProp: 'checked' },
|
|
16
|
+
styles: switchStyles,
|
|
17
|
+
template: (p) => html `<span part="base" class="base"><span part="track" class="track${p.checked ? ' on' : ''}" data-ref="track"><span part="thumb" class="thumb"></span></span><span part="label" class="label"><slot></slot></span></span>`,
|
|
18
|
+
docs: {
|
|
19
|
+
summary: 'An on/off switch. The checked attribute is the default state.',
|
|
20
|
+
events: [
|
|
21
|
+
{ name: 'input', description: 'Fired when the user toggles the switch.' },
|
|
22
|
+
{ name: 'change', description: 'Fired after input when the user toggles the switch.' },
|
|
23
|
+
],
|
|
24
|
+
slots: [{ name: '', description: 'Label; also the accessible name.' }],
|
|
25
|
+
parts: [
|
|
26
|
+
{ name: 'base', description: 'Wrapper around track and label.' },
|
|
27
|
+
{ name: 'track', description: 'The inset track.' },
|
|
28
|
+
{ name: 'thumb', description: 'The raised thumb.' },
|
|
29
|
+
{ name: 'label', description: 'Wrapper around the default slot.' },
|
|
30
|
+
],
|
|
31
|
+
cssProperties: themeCssProperties,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { afterDispatch, dispatch, isActiveInRoot } from '../../core/dom.js';
|
|
2
|
+
import { FormControlElement } from '../../core/form-control.js';
|
|
3
|
+
import { requiredCheckboxMessage } from '../../core/validation.js';
|
|
4
|
+
import { switchDef } from './switch.def.js';
|
|
5
|
+
export class NeuSwitch extends FormControlElement {
|
|
6
|
+
static def = switchDef;
|
|
7
|
+
static extraProperties = ['defaultChecked'];
|
|
8
|
+
current;
|
|
9
|
+
dirty = false;
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.current = this.props.checked === true;
|
|
13
|
+
this.internals.role = 'switch';
|
|
14
|
+
this.addEventListener('click', (event) => this.onClick(event));
|
|
15
|
+
this.addEventListener('keydown', (event) => this.onKeyDown(event));
|
|
16
|
+
this.addEventListener('keyup', (event) => this.onKeyUp(event));
|
|
17
|
+
// Disarm when focus moves away, not when only the window loses focus.
|
|
18
|
+
this.addEventListener('blur', () => {
|
|
19
|
+
if (!isActiveInRoot(this))
|
|
20
|
+
this.toggleState('pressed', false);
|
|
21
|
+
});
|
|
22
|
+
// Focus coming back means Space may have been released while it was away.
|
|
23
|
+
this.addEventListener('focus', () => this.toggleState('pressed', false));
|
|
24
|
+
}
|
|
25
|
+
get checked() {
|
|
26
|
+
return this.current;
|
|
27
|
+
}
|
|
28
|
+
set checked(value) {
|
|
29
|
+
this.dirty = true;
|
|
30
|
+
this.setCurrent(Boolean(value));
|
|
31
|
+
}
|
|
32
|
+
get defaultChecked() {
|
|
33
|
+
return this.props.checked === true;
|
|
34
|
+
}
|
|
35
|
+
set defaultChecked(value) {
|
|
36
|
+
this.toggleAttribute('checked', Boolean(value));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Native checkbox order: flip now, let click listeners see it, then input + change once the
|
|
40
|
+
* click has finished dispatching (within it, unless propagation was stopped before window).
|
|
41
|
+
*/
|
|
42
|
+
onClick(event) {
|
|
43
|
+
const before = this.current;
|
|
44
|
+
this.dirty = true;
|
|
45
|
+
this.setCurrent(!before);
|
|
46
|
+
afterDispatch(event, () => {
|
|
47
|
+
if (event.defaultPrevented) {
|
|
48
|
+
this.setCurrent(before);
|
|
49
|
+
}
|
|
50
|
+
else if (this.isConnected && this.current === !before) {
|
|
51
|
+
this.markInteracted();
|
|
52
|
+
dispatch(this, 'input');
|
|
53
|
+
dispatch(this, 'change');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
onKeyDown(event) {
|
|
58
|
+
if (event.key === ' ') {
|
|
59
|
+
if (event.defaultPrevented)
|
|
60
|
+
return;
|
|
61
|
+
// Cancelled at once: deferred, an ancestor that stops propagation would let the page scroll.
|
|
62
|
+
// An ancestor claims Space in the capture phase, or by cancelling the keyup.
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
this.toggleState('pressed', true);
|
|
65
|
+
}
|
|
66
|
+
else if (event.key === 'Enter' && !event.repeat) {
|
|
67
|
+
// Not cancelled here, so a handler further up can still cancel the toggle.
|
|
68
|
+
afterDispatch(event, () => {
|
|
69
|
+
if (!event.defaultPrevented)
|
|
70
|
+
this.click();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Like a native checkbox, Space toggles on keyup only after its own keydown. */
|
|
75
|
+
onKeyUp(event) {
|
|
76
|
+
if (event.key !== ' ' || !this.internals.states.has('pressed'))
|
|
77
|
+
return;
|
|
78
|
+
this.toggleState('pressed', false);
|
|
79
|
+
afterDispatch(event, () => {
|
|
80
|
+
if (!event.defaultPrevented)
|
|
81
|
+
this.click();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
setCurrent(value) {
|
|
85
|
+
this.current = value;
|
|
86
|
+
this.toggleState('checked', value);
|
|
87
|
+
this.internals.ariaChecked = value ? 'true' : 'false';
|
|
88
|
+
this.ref('track')?.classList.toggle('on', value);
|
|
89
|
+
this.commit();
|
|
90
|
+
}
|
|
91
|
+
willUpdate(changed) {
|
|
92
|
+
if (changed.has('checked') && !this.dirty)
|
|
93
|
+
this.current = this.props.checked === true;
|
|
94
|
+
}
|
|
95
|
+
update(changed) {
|
|
96
|
+
if (changed.has('disabled'))
|
|
97
|
+
this.syncDisabledState();
|
|
98
|
+
if (changed.has('checked'))
|
|
99
|
+
this.setCurrent(this.current);
|
|
100
|
+
else if (changed.has('required') || changed.has('value'))
|
|
101
|
+
this.commit();
|
|
102
|
+
}
|
|
103
|
+
formValue() {
|
|
104
|
+
return this.current ? String(this.props.value) : null;
|
|
105
|
+
}
|
|
106
|
+
formState() {
|
|
107
|
+
return this.current ? 'checked' : 'unchecked';
|
|
108
|
+
}
|
|
109
|
+
validate() {
|
|
110
|
+
if (this.props.required === true && !this.current) {
|
|
111
|
+
this.internals.setValidity({ valueMissing: true }, requiredCheckboxMessage(), this.ref('track') ?? undefined);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.internals.setValidity({});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
resetToDefault() {
|
|
118
|
+
this.dirty = false;
|
|
119
|
+
this.setCurrent(this.props.checked === true);
|
|
120
|
+
}
|
|
121
|
+
restoreState(state) {
|
|
122
|
+
this.dirty = true;
|
|
123
|
+
this.setCurrent(state === 'checked');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const switchStyles = "\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 {\n display: inline-flex; vertical-align: middle; cursor: pointer; user-select: none;\n --_neu-track-w: 2.875rem; --_neu-track-h: 1.625rem; --_neu-thumb: 1.25rem;\n}\n:host([size='sm']) { --_neu-track-w: 2.25rem; --_neu-track-h: 1.25rem; --_neu-thumb: 0.9375rem; }\n:host([size='lg']) { --_neu-track-w: 3.5rem; --_neu-track-h: 2rem; --_neu-thumb: 1.5rem; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: inline-flex; align-items: center; gap: 0.625rem; }\n.track {\n position: relative; flex: none; inline-size: var(--_neu-track-w); block-size: var(--_neu-track-h);\n border-radius: 999px; background: var(--_neu-bg); box-shadow: var(--_neu-inset-sm);\n transition: background-color var(--_neu-dur) ease, box-shadow var(--_neu-dur) ease;\n}\n.track.on { background: var(--_neu-accent-soft); }\n.thumb {\n position: absolute; inset-block-start: 50%;\n inset-inline-start: calc((var(--_neu-track-h) - var(--_neu-thumb)) / 2);\n inline-size: var(--_neu-thumb); block-size: var(--_neu-thumb); border-radius: 50%;\n background: var(--_neu-bg); box-shadow: var(--_neu-raised-sm); transform: translateY(-50%);\n transition: inset-inline-start var(--_neu-dur) ease;\n}\n.track.on .thumb { inset-inline-start: calc(var(--_neu-track-w) - var(--_neu-thumb) - (var(--_neu-track-h) - var(--_neu-thumb)) / 2); }\n:host(:focus-visible) .track { box-shadow: var(--_neu-inset-sm), var(--_neu-focus); }\n:host(:disabled) .base, :host([disabled]) .base { opacity: 0.55; }\n:host(:state(user-invalid)) .track { box-shadow: var(--_neu-inset-sm), 0 0 0 2px color-mix(in oklab, var(--_neu-danger) 55%, transparent); }\n.label { color: var(--_neu-fg); }\n";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { hostTheme } from '../../core/styles.js';
|
|
2
|
+
export const switchStyles = `${hostTheme}
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-flex; vertical-align: middle; cursor: pointer; user-select: none;
|
|
5
|
+
--_neu-track-w: 2.875rem; --_neu-track-h: 1.625rem; --_neu-thumb: 1.25rem;
|
|
6
|
+
}
|
|
7
|
+
:host([size='sm']) { --_neu-track-w: 2.25rem; --_neu-track-h: 1.25rem; --_neu-thumb: 0.9375rem; }
|
|
8
|
+
:host([size='lg']) { --_neu-track-w: 3.5rem; --_neu-track-h: 2rem; --_neu-thumb: 1.5rem; }
|
|
9
|
+
:host(:disabled), :host([disabled]) { cursor: not-allowed; }
|
|
10
|
+
.base { display: inline-flex; align-items: center; gap: 0.625rem; }
|
|
11
|
+
.track {
|
|
12
|
+
position: relative; flex: none; inline-size: var(--_neu-track-w); block-size: var(--_neu-track-h);
|
|
13
|
+
border-radius: 999px; background: var(--_neu-bg); box-shadow: var(--_neu-inset-sm);
|
|
14
|
+
transition: background-color var(--_neu-dur) ease, box-shadow var(--_neu-dur) ease;
|
|
15
|
+
}
|
|
16
|
+
.track.on { background: var(--_neu-accent-soft); }
|
|
17
|
+
.thumb {
|
|
18
|
+
position: absolute; inset-block-start: 50%;
|
|
19
|
+
inset-inline-start: calc((var(--_neu-track-h) - var(--_neu-thumb)) / 2);
|
|
20
|
+
inline-size: var(--_neu-thumb); block-size: var(--_neu-thumb); border-radius: 50%;
|
|
21
|
+
background: var(--_neu-bg); box-shadow: var(--_neu-raised-sm); transform: translateY(-50%);
|
|
22
|
+
transition: inset-inline-start var(--_neu-dur) ease;
|
|
23
|
+
}
|
|
24
|
+
.track.on .thumb { inset-inline-start: calc(var(--_neu-track-w) - var(--_neu-thumb) - (var(--_neu-track-h) - var(--_neu-thumb)) / 2); }
|
|
25
|
+
:host(:focus-visible) .track { box-shadow: var(--_neu-inset-sm), var(--_neu-focus); }
|
|
26
|
+
:host(:disabled) .base, :host([disabled]) .base { opacity: 0.55; }
|
|
27
|
+
:host(:state(user-invalid)) .track { box-shadow: var(--_neu-inset-sm), 0 0 0 2px color-mix(in oklab, var(--_neu-danger) 55%, transparent); }
|
|
28
|
+
.label { color: var(--_neu-fg); }
|
|
29
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enter in a text control submits like a native input (HTML §4.10.21.2). With a default button
|
|
3
|
+
* (the form's first submit button, form="id" ones included) it clicks that button unless it is
|
|
4
|
+
* disabled, so the button's own guards and click listeners apply. Without one, it submits only
|
|
5
|
+
* when a single field blocks implicit submission. Either way it is a submit attempt in its own
|
|
6
|
+
* right, so invalid controls reveal user-invalid.
|
|
7
|
+
*/
|
|
8
|
+
export declare function implicitSubmit(form: HTMLFormElement | null): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isSubmitButton, markSubmitAttempt } from '../../core/form-control.js';
|
|
2
|
+
/** Input types that block implicit submission when the form has no submit button (HTML §4.10.21.2). */
|
|
3
|
+
const BLOCKING_TYPES = new Set([
|
|
4
|
+
'text',
|
|
5
|
+
'search',
|
|
6
|
+
'url',
|
|
7
|
+
'tel',
|
|
8
|
+
'email',
|
|
9
|
+
'password',
|
|
10
|
+
'date',
|
|
11
|
+
'month',
|
|
12
|
+
'week',
|
|
13
|
+
'time',
|
|
14
|
+
'datetime-local',
|
|
15
|
+
'number',
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Enter in a text control submits like a native input (HTML §4.10.21.2). With a default button
|
|
19
|
+
* (the form's first submit button, form="id" ones included) it clicks that button unless it is
|
|
20
|
+
* disabled, so the button's own guards and click listeners apply. Without one, it submits only
|
|
21
|
+
* when a single field blocks implicit submission. Either way it is a submit attempt in its own
|
|
22
|
+
* right, so invalid controls reveal user-invalid.
|
|
23
|
+
*/
|
|
24
|
+
export function implicitSubmit(form) {
|
|
25
|
+
if (!form)
|
|
26
|
+
return;
|
|
27
|
+
const elements = [...form.elements];
|
|
28
|
+
const defaultButton = elements.find(isSubmitButton);
|
|
29
|
+
if (defaultButton) {
|
|
30
|
+
if (defaultButton.matches(':disabled') || defaultButton.hasAttribute('disabled'))
|
|
31
|
+
return;
|
|
32
|
+
markSubmitAttempt(form);
|
|
33
|
+
defaultButton.click();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const blocking = elements.filter((el) => (el instanceof HTMLInputElement && BLOCKING_TYPES.has(el.type)) ||
|
|
37
|
+
el.localName === 'neu-text-field');
|
|
38
|
+
if (blocking.length !== 1)
|
|
39
|
+
return;
|
|
40
|
+
markSubmitAttempt(form);
|
|
41
|
+
form.requestSubmit();
|
|
42
|
+
}
|