sv5ui 1.5.1 → 1.7.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.
Files changed (60) hide show
  1. package/dist/Calendar/Calendar.svelte +48 -6
  2. package/dist/Calendar/calendar.types.d.ts +19 -0
  3. package/dist/Calendar/calendar.variants.js +2 -1
  4. package/dist/Carousel/Carousel.svelte +279 -0
  5. package/dist/Carousel/Carousel.svelte.d.ts +26 -0
  6. package/dist/Carousel/carousel.types.d.ts +242 -0
  7. package/dist/Carousel/carousel.types.js +1 -0
  8. package/dist/Carousel/carousel.variants.d.ts +408 -0
  9. package/dist/Carousel/carousel.variants.js +88 -0
  10. package/dist/Carousel/index.d.ts +2 -0
  11. package/dist/Carousel/index.js +1 -0
  12. package/dist/Checkbox/Checkbox.svelte +8 -2
  13. package/dist/CheckboxGroup/CheckboxGroup.svelte +15 -2
  14. package/dist/FileUpload/FileUpload.svelte +81 -10
  15. package/dist/FileUpload/file-upload.types.d.ts +39 -0
  16. package/dist/FileUpload/index.d.ts +1 -1
  17. package/dist/Form/Form.svelte +203 -0
  18. package/dist/Form/Form.svelte.d.ts +26 -0
  19. package/dist/Form/form.context.svelte.d.ts +64 -0
  20. package/dist/Form/form.context.svelte.js +478 -0
  21. package/dist/Form/form.types.d.ts +164 -0
  22. package/dist/Form/form.types.js +12 -0
  23. package/dist/Form/form.variants.d.ts +39 -0
  24. package/dist/Form/form.variants.js +17 -0
  25. package/dist/Form/index.d.ts +4 -0
  26. package/dist/Form/index.js +6 -0
  27. package/dist/Form/validate-schema.d.ts +13 -0
  28. package/dist/Form/validate-schema.js +113 -0
  29. package/dist/FormField/FormField.svelte +71 -8
  30. package/dist/FormField/form-field.types.d.ts +15 -0
  31. package/dist/Input/Input.svelte +31 -5
  32. package/dist/Input/Input.svelte.d.ts +25 -4
  33. package/dist/Input/input.types.d.ts +24 -3
  34. package/dist/Modal/Modal.svelte +14 -3
  35. package/dist/Modal/modal.types.d.ts +15 -4
  36. package/dist/Modal/modal.variants.d.ts +110 -20
  37. package/dist/Modal/modal.variants.js +27 -9
  38. package/dist/PinInput/PinInput.svelte +27 -6
  39. package/dist/PinInput/pin-input.types.d.ts +11 -0
  40. package/dist/RadioGroup/RadioGroup.svelte +17 -3
  41. package/dist/Select/Select.svelte +100 -19
  42. package/dist/Select/select.types.d.ts +44 -2
  43. package/dist/SelectMenu/SelectMenu.svelte +215 -23
  44. package/dist/SelectMenu/select-menu.types.d.ts +62 -1
  45. package/dist/SelectMenu/select-menu.variants.d.ts +26 -0
  46. package/dist/SelectMenu/select-menu.variants.js +34 -6
  47. package/dist/Slideover/Slideover.svelte +13 -2
  48. package/dist/Slideover/slideover.types.d.ts +14 -3
  49. package/dist/Slideover/slideover.variants.d.ts +85 -5
  50. package/dist/Slideover/slideover.variants.js +42 -12
  51. package/dist/Slider/Slider.svelte +4 -1
  52. package/dist/Switch/Switch.svelte +8 -2
  53. package/dist/Textarea/Textarea.svelte +27 -1
  54. package/dist/hooks/index.d.ts +1 -1
  55. package/dist/hooks/index.js +1 -1
  56. package/dist/hooks/useFormField.svelte.d.ts +64 -0
  57. package/dist/hooks/useFormField.svelte.js +70 -1
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2 -0
  60. package/package.json +31 -3
@@ -42,10 +42,21 @@ export interface SlideoverProps extends RootProps, ContentProps {
42
42
  */
43
43
  overlay?: SlideoverVariantProps['overlay'];
44
44
  /**
45
- * Animate the slideover on open and close.
46
- * @default true
45
+ * Controls the entrance/exit animation.
46
+ * - `'none'` / `false`: no animation
47
+ * - `'fade'`: overlay + content fade
48
+ * - `'slide'` / `true`: overlay fade + content slide-in from the chosen side (default)
49
+ * - `'scale'`: overlay fade + content scale-in
50
+ * @default 'slide'
51
+ */
52
+ transition?: SlideoverVariantProps['transition'] | boolean;
53
+ /**
54
+ * Controls the panel dimension along its axis. For `side="left"` /
55
+ * `side="right"` this sets `max-width`; for `side="top"` / `side="bottom"`
56
+ * this sets `max-height`.
57
+ * @default 'md'
47
58
  */
48
- transition?: SlideoverVariantProps['transition'];
59
+ size?: SlideoverVariantProps['size'];
49
60
  /**
50
61
  * Display the slideover with inset margins and rounded corners.
51
62
  * @default false
@@ -1,8 +1,17 @@
1
1
  import { type VariantProps } from 'tailwind-variants';
2
2
  export declare const slideoverVariants: import("tailwind-variants").TVReturnType<{
3
3
  transition: {
4
- true: {
4
+ none: {};
5
+ fade: {
6
+ overlay: string;
7
+ content: string;
8
+ };
9
+ slide: {
10
+ overlay: string;
11
+ };
12
+ scale: {
5
13
  overlay: string;
14
+ content: string;
6
15
  };
7
16
  };
8
17
  side: {
@@ -19,6 +28,13 @@ export declare const slideoverVariants: import("tailwind-variants").TVReturnType
19
28
  content: string;
20
29
  };
21
30
  };
31
+ size: {
32
+ sm: {};
33
+ md: {};
34
+ lg: {};
35
+ xl: {};
36
+ full: {};
37
+ };
22
38
  inset: {
23
39
  true: {};
24
40
  false: {
@@ -43,9 +59,18 @@ export declare const slideoverVariants: import("tailwind-variants").TVReturnType
43
59
  close: string;
44
60
  }, undefined, {
45
61
  transition: {
46
- true: {
62
+ none: {};
63
+ fade: {
64
+ overlay: string;
65
+ content: string;
66
+ };
67
+ slide: {
47
68
  overlay: string;
48
69
  };
70
+ scale: {
71
+ overlay: string;
72
+ content: string;
73
+ };
49
74
  };
50
75
  side: {
51
76
  top: {
@@ -61,6 +86,13 @@ export declare const slideoverVariants: import("tailwind-variants").TVReturnType
61
86
  content: string;
62
87
  };
63
88
  };
89
+ size: {
90
+ sm: {};
91
+ md: {};
92
+ lg: {};
93
+ xl: {};
94
+ full: {};
95
+ };
64
96
  inset: {
65
97
  true: {};
66
98
  false: {
@@ -85,9 +117,18 @@ export declare const slideoverVariants: import("tailwind-variants").TVReturnType
85
117
  close: string;
86
118
  }, import("tailwind-variants").TVReturnType<{
87
119
  transition: {
88
- true: {
120
+ none: {};
121
+ fade: {
122
+ overlay: string;
123
+ content: string;
124
+ };
125
+ slide: {
89
126
  overlay: string;
90
127
  };
128
+ scale: {
129
+ overlay: string;
130
+ content: string;
131
+ };
91
132
  };
92
133
  side: {
93
134
  top: {
@@ -103,6 +144,13 @@ export declare const slideoverVariants: import("tailwind-variants").TVReturnType
103
144
  content: string;
104
145
  };
105
146
  };
147
+ size: {
148
+ sm: {};
149
+ md: {};
150
+ lg: {};
151
+ xl: {};
152
+ full: {};
153
+ };
106
154
  inset: {
107
155
  true: {};
108
156
  false: {
@@ -131,8 +179,17 @@ export type SlideoverSlots = keyof ReturnType<typeof slideoverVariants>;
131
179
  export declare const slideoverDefaults: {
132
180
  defaultVariants: import("tailwind-variants").TVDefaultVariants<{
133
181
  transition: {
134
- true: {
182
+ none: {};
183
+ fade: {
184
+ overlay: string;
185
+ content: string;
186
+ };
187
+ slide: {
188
+ overlay: string;
189
+ };
190
+ scale: {
135
191
  overlay: string;
192
+ content: string;
136
193
  };
137
194
  };
138
195
  side: {
@@ -149,6 +206,13 @@ export declare const slideoverDefaults: {
149
206
  content: string;
150
207
  };
151
208
  };
209
+ size: {
210
+ sm: {};
211
+ md: {};
212
+ lg: {};
213
+ xl: {};
214
+ full: {};
215
+ };
152
216
  inset: {
153
217
  true: {};
154
218
  false: {
@@ -173,9 +237,18 @@ export declare const slideoverDefaults: {
173
237
  close: string;
174
238
  }, {
175
239
  transition: {
176
- true: {
240
+ none: {};
241
+ fade: {
242
+ overlay: string;
243
+ content: string;
244
+ };
245
+ slide: {
177
246
  overlay: string;
178
247
  };
248
+ scale: {
249
+ overlay: string;
250
+ content: string;
251
+ };
179
252
  };
180
253
  side: {
181
254
  top: {
@@ -191,6 +264,13 @@ export declare const slideoverDefaults: {
191
264
  content: string;
192
265
  };
193
266
  };
267
+ size: {
268
+ sm: {};
269
+ md: {};
270
+ lg: {};
271
+ xl: {};
272
+ full: {};
273
+ };
194
274
  inset: {
195
275
  true: {};
196
276
  false: {
@@ -14,24 +14,40 @@ export const slideoverVariants = tv({
14
14
  },
15
15
  variants: {
16
16
  transition: {
17
- true: {
17
+ none: {},
18
+ fade: {
19
+ overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_150ms_ease-in]',
20
+ content: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_150ms_ease-in]'
21
+ },
22
+ slide: {
18
23
  overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_150ms_ease-in]'
24
+ },
25
+ scale: {
26
+ overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_150ms_ease-in]',
27
+ content: 'data-[state=open]:animate-[scale-in_200ms_cubic-bezier(0.32,0.72,0,1)] data-[state=closed]:animate-[scale-out_150ms_cubic-bezier(0.32,0.72,0,1)]'
19
28
  }
20
29
  },
21
30
  side: {
22
31
  top: {
23
- content: 'fixed top-0 inset-x-0 max-h-[90dvh]'
32
+ content: 'fixed top-0 inset-x-0'
24
33
  },
25
34
  right: {
26
- content: 'fixed right-0 inset-y-0 w-full max-w-md'
35
+ content: 'fixed right-0 inset-y-0 w-full'
27
36
  },
28
37
  bottom: {
29
- content: 'fixed bottom-0 inset-x-0 max-h-[90dvh]'
38
+ content: 'fixed bottom-0 inset-x-0'
30
39
  },
31
40
  left: {
32
- content: 'fixed left-0 inset-y-0 w-full max-w-md'
41
+ content: 'fixed left-0 inset-y-0 w-full'
33
42
  }
34
43
  },
44
+ size: {
45
+ sm: {},
46
+ md: {},
47
+ lg: {},
48
+ xl: {},
49
+ full: {}
50
+ },
35
51
  inset: {
36
52
  true: {},
37
53
  false: {
@@ -45,39 +61,52 @@ export const slideoverVariants = tv({
45
61
  }
46
62
  },
47
63
  compoundVariants: [
64
+ // Side-specific slide animations
48
65
  {
49
- transition: true,
66
+ transition: 'slide',
50
67
  side: 'top',
51
68
  class: {
52
69
  content: 'data-[state=open]:animate-[slide-in-full-top_200ms_ease-out,fade-in_200ms_ease-out] data-[state=closed]:animate-[slide-out-full-top_150ms_ease-in,fade-out_150ms_ease-in]'
53
70
  }
54
71
  },
55
72
  {
56
- transition: true,
73
+ transition: 'slide',
57
74
  side: 'right',
58
75
  class: {
59
76
  content: 'data-[state=open]:animate-[slide-in-full-right_200ms_ease-out,fade-in_200ms_ease-out] data-[state=closed]:animate-[slide-out-full-right_150ms_ease-in,fade-out_150ms_ease-in]'
60
77
  }
61
78
  },
62
79
  {
63
- transition: true,
80
+ transition: 'slide',
64
81
  side: 'bottom',
65
82
  class: {
66
83
  content: 'data-[state=open]:animate-[slide-in-full-bottom_200ms_ease-out,fade-in_200ms_ease-out] data-[state=closed]:animate-[slide-out-full-bottom_150ms_ease-in,fade-out_150ms_ease-in]'
67
84
  }
68
85
  },
69
86
  {
70
- transition: true,
87
+ transition: 'slide',
71
88
  side: 'left',
72
89
  class: {
73
90
  content: 'data-[state=open]:animate-[slide-in-full-left_200ms_ease-out,fade-in_200ms_ease-out] data-[state=closed]:animate-[slide-out-full-left_150ms_ease-in,fade-out_150ms_ease-in]'
74
91
  }
75
92
  },
93
+ // Sizes — left/right control width, top/bottom control height
94
+ { side: ['left', 'right'], size: 'sm', class: { content: 'max-w-sm' } },
95
+ { side: ['left', 'right'], size: 'md', class: { content: 'max-w-md' } },
96
+ { side: ['left', 'right'], size: 'lg', class: { content: 'max-w-lg' } },
97
+ { side: ['left', 'right'], size: 'xl', class: { content: 'max-w-xl' } },
98
+ { side: ['left', 'right'], size: 'full', class: { content: 'max-w-full' } },
99
+ { side: ['top', 'bottom'], size: 'sm', class: { content: 'max-h-[40dvh]' } },
100
+ { side: ['top', 'bottom'], size: 'md', class: { content: 'max-h-[60dvh]' } },
101
+ { side: ['top', 'bottom'], size: 'lg', class: { content: 'max-h-[75dvh]' } },
102
+ { side: ['top', 'bottom'], size: 'xl', class: { content: 'max-h-[90dvh]' } },
103
+ { side: ['top', 'bottom'], size: 'full', class: { content: 'max-h-full' } },
104
+ // Inset positioning + rounded corners + shadow ring per side
76
105
  {
77
106
  inset: true,
78
107
  side: 'top',
79
108
  class: {
80
- content: 'top-4 inset-x-4 max-h-[calc(90dvh-2rem)] rounded-xl shadow-lg ring ring-outline-variant'
109
+ content: 'top-4 inset-x-4 rounded-xl shadow-lg ring ring-outline-variant'
81
110
  }
82
111
  },
83
112
  {
@@ -91,7 +120,7 @@ export const slideoverVariants = tv({
91
120
  inset: true,
92
121
  side: 'bottom',
93
122
  class: {
94
- content: 'bottom-4 inset-x-4 max-h-[calc(90dvh-2rem)] rounded-xl shadow-lg ring ring-outline-variant'
123
+ content: 'bottom-4 inset-x-4 rounded-xl shadow-lg ring ring-outline-variant'
95
124
  }
96
125
  },
97
126
  {
@@ -103,8 +132,9 @@ export const slideoverVariants = tv({
103
132
  }
104
133
  ],
105
134
  defaultVariants: {
106
- transition: true,
135
+ transition: 'slide',
107
136
  side: 'right',
137
+ size: 'md',
108
138
  inset: false,
109
139
  overlay: true
110
140
  }
@@ -8,7 +8,7 @@
8
8
  import { Slider, useId } from 'bits-ui'
9
9
  import { sliderVariants, sliderDefaults } from './slider.variants.js'
10
10
  import { getComponentConfig } from '../config.js'
11
- import { useFormField } from '../hooks/useFormField.svelte.js'
11
+ import { useFormField, useFormFieldEmit } from '../hooks/useFormField.svelte.js'
12
12
 
13
13
  const config = getComponentConfig('slider', sliderDefaults)
14
14
 
@@ -37,6 +37,7 @@
37
37
  }: Props = $props()
38
38
 
39
39
  const formFieldContext = useFormField()
40
+ const emit = useFormFieldEmit()
40
41
 
41
42
  const autoId = useId()
42
43
  const hasError = $derived(
@@ -59,10 +60,12 @@
59
60
 
60
61
  function handleValueChange(v: number[]) {
61
62
  value = isMultiple ? v : (v[0] ?? min)
63
+ emit.onInput()
62
64
  onValueChange?.(value)
63
65
  }
64
66
 
65
67
  function handleValueCommit(v: number[]) {
68
+ emit.onChange()
66
69
  onValueCommit?.(isMultiple ? v : (v[0] ?? min))
67
70
  }
68
71
 
@@ -9,7 +9,7 @@
9
9
  import { switchVariants, switchDefaults } from './switch.variants.js'
10
10
  import { getComponentConfig, iconsDefaults } from '../config.js'
11
11
  import Icon from '../Icon/Icon.svelte'
12
- import { useFormField } from '../hooks/useFormField.svelte.js'
12
+ import { useFormField, useFormFieldEmit } from '../hooks/useFormField.svelte.js'
13
13
 
14
14
  const config = getComponentConfig('switch', switchDefaults)
15
15
  const icons = getComponentConfig('icons', iconsDefaults)
@@ -39,6 +39,7 @@
39
39
  }: Props = $props()
40
40
 
41
41
  const formFieldContext = useFormField()
42
+ const emit = useFormFieldEmit()
42
43
 
43
44
  const hasError = $derived(
44
45
  formFieldContext?.error !== undefined && formFieldContext?.error !== false
@@ -108,7 +109,12 @@
108
109
  <div class={classes.container}>
109
110
  <Switch.Root
110
111
  bind:checked
111
- {onCheckedChange}
112
+ onCheckedChange={(val) => {
113
+ emit.onChange()
114
+ onCheckedChange?.(val)
115
+ }}
116
+ onblur={() => emit.onBlur()}
117
+ onfocus={() => emit.onFocus()}
112
118
  id={resolvedId}
113
119
  name={resolvedName}
114
120
  {value}
@@ -13,7 +13,7 @@
13
13
  type FieldGroupVariantProps
14
14
  } from '../FieldGroup/field-group.variants.js'
15
15
  import Icon from '../Icon/Icon.svelte'
16
- import { useFormField } from '../hooks/useFormField.svelte.js'
16
+ import { useFormField, useFormFieldEmit } from '../hooks/useFormField.svelte.js'
17
17
 
18
18
  const config = getComponentConfig('textarea', textareaDefaults)
19
19
  const icons = getComponentConfig('icons', iconsDefaults)
@@ -41,10 +41,32 @@
41
41
  rows = 3,
42
42
  maxrows = 0,
43
43
  class: className,
44
+ onblur,
45
+ oninput,
46
+ onchange,
47
+ onfocus,
44
48
  ...restProps
45
49
  }: Props = $props()
46
50
 
47
51
  const formFieldContext = useFormField()
52
+ const emit = useFormFieldEmit()
53
+
54
+ function handleBlur(event: FocusEvent & { currentTarget: HTMLTextAreaElement }) {
55
+ emit.onBlur()
56
+ onblur?.(event)
57
+ }
58
+ function handleInput(event: Event & { currentTarget: HTMLTextAreaElement }) {
59
+ emit.onInput()
60
+ oninput?.(event)
61
+ }
62
+ function handleChange(event: Event & { currentTarget: HTMLTextAreaElement }) {
63
+ emit.onChange()
64
+ onchange?.(event)
65
+ }
66
+ function handleFocus(event: FocusEvent & { currentTarget: HTMLTextAreaElement }) {
67
+ emit.onFocus()
68
+ onfocus?.(event)
69
+ }
48
70
 
49
71
  const fieldGroupContext = getContext<
50
72
  | {
@@ -175,6 +197,10 @@
175
197
  aria-describedby={ariaDescribedBy}
176
198
  aria-invalid={resolvedHighlight ? true : undefined}
177
199
  class={classes.base}
200
+ onblur={handleBlur}
201
+ oninput={handleInput}
202
+ onchange={handleChange}
203
+ onfocus={handleFocus}
178
204
  ></textarea>
179
205
 
180
206
  {#if trailingSlot}
@@ -2,7 +2,7 @@ export { useMediaQuery } from './useMediaQuery.svelte.js';
2
2
  export type { UseMediaQueryOptions } from './useMediaQuery.svelte.js';
3
3
  export { useClipboard } from './useClipboard.svelte.js';
4
4
  export type { UseClipboardOptions } from './useClipboard.svelte.js';
5
- export { useFormField } from './useFormField.svelte.js';
5
+ export { useFormField, useFormFieldEmit, wireFormEvents, FORM_FIELD_CONTEXT_KEY } from './useFormField.svelte.js';
6
6
  export type { FormFieldContext } from './useFormField.svelte.js';
7
7
  export { useClickOutside } from './useClickOutside.svelte.js';
8
8
  export type { UseClickOutsideOptions } from './useClickOutside.svelte.js';
@@ -1,6 +1,6 @@
1
1
  export { useMediaQuery } from './useMediaQuery.svelte.js';
2
2
  export { useClipboard } from './useClipboard.svelte.js';
3
- export { useFormField } from './useFormField.svelte.js';
3
+ export { useFormField, useFormFieldEmit, wireFormEvents, FORM_FIELD_CONTEXT_KEY } from './useFormField.svelte.js';
4
4
  export { useClickOutside } from './useClickOutside.svelte.js';
5
5
  export { useInfiniteScroll } from './useInfiniteScroll.svelte.js';
6
6
  export { useEscapeKeydown } from './useEscapeKeydown.svelte.js';
@@ -1,9 +1,21 @@
1
1
  import type { FormFieldProps } from '../FormField/form-field.types.js';
2
+ /**
3
+ * Symbol key for the FormField context. Using a Symbol instead of a string
4
+ * prevents collisions with unrelated `getContext('formField')` calls from
5
+ * user code or other libraries.
6
+ */
7
+ export declare const FORM_FIELD_CONTEXT_KEY: unique symbol;
2
8
  export interface FormFieldContext {
3
9
  name?: string;
4
10
  size: NonNullable<FormFieldProps['size']>;
5
11
  error?: string | boolean;
6
12
  ariaId: string;
13
+ /** Whether input events should validate this field before first blur. */
14
+ eagerValidation?: boolean;
15
+ /** Per-field override for the input debounce delay. */
16
+ validateOnInputDelay?: number;
17
+ /** Regex pattern used to match form errors for this field (in addition to exact name). */
18
+ errorPattern?: RegExp;
7
19
  }
8
20
  /**
9
21
  * Access the nearest FormField context. Returns `undefined` when used outside a FormField.
@@ -19,3 +31,55 @@ export interface FormFieldContext {
19
31
  * ```
20
32
  */
21
33
  export declare function useFormField(): FormFieldContext | undefined;
34
+ /**
35
+ * Event emitter helpers for inputs nested inside a `<FormField>` within a `<Form>`.
36
+ * Each returned function is a safe no-op when used outside a Form, so inputs can
37
+ * unconditionally wire them to their native events.
38
+ *
39
+ * @example
40
+ * ```svelte
41
+ * <script>
42
+ * import { useFormFieldEmit } from 'sv5ui'
43
+ * const emit = useFormFieldEmit()
44
+ * </script>
45
+ *
46
+ * <input onblur={emit.onBlur} oninput={emit.onInput} onchange={emit.onChange} onfocus={emit.onFocus} />
47
+ * ```
48
+ */
49
+ export declare function useFormFieldEmit(): {
50
+ onBlur(): void;
51
+ onFocus(): void;
52
+ onChange(): void;
53
+ onInput(): void;
54
+ };
55
+ /**
56
+ * Wires native DOM input events to the parent Form's event emitters while
57
+ * preserving any user-supplied handlers. Reduces boilerplate in wrapper
58
+ * components (Input, Textarea, etc.) from ~20 lines of handler definitions
59
+ * to 4 lines:
60
+ *
61
+ * ```svelte
62
+ * <script>
63
+ * const events = wireFormEvents({ onblur, oninput, onchange, onfocus })
64
+ * </script>
65
+ *
66
+ * <input {...events} />
67
+ * ```
68
+ *
69
+ * Each handler fires the Form emitter first, then calls the user handler
70
+ * (if any) with the original event.
71
+ */
72
+ type InputEventHandler<E extends Event = Event> = (event: E) => void;
73
+ type FocusEventHandler = InputEventHandler<FocusEvent>;
74
+ export declare function wireFormEvents(userHandlers: {
75
+ onblur?: FocusEventHandler | null;
76
+ oninput?: InputEventHandler | null;
77
+ onchange?: InputEventHandler | null;
78
+ onfocus?: FocusEventHandler | null;
79
+ }): {
80
+ onblur(event: FocusEvent): void;
81
+ oninput(event: Event): void;
82
+ onchange(event: Event): void;
83
+ onfocus(event: FocusEvent): void;
84
+ };
85
+ export {};
@@ -1,4 +1,11 @@
1
1
  import { getContext } from 'svelte';
2
+ import { getFormContext } from '../Form/form.context.svelte.js';
3
+ /**
4
+ * Symbol key for the FormField context. Using a Symbol instead of a string
5
+ * prevents collisions with unrelated `getContext('formField')` calls from
6
+ * user code or other libraries.
7
+ */
8
+ export const FORM_FIELD_CONTEXT_KEY = Symbol('sv5ui:form-field');
2
9
  /**
3
10
  * Access the nearest FormField context. Returns `undefined` when used outside a FormField.
4
11
  *
@@ -13,5 +20,67 @@ import { getContext } from 'svelte';
13
20
  * ```
14
21
  */
15
22
  export function useFormField() {
16
- return getContext('formField');
23
+ return getContext(FORM_FIELD_CONTEXT_KEY);
24
+ }
25
+ /**
26
+ * Event emitter helpers for inputs nested inside a `<FormField>` within a `<Form>`.
27
+ * Each returned function is a safe no-op when used outside a Form, so inputs can
28
+ * unconditionally wire them to their native events.
29
+ *
30
+ * @example
31
+ * ```svelte
32
+ * <script>
33
+ * import { useFormFieldEmit } from 'sv5ui'
34
+ * const emit = useFormFieldEmit()
35
+ * </script>
36
+ *
37
+ * <input onblur={emit.onBlur} oninput={emit.onInput} onchange={emit.onChange} onfocus={emit.onFocus} />
38
+ * ```
39
+ */
40
+ export function useFormFieldEmit() {
41
+ const fieldCtx = getContext(FORM_FIELD_CONTEXT_KEY);
42
+ const formCtx = getFormContext();
43
+ return {
44
+ onBlur() {
45
+ const n = fieldCtx?.name;
46
+ if (n)
47
+ formCtx?.onBlur(n);
48
+ },
49
+ onFocus() {
50
+ const n = fieldCtx?.name;
51
+ if (n)
52
+ formCtx?.onFocus(n);
53
+ },
54
+ onChange() {
55
+ const n = fieldCtx?.name;
56
+ if (n)
57
+ formCtx?.onChange(n);
58
+ },
59
+ onInput() {
60
+ const n = fieldCtx?.name;
61
+ if (n)
62
+ formCtx?.onInput(n, fieldCtx?.eagerValidation);
63
+ }
64
+ };
65
+ }
66
+ export function wireFormEvents(userHandlers) {
67
+ const emit = useFormFieldEmit();
68
+ return {
69
+ onblur(event) {
70
+ emit.onBlur();
71
+ userHandlers.onblur?.(event);
72
+ },
73
+ oninput(event) {
74
+ emit.onInput();
75
+ userHandlers.oninput?.(event);
76
+ },
77
+ onchange(event) {
78
+ emit.onChange();
79
+ userHandlers.onchange?.(event);
80
+ },
81
+ onfocus(event) {
82
+ emit.onFocus();
83
+ userHandlers.onfocus?.(event);
84
+ }
85
+ };
17
86
  }
package/dist/index.d.ts CHANGED
@@ -31,6 +31,7 @@ export * from './Tabs/index.js';
31
31
  export * from './Pagination/index.js';
32
32
  export * from './FieldGroup/index.js';
33
33
  export * from './FormField/index.js';
34
+ export * from './Form/index.js';
34
35
  export * from './Input/index.js';
35
36
  export * from './Textarea/index.js';
36
37
  export * from './Select/index.js';
@@ -45,6 +46,7 @@ export * from './PinInput/index.js';
45
46
  export * from './ThemeModeButton/index.js';
46
47
  export * from './Table/index.js';
47
48
  export * from './Toast/index.js';
49
+ export * from './Carousel/index.js';
48
50
  export * from './hooks/index.js';
49
51
  export { defineConfig } from './config.js';
50
52
  export type { UIConfig } from './config.js';
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ export * from './Tabs/index.js';
32
32
  export * from './Pagination/index.js';
33
33
  export * from './FieldGroup/index.js';
34
34
  export * from './FormField/index.js';
35
+ export * from './Form/index.js';
35
36
  export * from './Input/index.js';
36
37
  export * from './Textarea/index.js';
37
38
  export * from './Select/index.js';
@@ -46,6 +47,7 @@ export * from './PinInput/index.js';
46
47
  export * from './ThemeModeButton/index.js';
47
48
  export * from './Table/index.js';
48
49
  export * from './Toast/index.js';
50
+ export * from './Carousel/index.js';
49
51
  // Composables
50
52
  export * from './hooks/index.js';
51
53
  // Configuration