@neumorphism-ui/elements 1.0.0-next.2 → 1.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/cdn/neu.min.js +31 -39
  2. package/custom-elements.json +495 -248
  3. package/dist/components/avatar/avatar.d.ts +0 -3
  4. package/dist/components/avatar/avatar.def.d.ts +0 -3
  5. package/dist/components/avatar/avatar.def.js +4 -18
  6. package/dist/components/avatar/avatar.docs.d.ts +2 -0
  7. package/dist/components/avatar/avatar.docs.js +22 -0
  8. package/dist/components/avatar/avatar.js +7 -1
  9. package/dist/components/badge/badge.d.ts +0 -2
  10. package/dist/components/badge/badge.def.d.ts +0 -2
  11. package/dist/components/badge/badge.def.js +1 -14
  12. package/dist/components/badge/badge.docs.d.ts +2 -0
  13. package/dist/components/badge/badge.docs.js +17 -0
  14. package/dist/components/button/button.def.d.ts +0 -5
  15. package/dist/components/button/button.def.js +3 -25
  16. package/dist/components/button/button.docs.d.ts +2 -0
  17. package/dist/components/button/button.docs.js +29 -0
  18. package/dist/components/button-group/button-group.d.ts +0 -2
  19. package/dist/components/button-group/button-group.def.d.ts +0 -2
  20. package/dist/components/button-group/button-group.def.js +1 -10
  21. package/dist/components/button-group/button-group.docs.d.ts +2 -0
  22. package/dist/components/button-group/button-group.docs.js +13 -0
  23. package/dist/components/checkbox/checkbox.d.ts +1 -5
  24. package/dist/components/checkbox/checkbox.def.d.ts +1 -5
  25. package/dist/components/checkbox/checkbox.def.js +6 -22
  26. package/dist/components/checkbox/checkbox.docs.d.ts +2 -0
  27. package/dist/components/checkbox/checkbox.docs.js +27 -0
  28. package/dist/components/divider/divider.d.ts +0 -1
  29. package/dist/components/divider/divider.def.d.ts +0 -1
  30. package/dist/components/divider/divider.def.js +0 -9
  31. package/dist/components/divider/divider.docs.d.ts +2 -0
  32. package/dist/components/divider/divider.docs.js +12 -0
  33. package/dist/components/icon/icon.d.ts +0 -3
  34. package/dist/components/icon/icon.def.d.ts +0 -3
  35. package/dist/components/icon/icon.def.js +3 -10
  36. package/dist/components/icon/icon.docs.d.ts +2 -0
  37. package/dist/components/icon/icon.docs.js +13 -0
  38. package/dist/components/icon/system-icons.d.ts +1 -1
  39. package/dist/components/icon/system-icons.js +3 -3
  40. package/dist/components/icon-button/icon-button.d.ts +0 -5
  41. package/dist/components/icon-button/icon-button.def.d.ts +0 -5
  42. package/dist/components/icon-button/icon-button.def.js +3 -17
  43. package/dist/components/icon-button/icon-button.docs.d.ts +2 -0
  44. package/dist/components/icon-button/icon-button.docs.js +26 -0
  45. package/dist/components/number-field/number-field.d.ts +0 -5
  46. package/dist/components/number-field/number-field.def.d.ts +0 -10
  47. package/dist/components/number-field/number-field.def.js +5 -27
  48. package/dist/components/number-field/number-field.docs.d.ts +2 -0
  49. package/dist/components/number-field/number-field.docs.js +45 -0
  50. package/dist/components/number-field/number-field.js +2 -2
  51. package/dist/components/number-field/number-field.styles.d.ts +1 -1
  52. package/dist/components/option/option.d.ts +0 -2
  53. package/dist/components/option/option.def.d.ts +0 -2
  54. package/dist/components/option/option.def.js +2 -9
  55. package/dist/components/option/option.docs.d.ts +2 -0
  56. package/dist/components/option/option.docs.js +12 -0
  57. package/dist/components/radio/radio.d.ts +0 -2
  58. package/dist/components/radio/radio.def.d.ts +0 -2
  59. package/dist/components/radio/radio.def.js +2 -15
  60. package/dist/components/radio/radio.docs.d.ts +2 -0
  61. package/dist/components/radio/radio.docs.js +18 -0
  62. package/dist/components/radio-group/radio-group.d.ts +3 -4
  63. package/dist/components/radio-group/radio-group.def.d.ts +0 -4
  64. package/dist/components/radio-group/radio-group.def.js +4 -19
  65. package/dist/components/radio-group/radio-group.docs.d.ts +2 -0
  66. package/dist/components/radio-group/radio-group.docs.js +37 -0
  67. package/dist/components/radio-group/radio-group.js +28 -4
  68. package/dist/components/radio-group/radio-group.styles.d.ts +1 -1
  69. package/dist/components/radio-group/radio-group.styles.js +3 -5
  70. package/dist/components/select/select.d.ts +1 -5
  71. package/dist/components/select/select.def.d.ts +0 -4
  72. package/dist/components/select/select.def.js +5 -29
  73. package/dist/components/select/select.docs.d.ts +2 -0
  74. package/dist/components/select/select.docs.js +65 -0
  75. package/dist/components/select/select.js +16 -10
  76. package/dist/components/select/select.styles.d.ts +1 -1
  77. package/dist/components/select/select.styles.js +1 -5
  78. package/dist/components/shared/field.styles.d.ts +1 -1
  79. package/dist/components/shared/field.styles.js +2 -4
  80. package/dist/components/slider/slider.d.ts +0 -2
  81. package/dist/components/slider/slider.def.d.ts +0 -2
  82. package/dist/components/slider/slider.def.js +2 -24
  83. package/dist/components/slider/slider.docs.d.ts +2 -0
  84. package/dist/components/slider/slider.docs.js +45 -0
  85. package/dist/components/spinner/spinner.d.ts +0 -1
  86. package/dist/components/spinner/spinner.def.d.ts +0 -1
  87. package/dist/components/spinner/spinner.def.js +1 -13
  88. package/dist/components/spinner/spinner.docs.d.ts +2 -0
  89. package/dist/components/spinner/spinner.docs.js +17 -0
  90. package/dist/components/surface/surface.d.ts +0 -4
  91. package/dist/components/surface/surface.def.d.ts +0 -4
  92. package/dist/components/surface/surface.def.js +3 -12
  93. package/dist/components/surface/surface.docs.d.ts +2 -0
  94. package/dist/components/surface/surface.docs.js +15 -0
  95. package/dist/components/switch/switch.d.ts +0 -5
  96. package/dist/components/switch/switch.def.d.ts +0 -5
  97. package/dist/components/switch/switch.def.js +5 -21
  98. package/dist/components/switch/switch.docs.d.ts +2 -0
  99. package/dist/components/switch/switch.docs.js +26 -0
  100. package/dist/components/text-field/text-field.d.ts +0 -6
  101. package/dist/components/text-field/text-field.def.d.ts +0 -12
  102. package/dist/components/text-field/text-field.def.js +5 -30
  103. package/dist/components/text-field/text-field.docs.d.ts +2 -0
  104. package/dist/components/text-field/text-field.docs.js +45 -0
  105. package/dist/components/text-field/text-field.js +2 -2
  106. package/dist/components/text-field/text-field.styles.d.ts +1 -1
  107. package/dist/components/textarea/textarea.d.ts +0 -6
  108. package/dist/components/textarea/textarea.def.d.ts +0 -12
  109. package/dist/components/textarea/textarea.def.js +5 -26
  110. package/dist/components/textarea/textarea.docs.d.ts +2 -0
  111. package/dist/components/textarea/textarea.docs.js +40 -0
  112. package/dist/components/textarea/textarea.styles.d.ts +1 -1
  113. package/dist/components/theme-docs.d.ts +3 -0
  114. package/dist/components/theme-docs.js +8 -0
  115. package/dist/core/define.d.ts +4 -1
  116. package/dist/core/dom.d.ts +4 -0
  117. package/dist/core/dom.js +7 -0
  118. package/dist/core/form-control.js +4 -1
  119. package/dist/core/neu-element.d.ts +2 -2
  120. package/dist/core/neu-element.js +10 -7
  121. package/dist/core/props.d.ts +13 -1
  122. package/dist/core/props.js +13 -3
  123. package/dist/core/styles.d.ts +0 -2
  124. package/dist/core/styles.js +0 -7
  125. package/dist/docs.d.ts +3 -0
  126. package/dist/docs.js +41 -0
  127. package/dist/ssr.d.ts +1 -1
  128. package/dist/ssr.js +1 -1
  129. package/package.json +5 -1
@@ -18,23 +18,19 @@ export declare const radioGroupDef: import("../../core/define.js").ComponentDef<
18
18
  };
19
19
  readonly name: {
20
20
  readonly type: "string";
21
- readonly reflect: true;
22
21
  };
23
22
  readonly value: {
24
23
  readonly type: "string";
25
24
  };
26
25
  readonly required: {
27
26
  readonly type: "boolean";
28
- readonly reflect: true;
29
27
  };
30
28
  readonly disabled: {
31
29
  readonly type: "boolean";
32
- readonly reflect: true;
33
30
  };
34
31
  readonly orientation: {
35
32
  readonly type: "string";
36
33
  readonly default: "vertical";
37
34
  readonly values: readonly ["vertical", "horizontal"];
38
- readonly reflect: true;
39
35
  };
40
36
  }>;
@@ -1,6 +1,5 @@
1
1
  import { defineComponent } from '../../core/define.js';
2
2
  import { html } from '../../core/html.js';
3
- import { themeCssProperties } from '../../core/styles.js';
4
3
  import { radioGroupStyles } from './radio-group.styles.js';
5
4
  /**
6
5
  * Escapes a value for a double-quoted CSS string. Besides the characters that end the string,
@@ -25,31 +24,17 @@ export const radioGroupDef = defineComponent({
25
24
  tag: 'neu-radio-group',
26
25
  props: {
27
26
  label: { type: 'string' },
28
- name: { type: 'string', reflect: true },
27
+ name: { type: 'string' },
29
28
  value: { type: 'string' },
30
- required: { type: 'boolean', reflect: true },
31
- disabled: { type: 'boolean', reflect: true },
29
+ required: { type: 'boolean' },
30
+ disabled: { type: 'boolean' },
32
31
  orientation: {
33
32
  type: 'string',
34
33
  default: 'vertical',
35
34
  values: ['vertical', 'horizontal'],
36
- reflect: true,
37
35
  },
38
36
  },
39
37
  form: { valueProp: 'value' },
40
38
  styles: radioGroupStyles,
41
- template: (p) => html `<div part="base" class="base"><span part="label" class="label" data-ref="label">${p.label}</span><span class="items"><slot></slot></span><style data-ref="mark">${markRule(p.value)}</style></div>`,
42
- docs: {
43
- summary: 'A group of neu-radio elements with one selected value, arrow-key navigation and form support. The value attribute is the default value.',
44
- events: [
45
- { name: 'input', description: 'Fired when the user selects a radio.' },
46
- { name: 'change', description: 'Fired after input when the user selects a radio.' },
47
- ],
48
- slots: [{ name: '', description: 'neu-radio elements.' }],
49
- parts: [
50
- { name: 'base', description: 'Outer wrapper.' },
51
- { name: 'label', description: 'The group label.' },
52
- ],
53
- cssProperties: themeCssProperties,
54
- },
39
+ template: (p) => html `<div part="base" class="base"><span part="label" class="label"><slot name="label"><span class="label-text" data-ref="label-text">${p.label}</span></slot></span><span class="items"><slot></slot></span><style data-ref="mark">${markRule(p.value)}</style></div>`,
55
40
  });
@@ -0,0 +1,2 @@
1
+ import type { ComponentDocs } from '../../core/define.js';
2
+ export declare const radioGroupDocs: ComponentDocs;
@@ -0,0 +1,37 @@
1
+ import { themeCssProperties } from '../theme-docs.js';
2
+ export const radioGroupDocs = {
3
+ summary: 'A group of neu-radio elements with one selected value, arrow-key navigation and form support. The value attribute is the default value.',
4
+ descriptions: {
5
+ label: 'Visible label that names the group.',
6
+ name: 'The name under which the selected value is submitted with a form.',
7
+ value: 'The value of the currently selected radio.',
8
+ required: 'Makes the form invalid until a radio is selected.',
9
+ disabled: 'Prevents interaction with every radio in the group.',
10
+ orientation: 'The direction the radios are laid out in.',
11
+ defaultValue: 'The value selected at the start, which a form reset restores; set by the value attribute.',
12
+ },
13
+ events: [
14
+ { name: 'input', description: 'Fired when the user selects a radio.' },
15
+ { name: 'change', description: 'Fired after input when the user selects a radio.' },
16
+ ],
17
+ slots: [
18
+ { name: '', description: 'neu-radio elements.' },
19
+ { name: 'label', description: 'Rich label; replaces the label attribute.' },
20
+ ],
21
+ parts: [
22
+ { name: 'base', description: 'Outer wrapper.' },
23
+ { name: 'label', description: 'The group label.' },
24
+ ],
25
+ cssProperties: themeCssProperties,
26
+ keyboard: [
27
+ {
28
+ name: 'ArrowDown / ArrowRight',
29
+ description: 'Moves to the next radio and selects it, wrapping and skipping disabled radios; ArrowLeft and ArrowRight swap in right-to-left text.',
30
+ },
31
+ {
32
+ name: 'ArrowUp / ArrowLeft',
33
+ description: 'Moves to the previous radio and selects it, wrapping and skipping disabled radios; ArrowLeft and ArrowRight swap in right-to-left text.',
34
+ },
35
+ { name: 'Space', description: 'Selects the focused radio.' },
36
+ ],
37
+ };
@@ -1,4 +1,4 @@
1
- import { dispatch, setText } from '../../core/dom.js';
1
+ import { dispatch, inSlotted, setText } from '../../core/dom.js';
2
2
  import { FormControlElement } from '../../core/form-control.js';
3
3
  import { requiredMessage } from '../../core/validation.js';
4
4
  import { RESYNC, SET_CHECKED, SET_GROUP_DISABLED } from '../radio/radio-link.js';
@@ -127,14 +127,27 @@ export class NeuRadioGroup extends FormControlElement {
127
127
  setText(this.ref('mark'), rule === '' ? '' : rule.value);
128
128
  this.commit();
129
129
  }
130
+ /** The group's name: the slotted label's text when there is one, else the label attribute. */
131
+ nameGroup() {
132
+ const slot = this.shadowRoot?.querySelector('slot[name="label"]');
133
+ const text = (slot?.assignedNodes() ?? [])
134
+ .map((node) => node.textContent)
135
+ .join('')
136
+ .replace(/\s+/g, ' ')
137
+ .trim();
138
+ this.internals.ariaLabel = text || String(this.props.label) || null;
139
+ }
140
+ rendered() {
141
+ this.shadowRoot.querySelector('slot[name="label"]').addEventListener('slotchange', () => this.nameGroup());
142
+ }
130
143
  willUpdate(changed) {
131
144
  if (changed.has('value') && !this.dirty)
132
145
  this.current = this.getAttribute('value');
133
146
  }
134
147
  update(changed) {
135
148
  if (changed.has('label')) {
136
- setText(this.ref('label'), String(this.props.label));
137
- this.internals.ariaLabel = String(this.props.label) || null;
149
+ setText(this.ref('label-text'), String(this.props.label));
150
+ this.nameGroup();
138
151
  }
139
152
  if (changed.has('required'))
140
153
  this.internals.ariaRequired = this.props.required ? 'true' : null;
@@ -144,13 +157,24 @@ export class NeuRadioGroup extends FormControlElement {
144
157
  this.sync();
145
158
  }
146
159
  connected() {
147
- this.observer ??= new MutationObserver(() => this.sync());
160
+ // Radios coming, going or changing value or disabled resync the group. Frameworks rewrite the
161
+ // slotted label's text in place, which fires no slotchange; that renames the group only.
162
+ this.observer ??= new MutationObserver((records) => {
163
+ const label = records.filter((record) => inSlotted(this, record.target));
164
+ if (label.length)
165
+ this.nameGroup();
166
+ if (records.some((r) => r.type !== 'characterData' && !label.includes(r)))
167
+ this.sync();
168
+ });
148
169
  this.observer.observe(this, {
149
170
  childList: true,
150
171
  subtree: true,
172
+ characterData: true,
151
173
  attributes: true,
152
174
  attributeFilter: ['disabled', 'value'],
153
175
  });
176
+ // The label may have changed while the group was detached and nothing was observing.
177
+ this.nameGroup();
154
178
  this.sync();
155
179
  }
156
180
  disconnected() {
@@ -1 +1 @@
1
- export declare const radioGroupStyles = "\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 {\n display: block; margin-block-end: 0.5rem;\n font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted);\n}\n.label:empty { display: none; }\n.items { display: flex; flex-direction: column; align-items: flex-start; gap: 0.75rem; }\n:host([orientation='horizontal']) .items { flex-direction: row; flex-wrap: wrap; align-items: center; }\n:host(:disabled) .items, :host([disabled]) .items { opacity: 0.55; cursor: not-allowed; }\n:host(:state(user-invalid)) .label { color: var(--_neu-danger); }\n";
1
+ export declare const radioGroupStyles = "\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 { display: block; font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted); }\n.label-text, ::slotted([slot='label']) { display: block; margin-block-end: 0.5rem; }\n.label-text:empty { display: none; }\n.items { display: flex; flex-direction: column; align-items: flex-start; gap: 0.75rem; }\n:host([orientation='horizontal']) .items { flex-direction: row; flex-wrap: wrap; align-items: center; }\n:host(:disabled) .items, :host([disabled]) .items { opacity: 0.55; cursor: not-allowed; }\n:host(:state(user-invalid)) .label { color: var(--_neu-danger); }\n";
@@ -3,11 +3,9 @@ export const radioGroupStyles = `${hostTheme}
3
3
  :host { display: block; }
4
4
  :host(:disabled), :host([disabled]) { cursor: not-allowed; }
5
5
  .base { display: flex; flex-direction: column; }
6
- .label {
7
- display: block; margin-block-end: 0.5rem;
8
- font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted);
9
- }
10
- .label:empty { display: none; }
6
+ .label { display: block; font-size: 0.8125rem; font-weight: 600; color: var(--_neu-fg-muted); }
7
+ .label-text, ::slotted([slot='label']) { display: block; margin-block-end: 0.5rem; }
8
+ .label-text:empty { display: none; }
11
9
  .items { display: flex; flex-direction: column; align-items: flex-start; gap: 0.75rem; }
12
10
  :host([orientation='horizontal']) .items { flex-direction: row; flex-wrap: wrap; align-items: center; }
13
11
  :host(:disabled) .items, :host([disabled]) .items { opacity: 0.55; cursor: not-allowed; }
@@ -6,7 +6,6 @@ export declare class NeuSelect extends FormControlElement {
6
6
  };
7
7
  readonly name: {
8
8
  readonly type: "string";
9
- readonly reflect: true;
10
9
  };
11
10
  readonly label: {
12
11
  readonly type: "string";
@@ -22,17 +21,14 @@ export declare class NeuSelect extends FormControlElement {
22
21
  };
23
22
  readonly required: {
24
23
  readonly type: "boolean";
25
- readonly reflect: true;
26
24
  };
27
25
  readonly disabled: {
28
26
  readonly type: "boolean";
29
- readonly reflect: true;
30
27
  };
31
28
  readonly size: {
32
29
  readonly type: "string";
33
30
  readonly default: "md";
34
31
  readonly values: readonly ["sm", "md", "lg"];
35
- readonly reflect: true;
36
32
  };
37
33
  }>;
38
34
  static extraProperties: string[];
@@ -81,7 +77,7 @@ export declare class NeuSelect extends FormControlElement {
81
77
  private activeIndex;
82
78
  /** Builds the listbox from the options and places it; the active option stays if it can. */
83
79
  private render;
84
- /** Makes option `i` the active one (none for -1) and scrolls it into view. */
80
+ /** Makes option `i` the active one (none for -1); `scroll` brings it into view. */
85
81
  private activate;
86
82
  /** Puts the listbox under the combobox at its width, or above it when that has more room. */
87
83
  private readonly place;
@@ -7,7 +7,6 @@ export declare const selectDef: import("../../core/define.js").ComponentDef<{
7
7
  };
8
8
  readonly name: {
9
9
  readonly type: "string";
10
- readonly reflect: true;
11
10
  };
12
11
  readonly label: {
13
12
  readonly type: "string";
@@ -23,16 +22,13 @@ export declare const selectDef: import("../../core/define.js").ComponentDef<{
23
22
  };
24
23
  readonly required: {
25
24
  readonly type: "boolean";
26
- readonly reflect: true;
27
25
  };
28
26
  readonly disabled: {
29
27
  readonly type: "boolean";
30
- readonly reflect: true;
31
28
  };
32
29
  readonly size: {
33
30
  readonly type: "string";
34
31
  readonly default: "md";
35
32
  readonly values: readonly ["sm", "md", "lg"];
36
- readonly reflect: true;
37
33
  };
38
34
  }>;
@@ -1,6 +1,5 @@
1
1
  import { defineComponent } from '../../core/define.js';
2
2
  import { html, unsafeHTML } from '../../core/html.js';
3
- import { themeCssProperties } from '../../core/styles.js';
4
3
  import { chevronDownBody, svgWrap } from '../icon/system-icons.js';
5
4
  import { selectStyles } from './select.styles.js';
6
5
  /** The option a select shows for `value`: the first one whose value matches, as in the client. */
@@ -12,14 +11,14 @@ export const selectDef = defineComponent({
12
11
  tag: 'neu-select',
13
12
  props: {
14
13
  value: { type: 'string' },
15
- name: { type: 'string', reflect: true },
14
+ name: { type: 'string' },
16
15
  label: { type: 'string' },
17
16
  placeholder: { type: 'string' },
18
17
  helpText: { type: 'string' },
19
18
  errorText: { type: 'string' },
20
- required: { type: 'boolean', reflect: true },
21
- disabled: { type: 'boolean', reflect: true },
22
- size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
19
+ required: { type: 'boolean' },
20
+ disabled: { type: 'boolean' },
21
+ size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'] },
23
22
  },
24
23
  delegatesFocus: true,
25
24
  form: { valueProp: 'value' },
@@ -31,29 +30,6 @@ export const selectDef = defineComponent({
31
30
  // Options the server could not read (e.g. a custom component's children) leave a set value's
32
31
  // display empty rather than showing the placeholder; update() fills it after hydration.
33
32
  const unread = !picked && p.value !== '' && ctx?.options?.length === 0;
34
- return html `<div part="base" class="base"><span part="label" class="label" id="label" data-ref="label">${p.label}</span><div part="combobox" class="combobox" id="combobox" role="combobox"${p.disabled ? html ` aria-disabled="true"` : html ` tabindex="0"`} aria-haspopup="listbox" aria-expanded="false" aria-controls="listbox" aria-labelledby="label" aria-describedby="help"${p.required ? html ` aria-required="true"` : ''} data-ref="combobox"><span part="display" class="display${picked || unread ? '' : ' placeholder'}" data-ref="display">${picked ? picked.label : unread ? '' : p.placeholder}</span><span part="chevron" class="chevron">${chevron}</span></div><div part="listbox" class="listbox" id="listbox" role="listbox" aria-labelledby="label" popover="auto" data-ref="listbox"></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>`;
35
- },
36
- docs: {
37
- summary: 'A single-value select (the APG select-only combobox): a raised trigger and a popover listbox built from the neu-option children, which are data only. The value attribute is the default value.',
38
- events: [
39
- { name: 'input', description: 'Fired when the user chooses an option with a new value.' },
40
- { name: 'change', description: 'Fired after input when the user chooses a new value.' },
41
- { name: 'neu-open', description: 'Fired when the listbox opens.' },
42
- { name: 'neu-close', description: 'Fired when the listbox closes.' },
43
- ],
44
- slots: [
45
- { name: 'help-text', description: 'Rich help text; replaces the help-text attribute.' },
46
- ],
47
- parts: [
48
- { name: 'base', description: 'Outer wrapper.' },
49
- { name: 'label', description: 'The label text.' },
50
- { name: 'combobox', description: 'The raised trigger that holds focus.' },
51
- { name: 'display', description: 'The selected label, or the placeholder.' },
52
- { name: 'chevron', description: 'The chevron icon.' },
53
- { name: 'listbox', description: 'The popover list of options.' },
54
- { name: 'option', description: 'One option in the listbox.' },
55
- { name: 'help-text', description: 'Help or error text area.' },
56
- ],
57
- cssProperties: themeCssProperties,
33
+ return html `<div part="base" class="base"><span part="label" class="label" id="label" data-ref="label"><slot name="label"><span class="label-text" data-ref="label-text">${p.label}</span></slot></span><div part="combobox" class="combobox" id="combobox" role="combobox"${p.disabled ? html ` aria-disabled="true"` : html ` tabindex="0"`} aria-haspopup="listbox" aria-expanded="false" aria-controls="listbox" aria-labelledby="label" aria-describedby="help"${p.required ? html ` aria-required="true"` : ''} data-ref="combobox"><span part="display" class="display${picked || unread ? '' : ' placeholder'}" data-ref="display">${picked ? picked.label : unread ? '' : p.placeholder}</span><span part="chevron" class="chevron">${chevron}</span></div><div part="listbox" class="listbox" id="listbox" role="listbox" aria-labelledby="label" popover="auto" data-ref="listbox"></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>`;
58
34
  },
59
35
  });
@@ -0,0 +1,2 @@
1
+ import type { ComponentDocs } from '../../core/define.js';
2
+ export declare const selectDocs: ComponentDocs;
@@ -0,0 +1,65 @@
1
+ import { themeCssProperties } from '../theme-docs.js';
2
+ export const selectDocs = {
3
+ summary: 'A single-value select (the APG select-only combobox): a raised trigger and a popover listbox built from the neu-option children, which are data only. The value attribute is the default value.',
4
+ descriptions: {
5
+ value: 'The value of the currently chosen option.',
6
+ name: 'The name under which the value is submitted with a form.',
7
+ label: 'Visible label text that names the select.',
8
+ placeholder: 'Text shown while no option is chosen.',
9
+ helpText: 'Guidance shown below the select; the help-text slot replaces it.',
10
+ errorText: "Shown in place of the browser's validation message once the select is invalid after user interaction; does not itself make the select invalid.",
11
+ required: 'Makes the form invalid until an option is chosen.',
12
+ disabled: 'Prevents interaction and leaves the select out of form submission.',
13
+ size: 'The select size.',
14
+ defaultValue: 'The value chosen at the start, which a form reset restores; set by the value attribute.',
15
+ },
16
+ events: [
17
+ { name: 'input', description: 'Fired when the user chooses an option with a new value.' },
18
+ { name: 'change', description: 'Fired after input when the user chooses a new value.' },
19
+ { name: 'neu-open', description: 'Fired when the listbox opens.' },
20
+ { name: 'neu-close', description: 'Fired when the listbox closes.' },
21
+ ],
22
+ slots: [
23
+ { name: 'label', description: 'Rich label; replaces the label attribute.' },
24
+ { name: 'help-text', description: 'Rich help text; replaces the help-text attribute.' },
25
+ ],
26
+ parts: [
27
+ { name: 'base', description: 'Outer wrapper.' },
28
+ { name: 'label', description: 'The label text.' },
29
+ { name: 'combobox', description: 'The raised trigger that holds focus.' },
30
+ { name: 'display', description: 'The selected label, or the placeholder.' },
31
+ { name: 'chevron', description: 'The chevron icon.' },
32
+ { name: 'listbox', description: 'The popover list of options.' },
33
+ { name: 'option', description: 'One option in the listbox.' },
34
+ { name: 'help-text', description: 'Help or error text area.' },
35
+ ],
36
+ cssProperties: themeCssProperties,
37
+ keyboard: [
38
+ { name: 'ArrowDown / ArrowUp / Enter / Space', description: 'When closed: opens the listbox.' },
39
+ {
40
+ name: 'Home / End',
41
+ description: 'When closed: opens the listbox on the first or last option.',
42
+ },
43
+ {
44
+ name: 'Printable characters',
45
+ description: 'When closed: opens the listbox on the first matching option.',
46
+ },
47
+ {
48
+ name: 'ArrowDown / ArrowUp',
49
+ description: 'When open: moves to the next or previous option.',
50
+ },
51
+ { name: 'Home / End', description: 'When open: moves to the first or last option.' },
52
+ { name: 'PageDown / PageUp', description: 'When open: moves ten options.' },
53
+ { name: 'Enter / Space', description: 'When open: chooses the active option and closes.' },
54
+ { name: 'Escape', description: 'When open: closes without changing the value.' },
55
+ {
56
+ name: 'Tab',
57
+ description: 'When open: chooses the active option, closes and moves focus on.',
58
+ },
59
+ { name: 'Alt+ArrowUp', description: 'When open: chooses the active option and closes.' },
60
+ {
61
+ name: 'Printable characters',
62
+ description: 'When open: moves to the matching option; characters typed within 500 ms add up.',
63
+ },
64
+ ],
65
+ };
@@ -1,4 +1,4 @@
1
- import { dispatch, setOptionalAttribute, setText } from '../../core/dom.js';
1
+ import { dispatch, inSlotted, setOptionalAttribute, setText } from '../../core/dom.js';
2
2
  import { FormControlElement } from '../../core/form-control.js';
3
3
  import { requiredMessage } from '../../core/validation.js';
4
4
  import { readOption } from '../option/option-data.js';
@@ -107,15 +107,15 @@ export class NeuSelect extends FormControlElement {
107
107
  const selected = picked ? this.opts.indexOf(picked) : -1;
108
108
  this.activate(kept >= 0 && !this.opts[kept].disabled ? kept : selected >= 0 ? selected : this.step(0, 1));
109
109
  }
110
- /** Makes option `i` the active one (none for -1) and scrolls it into view. */
111
- activate(i) {
110
+ /** Makes option `i` the active one (none for -1); `scroll` brings it into view. */
111
+ activate(i, scroll = true) {
112
112
  const listbox = this.ref('listbox');
113
113
  this.activeSource = this.opts[i]?.source;
114
114
  const items = [...listbox.children];
115
115
  items.forEach((item, n) => item.classList.toggle('active', n === i));
116
116
  const item = items[i];
117
117
  setOptionalAttribute(this.ref('combobox'), 'aria-activedescendant', item ? item.id : '');
118
- if (!item)
118
+ if (!item || !scroll)
119
119
  return;
120
120
  // Keep the listbox padding in view too, so Home and End scroll to the very top and bottom.
121
121
  const pad = items[0].offsetTop;
@@ -225,10 +225,12 @@ export class NeuSelect extends FormControlElement {
225
225
  this.activate(found);
226
226
  }
227
227
  onKey(event) {
228
- if (this.isDisabled || event.ctrlKey || event.metaKey)
228
+ const { key, altKey, ctrlKey } = event;
229
+ const printable = key.length === 1 && key !== ' ' && (!altKey || ctrlKey);
230
+ // Ctrl+Alt is AltGr on many layouts: a character it types is not a shortcut. Other Ctrl+Alt
231
+ // chords (assistive tech, the OS) are.
232
+ if (this.isDisabled || event.metaKey || (ctrlKey && !(altKey && printable)))
229
233
  return;
230
- const { key, altKey } = event;
231
- const printable = key.length === 1 && key !== ' ' && !altKey;
232
234
  const active = this.activeIndex();
233
235
  const last = this.opts.length - 1;
234
236
  const moves = {
@@ -297,7 +299,7 @@ export class NeuSelect extends FormControlElement {
297
299
  listbox.addEventListener('pointermove', (event) => {
298
300
  const i = this.optionAt(event);
299
301
  if (i >= 0 && i !== this.activeIndex() && !this.opts[i]?.disabled)
300
- this.activate(i);
302
+ this.activate(i, false);
301
303
  });
302
304
  listbox.addEventListener('click', (event) => {
303
305
  const i = this.optionAt(event);
@@ -326,7 +328,7 @@ export class NeuSelect extends FormControlElement {
326
328
  update(changed) {
327
329
  const combobox = this.ref('combobox');
328
330
  if (changed.has('label'))
329
- setText(this.ref('label'), String(this.props.label));
331
+ setText(this.ref('label-text'), String(this.props.label));
330
332
  if (changed.has('helpText'))
331
333
  setText(this.ref('help-text'), String(this.props.helpText));
332
334
  if (changed.has('required')) {
@@ -345,7 +347,11 @@ export class NeuSelect extends FormControlElement {
345
347
  this.commit();
346
348
  }
347
349
  connected() {
348
- this.observer ??= new MutationObserver(() => this.sync());
350
+ // A rich label or help text holds no options: rewriting it needs no re-read.
351
+ this.observer ??= new MutationObserver((records) => {
352
+ if (records.some((record) => !inSlotted(this, record.target)))
353
+ this.sync();
354
+ });
349
355
  this.observer.observe(this, {
350
356
  childList: true,
351
357
  subtree: true,
@@ -1 +1 @@
1
- export declare const selectStyles = "\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.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:empty { display: none; }\n.combobox {\n display: flex; align-items: center; gap: 0.5rem; min-block-size: 2.75rem; padding-inline: 0.875rem 0.75rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-raised-sm);\n font-size: 0.9375rem; cursor: pointer; user-select: none; outline: none;\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .combobox { min-block-size: 2.25rem; padding-inline: 0.75rem 0.625rem; font-size: 0.875rem; }\n:host([size='lg']) .combobox { min-block-size: 3.25rem; padding-inline: 1rem 0.875rem; font-size: 1rem; }\n.combobox[aria-expanded='true'] { box-shadow: var(--_neu-inset-sm); }\n.combobox:focus-visible { box-shadow: var(--_neu-raised-sm), var(--_neu-focus); }\n.combobox[aria-expanded='true']:focus-visible { box-shadow: var(--_neu-inset-sm), var(--_neu-focus); }\n:host(:state(user-invalid)) .combobox {\n box-shadow:\n calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) color-mix(in oklab, var(--_neu-danger) 45%, var(--_neu-sd)),\n calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n}\n:host(:disabled) .combobox, :host([disabled]) .combobox { opacity: 0.6; cursor: not-allowed; }\n.display { flex: 1; min-inline-size: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.placeholder { color: var(--_neu-fg-muted); }\n.chevron { display: inline-flex; flex: none; color: var(--_neu-fg-muted); transition: transform var(--_neu-dur) ease; }\n.chevron svg { inline-size: 1rem; block-size: 1rem; }\n.combobox[aria-expanded='true'] .chevron { transform: rotate(180deg); }\n.listbox {\n position: fixed; inset: auto; margin: 0; max-block-size: 16rem; overflow: auto;\n padding: 0.375rem; border: none; border-radius: var(--_neu-r);\n background: var(--_neu-bg); color: var(--_neu-fg); box-shadow: var(--_neu-raised); font-size: 0.9375rem;\n}\n.option { padding: 0.5rem 0.625rem; border-radius: calc(var(--_neu-r) * 0.75); cursor: pointer; user-select: none; }\n.option.active { box-shadow: var(--_neu-inset-sm); }\n.option[aria-selected='true'] { color: var(--_neu-accent-c); font-weight: 600; }\n.option[aria-disabled='true'] { color: var(--_neu-fg-muted); cursor: not-allowed; }\n";
1
+ export declare const selectStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: block; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: flex; flex-direction: column; }\n.label { font-size: 0.8125rem; font-weight: 600; line-height: 1rem; color: var(--_neu-fg-muted); }\n.label-text, ::slotted([slot='label']) { display: block; margin-block-end: 0.375rem; }\n.label-text:empty { display: none; }\n.control {\n display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }\n:host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }\n:host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }\n:host(:state(user-invalid)) .control {\n box-shadow:\n inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),\n inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n}\n:host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }\n.input {\n flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;\n background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;\n}\n.input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }\n.help { font-size: 0.75rem; color: var(--_neu-fg-muted); }\n.help-text, ::slotted([slot='help-text']) { display: block; margin-block-start: 0.375rem; }\n.help-text:empty { display: none; }\n.error-text { display: none; margin-block-start: 0.375rem; color: var(--_neu-danger); }\n:host(:state(user-invalid)) slot[name='help-text'] { display: none; }\n:host(:state(user-invalid)) .error-text:not(:empty) { display: block; }\n\n.label { display: block; }\n.combobox {\n display: flex; align-items: center; gap: 0.5rem; min-block-size: 2.75rem; padding-inline: 0.875rem 0.75rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-raised-sm);\n font-size: 0.9375rem; cursor: pointer; user-select: none; outline: none;\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .combobox { min-block-size: 2.25rem; padding-inline: 0.75rem 0.625rem; font-size: 0.875rem; }\n:host([size='lg']) .combobox { min-block-size: 3.25rem; padding-inline: 1rem 0.875rem; font-size: 1rem; }\n.combobox[aria-expanded='true'] { box-shadow: var(--_neu-inset-sm); }\n.combobox:focus-visible { box-shadow: var(--_neu-raised-sm), var(--_neu-focus); }\n.combobox[aria-expanded='true']:focus-visible { box-shadow: var(--_neu-inset-sm), var(--_neu-focus); }\n:host(:state(user-invalid)) .combobox {\n box-shadow:\n calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) color-mix(in oklab, var(--_neu-danger) 45%, var(--_neu-sd)),\n calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n}\n:host(:disabled) .combobox, :host([disabled]) .combobox { opacity: 0.6; cursor: not-allowed; }\n.display { flex: 1; min-inline-size: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n.placeholder { color: var(--_neu-fg-muted); }\n.chevron { display: inline-flex; flex: none; color: var(--_neu-fg-muted); transition: transform var(--_neu-dur) ease; }\n.chevron svg { inline-size: 1rem; block-size: 1rem; }\n.combobox[aria-expanded='true'] .chevron { transform: rotate(180deg); }\n.listbox {\n position: fixed; inset: auto; margin: 0; max-block-size: 16rem; overflow: auto;\n padding: 0.375rem; border: none; border-radius: var(--_neu-r);\n background: var(--_neu-bg); color: var(--_neu-fg); box-shadow: var(--_neu-raised); font-size: 0.9375rem;\n}\n.option { padding: 0.5rem 0.625rem; border-radius: calc(var(--_neu-r) * 0.75); cursor: pointer; user-select: none; }\n.option.active { box-shadow: var(--_neu-inset-sm); }\n.option[aria-selected='true'] { color: var(--_neu-accent-c); font-weight: 600; }\n.option[aria-disabled='true'] { color: var(--_neu-fg-muted); cursor: not-allowed; }\n";
@@ -1,10 +1,6 @@
1
1
  import { fieldStyles } from '../shared/field.styles.js';
2
2
  export const selectStyles = `${fieldStyles}
3
- .label {
4
- display: block; margin-block-end: 0.375rem;
5
- font-size: 0.8125rem; font-weight: 600; line-height: 1rem; color: var(--_neu-fg-muted);
6
- }
7
- .label:empty { display: none; }
3
+ .label { display: block; }
8
4
  .combobox {
9
5
  display: flex; align-items: center; gap: 0.5rem; min-block-size: 2.75rem; padding-inline: 0.875rem 0.75rem;
10
6
  border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-raised-sm);
@@ -1 +1 @@
1
- export declare const fieldStyles = "\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";
1
+ export declare const fieldStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: block; }\n:host(:disabled), :host([disabled]) { cursor: not-allowed; }\n.base { display: flex; flex-direction: column; }\n.label { font-size: 0.8125rem; font-weight: 600; line-height: 1rem; color: var(--_neu-fg-muted); }\n.label-text, ::slotted([slot='label']) { display: block; margin-block-end: 0.375rem; }\n.label-text:empty { display: none; }\n.control {\n display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;\n border-radius: var(--_neu-r); background: var(--_neu-bg); box-shadow: var(--_neu-inset);\n transition: box-shadow var(--_neu-dur) ease;\n}\n:host([size='sm']) .control { min-block-size: 2.25rem; padding-inline: 0.75rem; box-shadow: var(--_neu-inset-sm); }\n:host([size='lg']) .control { min-block-size: 3.25rem; padding-inline: 1rem; }\n:host(:focus-within) .control { box-shadow: var(--_neu-inset), var(--_neu-focus); }\n:host(:state(user-invalid)) .control {\n box-shadow:\n inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) color-mix(in oklab, var(--_neu-danger) 35%, var(--_neu-sd)),\n inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n}\n:host(:disabled) .control, :host([disabled]) .control { box-shadow: var(--_neu-inset-sm); opacity: 0.6; }\n.input {\n flex: 1; min-inline-size: 0; align-self: stretch; padding: 0; border: none; outline: none;\n background: transparent; color: inherit; font: inherit; font-size: 0.9375rem;\n}\n.input::placeholder { color: var(--_neu-fg-muted); opacity: 1; }\n.help { font-size: 0.75rem; color: var(--_neu-fg-muted); }\n.help-text, ::slotted([slot='help-text']) { display: block; margin-block-start: 0.375rem; }\n.help-text:empty { display: none; }\n.error-text { display: none; margin-block-start: 0.375rem; color: var(--_neu-danger); }\n:host(:state(user-invalid)) slot[name='help-text'] { display: none; }\n:host(:state(user-invalid)) .error-text:not(:empty) { display: block; }\n";
@@ -3,10 +3,8 @@ export const fieldStyles = `${hostTheme}
3
3
  :host { display: block; }
4
4
  :host(:disabled), :host([disabled]) { cursor: not-allowed; }
5
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; line-height: 1rem; color: var(--_neu-fg-muted);
9
- }
6
+ .label { font-size: 0.8125rem; font-weight: 600; line-height: 1rem; color: var(--_neu-fg-muted); }
7
+ .label-text, ::slotted([slot='label']) { display: block; margin-block-end: 0.375rem; }
10
8
  .label-text:empty { display: none; }
11
9
  .control {
12
10
  display: flex; align-items: center; min-block-size: 2.75rem; padding-inline: 0.875rem;
@@ -19,14 +19,12 @@ export declare class NeuSlider extends FormControlElement {
19
19
  };
20
20
  readonly name: {
21
21
  readonly type: "string";
22
- readonly reflect: true;
23
22
  };
24
23
  readonly label: {
25
24
  readonly type: "string";
26
25
  };
27
26
  readonly disabled: {
28
27
  readonly type: "boolean";
29
- readonly reflect: true;
30
28
  };
31
29
  }>;
32
30
  static extraProperties: string[];
@@ -27,13 +27,11 @@ export declare const sliderDef: import("../../core/define.js").ComponentDef<{
27
27
  };
28
28
  readonly name: {
29
29
  readonly type: "string";
30
- readonly reflect: true;
31
30
  };
32
31
  readonly label: {
33
32
  readonly type: "string";
34
33
  };
35
34
  readonly disabled: {
36
35
  readonly type: "boolean";
37
- readonly reflect: true;
38
36
  };
39
37
  }>;
@@ -1,6 +1,5 @@
1
1
  import { defineComponent } from '../../core/define.js';
2
2
  import { html } from '../../core/html.js';
3
- import { themeCssProperties } from '../../core/styles.js';
4
3
  import { stepUnit } from '../shared/step.js';
5
4
  import { sliderStyles } from './slider.styles.js';
6
5
  /** Decimal places of a number as written, exponent notation included. */
@@ -44,33 +43,12 @@ export const sliderDef = defineComponent({
44
43
  min: { type: 'number', default: 0 },
45
44
  max: { type: 'number', default: 100 },
46
45
  step: { type: 'number', default: 1 },
47
- name: { type: 'string', reflect: true },
46
+ name: { type: 'string' },
48
47
  label: { type: 'string' },
49
- disabled: { type: 'boolean', reflect: true },
48
+ disabled: { type: 'boolean' },
50
49
  },
51
50
  form: { valueProp: 'value' },
52
51
  properties: { valueFormatter: '(value: number) => string' },
53
52
  styles: sliderStyles,
54
53
  template: (p) => html `<span part="base" class="base" data-ref="base" style="${sliderStyle(sliderValue(p.value, p.min, p.max, p.step), p.min, p.max)}"><span part="track" class="track" data-ref="track"><span part="fill" class="fill"></span><span part="thumb" class="thumb"></span></span></span>`,
55
- docs: {
56
- summary: 'A horizontal slider for a number. The value attribute is the default; the value property is the current value. The valueFormatter property turns the value into the text a screen reader announces.',
57
- events: [
58
- {
59
- name: 'input',
60
- description: 'Fired on every change while the user drags or presses a key.',
61
- },
62
- {
63
- name: 'change',
64
- description: 'Fired after the user commits a change: on release, or per key.',
65
- },
66
- ],
67
- slots: [],
68
- parts: [
69
- { name: 'base', description: 'Wrapper around the track.' },
70
- { name: 'track', description: 'The inset groove.' },
71
- { name: 'fill', description: 'The part of the track up to the thumb.' },
72
- { name: 'thumb', description: 'The raised handle.' },
73
- ],
74
- cssProperties: themeCssProperties,
75
- },
76
54
  });
@@ -0,0 +1,2 @@
1
+ import type { ComponentDocs } from '../../core/define.js';
2
+ export declare const sliderDocs: ComponentDocs;