@neumorphism-ui/elements 1.0.0-next.0 → 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.
Files changed (102) hide show
  1. package/README.md +12 -1
  2. package/cdn/neu.min.js +184 -51
  3. package/custom-elements.json +2232 -282
  4. package/dist/all.d.ts +8 -0
  5. package/dist/all.js +8 -0
  6. package/dist/checkbox.d.ts +3 -0
  7. package/dist/checkbox.js +4 -0
  8. package/dist/components/button/button.d.ts +0 -2
  9. package/dist/components/button/button.js +3 -36
  10. package/dist/components/checkbox/checkbox.d.ts +44 -0
  11. package/dist/components/checkbox/checkbox.def.d.ts +31 -0
  12. package/dist/components/checkbox/checkbox.def.js +37 -0
  13. package/dist/components/checkbox/checkbox.js +40 -0
  14. package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
  15. package/dist/components/checkbox/checkbox.styles.js +28 -0
  16. package/dist/components/icon/system-icons.d.ts +10 -0
  17. package/dist/components/icon/system-icons.js +22 -11
  18. package/dist/components/number-field/number-field.d.ts +84 -0
  19. package/dist/components/number-field/number-field.def.d.ts +125 -0
  20. package/dist/components/number-field/number-field.def.js +62 -0
  21. package/dist/components/number-field/number-field.js +121 -0
  22. package/dist/components/number-field/number-field.styles.d.ts +1 -0
  23. package/dist/components/number-field/number-field.styles.js +15 -0
  24. package/dist/components/option/option-data.d.ts +8 -0
  25. package/dist/components/option/option-data.js +12 -0
  26. package/dist/components/option/option.d.ts +19 -0
  27. package/dist/components/option/option.def.d.ts +10 -0
  28. package/dist/components/option/option.def.js +19 -0
  29. package/dist/components/option/option.js +17 -0
  30. package/dist/components/option/option.styles.d.ts +2 -0
  31. package/dist/components/option/option.styles.js +5 -0
  32. package/dist/components/radio/radio-link.d.ts +11 -0
  33. package/dist/components/radio/radio-link.js +5 -0
  34. package/dist/components/radio/radio.d.ts +29 -0
  35. package/dist/components/radio/radio.def.d.ts +11 -0
  36. package/dist/components/radio/radio.def.js +25 -0
  37. package/dist/components/radio/radio.js +43 -0
  38. package/dist/components/radio/radio.styles.d.ts +1 -0
  39. package/dist/components/radio/radio.styles.js +21 -0
  40. package/dist/components/radio-group/radio-group.d.ts +77 -0
  41. package/dist/components/radio-group/radio-group.def.d.ts +40 -0
  42. package/dist/components/radio-group/radio-group.def.js +55 -0
  43. package/dist/components/radio-group/radio-group.js +184 -0
  44. package/dist/components/radio-group/radio-group.styles.d.ts +1 -0
  45. package/dist/components/radio-group/radio-group.styles.js +15 -0
  46. package/dist/components/select/select.d.ts +119 -0
  47. package/dist/components/select/select.def.d.ts +38 -0
  48. package/dist/components/select/select.def.js +59 -0
  49. package/dist/components/select/select.js +385 -0
  50. package/dist/components/select/select.styles.d.ts +1 -0
  51. package/dist/components/select/select.styles.js +39 -0
  52. package/dist/components/shared/checkable.d.ts +45 -0
  53. package/dist/components/shared/checkable.js +108 -0
  54. package/dist/components/shared/field.styles.d.ts +1 -0
  55. package/dist/components/shared/field.styles.js +36 -0
  56. package/dist/components/shared/press.d.ts +7 -0
  57. package/dist/components/shared/press.js +42 -0
  58. package/dist/components/shared/step.d.ts +2 -0
  59. package/dist/components/shared/step.js +2 -0
  60. package/dist/components/shared/text-control.d.ts +72 -0
  61. package/dist/components/shared/text-control.js +191 -0
  62. package/dist/components/slider/slider.d.ts +69 -0
  63. package/dist/components/slider/slider.def.d.ts +39 -0
  64. package/dist/components/slider/slider.def.js +76 -0
  65. package/dist/components/slider/slider.js +170 -0
  66. package/dist/components/slider/slider.styles.d.ts +1 -0
  67. package/dist/components/slider/slider.styles.js +16 -0
  68. package/dist/components/switch/switch.d.ts +4 -30
  69. package/dist/components/switch/switch.js +5 -117
  70. package/dist/components/text-field/implicit-submit.d.ts +5 -0
  71. package/dist/components/text-field/implicit-submit.js +8 -3
  72. package/dist/components/text-field/text-field.d.ts +9 -36
  73. package/dist/components/text-field/text-field.js +21 -178
  74. package/dist/components/text-field/text-field.styles.d.ts +1 -1
  75. package/dist/components/text-field/text-field.styles.js +2 -35
  76. package/dist/components/textarea/textarea.d.ts +98 -0
  77. package/dist/components/textarea/textarea.def.d.ts +139 -0
  78. package/dist/components/textarea/textarea.def.js +65 -0
  79. package/dist/components/textarea/textarea.js +101 -0
  80. package/dist/components/textarea/textarea.styles.d.ts +1 -0
  81. package/dist/components/textarea/textarea.styles.js +9 -0
  82. package/dist/core/define.d.ts +16 -1
  83. package/dist/core/form-control.js +3 -1
  84. package/dist/core/validation.d.ts +6 -2
  85. package/dist/core/validation.js +15 -8
  86. package/dist/number-field.d.ts +3 -0
  87. package/dist/number-field.js +4 -0
  88. package/dist/option.d.ts +3 -0
  89. package/dist/option.js +4 -0
  90. package/dist/radio-group.d.ts +4 -0
  91. package/dist/radio-group.js +6 -0
  92. package/dist/radio.d.ts +3 -0
  93. package/dist/radio.js +4 -0
  94. package/dist/select.d.ts +3 -0
  95. package/dist/select.js +4 -0
  96. package/dist/slider.d.ts +3 -0
  97. package/dist/slider.js +4 -0
  98. package/dist/ssr.d.ts +9 -5
  99. package/dist/ssr.js +25 -5
  100. package/dist/textarea.d.ts +3 -0
  101. package/dist/textarea.js +4 -0
  102. package/package.json +41 -1
@@ -0,0 +1,16 @@
1
+ import { hostTheme } from '../../core/styles.js';
2
+ export const sliderStyles = `${hostTheme}
3
+ :host { display: block; min-block-size: 1.25rem; touch-action: pan-y; cursor: pointer; user-select: none; }
4
+ :host(:disabled), :host([disabled]) { cursor: not-allowed; }
5
+ .base { position: relative; display: flex; align-items: center; block-size: 1.25rem; padding-inline: 0.625rem; }
6
+ .track { position: relative; flex: 1; block-size: 0.5rem; border-radius: 999px; background: var(--_neu-bg); box-shadow: var(--_neu-inset-sm); }
7
+ .fill { position: absolute; inset-block: 0; inset-inline-start: 0; inline-size: calc(var(--_neu-pos) * 100%); border-radius: inherit; background: var(--_neu-accent-soft); }
8
+ .thumb {
9
+ position: absolute; inset-block-start: 50%; inset-inline-start: calc(var(--_neu-pos) * 100%);
10
+ inline-size: 1.25rem; block-size: 1.25rem; margin-block-start: -0.625rem; margin-inline-start: -0.625rem;
11
+ border-radius: 50%; background: var(--_neu-bg); box-shadow: var(--_neu-raised-sm);
12
+ transition: box-shadow var(--_neu-dur) ease;
13
+ }
14
+ :host(:focus-visible) .thumb { box-shadow: var(--_neu-raised-sm), var(--_neu-focus); }
15
+ :host(:disabled) .base, :host([disabled]) .base { opacity: 0.55; }
16
+ `;
@@ -1,5 +1,5 @@
1
- import { FormControlElement } from '../../core/form-control.js';
2
- export declare class NeuSwitch extends FormControlElement {
1
+ import { CheckableElement } from '../shared/checkable.js';
2
+ export declare class NeuSwitch extends CheckableElement {
3
3
  static def: import("../../ssr.js").ComponentDef<{
4
4
  readonly checked: {
5
5
  readonly type: "boolean";
@@ -28,33 +28,7 @@ export declare class NeuSwitch extends FormControlElement {
28
28
  readonly reflect: true;
29
29
  };
30
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;
31
+ protected readonly anchor = "track";
39
32
  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;
33
+ protected paint(): void;
60
34
  }
@@ -1,125 +1,13 @@
1
- import { afterDispatch, dispatch, isActiveInRoot } from '../../core/dom.js';
2
- import { FormControlElement } from '../../core/form-control.js';
3
- import { requiredCheckboxMessage } from '../../core/validation.js';
1
+ import { CheckableElement } from '../shared/checkable.js';
4
2
  import { switchDef } from './switch.def.js';
5
- export class NeuSwitch extends FormControlElement {
3
+ export class NeuSwitch extends CheckableElement {
6
4
  static def = switchDef;
7
- static extraProperties = ['defaultChecked'];
8
- current;
9
- dirty = false;
5
+ anchor = 'track';
10
6
  constructor() {
11
7
  super();
12
- this.current = this.props.checked === true;
13
8
  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
9
  }
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');
10
+ paint() {
11
+ this.ref('track')?.classList.toggle('on', this.checked);
124
12
  }
125
13
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Static brand of the custom elements that block implicit submission like a text input. A
3
+ * subclass inherits it, so it counts under any tag name.
4
+ */
5
+ export declare const BLOCKS_IMPLICIT_SUBMIT: unique symbol;
1
6
  /**
2
7
  * Enter in a text control submits like a native input (HTML §4.10.21.2). With a default button
3
8
  * (the form's first submit button, form="id" ones included) it clicks that button unless it is
@@ -14,6 +14,13 @@ const BLOCKING_TYPES = new Set([
14
14
  'datetime-local',
15
15
  'number',
16
16
  ]);
17
+ /**
18
+ * Static brand of the custom elements that block implicit submission like a text input. A
19
+ * subclass inherits it, so it counts under any tag name.
20
+ */
21
+ export const BLOCKS_IMPLICIT_SUBMIT = Symbol.for('neu-ui.blocks-implicit-submit');
22
+ const blocks = (el) => (el instanceof HTMLInputElement && BLOCKING_TYPES.has(el.type)) ||
23
+ el.constructor[BLOCKS_IMPLICIT_SUBMIT] === true;
17
24
  /**
18
25
  * Enter in a text control submits like a native input (HTML §4.10.21.2). With a default button
19
26
  * (the form's first submit button, form="id" ones included) it clicks that button unless it is
@@ -33,9 +40,7 @@ export function implicitSubmit(form) {
33
40
  defaultButton.click();
34
41
  return;
35
42
  }
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)
43
+ if (elements.filter(blocks).length !== 1)
39
44
  return;
40
45
  markSubmitAttempt(form);
41
46
  form.requestSubmit();
@@ -1,5 +1,6 @@
1
- import { FormControlElement } from '../../core/form-control.js';
2
- export declare class NeuTextField extends FormControlElement {
1
+ import { TextControlElement } from '../shared/text-control.js';
2
+ import { BLOCKS_IMPLICIT_SUBMIT } from './implicit-submit.js';
3
+ export declare class NeuTextField extends TextControlElement {
3
4
  static def: import("../../ssr.js").ComponentDef<{
4
5
  readonly type: {
5
6
  readonly type: "string";
@@ -70,47 +71,19 @@ export declare class NeuTextField extends FormControlElement {
70
71
  readonly reflect: true;
71
72
  };
72
73
  }>;
74
+ static readonly [BLOCKS_IMPLICIT_SUBMIT] = true;
73
75
  static extraProperties: string[];
74
76
  type: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url';
75
- name: string;
76
- label: string;
77
- placeholder: string;
78
- helpText: string;
79
- errorText: string;
80
- required: boolean;
81
- disabled: boolean;
82
- readOnly: boolean;
83
77
  minLength: number;
84
78
  maxLength: number;
85
79
  pattern: string;
86
- autocomplete: string;
87
80
  size: 'sm' | 'md' | 'lg';
88
- /** True once the value was edited or set as a property; the default then stops applying. */
89
- private dirty;
90
- /** A value set before the shadow root exists. */
91
- private pendingValue;
92
- get value(): string;
93
- set value(next: string);
94
- get defaultValue(): string;
95
- set defaultValue(next: string);
96
- /** Focuses the inner input directly, so the caret stays where it was (no select-all). */
97
- focus(options?: FocusOptions): void;
98
- select(): void;
99
- setSelectionRange(start: number, end: number, direction?: 'forward' | 'backward' | 'none'): void;
100
- private get p();
101
- private get input();
81
+ protected readonly commitProps: string[];
82
+ private get tp();
102
83
  /** A detached input of this type holding `value`, sanitized the way the browser does it. */
103
- private probe;
104
- /** Stands in for the inner input until it exists, so validity is right from the start. */
105
- private validityProbe;
106
- protected rendered(): void;
84
+ protected probe(value: string): HTMLInputElement;
85
+ protected controlKeyDown(event: KeyboardEvent): void;
107
86
  protected update(changed: ReadonlySet<string>): void;
108
- /** update() only runs once connected; before that, keep the value and validity current here. */
109
- protected willUpdate(changed: ReadonlySet<string>): void;
110
- protected formValue(): string;
111
- /** Like a native input, a password or autocomplete=off field is never saved for restore. */
87
+ /** Like a native input, a password field is never saved for restore. */
112
88
  protected formState(): string | null;
113
- protected validate(): void;
114
- protected resetToDefault(): void;
115
- protected restoreState(state: string): void;
116
89
  }
@@ -1,207 +1,50 @@
1
- import { afterDispatch, dispatch, setOptionalAttribute, setText } from '../../core/dom.js';
2
- import { FormControlElement } from '../../core/form-control.js';
3
- import { copyValidity } from '../../core/validation.js';
4
- import { implicitSubmit } from './implicit-submit.js';
1
+ import { afterDispatch, setOptionalAttribute } from '../../core/dom.js';
2
+ import { baseCommitProps, TextControlElement } from '../shared/text-control.js';
3
+ import { BLOCKS_IMPLICIT_SUBMIT, implicitSubmit } from './implicit-submit.js';
5
4
  import { textFieldDef } from './text-field.def.js';
6
- /** Props that can change the form value, its restore state or its validity. */
7
- const COMMIT_PROPS = [
8
- 'value',
9
- 'type',
10
- 'required',
11
- 'disabled',
12
- 'readOnly',
13
- 'minLength',
14
- 'maxLength',
15
- 'pattern',
16
- 'errorText',
17
- 'autocomplete',
18
- ];
19
- function needsCommit(changed) {
20
- return COMMIT_PROPS.some((key) => changed.has(key));
21
- }
22
- export class NeuTextField extends FormControlElement {
5
+ export class NeuTextField extends TextControlElement {
23
6
  static def = textFieldDef;
7
+ static [BLOCKS_IMPLICIT_SUBMIT] = true;
24
8
  static extraProperties = ['defaultValue'];
25
9
  // inputMode and enterKeyHint are inherited from HTMLElement with the right type.
26
- /** True once the value was edited or set as a property; the default then stops applying. */
27
- dirty = false;
28
- /** A value set before the shadow root exists. */
29
- pendingValue = null;
30
- get value() {
31
- return this.input?.value ?? this.pendingValue ?? this.p.value;
32
- }
33
- set value(next) {
34
- const text = next === null || next === undefined ? '' : String(next);
35
- this.dirty = true;
36
- const input = this.input;
37
- if (input)
38
- input.value = text;
39
- else
40
- this.pendingValue = text;
41
- this.commit();
42
- }
43
- get defaultValue() {
44
- return this.p.value;
45
- }
46
- set defaultValue(next) {
47
- this.setAttribute('value', String(next));
48
- }
49
- /** Focuses the inner input directly, so the caret stays where it was (no select-all). */
50
- focus(options) {
51
- const input = this.input;
52
- if (input)
53
- input.focus(options);
54
- else
55
- super.focus(options);
56
- }
57
- select() {
58
- this.input?.select();
59
- }
60
- setSelectionRange(start, end, direction) {
61
- this.input?.setSelectionRange(start, end, direction);
62
- }
63
- get p() {
10
+ commitProps = [...baseCommitProps, 'type', 'pattern'];
11
+ get tp() {
64
12
  return this.props;
65
13
  }
66
- get input() {
67
- return this.ref('input');
68
- }
69
14
  /** A detached input of this type holding `value`, sanitized the way the browser does it. */
70
15
  probe(value) {
71
16
  const probe = document.createElement('input');
72
- probe.type = this.p.type;
17
+ probe.type = this.tp.type;
73
18
  probe.value = value;
19
+ if (this.tp.pattern)
20
+ probe.setAttribute('pattern', this.tp.pattern);
74
21
  return probe;
75
22
  }
76
- /** Stands in for the inner input until it exists, so validity is right from the start. */
77
- validityProbe() {
78
- const p = this.p;
79
- const probe = this.probe(this.value);
80
- probe.required = p.required;
81
- probe.readOnly = p.readOnly;
82
- probe.disabled = this.isDisabled;
83
- if (p.pattern)
84
- probe.setAttribute('pattern', p.pattern);
85
- if (p.minLength >= 0)
86
- probe.setAttribute('minlength', String(p.minLength));
87
- if (p.maxLength >= 0)
88
- probe.setAttribute('maxlength', String(p.maxLength));
89
- return probe;
90
- }
91
- rendered() {
92
- const input = this.input;
93
- // A declarative root adopted without its input (cut short by the parser): nothing to wire up.
94
- if (!input)
23
+ controlKeyDown(event) {
24
+ if (event.key !== 'Enter' || event.isComposing)
95
25
  return;
96
- // An SSR'd input works before JS loads; keep whatever was typed or autofilled meanwhile.
97
- // Compare with the sanitized default: the browser trims or strips some values by itself.
98
- if (input.value !== this.probe(this.p.value).value)
99
- this.dirty = true;
100
- if (this.pendingValue !== null) {
101
- input.value = this.pendingValue;
102
- this.pendingValue = null;
103
- }
104
- input.addEventListener('input', () => {
105
- this.dirty = true;
106
- this.commit();
107
- });
108
- input.addEventListener('change', () => {
109
- this.markInteracted();
110
- dispatch(this, 'change');
111
- });
112
- input.addEventListener('keydown', (event) => {
113
- if (event.key !== 'Enter' || event.isComposing)
114
- return;
115
- // Like a native input: submit once the keydown has finished dispatching, unless a listener
116
- // (on the host or further up) cancelled it.
117
- afterDispatch(event, () => {
118
- if (!event.defaultPrevented)
119
- implicitSubmit(this.internals.form);
120
- });
26
+ // Like a native input: submit once the keydown has finished dispatching, unless a listener
27
+ // (on the host or further up) cancelled it.
28
+ afterDispatch(event, () => {
29
+ if (!event.defaultPrevented)
30
+ implicitSubmit(this.internals.form);
121
31
  });
122
32
  }
123
33
  update(changed) {
124
- const p = this.p;
34
+ const p = this.tp;
125
35
  const input = this.input;
126
36
  if (input) {
127
37
  if (changed.has('type') && input.type !== p.type)
128
38
  input.type = p.type;
129
- if (changed.has('value')) {
130
- if (input.defaultValue !== p.value)
131
- input.defaultValue = p.value;
132
- if (!this.dirty)
133
- input.value = p.value;
134
- }
135
- if (changed.has('placeholder'))
136
- setOptionalAttribute(input, 'placeholder', p.placeholder);
137
- if (changed.has('required'))
138
- input.required = p.required;
139
- if (changed.has('disabled'))
140
- input.disabled = this.isDisabled;
141
- if (changed.has('readOnly'))
142
- input.readOnly = p.readOnly;
143
- if (changed.has('minLength')) {
144
- setOptionalAttribute(input, 'minlength', p.minLength >= 0 ? String(p.minLength) : '');
145
- }
146
- if (changed.has('maxLength')) {
147
- setOptionalAttribute(input, 'maxlength', p.maxLength >= 0 ? String(p.maxLength) : '');
148
- }
149
39
  if (changed.has('pattern'))
150
40
  setOptionalAttribute(input, 'pattern', p.pattern);
151
- if (changed.has('autocomplete'))
152
- setOptionalAttribute(input, 'autocomplete', p.autocomplete);
153
41
  if (changed.has('inputMode'))
154
42
  setOptionalAttribute(input, 'inputmode', p.inputMode);
155
- if (changed.has('enterKeyHint'))
156
- setOptionalAttribute(input, 'enterkeyhint', p.enterKeyHint);
157
43
  }
158
- if (changed.has('label'))
159
- setText(this.ref('label-text'), p.label);
160
- if (changed.has('helpText'))
161
- setText(this.ref('help-text'), p.helpText);
162
- if (needsCommit(changed))
163
- this.commit();
44
+ super.update(changed);
164
45
  }
165
- /** update() only runs once connected; before that, keep the value and validity current here. */
166
- willUpdate(changed) {
167
- if (!this.isConnected && !this.input && needsCommit(changed))
168
- this.commit();
169
- }
170
- formValue() {
171
- return this.value;
172
- }
173
- /** Like a native input, a password or autocomplete=off field is never saved for restore. */
46
+ /** Like a native input, a password field is never saved for restore. */
174
47
  formState() {
175
- const p = this.p;
176
- if (p.type === 'password' || p.autocomplete.trim().toLowerCase() === 'off')
177
- return null;
178
- return this.value;
179
- }
180
- validate() {
181
- const input = this.input;
182
- const source = input ?? this.validityProbe();
183
- // A disabled or readonly input still sets flags such as typeMismatch, but is barred from
184
- // validation and has no message; setValidity() would throw on flags without a message.
185
- if (!source.willValidate || source.validity.valid)
186
- this.internals.setValidity({});
187
- else {
188
- this.internals.setValidity(copyValidity(source.validity), source.validationMessage, input ?? undefined);
189
- }
190
- setText(this.ref('error-text'), this.p.errorText || input?.validationMessage || '');
191
- }
192
- resetToDefault() {
193
- this.dirty = false;
194
- this.pendingValue = null;
195
- const input = this.input;
196
- if (input)
197
- input.value = this.p.value;
198
- }
199
- restoreState(state) {
200
- this.dirty = true;
201
- const input = this.input;
202
- if (input)
203
- input.value = state;
204
- else
205
- this.pendingValue = state;
48
+ return this.tp.type === 'password' ? null : super.formState();
206
49
  }
207
50
  }
@@ -1 +1 @@
1
- export declare const textFieldStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: block; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: flex; flex-direction: column; }\n.label-text, ::slotted([slot='label']) {\n display: block; margin-block-end: 0.375rem;\n font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted);\n}\n.label-text:empty { display: none; }\n.control {\n display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }\n:host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }\n:host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }\n:host(:state(user-invalid)) .control {\n box-shadow:\n inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),\n inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n}\n:host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }\n.input {\n flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;\n background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;\n}\n.input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }\n.prefix, .suffix { display: inline-flex; }\n::slotted([slot='prefix']) { margin-inline-end: 0.5rem; }\n::slotted([slot='suffix']) { margin-inline-start: 0.5rem; }\n.help { font-size: 0.75rem; color: var(--_neu-fg-muted); }\n.help-text, ::slotted([slot='help-text']) { display: block; margin-block-start: 0.375rem; }\n.help-text:empty { display: none; }\n.error-text { display: none; margin-block-start: 0.375rem; color: var(--_neu-danger); }\n:host(:state(user-invalid)) slot[name='help-text'] { display: none; }\n:host(:state(user-invalid)) .error-text:not(:empty) { display: block; }\n";
1
+ export declare const textFieldStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: block; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: flex; flex-direction: column; }\n.label-text, ::slotted([slot='label']) {\n display: block; margin-block-end: 0.375rem;\n font-size: 0.8125rem; font-weight: 600; line-height: 1rem; color: var(--_neu-fg-muted);\n}\n.label-text:empty { display: none; }\n.control {\n display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }\n:host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }\n:host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }\n:host(:state(user-invalid)) .control {\n box-shadow:\n inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),\n inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n}\n:host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }\n.input {\n flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;\n background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;\n}\n.input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }\n.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.prefix, .suffix { display: inline-flex; }\n::slotted([slot='prefix']) { margin-inline-end: 0.5rem; }\n::slotted([slot='suffix']) { margin-inline-start: 0.5rem; }\n";
@@ -1,39 +1,6 @@
1
- import { hostTheme } from '../../core/styles.js';
2
- export const textFieldStyles = `${hostTheme}
3
- :host { display: block; }
4
- :host(:disabled), :host([disabled]) { cursor: not-allowed; }
5
- .base { display: flex; flex-direction: column; }
6
- .label-text, ::slotted([slot='label']) {
7
- display: block; margin-block-end: 0.375rem;
8
- font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted);
9
- }
10
- .label-text:empty { display: none; }
11
- .control {
12
- display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;
13
- border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);
14
- transition: box-shadow var(--_neu-dur) ease;
15
- }
16
- :host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }
17
- :host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }
18
- :host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }
19
- :host(:state(user-invalid)) .control {
20
- box-shadow:
21
- inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),
22
- inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);
23
- }
24
- :host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }
25
- .input {
26
- flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;
27
- background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;
28
- }
29
- .input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }
1
+ import { fieldStyles } from '../shared/field.styles.js';
2
+ export const textFieldStyles = `${fieldStyles}
30
3
  .prefix, .suffix { display: inline-flex; }
31
4
  ::slotted([slot='prefix']) { margin-inline-end: 0.5rem; }
32
5
  ::slotted([slot='suffix']) { margin-inline-start: 0.5rem; }
33
- .help { font-size: 0.75rem; color: var(--_neu-fg-muted); }
34
- .help-text, ::slotted([slot='help-text']) { display: block; margin-block-start: 0.375rem; }
35
- .help-text:empty { display: none; }
36
- .error-text { display: none; margin-block-start: 0.375rem; color: var(--_neu-danger); }
37
- :host(:state(user-invalid)) slot[name='help-text'] { display: none; }
38
- :host(:state(user-invalid)) .error-text:not(:empty) { display: block; }
39
6
  `;