@neumorphism-ui/elements 1.0.0-next.1 → 1.0.0-next.2
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 +184 -51
- package/custom-elements.json +2232 -282
- 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/button/button.d.ts +0 -2
- package/dist/components/button/button.js +3 -36
- package/dist/components/checkbox/checkbox.d.ts +44 -0
- package/dist/components/checkbox/checkbox.def.d.ts +31 -0
- package/dist/components/checkbox/checkbox.def.js +37 -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/icon/system-icons.d.ts +10 -0
- package/dist/components/icon/system-icons.js +22 -11
- package/dist/components/number-field/number-field.d.ts +84 -0
- package/dist/components/number-field/number-field.def.d.ts +125 -0
- package/dist/components/number-field/number-field.def.js +62 -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 +19 -0
- package/dist/components/option/option.def.d.ts +10 -0
- package/dist/components/option/option.def.js +19 -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 +29 -0
- package/dist/components/radio/radio.def.d.ts +11 -0
- package/dist/components/radio/radio.def.js +25 -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 +77 -0
- package/dist/components/radio-group/radio-group.def.d.ts +40 -0
- package/dist/components/radio-group/radio-group.def.js +55 -0
- package/dist/components/radio-group/radio-group.js +184 -0
- package/dist/components/radio-group/radio-group.styles.d.ts +1 -0
- package/dist/components/radio-group/radio-group.styles.js +15 -0
- package/dist/components/select/select.d.ts +119 -0
- package/dist/components/select/select.def.d.ts +38 -0
- package/dist/components/select/select.def.js +59 -0
- package/dist/components/select/select.js +385 -0
- package/dist/components/select/select.styles.d.ts +1 -0
- package/dist/components/select/select.styles.js +39 -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 +36 -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 +69 -0
- package/dist/components/slider/slider.def.d.ts +39 -0
- package/dist/components/slider/slider.def.js +76 -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/switch/switch.d.ts +4 -30
- 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 -36
- 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 +98 -0
- package/dist/components/textarea/textarea.def.d.ts +139 -0
- package/dist/components/textarea/textarea.def.js +65 -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/core/define.d.ts +16 -1
- package/dist/core/form-control.js +3 -1
- package/dist/core/validation.d.ts +6 -2
- package/dist/core/validation.js +15 -8
- 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 +9 -5
- package/dist/ssr.js +25 -5
- package/dist/textarea.d.ts +3 -0
- package/dist/textarea.js +4 -0
- package/package.json +41 -1
package/dist/all.d.ts
CHANGED
|
@@ -2,11 +2,19 @@ export * from './avatar.js';
|
|
|
2
2
|
export * from './badge.js';
|
|
3
3
|
export * from './button.js';
|
|
4
4
|
export * from './button-group.js';
|
|
5
|
+
export * from './checkbox.js';
|
|
5
6
|
export * from './divider.js';
|
|
6
7
|
export * from './icon.js';
|
|
7
8
|
export * from './icon-button.js';
|
|
8
9
|
export * from './icons.js';
|
|
10
|
+
export * from './number-field.js';
|
|
11
|
+
export * from './option.js';
|
|
12
|
+
export * from './radio.js';
|
|
13
|
+
export * from './radio-group.js';
|
|
14
|
+
export * from './select.js';
|
|
15
|
+
export * from './slider.js';
|
|
9
16
|
export * from './spinner.js';
|
|
10
17
|
export * from './surface.js';
|
|
11
18
|
export * from './switch.js';
|
|
12
19
|
export * from './text-field.js';
|
|
20
|
+
export * from './textarea.js';
|
package/dist/all.js
CHANGED
|
@@ -2,11 +2,19 @@ export * from './avatar.js';
|
|
|
2
2
|
export * from './badge.js';
|
|
3
3
|
export * from './button.js';
|
|
4
4
|
export * from './button-group.js';
|
|
5
|
+
export * from './checkbox.js';
|
|
5
6
|
export * from './divider.js';
|
|
6
7
|
export * from './icon.js';
|
|
7
8
|
export * from './icon-button.js';
|
|
8
9
|
export * from './icons.js';
|
|
10
|
+
export * from './number-field.js';
|
|
11
|
+
export * from './option.js';
|
|
12
|
+
export * from './radio.js';
|
|
13
|
+
export * from './radio-group.js';
|
|
14
|
+
export * from './select.js';
|
|
15
|
+
export * from './slider.js';
|
|
9
16
|
export * from './spinner.js';
|
|
10
17
|
export * from './surface.js';
|
|
11
18
|
export * from './switch.js';
|
|
12
19
|
export * from './text-field.js';
|
|
20
|
+
export * from './textarea.js';
|
package/dist/checkbox.js
ADDED
|
@@ -19,7 +19,5 @@ export declare class NeuButton extends NeuElement {
|
|
|
19
19
|
private onClickCapture;
|
|
20
20
|
/** Like a native button: submit or reset once the click has dispatched, unless it was cancelled. */
|
|
21
21
|
private onClick;
|
|
22
|
-
private onKeyDown;
|
|
23
|
-
private onKeyUp;
|
|
24
22
|
protected update(changed: ReadonlySet<string>): void;
|
|
25
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { afterDispatch
|
|
1
|
+
import { afterDispatch } from '../../core/dom.js';
|
|
2
2
|
import { markSubmitAttempt, SUBMITS } from '../../core/form-control.js';
|
|
3
3
|
import { NeuElement } from '../../core/neu-element.js';
|
|
4
|
+
import { pressable } from '../shared/press.js';
|
|
4
5
|
import { buttonDef } from './button.def.js';
|
|
5
6
|
export class NeuButton extends NeuElement {
|
|
6
7
|
static def = buttonDef;
|
|
@@ -13,15 +14,7 @@ export class NeuButton extends NeuElement {
|
|
|
13
14
|
this.internals.role = 'button';
|
|
14
15
|
this.addEventListener('click', (event) => this.onClickCapture(event), { capture: true });
|
|
15
16
|
this.addEventListener('click', (event) => this.onClick(event));
|
|
16
|
-
this.
|
|
17
|
-
this.addEventListener('keyup', (event) => this.onKeyUp(event));
|
|
18
|
-
// Disarm when focus moves away, not when only the window loses focus.
|
|
19
|
-
this.addEventListener('blur', () => {
|
|
20
|
-
if (!isActiveInRoot(this))
|
|
21
|
-
this.toggleState('pressed', false);
|
|
22
|
-
});
|
|
23
|
-
// Focus coming back means Space may have been released while it was away.
|
|
24
|
-
this.addEventListener('focus', () => this.toggleState('pressed', false));
|
|
17
|
+
pressable(this, this.internals.states, 'repeat', () => this.inactive);
|
|
25
18
|
}
|
|
26
19
|
get form() {
|
|
27
20
|
return this.internals.form;
|
|
@@ -52,32 +45,6 @@ export class NeuButton extends NeuElement {
|
|
|
52
45
|
}
|
|
53
46
|
});
|
|
54
47
|
}
|
|
55
|
-
onKeyDown(event) {
|
|
56
|
-
if (this.inactive)
|
|
57
|
-
return;
|
|
58
|
-
if (event.key === 'Enter') {
|
|
59
|
-
// Not cancelled here, so a handler further up can still cancel the activation.
|
|
60
|
-
afterDispatch(event, () => {
|
|
61
|
-
if (!event.defaultPrevented && !this.inactive)
|
|
62
|
-
this.click();
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
else if (event.key === ' ' && !event.defaultPrevented) {
|
|
66
|
-
// Cancelled at once: deferred, an ancestor that stops propagation would let the page scroll.
|
|
67
|
-
// An ancestor claims Space in the capture phase, or by cancelling the keyup.
|
|
68
|
-
event.preventDefault();
|
|
69
|
-
this.toggleState('pressed', true);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
onKeyUp(event) {
|
|
73
|
-
if (event.key !== ' ' || !this.internals.states.has('pressed'))
|
|
74
|
-
return;
|
|
75
|
-
this.toggleState('pressed', false);
|
|
76
|
-
afterDispatch(event, () => {
|
|
77
|
-
if (!event.defaultPrevented && !this.inactive)
|
|
78
|
-
this.click();
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
48
|
update(changed) {
|
|
82
49
|
if (changed.has('disabled') || changed.has('loading')) {
|
|
83
50
|
this.syncDisabledState();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CheckableElement } from '../shared/checkable.js';
|
|
2
|
+
export declare class NeuCheckbox extends CheckableElement {
|
|
3
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
4
|
+
readonly checked: {
|
|
5
|
+
readonly type: "boolean";
|
|
6
|
+
};
|
|
7
|
+
readonly indeterminate: {
|
|
8
|
+
readonly type: "boolean";
|
|
9
|
+
};
|
|
10
|
+
readonly name: {
|
|
11
|
+
readonly type: "string";
|
|
12
|
+
readonly reflect: true;
|
|
13
|
+
};
|
|
14
|
+
readonly value: {
|
|
15
|
+
readonly type: "string";
|
|
16
|
+
readonly default: "on";
|
|
17
|
+
readonly reflect: true;
|
|
18
|
+
};
|
|
19
|
+
readonly disabled: {
|
|
20
|
+
readonly type: "boolean";
|
|
21
|
+
readonly reflect: true;
|
|
22
|
+
};
|
|
23
|
+
readonly required: {
|
|
24
|
+
readonly type: "boolean";
|
|
25
|
+
readonly reflect: true;
|
|
26
|
+
};
|
|
27
|
+
readonly size: {
|
|
28
|
+
readonly type: "string";
|
|
29
|
+
readonly default: "md";
|
|
30
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
31
|
+
readonly reflect: true;
|
|
32
|
+
};
|
|
33
|
+
}>;
|
|
34
|
+
/** Only Space toggles, like a native checkbox. */
|
|
35
|
+
static enter: "none";
|
|
36
|
+
indeterminate: boolean;
|
|
37
|
+
protected readonly anchor = "control";
|
|
38
|
+
constructor();
|
|
39
|
+
protected ariaCheckedValue(): 'true' | 'false' | 'mixed';
|
|
40
|
+
/** Native: a click on an indeterminate box toggles checked and clears indeterminate. */
|
|
41
|
+
protected flip(): () => void;
|
|
42
|
+
protected paint(): void;
|
|
43
|
+
protected update(changed: ReadonlySet<string>): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const checkboxDef: import("../../core/define.js").ComponentDef<{
|
|
2
|
+
readonly checked: {
|
|
3
|
+
readonly type: "boolean";
|
|
4
|
+
};
|
|
5
|
+
readonly indeterminate: {
|
|
6
|
+
readonly type: "boolean";
|
|
7
|
+
};
|
|
8
|
+
readonly name: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly reflect: true;
|
|
11
|
+
};
|
|
12
|
+
readonly value: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
readonly default: "on";
|
|
15
|
+
readonly reflect: true;
|
|
16
|
+
};
|
|
17
|
+
readonly disabled: {
|
|
18
|
+
readonly type: "boolean";
|
|
19
|
+
readonly reflect: true;
|
|
20
|
+
};
|
|
21
|
+
readonly required: {
|
|
22
|
+
readonly type: "boolean";
|
|
23
|
+
readonly reflect: true;
|
|
24
|
+
};
|
|
25
|
+
readonly size: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
readonly default: "md";
|
|
28
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
29
|
+
readonly reflect: true;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html, unsafeHTML } from '../../core/html.js';
|
|
3
|
+
import { themeCssProperties } from '../../core/styles.js';
|
|
4
|
+
import { checkBody, minusBody, svgWrap } from '../icon/system-icons.js';
|
|
5
|
+
import { checkboxStyles } from './checkbox.styles.js';
|
|
6
|
+
const checkSvg = unsafeHTML(svgWrap(checkBody));
|
|
7
|
+
const minusSvg = unsafeHTML(svgWrap(minusBody));
|
|
8
|
+
export const checkboxDef = defineComponent({
|
|
9
|
+
tag: 'neu-checkbox',
|
|
10
|
+
props: {
|
|
11
|
+
checked: { type: 'boolean' },
|
|
12
|
+
indeterminate: { type: 'boolean' },
|
|
13
|
+
name: { type: 'string', reflect: true },
|
|
14
|
+
value: { type: 'string', default: 'on', reflect: true },
|
|
15
|
+
disabled: { type: 'boolean', reflect: true },
|
|
16
|
+
required: { type: 'boolean', reflect: true },
|
|
17
|
+
size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
|
|
18
|
+
},
|
|
19
|
+
form: { valueProp: 'checked' },
|
|
20
|
+
styles: checkboxStyles,
|
|
21
|
+
template: (p) => html `<span part="base" class="base"><span part="control" class="control${p.checked ? ' on' : ''}${p.indeterminate ? ' mixed' : ''}" data-ref="control"><span part="indicator" class="indicator">${checkSvg}${minusSvg}</span></span><span part="label" class="label"><slot></slot></span></span>`,
|
|
22
|
+
docs: {
|
|
23
|
+
summary: 'A checkbox. The checked attribute is the default state; indeterminate is a property whose attribute only sets the initial state.',
|
|
24
|
+
events: [
|
|
25
|
+
{ name: 'input', description: 'Fired when the user toggles the checkbox.' },
|
|
26
|
+
{ name: 'change', description: 'Fired after input when the user toggles the checkbox.' },
|
|
27
|
+
],
|
|
28
|
+
slots: [{ name: '', description: 'Label; also the accessible name.' }],
|
|
29
|
+
parts: [
|
|
30
|
+
{ name: 'base', description: 'Wrapper around control and label.' },
|
|
31
|
+
{ name: 'control', description: 'The inset box.' },
|
|
32
|
+
{ name: 'indicator', description: 'The check or minus mark.' },
|
|
33
|
+
{ name: 'label', description: 'Wrapper around the default slot.' },
|
|
34
|
+
],
|
|
35
|
+
cssProperties: themeCssProperties,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CheckableElement } from '../shared/checkable.js';
|
|
2
|
+
import { checkboxDef } from './checkbox.def.js';
|
|
3
|
+
export class NeuCheckbox extends CheckableElement {
|
|
4
|
+
static def = checkboxDef;
|
|
5
|
+
/** Only Space toggles, like a native checkbox. */
|
|
6
|
+
static enter = 'none';
|
|
7
|
+
anchor = 'control';
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.internals.role = 'checkbox';
|
|
11
|
+
}
|
|
12
|
+
ariaCheckedValue() {
|
|
13
|
+
return this.props.indeterminate === true ? 'mixed' : super.ariaCheckedValue();
|
|
14
|
+
}
|
|
15
|
+
/** Native: a click on an indeterminate box toggles checked and clears indeterminate. */
|
|
16
|
+
flip() {
|
|
17
|
+
const mixed = this.props.indeterminate === true;
|
|
18
|
+
if (!mixed)
|
|
19
|
+
return super.flip();
|
|
20
|
+
const checked = this.checked;
|
|
21
|
+
this.setProp('indeterminate', false);
|
|
22
|
+
this.checked = !checked;
|
|
23
|
+
return () => {
|
|
24
|
+
this.setProp('indeterminate', true);
|
|
25
|
+
this.checked = checked;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
paint() {
|
|
29
|
+
const mixed = this.props.indeterminate === true;
|
|
30
|
+
const control = this.ref('control');
|
|
31
|
+
control?.classList.toggle('on', this.checked);
|
|
32
|
+
control?.classList.toggle('mixed', mixed);
|
|
33
|
+
this.toggleState('indeterminate', mixed);
|
|
34
|
+
}
|
|
35
|
+
update(changed) {
|
|
36
|
+
super.update(changed);
|
|
37
|
+
if (changed.has('indeterminate'))
|
|
38
|
+
this.sync();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const checkboxStyles = "\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-box: 1.25rem;\n}\n:host([size='sm']) { --_neu-box: 1rem; }\n:host([size='lg']) { --_neu-box: 1.5rem; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: inline-flex; align-items: center; gap: 0.625rem; }\n.control {\n display: inline-flex; align-items: center; justify-content: center; flex: none;\n inline-size: var(--_neu-box); block-size: var(--_neu-box);\n border-radius: calc(var(--_neu-r) * 0.4); background: var(--_neu-bg); box-shadow: var(--_neu-inset-sm);\n color: var(--_neu-accent-c);\n transition: background-color var(--_neu-dur) ease, box-shadow var(--_neu-dur) ease;\n}\n.control.on, .control.mixed { background: var(--_neu-accent-soft); }\n.indicator { display: inline-flex; inline-size: 80%; block-size: 80%; opacity: 0; }\n.control.on .indicator, .control.mixed .indicator { opacity: 1; }\n.indicator svg { inline-size: 100%; block-size: 100%; }\n.indicator svg:last-child, .control.mixed .indicator svg:first-child { display: none; }\n.control.mixed .indicator svg:last-child { display: block; }\n:host(:focus-visible) .control { box-shadow: var(--_neu-inset-sm), var(--_neu-focus); }\n:host(:disabled) .base, :host([disabled]) .base { opacity: 0.55; }\n:host(:state(user-invalid)) .control { 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,28 @@
|
|
|
1
|
+
import { hostTheme } from '../../core/styles.js';
|
|
2
|
+
export const checkboxStyles = `${hostTheme}
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-flex; vertical-align: middle; cursor: pointer; user-select: none;
|
|
5
|
+
--_neu-box: 1.25rem;
|
|
6
|
+
}
|
|
7
|
+
:host([size='sm']) { --_neu-box: 1rem; }
|
|
8
|
+
:host([size='lg']) { --_neu-box: 1.5rem; }
|
|
9
|
+
:host(:disabled), :host([disabled]) { cursor: not-allowed; }
|
|
10
|
+
.base { display: inline-flex; align-items: center; gap: 0.625rem; }
|
|
11
|
+
.control {
|
|
12
|
+
display: inline-flex; align-items: center; justify-content: center; flex: none;
|
|
13
|
+
inline-size: var(--_neu-box); block-size: var(--_neu-box);
|
|
14
|
+
border-radius: calc(var(--_neu-r) * 0.4); background: var(--_neu-bg); box-shadow: var(--_neu-inset-sm);
|
|
15
|
+
color: var(--_neu-accent-c);
|
|
16
|
+
transition: background-color var(--_neu-dur) ease, box-shadow var(--_neu-dur) ease;
|
|
17
|
+
}
|
|
18
|
+
.control.on, .control.mixed { background: var(--_neu-accent-soft); }
|
|
19
|
+
.indicator { display: inline-flex; inline-size: 80%; block-size: 80%; opacity: 0; }
|
|
20
|
+
.control.on .indicator, .control.mixed .indicator { opacity: 1; }
|
|
21
|
+
.indicator svg { inline-size: 100%; block-size: 100%; }
|
|
22
|
+
.indicator svg:last-child, .control.mixed .indicator svg:first-child { display: none; }
|
|
23
|
+
.control.mixed .indicator svg:last-child { display: block; }
|
|
24
|
+
:host(:focus-visible) .control { box-shadow: var(--_neu-inset-sm), var(--_neu-focus); }
|
|
25
|
+
:host(:disabled) .base, :host([disabled]) .base { opacity: 0.55; }
|
|
26
|
+
:host(:state(user-invalid)) .control { box-shadow: var(--_neu-inset-sm), 0 0 0 2px color-mix(in oklab, var(--_neu-danger) 55%, transparent); }
|
|
27
|
+
.label { color: var(--_neu-fg); }
|
|
28
|
+
`;
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
export declare const checkBody = "<path d=\"M5 12.5l4.5 4.5L19 7.5\"/>";
|
|
2
|
+
export declare const chevronDownBody = "<path d=\"M6 9l6 6 6-6\"/>";
|
|
3
|
+
export declare const chevronUpBody = "<path d=\"M6 15l6-6 6 6\"/>";
|
|
4
|
+
export declare const plusBody = "<path d=\"M12 5v14M5 12h14\"/>";
|
|
5
|
+
export declare const minusBody = "<path d=\"M5 12h14\"/>";
|
|
6
|
+
export declare const xBody = "<path d=\"M6 6l12 12M18 6L6 18\"/>";
|
|
7
|
+
export declare const alertCircleBody = "<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7.5v5.5M12 16.5h.01\"/>";
|
|
8
|
+
export declare const userBody = "<circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 20c0-4 3.6-6 8-6s8 2 8 6\"/>";
|
|
9
|
+
/** The SVG element around an icon body. DOM-free. */
|
|
10
|
+
export declare function svgWrap(body: string): string;
|
|
1
11
|
/** The built-in icon as an SVG string, or undefined for an unknown name. DOM-free. */
|
|
2
12
|
export declare function systemIconSvg(name: string): string | undefined;
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
+
// Each body is its own export, so a component that needs one icon bundles only that icon.
|
|
2
|
+
export const checkBody = '<path d="M5 12.5l4.5 4.5L19 7.5"/>';
|
|
3
|
+
export const chevronDownBody = '<path d="M6 9l6 6 6-6"/>';
|
|
4
|
+
export const chevronUpBody = '<path d="M6 15l6-6 6 6"/>';
|
|
5
|
+
export const plusBody = '<path d="M12 5v14M5 12h14"/>';
|
|
6
|
+
export const minusBody = '<path d="M5 12h14"/>';
|
|
7
|
+
export const xBody = '<path d="M6 6l12 12M18 6L6 18"/>';
|
|
8
|
+
export const alertCircleBody = '<circle cx="12" cy="12" r="9"/><path d="M12 7.5v5.5M12 16.5h.01"/>';
|
|
9
|
+
export const userBody = '<circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-6 8-6s8 2 8 6"/>';
|
|
1
10
|
const BODIES = {
|
|
2
|
-
check:
|
|
3
|
-
'chevron-down':
|
|
4
|
-
'chevron-up':
|
|
5
|
-
plus:
|
|
6
|
-
minus:
|
|
7
|
-
x:
|
|
8
|
-
'alert-circle':
|
|
9
|
-
user:
|
|
11
|
+
check: checkBody,
|
|
12
|
+
'chevron-down': chevronDownBody,
|
|
13
|
+
'chevron-up': chevronUpBody,
|
|
14
|
+
plus: plusBody,
|
|
15
|
+
minus: minusBody,
|
|
16
|
+
x: xBody,
|
|
17
|
+
'alert-circle': alertCircleBody,
|
|
18
|
+
user: userBody,
|
|
10
19
|
};
|
|
20
|
+
/** The SVG element around an icon body. DOM-free. */
|
|
21
|
+
export function svgWrap(body) {
|
|
22
|
+
return `<svg part="svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${body}</svg>`;
|
|
23
|
+
}
|
|
11
24
|
/** The built-in icon as an SVG string, or undefined for an unknown name. DOM-free. */
|
|
12
25
|
export function systemIconSvg(name) {
|
|
13
26
|
const body = Object.hasOwn(BODIES, name) ? BODIES[name] : undefined;
|
|
14
|
-
|
|
15
|
-
return undefined;
|
|
16
|
-
return `<svg part="svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${body}</svg>`;
|
|
27
|
+
return body === undefined ? undefined : svgWrap(body);
|
|
17
28
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { TextControlElement } from '../shared/text-control.js';
|
|
2
|
+
import { BLOCKS_IMPLICIT_SUBMIT } from '../text-field/implicit-submit.js';
|
|
3
|
+
export declare class NeuNumberField extends TextControlElement {
|
|
4
|
+
static def: import("../../ssr.js").ComponentDef<{
|
|
5
|
+
readonly value: {
|
|
6
|
+
readonly type: "string";
|
|
7
|
+
};
|
|
8
|
+
readonly min: {
|
|
9
|
+
readonly type: "number";
|
|
10
|
+
readonly default: number;
|
|
11
|
+
};
|
|
12
|
+
readonly max: {
|
|
13
|
+
readonly type: "number";
|
|
14
|
+
readonly default: number;
|
|
15
|
+
};
|
|
16
|
+
readonly step: {
|
|
17
|
+
readonly type: "number";
|
|
18
|
+
readonly default: 1;
|
|
19
|
+
};
|
|
20
|
+
readonly name: {
|
|
21
|
+
readonly type: "string";
|
|
22
|
+
readonly reflect: true;
|
|
23
|
+
};
|
|
24
|
+
readonly label: {
|
|
25
|
+
readonly type: "string";
|
|
26
|
+
};
|
|
27
|
+
readonly placeholder: {
|
|
28
|
+
readonly type: "string";
|
|
29
|
+
};
|
|
30
|
+
readonly helpText: {
|
|
31
|
+
readonly type: "string";
|
|
32
|
+
};
|
|
33
|
+
readonly errorText: {
|
|
34
|
+
readonly type: "string";
|
|
35
|
+
};
|
|
36
|
+
readonly required: {
|
|
37
|
+
readonly type: "boolean";
|
|
38
|
+
readonly reflect: true;
|
|
39
|
+
};
|
|
40
|
+
readonly disabled: {
|
|
41
|
+
readonly type: "boolean";
|
|
42
|
+
readonly reflect: true;
|
|
43
|
+
};
|
|
44
|
+
readonly readOnly: {
|
|
45
|
+
readonly type: "boolean";
|
|
46
|
+
readonly reflect: true;
|
|
47
|
+
readonly attribute: "readonly";
|
|
48
|
+
};
|
|
49
|
+
readonly size: {
|
|
50
|
+
readonly type: "string";
|
|
51
|
+
readonly default: "md";
|
|
52
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
53
|
+
readonly reflect: true;
|
|
54
|
+
};
|
|
55
|
+
readonly incrementLabel: {
|
|
56
|
+
readonly type: "string";
|
|
57
|
+
readonly default: "Increase";
|
|
58
|
+
};
|
|
59
|
+
readonly decrementLabel: {
|
|
60
|
+
readonly type: "string";
|
|
61
|
+
readonly default: "Decrease";
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
64
|
+
static readonly [BLOCKS_IMPLICIT_SUBMIT] = true;
|
|
65
|
+
static extraProperties: string[];
|
|
66
|
+
min: number;
|
|
67
|
+
max: number;
|
|
68
|
+
step: number;
|
|
69
|
+
size: 'sm' | 'md' | 'lg';
|
|
70
|
+
incrementLabel: string;
|
|
71
|
+
decrementLabel: string;
|
|
72
|
+
protected readonly commitProps: string[];
|
|
73
|
+
private get np();
|
|
74
|
+
get valueAsNumber(): number;
|
|
75
|
+
set valueAsNumber(next: number);
|
|
76
|
+
/** The limits and step a native input of this field carries. */
|
|
77
|
+
private constrain;
|
|
78
|
+
protected probe(value: string): HTMLInputElement;
|
|
79
|
+
/** Moves the value, then reports it like a finished native edit: input (composed), then change. */
|
|
80
|
+
private move;
|
|
81
|
+
protected rendered(): void;
|
|
82
|
+
protected controlKeyDown(event: KeyboardEvent): void;
|
|
83
|
+
protected update(changed: ReadonlySet<string>): void;
|
|
84
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { InferProps } from '../../core/props.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
readonly value: {
|
|
4
|
+
readonly type: "string";
|
|
5
|
+
};
|
|
6
|
+
readonly min: {
|
|
7
|
+
readonly type: "number";
|
|
8
|
+
readonly default: number;
|
|
9
|
+
};
|
|
10
|
+
readonly max: {
|
|
11
|
+
readonly type: "number";
|
|
12
|
+
readonly default: number;
|
|
13
|
+
};
|
|
14
|
+
readonly step: {
|
|
15
|
+
readonly type: "number";
|
|
16
|
+
readonly default: 1;
|
|
17
|
+
};
|
|
18
|
+
readonly name: {
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly reflect: true;
|
|
21
|
+
};
|
|
22
|
+
readonly label: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
};
|
|
25
|
+
readonly placeholder: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
};
|
|
28
|
+
readonly helpText: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
};
|
|
31
|
+
readonly errorText: {
|
|
32
|
+
readonly type: "string";
|
|
33
|
+
};
|
|
34
|
+
readonly required: {
|
|
35
|
+
readonly type: "boolean";
|
|
36
|
+
readonly reflect: true;
|
|
37
|
+
};
|
|
38
|
+
readonly disabled: {
|
|
39
|
+
readonly type: "boolean";
|
|
40
|
+
readonly reflect: true;
|
|
41
|
+
};
|
|
42
|
+
readonly readOnly: {
|
|
43
|
+
readonly type: "boolean";
|
|
44
|
+
readonly reflect: true;
|
|
45
|
+
readonly attribute: "readonly";
|
|
46
|
+
};
|
|
47
|
+
readonly size: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
readonly default: "md";
|
|
50
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
51
|
+
readonly reflect: true;
|
|
52
|
+
};
|
|
53
|
+
readonly incrementLabel: {
|
|
54
|
+
readonly type: "string";
|
|
55
|
+
readonly default: "Increase";
|
|
56
|
+
};
|
|
57
|
+
readonly decrementLabel: {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly default: "Decrease";
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export type NumberFieldProps = InferProps<typeof props>;
|
|
63
|
+
/** iOS has no minus key on the decimal keypad: ask for it only when negatives are out of range. */
|
|
64
|
+
export declare const decimalPad: (min: number) => boolean;
|
|
65
|
+
export declare const numberFieldDef: import("../../core/define.js").ComponentDef<{
|
|
66
|
+
readonly value: {
|
|
67
|
+
readonly type: "string";
|
|
68
|
+
};
|
|
69
|
+
readonly min: {
|
|
70
|
+
readonly type: "number";
|
|
71
|
+
readonly default: number;
|
|
72
|
+
};
|
|
73
|
+
readonly max: {
|
|
74
|
+
readonly type: "number";
|
|
75
|
+
readonly default: number;
|
|
76
|
+
};
|
|
77
|
+
readonly step: {
|
|
78
|
+
readonly type: "number";
|
|
79
|
+
readonly default: 1;
|
|
80
|
+
};
|
|
81
|
+
readonly name: {
|
|
82
|
+
readonly type: "string";
|
|
83
|
+
readonly reflect: true;
|
|
84
|
+
};
|
|
85
|
+
readonly label: {
|
|
86
|
+
readonly type: "string";
|
|
87
|
+
};
|
|
88
|
+
readonly placeholder: {
|
|
89
|
+
readonly type: "string";
|
|
90
|
+
};
|
|
91
|
+
readonly helpText: {
|
|
92
|
+
readonly type: "string";
|
|
93
|
+
};
|
|
94
|
+
readonly errorText: {
|
|
95
|
+
readonly type: "string";
|
|
96
|
+
};
|
|
97
|
+
readonly required: {
|
|
98
|
+
readonly type: "boolean";
|
|
99
|
+
readonly reflect: true;
|
|
100
|
+
};
|
|
101
|
+
readonly disabled: {
|
|
102
|
+
readonly type: "boolean";
|
|
103
|
+
readonly reflect: true;
|
|
104
|
+
};
|
|
105
|
+
readonly readOnly: {
|
|
106
|
+
readonly type: "boolean";
|
|
107
|
+
readonly reflect: true;
|
|
108
|
+
readonly attribute: "readonly";
|
|
109
|
+
};
|
|
110
|
+
readonly size: {
|
|
111
|
+
readonly type: "string";
|
|
112
|
+
readonly default: "md";
|
|
113
|
+
readonly values: readonly ["sm", "md", "lg"];
|
|
114
|
+
readonly reflect: true;
|
|
115
|
+
};
|
|
116
|
+
readonly incrementLabel: {
|
|
117
|
+
readonly type: "string";
|
|
118
|
+
readonly default: "Increase";
|
|
119
|
+
};
|
|
120
|
+
readonly decrementLabel: {
|
|
121
|
+
readonly type: "string";
|
|
122
|
+
readonly default: "Decrease";
|
|
123
|
+
};
|
|
124
|
+
}>;
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { defineComponent } from '../../core/define.js';
|
|
2
|
+
import { html, unsafeHTML } from '../../core/html.js';
|
|
3
|
+
import { themeCssProperties } from '../../core/styles.js';
|
|
4
|
+
import { minusBody, plusBody, svgWrap } from '../icon/system-icons.js';
|
|
5
|
+
import { stepUnit } from '../shared/step.js';
|
|
6
|
+
import { numberFieldStyles } from './number-field.styles.js';
|
|
7
|
+
const props = {
|
|
8
|
+
value: { type: 'string' },
|
|
9
|
+
min: { type: 'number', default: Number.NaN },
|
|
10
|
+
max: { type: 'number', default: Number.NaN },
|
|
11
|
+
step: { type: 'number', default: 1 },
|
|
12
|
+
name: { type: 'string', reflect: true },
|
|
13
|
+
label: { type: 'string' },
|
|
14
|
+
placeholder: { type: 'string' },
|
|
15
|
+
helpText: { type: 'string' },
|
|
16
|
+
errorText: { type: 'string' },
|
|
17
|
+
required: { type: 'boolean', reflect: true },
|
|
18
|
+
disabled: { type: 'boolean', reflect: true },
|
|
19
|
+
readOnly: { type: 'boolean', reflect: true, attribute: 'readonly' },
|
|
20
|
+
size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
|
|
21
|
+
incrementLabel: { type: 'string', default: 'Increase' },
|
|
22
|
+
decrementLabel: { type: 'string', default: 'Decrease' },
|
|
23
|
+
};
|
|
24
|
+
/** iOS has no minus key on the decimal keypad: ask for it only when negatives are out of range. */
|
|
25
|
+
export const decimalPad = (min) => Number.isFinite(min) && min >= 0;
|
|
26
|
+
/** Attributes of the inner <input> that only appear when set. */
|
|
27
|
+
function inputAttributes(p) {
|
|
28
|
+
return html `${decimalPad(p.min) ? html ` inputmode="decimal"` : ''}${Number.isFinite(p.min) ? html ` min="${p.min}"` : ''}${Number.isFinite(p.max) ? html ` max="${p.max}"` : ''}${p.placeholder ? html ` placeholder="${p.placeholder}"` : ''}${p.required ? html ` required` : ''}${p.disabled ? html ` disabled` : ''}${p.readOnly ? html ` readonly` : ''}`;
|
|
29
|
+
}
|
|
30
|
+
const icon = (body) => unsafeHTML(svgWrap(body));
|
|
31
|
+
export const numberFieldDef = defineComponent({
|
|
32
|
+
tag: 'neu-number-field',
|
|
33
|
+
props,
|
|
34
|
+
delegatesFocus: true,
|
|
35
|
+
form: { valueProp: 'value' },
|
|
36
|
+
styles: numberFieldStyles,
|
|
37
|
+
template: (p) => {
|
|
38
|
+
const off = p.disabled || p.readOnly ? html ` disabled` : '';
|
|
39
|
+
return 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"><button part="decrement" class="step" type="button" tabindex="-1" aria-label="${p.decrementLabel}" data-ref="decrement"${off}>${icon(minusBody)}</button><input part="input" class="input" id="input" data-ref="input" type="number" value="${p.value}" step="${stepUnit(p.step)}" aria-describedby="help"${inputAttributes(p)}><button part="increment" class="step" type="button" tabindex="-1" aria-label="${p.incrementLabel}" data-ref="increment"${off}>${icon(plusBody)}</button></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
|
+
},
|
|
41
|
+
docs: {
|
|
42
|
+
summary: 'A numeric input with an inset field and stepper buttons. The value attribute is the default value.',
|
|
43
|
+
events: [
|
|
44
|
+
{ name: 'input', description: 'Fired on every edit and every step (composed).' },
|
|
45
|
+
{ name: 'change', description: 'Fired when an edit is committed, or after each step.' },
|
|
46
|
+
],
|
|
47
|
+
slots: [
|
|
48
|
+
{ name: 'label', description: 'Rich label; replaces the label attribute.' },
|
|
49
|
+
{ name: 'help-text', description: 'Rich help text; replaces the help-text attribute.' },
|
|
50
|
+
],
|
|
51
|
+
parts: [
|
|
52
|
+
{ name: 'base', description: 'Outer wrapper.' },
|
|
53
|
+
{ name: 'label', description: 'The <label> element.' },
|
|
54
|
+
{ name: 'control', description: 'The inset field around the input and buttons.' },
|
|
55
|
+
{ name: 'input', description: 'The native <input type="number">.' },
|
|
56
|
+
{ name: 'decrement', description: 'The button that lowers the value.' },
|
|
57
|
+
{ name: 'increment', description: 'The button that raises the value.' },
|
|
58
|
+
{ name: 'help-text', description: 'Help or error text area.' },
|
|
59
|
+
],
|
|
60
|
+
cssProperties: themeCssProperties,
|
|
61
|
+
},
|
|
62
|
+
});
|