@marianmeres/stuic 3.187.0 → 3.188.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
@@ -467,6 +467,10 @@ File upload input.
467
467
 
468
468
  Form-wrapped switch toggle.
469
469
 
470
+ #### `FieldColorPicker`
471
+
472
+ Form-wrapped `ColorPicker`: visible label (names the swatch group via `aria-labelledby`), description, validation box, label-left layout.
473
+
470
474
  #### `FieldOptions`
471
475
 
472
476
  Multi-select options field.
package/README.md CHANGED
@@ -175,11 +175,11 @@ AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, Header,
175
175
 
176
176
  ### Forms & Inputs
177
177
 
178
- FieldInput, FieldMoney, FieldDate, FieldDateRange, Calendar, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldSingleAsset, FieldOptions, FieldKeyValues, FieldTable, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
178
+ FieldInput, FieldMoney, FieldDate, FieldDateRange, Calendar, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldSingleAsset, FieldOptions, FieldKeyValues, FieldTable, FieldObject, FieldSwitch, FieldColorPicker, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
179
179
 
180
180
  ### Buttons & Controls
181
181
 
182
- Button, ButtonGroupRadio, Switch, Slider, RangeSlider, TwCheck, ListItemButton, X
182
+ Button, ButtonGroupRadio, Switch, ColorPicker, Slider, RangeSlider, TwCheck, ListItemButton, X
183
183
 
184
184
  ### Feedback & Notifications
185
185
 
package/dist/README.md CHANGED
@@ -46,6 +46,7 @@ npm install @marianmeres/stuic
46
46
  - **FieldOptions** - Modal-based multi-select picker (optional inline `chips` display)
47
47
  - **FieldKeyValues** - Key-value pairs editor with JSON serialization
48
48
  - **FieldSwitch** - Toggle switch within a form
49
+ - **FieldColorPicker** - Color picker (swatches + custom color) within a form
49
50
  - **Fieldset** - Group of form fields with legend
50
51
 
51
52
  ### Buttons & Controls
@@ -49,6 +49,13 @@
49
49
  disabled?: boolean;
50
50
  /** Accessible name of the swatch group (default `t("color")`, "Color") */
51
51
  label?: string;
52
+ /**
53
+ * Id of an element that names the swatch group (`aria-labelledby`). When set,
54
+ * it replaces the group's `aria-label` — what `FieldColorPicker` uses to let
55
+ * its visible label name the swatches. `...rest` cannot do this: it lands on
56
+ * the root, not on the radiogroup.
57
+ */
58
+ labelledby?: string;
52
59
  /** Form field name (hidden input) */
53
60
  name?: string;
54
61
  /** Require a non-empty value (enforced by the built-in validator) */
@@ -92,6 +99,7 @@
92
99
  allowClear = true,
93
100
  disabled = false,
94
101
  label,
102
+ labelledby,
95
103
  name,
96
104
  required = false,
97
105
  t = t_default,
@@ -323,6 +331,22 @@
323
331
  export function getValidation(): ValidationResult | undefined {
324
332
  return _validation;
325
333
  }
334
+
335
+ /**
336
+ * Focus the group's tab stop (the checked swatch, or the first one), or —
337
+ * with no swatches rendered — the first custom-color control.
338
+ */
339
+ export function focus(): void {
340
+ (
341
+ el?.querySelector<HTMLElement>(`[role="radiogroup"] [tabindex="0"]`) ??
342
+ el?.querySelector<HTMLElement>(`input:not([type="hidden"])`)
343
+ )?.focus();
344
+ }
345
+
346
+ /** Scroll the picker into view. Defaults to smooth + center. */
347
+ export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
348
+ el?.scrollIntoView?.({ behavior: "smooth", block: "center", ...opts });
349
+ }
326
350
  </script>
327
351
 
328
352
  <div
@@ -340,7 +364,8 @@
340
364
  <div
341
365
  class={unstyled ? undefined : "stuic-color-picker-swatches"}
342
366
  role="radiogroup"
343
- aria-label={label || t("color", null, "Color")}
367
+ aria-labelledby={labelledby || undefined}
368
+ aria-label={labelledby ? undefined : label || t("color", null, "Color")}
344
369
  aria-required={required ? "true" : undefined}
345
370
  aria-disabled={disabled ? "true" : undefined}
346
371
  aria-invalid={_validation && !_validation.valid ? "true" : undefined}
@@ -40,6 +40,13 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" |
40
40
  disabled?: boolean;
41
41
  /** Accessible name of the swatch group (default `t("color")`, "Color") */
42
42
  label?: string;
43
+ /**
44
+ * Id of an element that names the swatch group (`aria-labelledby`). When set,
45
+ * it replaces the group's `aria-label` — what `FieldColorPicker` uses to let
46
+ * its visible label name the swatches. `...rest` cannot do this: it lands on
47
+ * the root, not on the radiogroup.
48
+ */
49
+ labelledby?: string;
43
50
  /** Form field name (hidden input) */
44
51
  name?: string;
45
52
  /** Require a non-empty value (enforced by the built-in validator) */
@@ -70,6 +77,8 @@ declare const ColorPicker: import("svelte").Component<Props, {
70
77
  validate: () => ValidationResult | undefined;
71
78
  clearValidation: () => void;
72
79
  getValidation: () => ValidationResult | undefined;
80
+ focus: () => void;
81
+ scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
73
82
  }, "el" | "value" | "inputEl">;
74
83
  type ColorPicker = ReturnType<typeof ColorPicker>;
75
84
  export default ColorPicker;
@@ -16,8 +16,12 @@ own full-screen color UI, so there is no anchored popover to fight the on-screen
16
16
  keyboard. Swatches grow to a ~44px hit target on a coarse pointer, and the hex field
17
17
  carries the iOS zoom guard.
18
18
 
19
+ In a form layout, use [`FieldColorPicker`](../Input/README.md#color-picker): this picker
20
+ inside the standard field shell (visible label, description, validation box, label-left
21
+ layout).
22
+
19
23
  Adjacent but different: `Rating` (the same input-with-hidden-input shape),
20
- `FieldSelect` (a labeled field wrapper), `ThemePreview` (theme token swatches).
24
+ `ThemePreview` (theme token swatches).
21
25
 
22
26
  ## Props
23
27
 
@@ -29,7 +33,8 @@ Adjacent but different: `Rating` (the same input-with-hidden-input shape),
29
33
  | `custom` | `"both" \| "native" \| "text" \| false` | `"both"` | Which custom-color controls to render under the palette |
30
34
  | `allowClear` | `boolean` | `true` | A crossed-out "no color" swatch, Delete / Backspace, and an emptied hex field all set `""` |
31
35
  | `disabled` | `boolean` | `false` | No interaction; the hidden input is disabled too (nothing submits) |
32
- | `label` | `string` | `"Color"` | Accessible name of the swatch group (via `t("color")` by default) |
36
+ | `label` | `string` | `"Color"` | Accessible name of the swatch group (via `t("color")` by default). Not a visible label — see `FieldColorPicker` |
37
+ | `labelledby` | `string` | — | Id of the element naming the swatch group (`aria-labelledby`); when set, replaces `label`'s `aria-label` |
33
38
  | `name` | `string` | — | Form field name of the hidden input |
34
39
  | `required` | `boolean` | `false` | Require a non-empty value — enforced by the built-in validator (hidden inputs skip native constraint validation) |
35
40
  | `t` | `TranslateFn` | English | i18n translate function (see below) |
@@ -46,11 +51,13 @@ Other attributes (`id`, `style`, `data-*`, …) are passed to the root `div`.
46
51
 
47
52
  ### Imperative API (via `bind:this`)
48
53
 
49
- | Method | Description |
50
- | ------------------- | -------------------------------------------------------------------- |
51
- | `validate()` | Trigger validation now; returns the `ValidationResult` |
52
- | `clearValidation()` | Clear the stored result and the hidden input's custom validity |
53
- | `getValidation()` | The last validation result (also reported via `setValidationResult`) |
54
+ | Method | Description |
55
+ | ------------------- | ----------------------------------------------------------------------- |
56
+ | `validate()` | Trigger validation now; returns the `ValidationResult` |
57
+ | `clearValidation()` | Clear the stored result and the hidden input's custom validity |
58
+ | `getValidation()` | The last validation result (also reported via `setValidationResult`) |
59
+ | `focus()` | Focus the checked swatch (the group's tab stop), else the first control |
60
+ | `scrollIntoView()` | Scroll the picker into view (defaults: `smooth` + `center`) |
54
61
 
55
62
  ## Usage
56
63
 
@@ -114,6 +121,9 @@ resolve to near-identical greys.
114
121
  </form>
115
122
  ```
116
123
 
124
+ A bare picker only reports the result (`setValidationResult`, `aria-invalid`); it renders no
125
+ message. `FieldColorPicker` shows it in the field's validation box.
126
+
117
127
  ## Preview vs commit
118
128
 
119
129
  `value` updates **live** while the native picker is being dragged and while a valid
@@ -144,6 +154,8 @@ value when `allowClear` is on.
144
154
  Up / Down step by one **rendered** row (measured from the layout, so it stays right
145
155
  when a narrow screen wraps to fewer per row) and stop at the edges.
146
156
  `aria-required` / `aria-invalid` sit on the group.
157
+ - The group is named by `label` (an `aria-label`, default "Color") or, when `labelledby`
158
+ is set, by that element instead — never both.
147
159
  - Every swatch is named: an entry's `label` (through `t`) or, without one, its color
148
160
  string. The clear swatch is "No color".
149
161
  - Selection is signalled by a ring drawn outside the swatch, not only by color.
@@ -0,0 +1,201 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from "svelte";
3
+ import type { ValidateOptions } from "../../actions/validate.svelte.js";
4
+ import type { TranslateFn } from "../../types.js";
5
+ import type { ColorPickerCustom } from "../ColorPicker/ColorPicker.svelte";
6
+ import type { ColorPickerSwatch } from "../ColorPicker/palettes.js";
7
+ import type { THC } from "../Thc/Thc.svelte";
8
+ import type { InputWrapClassProps } from "./types.js";
9
+
10
+ type SnippetWithId = Snippet<[{ id: string }]>;
11
+
12
+ export interface Props extends InputWrapClassProps, Record<string, any> {
13
+ /** Current color (bindable). Any CSS color string; `""` = no color */
14
+ value?: string;
15
+ label?: SnippetWithId | THC;
16
+ description?: SnippetWithId | THC;
17
+ class?: string;
18
+ id?: string;
19
+ renderSize?: "sm" | "md" | "lg" | string;
20
+ required?: boolean;
21
+ disabled?: boolean;
22
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
23
+ labelAfter?: SnippetWithId | THC;
24
+ inputBefore?: SnippetWithId | THC;
25
+ inputAfter?: SnippetWithId | THC;
26
+ inputBelow?: SnippetWithId | THC;
27
+ below?: SnippetWithId | THC;
28
+ labelLeft?: boolean;
29
+ labelLeftWidth?: "normal" | "wide";
30
+ labelLeftBreakpoint?: number;
31
+ /** Classes for the underlying <ColorPicker> root */
32
+ classInput?: string;
33
+ style?: string;
34
+ //
35
+ // Below: forwarded as-is to the underlying <ColorPicker>.
36
+ //
37
+ /** The swatches: color strings or `{ value, label }` objects */
38
+ palette?: ColorPickerSwatch[];
39
+ /** Cap the palette at this many swatches per row */
40
+ columns?: number;
41
+ /** Which custom-color controls to show (default `"both"`) */
42
+ custom?: ColorPickerCustom;
43
+ /** Offer clearing (the "no color" swatch, Delete / Backspace) */
44
+ allowClear?: boolean;
45
+ /** Form field name (hidden input) */
46
+ name?: string;
47
+ /** i18n translate function (see `createColorPickerT`) */
48
+ t?: TranslateFn;
49
+ /** Fires when the user commits a color (not while dragging the native picker) */
50
+ onchange?: (value: string) => void;
51
+ /** Class for every swatch button */
52
+ classSwatch?: string;
53
+ }
54
+ </script>
55
+
56
+ <script lang="ts">
57
+ import type { ValidationResult } from "../../actions/validate.svelte.js";
58
+ import { getId } from "../../utils/get-id.js";
59
+ import { twMerge } from "../../utils/tw-merge.js";
60
+ import ColorPicker from "../ColorPicker/ColorPicker.svelte";
61
+ import InputWrap from "./_internal/InputWrap.svelte";
62
+
63
+ let {
64
+ value = $bindable(""),
65
+ label = "",
66
+ id = getId(),
67
+ description,
68
+ class: classProp,
69
+ renderSize = "md",
70
+ //
71
+ required = false,
72
+ disabled = false,
73
+ //
74
+ // Renamed local binding to avoid collision with `export function validate()` below.
75
+ validate: validateProp,
76
+ //
77
+ labelAfter,
78
+ inputBefore,
79
+ inputAfter,
80
+ inputBelow,
81
+ below,
82
+ //
83
+ labelLeft = false,
84
+ labelLeftWidth = "normal",
85
+ labelLeftBreakpoint = 480,
86
+ //
87
+ classInput,
88
+ //
89
+ palette,
90
+ columns,
91
+ custom,
92
+ allowClear,
93
+ name,
94
+ t,
95
+ onchange,
96
+ classSwatch,
97
+ //
98
+ classLabel,
99
+ classLabelBox,
100
+ classInputBox,
101
+ classInputBoxWrap,
102
+ classInputBoxWrapInvalid,
103
+ classDescBox,
104
+ classDescBoxToggle,
105
+ classBelowBox,
106
+ classValidationBox,
107
+ style = "",
108
+ //
109
+ ...rest
110
+ }: Props = $props();
111
+
112
+ //
113
+ let validation: ValidationResult | undefined = $state();
114
+ const setValidationResult = (res: ValidationResult) => (validation = res);
115
+
116
+ // Delegate the imperative API to the inner ColorPicker.
117
+ let pickerRef: ColorPicker | undefined = $state();
118
+
119
+ /** Trigger validation now. Renders the inline message if invalid. */
120
+ export function validate(): ValidationResult | undefined {
121
+ pickerRef?.validate();
122
+ return validation;
123
+ }
124
+
125
+ /** Clear the inline validation message. */
126
+ export function clearValidation(): void {
127
+ pickerRef?.clearValidation?.();
128
+ validation = undefined;
129
+ }
130
+
131
+ /** Current validation state. */
132
+ export function getValidation(): ValidationResult | undefined {
133
+ return validation;
134
+ }
135
+
136
+ /** Focus the swatch group's tab stop (or the first custom-color control). */
137
+ export function focus(): void {
138
+ pickerRef?.focus?.();
139
+ }
140
+
141
+ /** Scroll the field into view. Defaults to smooth + center. */
142
+ export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
143
+ pickerRef?.scrollIntoView?.(opts);
144
+ }
145
+ </script>
146
+
147
+ <InputWrap
148
+ {description}
149
+ class={classProp}
150
+ size={renderSize}
151
+ {id}
152
+ {label}
153
+ {labelAfter}
154
+ {inputBefore}
155
+ {inputAfter}
156
+ {inputBelow}
157
+ {below}
158
+ {required}
159
+ {disabled}
160
+ {labelLeft}
161
+ {labelLeftWidth}
162
+ {labelLeftBreakpoint}
163
+ {classLabel}
164
+ {classLabelBox}
165
+ {classInputBox}
166
+ {classInputBoxWrapInvalid}
167
+ {classDescBox}
168
+ {classDescBoxToggle}
169
+ {classBelowBox}
170
+ {classValidationBox}
171
+ {validation}
172
+ classInputBoxWrap={twMerge("input-wrap-transparent", classInputBoxWrap)}
173
+ {style}
174
+ >
175
+ <!--
176
+ `labelledby` (not the InputWrap's `for={id}`) is what names the swatch group: a
177
+ radiogroup is a <div>, which a `for` cannot label. Matches InputWrap's own
178
+ `{id}-label`, and only when there is a label to point at — otherwise the picker
179
+ keeps its own `t("color")` name. The picker's string `label` prop is deliberately
180
+ not forwarded: here `label` is the visible one.
181
+ -->
182
+ <ColorPicker
183
+ bind:this={pickerRef}
184
+ bind:value
185
+ {palette}
186
+ {columns}
187
+ {custom}
188
+ {allowClear}
189
+ {name}
190
+ {t}
191
+ {onchange}
192
+ {classSwatch}
193
+ {required}
194
+ {disabled}
195
+ class={classInput}
196
+ labelledby={label ? `${id}-label` : undefined}
197
+ validate={validateProp}
198
+ {setValidationResult}
199
+ {...rest}
200
+ />
201
+ </InputWrap>
@@ -0,0 +1,59 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ValidateOptions } from "../../actions/validate.svelte.js";
3
+ import type { TranslateFn } from "../../types.js";
4
+ import type { ColorPickerCustom } from "../ColorPicker/ColorPicker.svelte";
5
+ import type { ColorPickerSwatch } from "../ColorPicker/palettes.js";
6
+ import type { THC } from "../Thc/Thc.svelte";
7
+ import type { InputWrapClassProps } from "./types.js";
8
+ type SnippetWithId = Snippet<[{
9
+ id: string;
10
+ }]>;
11
+ export interface Props extends InputWrapClassProps, Record<string, any> {
12
+ /** Current color (bindable). Any CSS color string; `""` = no color */
13
+ value?: string;
14
+ label?: SnippetWithId | THC;
15
+ description?: SnippetWithId | THC;
16
+ class?: string;
17
+ id?: string;
18
+ renderSize?: "sm" | "md" | "lg" | string;
19
+ required?: boolean;
20
+ disabled?: boolean;
21
+ validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
22
+ labelAfter?: SnippetWithId | THC;
23
+ inputBefore?: SnippetWithId | THC;
24
+ inputAfter?: SnippetWithId | THC;
25
+ inputBelow?: SnippetWithId | THC;
26
+ below?: SnippetWithId | THC;
27
+ labelLeft?: boolean;
28
+ labelLeftWidth?: "normal" | "wide";
29
+ labelLeftBreakpoint?: number;
30
+ /** Classes for the underlying <ColorPicker> root */
31
+ classInput?: string;
32
+ style?: string;
33
+ /** The swatches: color strings or `{ value, label }` objects */
34
+ palette?: ColorPickerSwatch[];
35
+ /** Cap the palette at this many swatches per row */
36
+ columns?: number;
37
+ /** Which custom-color controls to show (default `"both"`) */
38
+ custom?: ColorPickerCustom;
39
+ /** Offer clearing (the "no color" swatch, Delete / Backspace) */
40
+ allowClear?: boolean;
41
+ /** Form field name (hidden input) */
42
+ name?: string;
43
+ /** i18n translate function (see `createColorPickerT`) */
44
+ t?: TranslateFn;
45
+ /** Fires when the user commits a color (not while dragging the native picker) */
46
+ onchange?: (value: string) => void;
47
+ /** Class for every swatch button */
48
+ classSwatch?: string;
49
+ }
50
+ import type { ValidationResult } from "../../actions/validate.svelte.js";
51
+ declare const FieldColorPicker: import("svelte").Component<Props, {
52
+ validate: () => ValidationResult | undefined;
53
+ clearValidation: () => void;
54
+ getValidation: () => ValidationResult | undefined;
55
+ focus: () => void;
56
+ scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
57
+ }, "value">;
58
+ type FieldColorPicker = ReturnType<typeof FieldColorPicker>;
59
+ export default FieldColorPicker;
@@ -15,6 +15,7 @@ A comprehensive form input system with multiple field components, validation sup
15
15
  | `FieldCheckbox` | Single checkbox with label |
16
16
  | `FieldRadios` | Radio button group |
17
17
  | `FieldSwitch` | Toggle switch field |
18
+ | `FieldColorPicker` | Color picker (swatches + custom color) field — see below |
18
19
  | `FieldFile` | File upload input |
19
20
  | `FieldAssets` | Asset/image upload with preview |
20
21
  | `FieldSingleAsset` | One asset (avatar, logo, cover, one document) — see below |
@@ -184,6 +185,50 @@ The visible label names the control via `aria-labelledby` (the switch is announc
184
185
  `switch, on/off` with that name). Clicking the label text does not toggle — the switch's
185
186
  own root is a `<label>`, so an HTML `for` association is not possible; click the switch.
186
187
 
188
+ ### Color Picker
189
+
190
+ `FieldColorPicker` wraps a [`ColorPicker`](../ColorPicker/README.md) in the standard field
191
+ scaffolding — visible label, description, validation box, `labelLeft*`, every
192
+ `InputWrapClassProps` member — the way `FieldSwitch` wraps `Switch`.
193
+
194
+ ```svelte
195
+ <script lang="ts">
196
+ import { FieldColorPicker } from "stuic";
197
+
198
+ let accent = $state("");
199
+ </script>
200
+
201
+ <FieldColorPicker
202
+ label="Page colour"
203
+ description="Used for buttons and links on the public page."
204
+ name="accent"
205
+ required
206
+ bind:value={accent}
207
+ />
208
+ ```
209
+
210
+ | Prop | Goes to |
211
+ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
212
+ | `value` (bindable), `palette`, `columns`, `custom`, `allowClear`, `name`, `t`, `onchange` | the inner `<ColorPicker>` (same semantics, same defaults) |
213
+ | `classSwatch` | every swatch button |
214
+ | `classInput` | the inner `<ColorPicker>`'s root class |
215
+ | `required`, `disabled`, `validate` | both: the shell (asterisk, dimming, message) and the picker |
216
+ | `renderSize` | the surrounding field shell only — swatch size is a CSS token |
217
+ | everything else unrecognized (`...rest`) | the inner `<ColorPicker>` root |
218
+
219
+ - **Naming.** The visible label names the swatch radiogroup via `aria-labelledby` →
220
+ `{id}-label`, with no competing `aria-label`. Without a `label` the group keeps the
221
+ picker's own `t("color")` name. Clicking the label text focuses nothing (a radiogroup is
222
+ not labelable by `for`) — same as `FieldSwitch`. With `palette={[]}` and
223
+ `allowClear={false}` there is no radiogroup, so the label names nothing; the hex field
224
+ keeps its own "Hex value" name.
225
+ - **Validation.** The picker's hidden input runs the `validate` action; the result renders
226
+ in the field's validation box. `validate()`, `clearValidation()`, `getValidation()`,
227
+ `focus()` (the checked swatch — the group's tab stop) and `scrollIntoView()` are
228
+ available via `bind:this`, so `validateAllFields` / `scrollToFirstInvalidField` work.
229
+ - **Layout.** The input wrap is transparent (no border / focus ring — the picker draws its
230
+ own swatches and hex field). A disabled field dims once (the shell), not twice.
231
+
187
232
  ### Input with Addons
188
233
 
189
234
  ```svelte
@@ -144,12 +144,18 @@
144
144
  /* ============================================================================
145
145
  BASE INPUT ELEMENT STYLES
146
146
  Replaces @tailwindcss/forms plugin for input elements
147
+
148
+ `:where(:not(.stuic-color-picker *))` (here, in the size variants and in the
149
+ iOS zoom guard) exempts a composite control that styles its own raw inputs:
150
+ FieldColorPicker's hex field and native picker would otherwise lose their
151
+ border, font and size to these rules. Inside `:where()`, so the specificity
152
+ every existing override was written against is unchanged.
147
153
  ============================================================================ */
148
154
 
149
155
  .stuic-input
150
156
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
151
157
  [type="file"]
152
- ),
158
+ ):where(:not(.stuic-color-picker *)),
153
159
  .stuic-input textarea,
154
160
  .stuic-input select {
155
161
  /* Reset browser defaults */
@@ -202,7 +208,7 @@
202
208
  .stuic-input[data-size="sm"]
203
209
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
204
210
  [type="file"]
205
- ),
211
+ ):where(:not(.stuic-color-picker *)),
206
212
  .stuic-input[data-size="sm"] textarea,
207
213
  .stuic-input[data-size="sm"] select {
208
214
  padding: var(--stuic-input-padding-y-sm) var(--stuic-input-padding-x-sm);
@@ -217,13 +223,13 @@
217
223
  .stuic-input[data-size="md"]
218
224
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
219
225
  [type="file"]
220
- ),
226
+ ):where(:not(.stuic-color-picker *)),
221
227
  .stuic-input[data-size="md"] textarea,
222
228
  .stuic-input[data-size="md"] select,
223
229
  .stuic-input:not([data-size])
224
230
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
225
231
  [type="file"]
226
- ),
232
+ ):where(:not(.stuic-color-picker *)),
227
233
  .stuic-input:not([data-size]) textarea,
228
234
  .stuic-input:not([data-size]) select {
229
235
  padding: var(--stuic-input-padding-y-md) var(--stuic-input-padding-x-md);
@@ -239,7 +245,7 @@
239
245
  .stuic-input[data-size="lg"]
240
246
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
241
247
  [type="file"]
242
- ),
248
+ ):where(:not(.stuic-color-picker *)),
243
249
  .stuic-input[data-size="lg"] textarea,
244
250
  .stuic-input[data-size="lg"] select {
245
251
  padding: var(--stuic-input-padding-y-lg) var(--stuic-input-padding-x-lg);
@@ -329,6 +335,12 @@
329
335
  box-shadow: none;
330
336
  }
331
337
 
338
+ /* FieldColorPicker: a disabled shell already dims the wrap — dimming the picker
339
+ again would compound to 0.25 */
340
+ .stuic-input.disabled .stuic-color-picker[data-disabled] {
341
+ opacity: 1;
342
+ }
343
+
332
344
  /* Size-specific input-wrap padding */
333
345
  .stuic-input[data-size="lg"] .input-wrap {
334
346
  padding: calc(var(--spacing) * 1);
@@ -841,7 +853,7 @@
841
853
  .stuic-input[data-size="sm"]
842
854
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
843
855
  [type="file"]
844
- ),
856
+ ):where(:not(.stuic-color-picker *)),
845
857
  .stuic-input[data-size="sm"] textarea,
846
858
  .stuic-input[data-size="sm"] select {
847
859
  font-size: max(
@@ -853,13 +865,13 @@
853
865
  .stuic-input[data-size="md"]
854
866
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
855
867
  [type="file"]
856
- ),
868
+ ):where(:not(.stuic-color-picker *)),
857
869
  .stuic-input[data-size="md"] textarea,
858
870
  .stuic-input[data-size="md"] select,
859
871
  .stuic-input:not([data-size])
860
872
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
861
873
  [type="file"]
862
- ),
874
+ ):where(:not(.stuic-color-picker *)),
863
875
  .stuic-input:not([data-size]) textarea,
864
876
  .stuic-input:not([data-size]) select {
865
877
  font-size: max(
@@ -871,7 +883,7 @@
871
883
  .stuic-input[data-size="lg"]
872
884
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
873
885
  [type="file"]
874
- ),
886
+ ):where(:not(.stuic-color-picker *)),
875
887
  .stuic-input[data-size="lg"] textarea,
876
888
  .stuic-input[data-size="lg"] select {
877
889
  font-size: max(
@@ -4,6 +4,7 @@ export { default as FieldSingleAsset, type Props as FieldSingleAssetProps, type
4
4
  export { createFieldSingleAssetT, FIELD_SINGLE_ASSET_MESSAGES_EN, type FieldSingleAssetMessageKey, type FieldSingleAssetMessages, } from "./field-single-asset-i18n.js";
5
5
  export { FIELD_SINGLE_ASSET_MESSAGES_SK } from "./field-single-asset-i18n-sk.js";
6
6
  export { default as FieldCheckbox, type Props as FieldCheckboxProps, } from "./FieldCheckbox.svelte";
7
+ export { default as FieldColorPicker, type Props as FieldColorPickerProps, } from "./FieldColorPicker.svelte";
7
8
  export { default as Honeypot, type Props as HoneypotProps } from "./Honeypot.svelte";
8
9
  export { default as TimeTrap, type Props as TimeTrapProps, type TimeTrapSnapshot, } from "./TimeTrap.svelte";
9
10
  export { default as FieldFile, type Props as FieldFileProps } from "./FieldFile.svelte";
@@ -4,6 +4,7 @@ export { default as FieldSingleAsset, } from "./FieldSingleAsset.svelte";
4
4
  export { createFieldSingleAssetT, FIELD_SINGLE_ASSET_MESSAGES_EN, } from "./field-single-asset-i18n.js";
5
5
  export { FIELD_SINGLE_ASSET_MESSAGES_SK } from "./field-single-asset-i18n-sk.js";
6
6
  export { default as FieldCheckbox, } from "./FieldCheckbox.svelte";
7
+ export { default as FieldColorPicker, } from "./FieldColorPicker.svelte";
7
8
  export { default as Honeypot } from "./Honeypot.svelte";
8
9
  export { default as TimeTrap, } from "./TimeTrap.svelte";
9
10
  export { default as FieldFile } from "./FieldFile.svelte";
@@ -7,7 +7,7 @@ import type { ValidationResult } from "../actions/validate.svelte.js";
7
7
  * Every STUIC `Field*` component (FieldInput, FieldPhoneNumber, FieldCountry,
8
8
  * FieldSelect, FieldCheckbox, FieldTextarea, FieldFile, FieldObject,
9
9
  * FieldAssets, FieldInputLocalized, FieldKeyValues, FieldLikeButton,
10
- * FieldRadios, FieldSwitch) satisfies this interface via `export function`.
10
+ * FieldRadios, FieldSwitch, FieldColorPicker) satisfies this interface via `export function`.
11
11
  */
12
12
  export interface ValidatableField {
13
13
  /** Run the validator now. Renders the inline error if invalid. */
@@ -73,6 +73,7 @@
73
73
  | CronInput | Cron expression editor with presets and validation |
74
74
  | Fieldset | Field grouping with legend |
75
75
  | FieldKeyValues | Key-value pair editor |
76
+ | FieldColorPicker | ColorPicker in the field shell: visible label names the swatch group, description, validation box |
76
77
  | FieldTable | Rows × typed columns editor (text/number/select/checkbox/date/url cells, container layout) |
77
78
  | FieldsBuilder | Field-definition list editor ("what properties does a thing have?") |
78
79
  | FieldAssets | File/asset management |
@@ -159,7 +160,7 @@ Use `validate={false}` to bypass stuic's validation entirely.
159
160
 
160
161
  > **Why default-on?** Hidden-input field components (`FieldPhoneNumber`,
161
162
  > `FieldCountry`, `FieldObject`, `FieldAssets`, `FieldSingleAsset`, `FieldInputLocalized`,
162
- > `FieldKeyValues`, `FieldTable`, `FieldLikeButton`, `FieldDate`, `FieldDateRange`, `Rating`) _must_ be default-on because hidden
163
+ > `FieldKeyValues`, `FieldTable`, `FieldLikeButton`, `FieldDate`, `FieldDateRange`, `ColorPicker`, `FieldColorPicker`, `Rating`) _must_ be default-on because hidden
163
164
  > inputs are excluded from native browser constraint validation — without the
164
165
  > stuic action enforcing `required` in a `customValidator`, the attribute is a
165
166
  > silent no-op. Plain-input field components were harmonized to the same
@@ -170,8 +171,8 @@ Use `validate={false}` to bypass stuic's validation entirely.
170
171
  Available on `FieldInput`, `FieldMoney`, `FieldTextarea`, `FieldCheckbox`,
171
172
  `FieldSelect`, `FieldFile`, `FieldObject`, `FieldAssets`, `FieldSingleAsset`, `FieldInputLocalized`,
172
173
  `FieldKeyValues`, `FieldTable`, `FieldPhoneNumber`, `FieldCountry`, `FieldLikeButton`,
173
- `FieldRadios`, `FieldSwitch`, `FieldOptions`, `FieldDate`, `FieldDateRange`, and
174
- `Switch`:
174
+ `FieldRadios`, `FieldSwitch`, `FieldColorPicker`, `FieldOptions`, `FieldDate`, `FieldDateRange`,
175
+ `Switch`, and `ColorPicker`:
175
176
 
176
177
  | Method | Returns | Purpose |
177
178
  | ----------------------- | ------------------------------- | ------------------------------------------------------------- |
@@ -928,7 +929,9 @@ Swatch values are **never parsed** — they go to CSS as `--stuic-color-picker-s
928
929
  | `onchange` | `(value: string) => void` | — | User **commits** only (see below) |
929
930
  | `t` | `TranslateFn` | English | Group label, swatch names, "no color", custom-color labels, required message |
930
931
 
931
- Class slots: `class`, `classSwatch`.
932
+ Class slots: `class`, `classSwatch`. `labelledby` (an element id) names the swatch group via `aria-labelledby` instead of `label`'s `aria-label`.
933
+
934
+ **In a form layout** use `FieldColorPicker` (exported from `Input`): the same `InputWrap` shell as every `Field*` (visible label, description, validation box, `labelLeft*`, `InputWrapClassProps`), with the picker props forwarded. The visible label names the radiogroup via `labelledby`; `focus()` / `scrollIntoView()` join the imperative API so `scrollToFirstInvalidField` works. The Input stylesheet exempts inputs inside `.stuic-color-picker` from its field-input rules (`:where(:not(.stuic-color-picker *))`, zero specificity) — otherwise the hex field and native picker would lose their border, font and size inside the shell.
932
935
 
933
936
  ### Preview vs commit
934
937
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.187.0",
3
+ "version": "3.188.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",