@delightstack/components 0.1.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/LICENSE +21 -0
- package/README.md +136 -0
- package/SKILL.md +149 -0
- package/bin/agents.js +63 -0
- package/dist/actions/Alert.svelte +202 -0
- package/dist/actions/Alert.svelte.d.ts +36 -0
- package/dist/actions/Alert.svelte.d.ts.map +1 -0
- package/dist/actions/Button.svelte +1450 -0
- package/dist/actions/Button.svelte.d.ts +56 -0
- package/dist/actions/Button.svelte.d.ts.map +1 -0
- package/dist/actions/ButtonGroup.svelte +111 -0
- package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
- package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
- package/dist/actions/CommandPalette.svelte +939 -0
- package/dist/actions/CommandPalette.svelte.d.ts +37 -0
- package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/actions/ContextMenu.svelte +138 -0
- package/dist/actions/ContextMenu.svelte.d.ts +54 -0
- package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
- package/dist/actions/Modal.svelte +474 -0
- package/dist/actions/Modal.svelte.d.ts +28 -0
- package/dist/actions/Modal.svelte.d.ts.map +1 -0
- package/dist/actions/Popover.svelte +1214 -0
- package/dist/actions/Popover.svelte.d.ts +31 -0
- package/dist/actions/Popover.svelte.d.ts.map +1 -0
- package/dist/actions/Portal.svelte +80 -0
- package/dist/actions/Portal.svelte.d.ts +17 -0
- package/dist/actions/Portal.svelte.d.ts.map +1 -0
- package/dist/actions/ThemeToggle.svelte +345 -0
- package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
- package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/actions/index.d.ts +13 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +10 -0
- package/dist/actions/scrollbar.d.ts +48 -0
- package/dist/actions/scrollbar.d.ts.map +1 -0
- package/dist/actions/scrollbar.js +404 -0
- package/dist/display/Accordion.svelte +586 -0
- package/dist/display/Accordion.svelte.d.ts +41 -0
- package/dist/display/Accordion.svelte.d.ts.map +1 -0
- package/dist/display/Avatar.svelte +527 -0
- package/dist/display/Avatar.svelte.d.ts +22 -0
- package/dist/display/Avatar.svelte.d.ts.map +1 -0
- package/dist/display/AvatarGroup.svelte +298 -0
- package/dist/display/AvatarGroup.svelte.d.ts +31 -0
- package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
- package/dist/display/Calendar.svelte +1366 -0
- package/dist/display/Calendar.svelte.d.ts +58 -0
- package/dist/display/Calendar.svelte.d.ts.map +1 -0
- package/dist/display/Chart.svelte +1426 -0
- package/dist/display/Chart.svelte.d.ts +35 -0
- package/dist/display/Chart.svelte.d.ts.map +1 -0
- package/dist/display/Code.svelte +780 -0
- package/dist/display/Code.svelte.d.ts +19 -0
- package/dist/display/Code.svelte.d.ts.map +1 -0
- package/dist/display/Comparison.svelte +686 -0
- package/dist/display/Comparison.svelte.d.ts +22 -0
- package/dist/display/Comparison.svelte.d.ts.map +1 -0
- package/dist/display/Counter.svelte +285 -0
- package/dist/display/Counter.svelte.d.ts +21 -0
- package/dist/display/Counter.svelte.d.ts.map +1 -0
- package/dist/display/Expand.svelte +48 -0
- package/dist/display/Expand.svelte.d.ts +9 -0
- package/dist/display/Expand.svelte.d.ts.map +1 -0
- package/dist/display/List.svelte +294 -0
- package/dist/display/List.svelte.d.ts +40 -0
- package/dist/display/List.svelte.d.ts.map +1 -0
- package/dist/display/ListContextReset.svelte +19 -0
- package/dist/display/ListContextReset.svelte.d.ts +7 -0
- package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
- package/dist/display/ListItem.svelte +834 -0
- package/dist/display/ListItem.svelte.d.ts +22 -0
- package/dist/display/ListItem.svelte.d.ts.map +1 -0
- package/dist/display/QR.svelte +1193 -0
- package/dist/display/QR.svelte.d.ts +23 -0
- package/dist/display/QR.svelte.d.ts.map +1 -0
- package/dist/display/SplitPane.svelte +744 -0
- package/dist/display/SplitPane.svelte.d.ts +25 -0
- package/dist/display/SplitPane.svelte.d.ts.map +1 -0
- package/dist/display/Stat.svelte +439 -0
- package/dist/display/Stat.svelte.d.ts +24 -0
- package/dist/display/Stat.svelte.d.ts.map +1 -0
- package/dist/display/Table.svelte +4654 -0
- package/dist/display/Table.svelte.d.ts +249 -0
- package/dist/display/Table.svelte.d.ts.map +1 -0
- package/dist/display/TableCellEditor.svelte +935 -0
- package/dist/display/TableCellEditor.svelte.d.ts +58 -0
- package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
- package/dist/display/Timeline.svelte +1258 -0
- package/dist/display/Timeline.svelte.d.ts +43 -0
- package/dist/display/Timeline.svelte.d.ts.map +1 -0
- package/dist/display/Tree.svelte +1740 -0
- package/dist/display/Tree.svelte.d.ts +74 -0
- package/dist/display/Tree.svelte.d.ts.map +1 -0
- package/dist/display/Typewriter.svelte +338 -0
- package/dist/display/Typewriter.svelte.d.ts +22 -0
- package/dist/display/Typewriter.svelte.d.ts.map +1 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +18 -0
- package/dist/feedback/Callout.svelte +529 -0
- package/dist/feedback/Callout.svelte.d.ts +24 -0
- package/dist/feedback/Callout.svelte.d.ts.map +1 -0
- package/dist/feedback/Confetti.svelte +631 -0
- package/dist/feedback/Confetti.svelte.d.ts +90 -0
- package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
- package/dist/feedback/Progress.svelte +382 -0
- package/dist/feedback/Progress.svelte.d.ts +25 -0
- package/dist/feedback/Progress.svelte.d.ts.map +1 -0
- package/dist/feedback/Toast.svelte +967 -0
- package/dist/feedback/Toast.svelte.d.ts +54 -0
- package/dist/feedback/Toast.svelte.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +7 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/index.js +4 -0
- package/dist/form/Checkbox.svelte +449 -0
- package/dist/form/Checkbox.svelte.d.ts +27 -0
- package/dist/form/Checkbox.svelte.d.ts.map +1 -0
- package/dist/form/Fieldset.svelte +410 -0
- package/dist/form/Fieldset.svelte.d.ts +22 -0
- package/dist/form/Fieldset.svelte.d.ts.map +1 -0
- package/dist/form/FileUpload.svelte +934 -0
- package/dist/form/FileUpload.svelte.d.ts +41 -0
- package/dist/form/FileUpload.svelte.d.ts.map +1 -0
- package/dist/form/Form.svelte +530 -0
- package/dist/form/Form.svelte.d.ts +120 -0
- package/dist/form/Form.svelte.d.ts.map +1 -0
- package/dist/form/Input.svelte +2858 -0
- package/dist/form/Input.svelte.d.ts +66 -0
- package/dist/form/Input.svelte.d.ts.map +1 -0
- package/dist/form/Radio.svelte +507 -0
- package/dist/form/Radio.svelte.d.ts +39 -0
- package/dist/form/Radio.svelte.d.ts.map +1 -0
- package/dist/form/Range.svelte +912 -0
- package/dist/form/Range.svelte.d.ts +33 -0
- package/dist/form/Range.svelte.d.ts.map +1 -0
- package/dist/form/Rating.svelte +429 -0
- package/dist/form/Rating.svelte.d.ts +28 -0
- package/dist/form/Rating.svelte.d.ts.map +1 -0
- package/dist/form/Select.svelte +1933 -0
- package/dist/form/Select.svelte.d.ts +54 -0
- package/dist/form/Select.svelte.d.ts.map +1 -0
- package/dist/form/Toggle.svelte +645 -0
- package/dist/form/Toggle.svelte.d.ts +50 -0
- package/dist/form/Toggle.svelte.d.ts.map +1 -0
- package/dist/form/index.d.ts +15 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +10 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/layout/README.md +172 -0
- package/dist/media/Carousel.svelte +2424 -0
- package/dist/media/Carousel.svelte.d.ts +47 -0
- package/dist/media/Carousel.svelte.d.ts.map +1 -0
- package/dist/media/Gallery.svelte +2881 -0
- package/dist/media/Gallery.svelte.d.ts +82 -0
- package/dist/media/Gallery.svelte.d.ts.map +1 -0
- package/dist/media/Image.svelte +389 -0
- package/dist/media/Image.svelte.d.ts +33 -0
- package/dist/media/Image.svelte.d.ts.map +1 -0
- package/dist/media/PDF.svelte +1793 -0
- package/dist/media/PDF.svelte.d.ts +44 -0
- package/dist/media/PDF.svelte.d.ts.map +1 -0
- package/dist/media/Panorama.svelte +1391 -0
- package/dist/media/Panorama.svelte.d.ts +47 -0
- package/dist/media/Panorama.svelte.d.ts.map +1 -0
- package/dist/media/Video.svelte +2501 -0
- package/dist/media/Video.svelte.d.ts +58 -0
- package/dist/media/Video.svelte.d.ts.map +1 -0
- package/dist/media/carousel.d.ts +211 -0
- package/dist/media/carousel.d.ts.map +1 -0
- package/dist/media/carousel.js +408 -0
- package/dist/media/index.d.ts +11 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +5 -0
- package/dist/navigation/BottomSheet.svelte +636 -0
- package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
- package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
- package/dist/navigation/Breadcrumbs.svelte +611 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/navigation/Pagination.svelte +641 -0
- package/dist/navigation/Pagination.svelte.d.ts +27 -0
- package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
- package/dist/navigation/Steps.svelte +965 -0
- package/dist/navigation/Steps.svelte.d.ts +43 -0
- package/dist/navigation/Steps.svelte.d.ts.map +1 -0
- package/dist/navigation/Tabs.svelte +698 -0
- package/dist/navigation/Tabs.svelte.d.ts +41 -0
- package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +5 -0
- package/package.json +139 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type InputType = 'text' | 'email' | 'password' | 'url' | 'tel' | 'search' | 'number' | 'textarea' | 'date' | 'time' | 'datetime-local' | 'color' | 'file';
|
|
2
|
+
export interface InputOption {
|
|
3
|
+
/** The value inserted into the input when this option is chosen */
|
|
4
|
+
value: string;
|
|
5
|
+
/** Display text for the option */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Whether this option cannot be selected */
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
/** Secondary descriptive text shown under the label */
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
import { type Component, type Snippet } from 'svelte';
|
|
13
|
+
declare const Input: Component<{
|
|
14
|
+
type?: InputType;
|
|
15
|
+
value?: string | number | boolean | string[] | File | File[] | null | undefined;
|
|
16
|
+
label?: string | undefined;
|
|
17
|
+
placeholder?: string | undefined;
|
|
18
|
+
label_display?: "float" | "pinned";
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
readonly?: boolean;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
name?: string | undefined;
|
|
23
|
+
skeleton?: boolean;
|
|
24
|
+
tooltip?: string | undefined;
|
|
25
|
+
error?: string | boolean | undefined;
|
|
26
|
+
parse?: ((value: unknown) => unknown) | undefined;
|
|
27
|
+
pattern?: string | undefined;
|
|
28
|
+
minlength?: number | undefined;
|
|
29
|
+
maxlength?: number | undefined;
|
|
30
|
+
min?: number | string | undefined;
|
|
31
|
+
max?: number | string | undefined;
|
|
32
|
+
step?: number | undefined;
|
|
33
|
+
size?: "0" | "1" | "2" | "3";
|
|
34
|
+
prefix?: string | undefined;
|
|
35
|
+
suffix?: string | undefined;
|
|
36
|
+
icon?: Component | undefined;
|
|
37
|
+
clearable?: boolean;
|
|
38
|
+
show_counter?: boolean;
|
|
39
|
+
description?: string | undefined;
|
|
40
|
+
dense?: boolean;
|
|
41
|
+
comfortable?: boolean;
|
|
42
|
+
filled?: boolean;
|
|
43
|
+
id?: string;
|
|
44
|
+
class?: string;
|
|
45
|
+
options?: InputOption[] | undefined;
|
|
46
|
+
onfilter?: ((query: string) => Promise<InputOption[]>) | undefined;
|
|
47
|
+
multiple?: boolean;
|
|
48
|
+
rows?: number;
|
|
49
|
+
auto_resize?: boolean;
|
|
50
|
+
show_toggle?: boolean;
|
|
51
|
+
strength_indicator?: boolean;
|
|
52
|
+
mask?: string | undefined;
|
|
53
|
+
accept?: string | undefined;
|
|
54
|
+
option?: Snippet<[InputOption]> | undefined;
|
|
55
|
+
oninput?: ((detail: {
|
|
56
|
+
value: string | number | boolean | string[] | File | File[] | null | undefined;
|
|
57
|
+
}) => void) | undefined;
|
|
58
|
+
onchange?: ((detail: {
|
|
59
|
+
value: string | number | boolean | string[] | File | File[] | null | undefined;
|
|
60
|
+
}) => void) | undefined;
|
|
61
|
+
onfocus?: (() => void) | undefined;
|
|
62
|
+
onblur?: (() => void) | undefined;
|
|
63
|
+
}, {}, "value">;
|
|
64
|
+
type Input = ReturnType<typeof Input>;
|
|
65
|
+
export default Input;
|
|
66
|
+
//# sourceMappingURL=Input.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Input.svelte.d.ts","sourceRoot":"","sources":["../../src/form/Input.svelte.ts"],"names":[],"mappings":"AAGC,MAAM,MAAM,SAAS,GAClB,MAAM,GACN,OAAO,GACP,UAAU,GACV,KAAK,GACL,KAAK,GACL,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,MAAM,GACN,MAAM,GACN,gBAAgB,GAChB,OAAO,GACP,MAAM,CAAC;AAEV,MAAM,WAAW,WAAW;IAC3B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAIF,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAg1ClE,QAAA,MAAM,KAAK;WAhzCqE,SAAS;;YAA8B,MAAM,GAAG,SAAS;kBAAgB,MAAM,GAAG,SAAS;oBAAkB,OAAO,GAAG,QAAQ;eAAa,OAAO;eAAa,OAAO;eAAa,OAAO;WAAS,MAAM,GAAG,SAAS;eAAa,OAAO;cAAY,MAAM,GAAG,SAAS;YAAU,MAAM,GAAG,OAAO,GAAG,SAAS;YAAU,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS;cAAY,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;UAAQ,MAAM,GAAG,MAAM,GAAG,SAAS;UAAQ,MAAM,GAAG,MAAM,GAAG,SAAS;WAAS,MAAM,GAAG,SAAS;WAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG;aAAW,MAAM,GAAG,SAAS;aAAW,MAAM,GAAG,SAAS;WAAS,SAAS,GAAG,SAAS;gBAAc,OAAO;mBAAiB,OAAO;kBAAgB,MAAM,GAAG,SAAS;YAAU,OAAO;kBAAgB,OAAO;aAAW,OAAO;;YAA8B,MAAM;cAAY,WAAW,EAAE,GAAG,SAAS;eAAa,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,SAAS;eAAa,OAAO;WAAS,MAAM;kBAAgB,OAAO;kBAAgB,OAAO;yBAAuB,OAAO;WAAS,MAAM,GAAG,SAAS;aAAW,MAAM,GAAG,SAAS;aAAW,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,SAAS;cAAY,CAAC,CAAC,MAAM,EAAE;QAAE,KAAK,0EAAY;KAAE,KAAK,IAAI,CAAC,GAAG,SAAS;eAAa,CAAC,CAAC,MAAM,EAAE;QAAE,KAAK,0EAAY;KAAE,KAAK,IAAI,CAAC,GAAG,SAAS;cAAY,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;aAAW,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;eAgzCv2C,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export { default as RadioGroup } from './Radio.svelte';
|
|
3
|
+
|
|
4
|
+
export interface RadioGroupContext {
|
|
5
|
+
/** The shared `name` attribute for all radios in the group */
|
|
6
|
+
name: string;
|
|
7
|
+
/** The currently selected value */
|
|
8
|
+
value: string;
|
|
9
|
+
/** Whether the whole group is disabled */
|
|
10
|
+
disabled: boolean;
|
|
11
|
+
/** The size applied to all radios in the group */
|
|
12
|
+
size: '0' | '1' | '2' | '3';
|
|
13
|
+
/** Selects the radio with the given value */
|
|
14
|
+
select: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import { ripple, tooltip } from '@delightstack/utilities';
|
|
20
|
+
import { getContext, setContext, type Snippet } from 'svelte';
|
|
21
|
+
import type { FormContext } from './Form.svelte';
|
|
22
|
+
|
|
23
|
+
const propId = $props.id();
|
|
24
|
+
|
|
25
|
+
/* ------------------------------------------------------------------ */
|
|
26
|
+
/* Determine whether this instance is a RadioGroup or a Radio */
|
|
27
|
+
/* ------------------------------------------------------------------ */
|
|
28
|
+
let {
|
|
29
|
+
/* --- Radio props --- */
|
|
30
|
+
/** The value of this radio option (required for Radio) */
|
|
31
|
+
value = $bindable(''),
|
|
32
|
+
|
|
33
|
+
/** Whether this individual radio is checked (bindable, for standalone use) */
|
|
34
|
+
checked = $bindable(false),
|
|
35
|
+
|
|
36
|
+
/** Whether the radio is disabled */
|
|
37
|
+
disabled = false,
|
|
38
|
+
|
|
39
|
+
/** The size of the radio. 0=16px, 1=20px, 2=24px, 3=28px */
|
|
40
|
+
size = '1' as '0' | '1' | '2' | '3',
|
|
41
|
+
|
|
42
|
+
/** The label text for the radio */
|
|
43
|
+
label = '',
|
|
44
|
+
|
|
45
|
+
/** A description shown below the label */
|
|
46
|
+
description = '',
|
|
47
|
+
|
|
48
|
+
/** The tooltip message shown on hover */
|
|
49
|
+
tooltip: tooltip_message = '',
|
|
50
|
+
|
|
51
|
+
/** Whether to display in a condensed view */
|
|
52
|
+
dense = false,
|
|
53
|
+
|
|
54
|
+
/** Whether to display in an expanded view */
|
|
55
|
+
comfortable = false,
|
|
56
|
+
|
|
57
|
+
/** The ID of the radio element */
|
|
58
|
+
id = propId,
|
|
59
|
+
|
|
60
|
+
/** The name attribute for the radio group / hidden input */
|
|
61
|
+
name = '',
|
|
62
|
+
|
|
63
|
+
/** Specifies a custom class name */
|
|
64
|
+
class: class_name = '',
|
|
65
|
+
|
|
66
|
+
/* --- RadioGroup-only props --- */
|
|
67
|
+
/** Whether the radios should lay out horizontally */
|
|
68
|
+
horizontal = false,
|
|
69
|
+
|
|
70
|
+
/** An error message (RadioGroup only) */
|
|
71
|
+
error = '',
|
|
72
|
+
|
|
73
|
+
/** Whether the field is required (RadioGroup only) */
|
|
74
|
+
required = false,
|
|
75
|
+
|
|
76
|
+
/** Parses & validates the value (e.g. a database table form field's
|
|
77
|
+
* `parse`). Inside a Form it is registered with the form, which runs it
|
|
78
|
+
* on the form's validation timing. RadioGroup only. */
|
|
79
|
+
parse = undefined as ((value: unknown) => unknown) | undefined,
|
|
80
|
+
|
|
81
|
+
/** Child snippet (RadioGroup renders children; Radio does not) */
|
|
82
|
+
children = undefined as undefined | Snippet,
|
|
83
|
+
|
|
84
|
+
/** Called when the selected value changes */
|
|
85
|
+
onchange = undefined as ((detail: { value: string }) => void) | undefined,
|
|
86
|
+
} = $props();
|
|
87
|
+
|
|
88
|
+
/* ------------------------------------------------------------------ */
|
|
89
|
+
/* RadioGroup behaviour (when children are provided) */
|
|
90
|
+
/* ------------------------------------------------------------------ */
|
|
91
|
+
// svelte-ignore state_referenced_locally
|
|
92
|
+
const isGroup = !!children;
|
|
93
|
+
|
|
94
|
+
const sizes: Record<string, number> = { '0': 16, '1': 20, '2': 24, '3': 28 };
|
|
95
|
+
|
|
96
|
+
/* ------------------------------------------------------------------ */
|
|
97
|
+
/* Form context integration (RadioGroup only) */
|
|
98
|
+
/* ------------------------------------------------------------------ */
|
|
99
|
+
|
|
100
|
+
const form_ctx = getContext<FormContext | undefined>('form');
|
|
101
|
+
let group_element = $state<HTMLElement | undefined>(undefined);
|
|
102
|
+
|
|
103
|
+
/** Disabled merges the parent form's disabled/submitting state */
|
|
104
|
+
const effectively_disabled = $derived(disabled || (form_ctx?.disabled ?? false));
|
|
105
|
+
|
|
106
|
+
/** Group error from the local prop or the parent form context */
|
|
107
|
+
const resolved_error = $derived.by(() => {
|
|
108
|
+
if (error) return error;
|
|
109
|
+
if (form_ctx && name && form_ctx.errors[name]) return form_ctx.errors[name];
|
|
110
|
+
return '';
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Group context
|
|
114
|
+
if (isGroup) {
|
|
115
|
+
const ctx = $state<RadioGroupContext>({
|
|
116
|
+
name: name || id,
|
|
117
|
+
value,
|
|
118
|
+
disabled,
|
|
119
|
+
size,
|
|
120
|
+
select(val: string) {
|
|
121
|
+
value = val;
|
|
122
|
+
if (form_ctx && name) {
|
|
123
|
+
form_ctx.setValue(name, val);
|
|
124
|
+
form_ctx.setTouched(name);
|
|
125
|
+
}
|
|
126
|
+
onchange?.({ value: val });
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
setContext<RadioGroupContext>('radio-group', ctx);
|
|
130
|
+
|
|
131
|
+
// Keep context in sync with props
|
|
132
|
+
$effect(() => {
|
|
133
|
+
ctx.name = name || id;
|
|
134
|
+
ctx.value = value;
|
|
135
|
+
ctx.disabled = effectively_disabled;
|
|
136
|
+
ctx.size = size;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Register the group with a parent Form (focus-on-error + field validator)
|
|
140
|
+
$effect(() => {
|
|
141
|
+
if (!form_ctx || !name) return;
|
|
142
|
+
if (group_element) form_ctx.register(name, group_element, parse);
|
|
143
|
+
return () => form_ctx.unregister(name);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Context-driven: drive the selected value from the form data when the
|
|
147
|
+
// group lives inside a Form and has a name — no bind:value needed.
|
|
148
|
+
$effect(() => {
|
|
149
|
+
if (!form_ctx || !name) return;
|
|
150
|
+
const ctx_value = form_ctx.getValue(name);
|
|
151
|
+
const next = ctx_value == null ? '' : String(ctx_value);
|
|
152
|
+
if (next !== value) value = next;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* ------------------------------------------------------------------ */
|
|
157
|
+
/* Radio (leaf) behaviour */
|
|
158
|
+
/* ------------------------------------------------------------------ */
|
|
159
|
+
const group = getContext<RadioGroupContext | undefined>('radio-group');
|
|
160
|
+
|
|
161
|
+
const isSelected = $derived(group ? group.value === value : checked);
|
|
162
|
+
const isDisabled = $derived(group ? group.disabled || disabled : disabled);
|
|
163
|
+
const effectiveSize = $derived(group ? group.size : size);
|
|
164
|
+
const effectiveName = $derived(group ? group.name : name);
|
|
165
|
+
const px = $derived(sizes[effectiveSize] ?? 20);
|
|
166
|
+
|
|
167
|
+
let animating = $state(false);
|
|
168
|
+
|
|
169
|
+
function select() {
|
|
170
|
+
if (isDisabled) return;
|
|
171
|
+
const wasSelected = isSelected;
|
|
172
|
+
if (group) {
|
|
173
|
+
group.select(value);
|
|
174
|
+
} else {
|
|
175
|
+
checked = true;
|
|
176
|
+
onchange?.({ value });
|
|
177
|
+
}
|
|
178
|
+
if (!wasSelected) {
|
|
179
|
+
animating = true;
|
|
180
|
+
setTimeout(() => (animating = false), 350);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
185
|
+
if (e.key === ' ') {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
select();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Arrow key navigation within a group
|
|
191
|
+
if (group && (e.key === 'ArrowDown' || e.key === 'ArrowRight')) {
|
|
192
|
+
e.preventDefault();
|
|
193
|
+
focusSibling(e.currentTarget as HTMLElement, 1);
|
|
194
|
+
}
|
|
195
|
+
if (group && (e.key === 'ArrowUp' || e.key === 'ArrowLeft')) {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
focusSibling(e.currentTarget as HTMLElement, -1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function focusSibling(current: HTMLElement, direction: number) {
|
|
202
|
+
const groupEl = current.closest('.radio-group');
|
|
203
|
+
if (!groupEl) return;
|
|
204
|
+
const radios = Array.from(
|
|
205
|
+
groupEl.querySelectorAll<HTMLElement>('[role="radio"]:not([aria-disabled="true"])'),
|
|
206
|
+
);
|
|
207
|
+
const idx = radios.indexOf(current);
|
|
208
|
+
if (idx === -1) return;
|
|
209
|
+
const next = radios[(idx + direction + radios.length) % radios.length];
|
|
210
|
+
if (next) {
|
|
211
|
+
next.focus();
|
|
212
|
+
next.click();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
</script>
|
|
216
|
+
|
|
217
|
+
{#if isGroup}
|
|
218
|
+
<!-- RadioGroup wrapper -->
|
|
219
|
+
<div
|
|
220
|
+
bind:this={group_element}
|
|
221
|
+
class={['radio-group', class_name].filter(Boolean).join(' ')}
|
|
222
|
+
class:horizontal
|
|
223
|
+
class:dense
|
|
224
|
+
class:comfortable
|
|
225
|
+
class:disabled={effectively_disabled}
|
|
226
|
+
class:has-error={!!resolved_error}
|
|
227
|
+
role="radiogroup"
|
|
228
|
+
aria-labelledby={label ? `${id}-group-label` : undefined}
|
|
229
|
+
aria-required={required || undefined}
|
|
230
|
+
{id}>
|
|
231
|
+
{#if label}
|
|
232
|
+
<span id="{id}-group-label" class="group-label">{label}</span>
|
|
233
|
+
{/if}
|
|
234
|
+
<div class="group-items" class:horizontal>
|
|
235
|
+
{@render children?.()}
|
|
236
|
+
</div>
|
|
237
|
+
{#if resolved_error}
|
|
238
|
+
<span class="error-text">{resolved_error}</span>
|
|
239
|
+
{/if}
|
|
240
|
+
</div>
|
|
241
|
+
{:else}
|
|
242
|
+
<!-- Individual Radio -->
|
|
243
|
+
<!-- The whole container is the click target so the hit area matches the
|
|
244
|
+
hover/press feedback (which keys off `.radio`). Keyboard activation and
|
|
245
|
+
arrow-key navigation stay on the focusable role="radio" indicator. -->
|
|
246
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
247
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
248
|
+
<div
|
|
249
|
+
class={['radio', class_name].filter(Boolean).join(' ')}
|
|
250
|
+
class:dense
|
|
251
|
+
class:comfortable
|
|
252
|
+
class:disabled={isDisabled}
|
|
253
|
+
{@attach tooltip(tooltip_message)}
|
|
254
|
+
style:--size="{px}px"
|
|
255
|
+
style:font-size={`var(--control-font-${effectiveSize})`}
|
|
256
|
+
onclick={select}>
|
|
257
|
+
<!-- Hidden native input for form submission -->
|
|
258
|
+
<input
|
|
259
|
+
type="radio"
|
|
260
|
+
id="{id}-input"
|
|
261
|
+
name={effectiveName}
|
|
262
|
+
{value}
|
|
263
|
+
checked={isSelected}
|
|
264
|
+
disabled={isDisabled}
|
|
265
|
+
{required}
|
|
266
|
+
tabindex={-1}
|
|
267
|
+
aria-hidden="true" />
|
|
268
|
+
|
|
269
|
+
<div
|
|
270
|
+
class="indicator-wrapper"
|
|
271
|
+
class:selected={isSelected}
|
|
272
|
+
role="radio"
|
|
273
|
+
tabindex={isDisabled ? -1 : 0}
|
|
274
|
+
aria-checked={isSelected}
|
|
275
|
+
aria-disabled={isDisabled}
|
|
276
|
+
aria-labelledby={label ? `${id}-label` : undefined}
|
|
277
|
+
{@attach ripple({ enabled: !isDisabled, centered: true, opacity: 0.15 })}
|
|
278
|
+
onkeydown={onKeyDown}>
|
|
279
|
+
<svg
|
|
280
|
+
class="indicator"
|
|
281
|
+
class:selected={isSelected}
|
|
282
|
+
class:animating
|
|
283
|
+
viewBox="0 0 24 24"
|
|
284
|
+
width={px}
|
|
285
|
+
height={px}
|
|
286
|
+
fill="none">
|
|
287
|
+
<circle class="ring" cx="12" cy="12" r="10" stroke-width="2" />
|
|
288
|
+
<circle class="dot" cx="12" cy="12" r="5" />
|
|
289
|
+
</svg>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{#if label || description}
|
|
293
|
+
<div class="content">
|
|
294
|
+
{#if label}
|
|
295
|
+
<span id="{id}-label" class="label">{label}</span>
|
|
296
|
+
{/if}
|
|
297
|
+
{#if description}
|
|
298
|
+
<span class="description">{description}</span>
|
|
299
|
+
{/if}
|
|
300
|
+
</div>
|
|
301
|
+
{/if}
|
|
302
|
+
</div>
|
|
303
|
+
{/if}
|
|
304
|
+
|
|
305
|
+
<style>
|
|
306
|
+
/* ========== RadioGroup ========== */
|
|
307
|
+
.radio-group {
|
|
308
|
+
display: flex;
|
|
309
|
+
flex-direction: column;
|
|
310
|
+
gap: 0.25em;
|
|
311
|
+
|
|
312
|
+
&.disabled {
|
|
313
|
+
opacity: 0.5;
|
|
314
|
+
pointer-events: none;
|
|
315
|
+
}
|
|
316
|
+
&.has-error {
|
|
317
|
+
.error-text {
|
|
318
|
+
display: block;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.group-label {
|
|
324
|
+
font-size: 0.85em;
|
|
325
|
+
color: var(--color-text-disabled, #888);
|
|
326
|
+
margin-bottom: 0.25em;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.group-items {
|
|
330
|
+
display: flex;
|
|
331
|
+
flex-direction: column;
|
|
332
|
+
gap: 0.25em;
|
|
333
|
+
|
|
334
|
+
&.horizontal {
|
|
335
|
+
flex-direction: row;
|
|
336
|
+
flex-wrap: wrap;
|
|
337
|
+
gap: 1em;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.error-text {
|
|
342
|
+
display: none;
|
|
343
|
+
font-size: 0.8em;
|
|
344
|
+
color: var(--color-error, #d32f2f);
|
|
345
|
+
margin-top: 0.25em;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* ========== Radio ========== */
|
|
349
|
+
.radio {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: flex-start;
|
|
352
|
+
gap: 0.5em;
|
|
353
|
+
/* The whole container is clickable, so the pointer cursor and the
|
|
354
|
+
hover/press feedback now line up with the actual hit area. Bound it
|
|
355
|
+
to its content so it can't stretch the hit area across the full group
|
|
356
|
+
width (align-items: stretch in .group-items would otherwise do so). */
|
|
357
|
+
width: fit-content;
|
|
358
|
+
cursor: pointer;
|
|
359
|
+
user-select: none;
|
|
360
|
+
position: relative;
|
|
361
|
+
perspective: 100px;
|
|
362
|
+
transition: translate 200ms ease;
|
|
363
|
+
|
|
364
|
+
&:not(.disabled):active {
|
|
365
|
+
translate: 0px 3px clamp(-10px, calc(0.2em - 12px), -2px);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
&.dense {
|
|
369
|
+
gap: 0.25em;
|
|
370
|
+
}
|
|
371
|
+
&.comfortable {
|
|
372
|
+
gap: 0.75em;
|
|
373
|
+
}
|
|
374
|
+
&.disabled {
|
|
375
|
+
opacity: 0.5;
|
|
376
|
+
pointer-events: none;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Hover: circular background tint on indicator (triggers from label hover too) */
|
|
380
|
+
&:not(.disabled):hover > .indicator-wrapper {
|
|
381
|
+
background: var(--hover-tint);
|
|
382
|
+
transition: none;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Active: press scale on indicator (triggers from label click too) */
|
|
386
|
+
&:not(.disabled):active > .indicator-wrapper {
|
|
387
|
+
transform: scale(0.9);
|
|
388
|
+
transition:
|
|
389
|
+
transform 80ms ease,
|
|
390
|
+
background 200ms ease;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* Hidden native radio, kept for form submission */
|
|
395
|
+
input {
|
|
396
|
+
position: absolute;
|
|
397
|
+
width: 1px;
|
|
398
|
+
height: 1px;
|
|
399
|
+
overflow: hidden;
|
|
400
|
+
clip: rect(0, 0, 0, 0);
|
|
401
|
+
white-space: nowrap;
|
|
402
|
+
border: 0;
|
|
403
|
+
padding: 0;
|
|
404
|
+
margin: -1px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.indicator-wrapper {
|
|
408
|
+
position: relative;
|
|
409
|
+
display: flex;
|
|
410
|
+
align-items: center;
|
|
411
|
+
justify-content: center;
|
|
412
|
+
width: calc(var(--size) + 20px);
|
|
413
|
+
height: calc(var(--size) + 20px);
|
|
414
|
+
border-radius: 50%;
|
|
415
|
+
cursor: pointer;
|
|
416
|
+
flex-shrink: 0;
|
|
417
|
+
overflow: hidden;
|
|
418
|
+
outline: none;
|
|
419
|
+
-webkit-tap-highlight-color: transparent;
|
|
420
|
+
--hover-tint: color-mix(in srgb, var(--color-text, currentColor) 12%, transparent);
|
|
421
|
+
transition:
|
|
422
|
+
background 200ms ease,
|
|
423
|
+
transform 150ms ease;
|
|
424
|
+
|
|
425
|
+
/* Accent-tinted hover when selected */
|
|
426
|
+
&.selected {
|
|
427
|
+
--hover-tint: color-mix(in srgb, var(--color-action, #1976d2) 16%, transparent);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
&:focus-visible {
|
|
431
|
+
box-shadow: 0 0 0 2px var(--color-text, currentColor);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.indicator {
|
|
436
|
+
flex-shrink: 0;
|
|
437
|
+
|
|
438
|
+
.ring {
|
|
439
|
+
stroke: var(--color-text-disabled, #999);
|
|
440
|
+
fill: transparent;
|
|
441
|
+
transition: stroke 150ms ease;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.dot {
|
|
445
|
+
fill: transparent;
|
|
446
|
+
transform-origin: center;
|
|
447
|
+
transform: scale(0);
|
|
448
|
+
transition:
|
|
449
|
+
fill 150ms ease,
|
|
450
|
+
transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
&.selected {
|
|
454
|
+
.ring {
|
|
455
|
+
stroke: var(--color-action, #1976d2);
|
|
456
|
+
}
|
|
457
|
+
.dot {
|
|
458
|
+
fill: var(--color-action, #1976d2);
|
|
459
|
+
transform: scale(1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Select animation: elastic dot scale + ring pulse */
|
|
464
|
+
&.animating {
|
|
465
|
+
animation: ring-pulse 350ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
466
|
+
|
|
467
|
+
.dot {
|
|
468
|
+
transition:
|
|
469
|
+
fill 150ms ease,
|
|
470
|
+
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@keyframes ring-pulse {
|
|
476
|
+
0% {
|
|
477
|
+
transform: scale(1);
|
|
478
|
+
}
|
|
479
|
+
40% {
|
|
480
|
+
transform: scale(1.1);
|
|
481
|
+
}
|
|
482
|
+
100% {
|
|
483
|
+
transform: scale(1);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.content {
|
|
488
|
+
display: flex;
|
|
489
|
+
flex-direction: column;
|
|
490
|
+
gap: 0.1em;
|
|
491
|
+
padding-top: calc((var(--size) + 20px) / 2 - 0.7em);
|
|
492
|
+
margin-left: -8px;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.label {
|
|
496
|
+
cursor: pointer;
|
|
497
|
+
user-select: none;
|
|
498
|
+
line-height: 1.4;
|
|
499
|
+
color: var(--color-text, inherit);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.description {
|
|
503
|
+
font-size: 0.85em;
|
|
504
|
+
color: var(--color-text-disabled, #888);
|
|
505
|
+
line-height: 1.3;
|
|
506
|
+
}
|
|
507
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export { default as RadioGroup } from './Radio.svelte';
|
|
2
|
+
export interface RadioGroupContext {
|
|
3
|
+
/** The shared `name` attribute for all radios in the group */
|
|
4
|
+
name: string;
|
|
5
|
+
/** The currently selected value */
|
|
6
|
+
value: string;
|
|
7
|
+
/** Whether the whole group is disabled */
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
/** The size applied to all radios in the group */
|
|
10
|
+
size: '0' | '1' | '2' | '3';
|
|
11
|
+
/** Selects the radio with the given value */
|
|
12
|
+
select: (value: string) => void;
|
|
13
|
+
}
|
|
14
|
+
import { type Snippet } from 'svelte';
|
|
15
|
+
declare const Radio: import("svelte").Component<{
|
|
16
|
+
value?: string;
|
|
17
|
+
checked?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
size?: "0" | "1" | "2" | "3";
|
|
20
|
+
label?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
tooltip?: string;
|
|
23
|
+
dense?: boolean;
|
|
24
|
+
comfortable?: boolean;
|
|
25
|
+
id?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
class?: string;
|
|
28
|
+
horizontal?: boolean;
|
|
29
|
+
error?: string;
|
|
30
|
+
required?: boolean;
|
|
31
|
+
parse?: ((value: unknown) => unknown) | undefined;
|
|
32
|
+
children?: undefined | Snippet;
|
|
33
|
+
onchange?: ((detail: {
|
|
34
|
+
value: string;
|
|
35
|
+
}) => void) | undefined;
|
|
36
|
+
}, {}, "value" | "checked">;
|
|
37
|
+
type Radio = ReturnType<typeof Radio>;
|
|
38
|
+
export default Radio;
|
|
39
|
+
//# sourceMappingURL=Radio.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Radio.svelte.d.ts","sourceRoot":"","sources":["../../src/form/Radio.svelte.ts"],"names":[],"mappings":"AAGC,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IACjC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC5B,6CAA6C;IAC7C,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAIF,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AA0P9D,QAAA,MAAM,KAAK;YAlPsE,MAAM;cAAY,OAAO;eAAa,OAAO;WAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG;YAAU,MAAM;kBAAgB,MAAM;cAAY,MAAM;YAAU,OAAO;kBAAgB,OAAO;;WAA6B,MAAM;YAAU,MAAM;iBAAe,OAAO;YAAU,MAAM;eAAa,OAAO;YAAU,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS;eAAa,SAAS,GAAG,OAAO;eAAa,CAAC,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,SAAS;2BAkPvc,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|