@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 +4 -0
- package/README.md +2 -2
- package/dist/README.md +1 -0
- package/dist/components/ColorPicker/ColorPicker.svelte +26 -1
- package/dist/components/ColorPicker/ColorPicker.svelte.d.ts +9 -0
- package/dist/components/ColorPicker/README.md +19 -7
- package/dist/components/Input/FieldColorPicker.svelte +201 -0
- package/dist/components/Input/FieldColorPicker.svelte.d.ts +59 -0
- package/dist/components/Input/README.md +45 -0
- package/dist/components/Input/index.css +21 -9
- package/dist/components/Input/index.d.ts +1 -0
- package/dist/components/Input/index.js +1 -0
- package/dist/utils/validate-fields.d.ts +1 -1
- package/docs/domains/components.md +7 -4
- package/package.json +1 -1
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-
|
|
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
|
-
`
|
|
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`,
|
|
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
|
|