sv5ui 1.5.0 → 1.6.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 (37) hide show
  1. package/README.md +60 -183
  2. package/dist/Checkbox/Checkbox.svelte +8 -2
  3. package/dist/CheckboxGroup/CheckboxGroup.svelte +15 -2
  4. package/dist/Form/Form.svelte +203 -0
  5. package/dist/Form/Form.svelte.d.ts +26 -0
  6. package/dist/Form/form.context.svelte.d.ts +64 -0
  7. package/dist/Form/form.context.svelte.js +478 -0
  8. package/dist/Form/form.types.d.ts +164 -0
  9. package/dist/Form/form.types.js +12 -0
  10. package/dist/Form/form.variants.d.ts +39 -0
  11. package/dist/Form/form.variants.js +17 -0
  12. package/dist/Form/index.d.ts +4 -0
  13. package/dist/Form/index.js +6 -0
  14. package/dist/Form/validate-schema.d.ts +13 -0
  15. package/dist/Form/validate-schema.js +113 -0
  16. package/dist/FormField/FormField.svelte +71 -8
  17. package/dist/FormField/form-field.types.d.ts +15 -0
  18. package/dist/Input/Input.svelte +31 -5
  19. package/dist/Input/Input.svelte.d.ts +25 -4
  20. package/dist/Input/input.types.d.ts +24 -3
  21. package/dist/PinInput/PinInput.svelte +9 -2
  22. package/dist/RadioGroup/RadioGroup.svelte +17 -3
  23. package/dist/Select/Select.svelte +14 -3
  24. package/dist/SelectMenu/SelectMenu.svelte +9 -2
  25. package/dist/Slider/Slider.svelte +4 -1
  26. package/dist/Switch/Switch.svelte +8 -2
  27. package/dist/Table/Table.svelte +11 -0
  28. package/dist/Table/table.types.d.ts +3 -0
  29. package/dist/Table/table.variants.js +5 -5
  30. package/dist/Textarea/Textarea.svelte +27 -1
  31. package/dist/hooks/index.d.ts +1 -1
  32. package/dist/hooks/index.js +1 -1
  33. package/dist/hooks/useFormField.svelte.d.ts +64 -0
  34. package/dist/hooks/useFormField.svelte.js +70 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/package.json +26 -3
@@ -8,7 +8,7 @@
8
8
  import { PinInput, useId } from 'bits-ui'
9
9
  import { pinInputVariants, pinInputDefaults } from './pin-input.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('pinInput', pinInputDefaults)
14
14
 
@@ -43,6 +43,7 @@
43
43
  }: Props = $props()
44
44
 
45
45
  const formFieldContext = useFormField()
46
+ const emit = useFormFieldEmit()
46
47
 
47
48
  const autoInputId = useId()
48
49
  const hasError = $derived(
@@ -69,9 +70,15 @@
69
70
  function handleValueChange(v: string) {
70
71
  const filtered = type === 'number' ? v.replace(/\D/g, '') : v
71
72
  value = filtered
73
+ emit.onInput()
72
74
  onValueChange?.(filtered)
73
75
  }
74
76
 
77
+ function handleComplete(v: string) {
78
+ emit.onChange()
79
+ onComplete?.(v)
80
+ }
81
+
75
82
  const slots = $derived(
76
83
  pinInputVariants({
77
84
  variant,
@@ -110,7 +117,7 @@
110
117
  maxlength={length}
111
118
  {disabled}
112
119
  {textalign}
113
- {onComplete}
120
+ onComplete={handleComplete}
114
121
  pasteTransformer={resolvedPasteTransformer}
115
122
  {pushPasswordManagerStrategy}
116
123
  inputId={resolvedInputId}
@@ -9,7 +9,7 @@
9
9
  import { radioGroupVariants, radioGroupDefaults } from './radio-group.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
  import type { RadioGroupItem } from './radio-group.types.js'
14
14
 
15
15
  const config = getComponentConfig('radioGroup', radioGroupDefaults)
@@ -43,6 +43,7 @@
43
43
  }: Props = $props()
44
44
 
45
45
  const formFieldContext = useFormField()
46
+ const emit = useFormFieldEmit()
46
47
 
47
48
  const hasError = $derived(
48
49
  formFieldContext?.error !== undefined && formFieldContext?.error !== false
@@ -159,10 +160,23 @@
159
160
  {/if}
160
161
  {/snippet}
161
162
 
162
- <div {...restProps} bind:this={ref} class={layoutClasses.root}>
163
+ <div
164
+ {...restProps}
165
+ bind:this={ref}
166
+ class={layoutClasses.root}
167
+ onfocusin={() => emit.onFocus()}
168
+ onfocusout={(e) => {
169
+ if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
170
+ emit.onBlur()
171
+ }
172
+ }}
173
+ >
163
174
  <RadioGroup.Root
164
175
  bind:value
165
- {onValueChange}
176
+ onValueChange={(val) => {
177
+ emit.onChange()
178
+ onValueChange?.(val)
179
+ }}
166
180
  id={resolvedId}
167
181
  name={resolvedName}
168
182
  disabled={isDisabled}
@@ -16,7 +16,7 @@
16
16
  import Icon from '../Icon/Icon.svelte'
17
17
  import Avatar from '../Avatar/Avatar.svelte'
18
18
  import type { AvatarSize } from '../Avatar/avatar.types.js'
19
- import { useFormField } from '../hooks/useFormField.svelte.js'
19
+ import { useFormField, useFormFieldEmit } from '../hooks/useFormField.svelte.js'
20
20
 
21
21
  const config = getComponentConfig('select', selectDefaults)
22
22
  const icons = getComponentConfig('icons', iconsDefaults)
@@ -69,6 +69,7 @@
69
69
 
70
70
  // ---- Form context ----
71
71
  const formFieldContext = useFormField()
72
+ const emit = useFormFieldEmit()
72
73
 
73
74
  const fieldGroupContext = getContext<
74
75
  | {
@@ -317,12 +318,22 @@
317
318
  <Select.Root
318
319
  type="single"
319
320
  bind:open
320
- onOpenChange={(val) => onOpenChange?.(val)}
321
+ onOpenChange={(val) => {
322
+ if (val) {
323
+ emit.onFocus()
324
+ } else {
325
+ emit.onBlur()
326
+ }
327
+ onOpenChange?.(val)
328
+ }}
321
329
  {disabled}
322
330
  {required}
323
331
  items={bitsItems}
324
332
  {value}
325
- onValueChange={(val) => (value = val)}
333
+ onValueChange={(val) => {
334
+ value = val
335
+ emit.onChange()
336
+ }}
326
337
  >
327
338
  <div bind:this={ref} class={rootClass}>
328
339
  {#if leadingSlot}
@@ -21,7 +21,7 @@
21
21
  import Avatar from '../Avatar/Avatar.svelte'
22
22
  import Input from '../Input/Input.svelte'
23
23
  import type { AvatarSize } from '../Avatar/avatar.types.js'
24
- import { useFormField } from '../hooks/useFormField.svelte.js'
24
+ import { useFormField, useFormFieldEmit } from '../hooks/useFormField.svelte.js'
25
25
 
26
26
  const config = getComponentConfig('selectMenu', selectMenuDefaults)
27
27
  const icons = getComponentConfig('icons', iconsDefaults)
@@ -78,6 +78,7 @@
78
78
 
79
79
  // ---- Form context ----
80
80
  const formFieldContext = useFormField()
81
+ const emit = useFormFieldEmit()
81
82
 
82
83
  const fieldGroupContext = getContext<
83
84
  | {
@@ -257,6 +258,9 @@
257
258
  function onUpdateOpen(val: boolean) {
258
259
  if (!val) {
259
260
  searchTerm = ''
261
+ emit.onBlur()
262
+ } else {
263
+ emit.onFocus()
260
264
  }
261
265
  onOpenChange?.(val)
262
266
  }
@@ -366,7 +370,10 @@
366
370
  {disabled}
367
371
  {required}
368
372
  {value}
369
- onValueChange={(val) => (value = val)}
373
+ onValueChange={(val) => {
374
+ value = val
375
+ emit.onChange()
376
+ }}
370
377
  name={resolvedName}
371
378
  >
372
379
  <div bind:this={ref} class={rootClass}>
@@ -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}
@@ -89,6 +89,7 @@
89
89
  striped = false,
90
90
  hoverable = config.defaultVariants.hoverable ?? true,
91
91
  sticky = false,
92
+ action,
92
93
 
93
94
  // Callbacks
94
95
  onRowClick,
@@ -112,6 +113,16 @@
112
113
  ...restProps
113
114
  }: TableProps<T> = $props()
114
115
 
116
+ // =========================================================================
117
+ // Apply Svelte action on root element
118
+ // =========================================================================
119
+ $effect(() => {
120
+ if (ref && action) {
121
+ const result = action(ref)
122
+ return () => result?.destroy?.()
123
+ }
124
+ })
125
+
115
126
  // =========================================================================
116
127
  // Change notifications — skip initial mount, fire only on subsequent changes
117
128
  // =========================================================================
@@ -1,4 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import type { Action } from 'svelte/action';
2
3
  import type { HTMLAttributes } from 'svelte/elements';
3
4
  import type { ClassNameValue } from 'tailwind-merge';
4
5
  import type { TableVariantProps, TableSlots } from './table.variants.js';
@@ -167,6 +168,8 @@ export interface TableProps<T extends Record<string, any> = Record<string, any>>
167
168
  hoverable?: boolean;
168
169
  /** Sticky header/footer. @default false */
169
170
  sticky?: boolean | 'header' | 'footer';
171
+ /** Svelte action to apply on the root scroll container (e.g. infinite scroll). */
172
+ action?: Action<HTMLElement>;
170
173
  /** Callback fired when a row is clicked. */
171
174
  onRowClick?: (row: T, index: number, event: MouseEvent) => void;
172
175
  /** Callback fired when pointer enters/leaves a row. Passes `null` on leave. */
@@ -1,7 +1,7 @@
1
1
  import { tv } from 'tailwind-variants';
2
2
  export const tableVariants = tv({
3
3
  slots: {
4
- root: 'relative w-full overflow-x-auto rounded-xl border border-outline-variant/50 bg-surface [contain:inline-size]',
4
+ root: 'relative w-full overflow-x-auto scrollbar-thin rounded-xl border border-outline-variant/50 bg-surface [contain:inline-size]',
5
5
  base: 'min-w-full',
6
6
  caption: 'sr-only',
7
7
  thead: 'relative bg-surface-container-low',
@@ -40,14 +40,14 @@ export const tableVariants = tv({
40
40
  },
41
41
  sticky: {
42
42
  true: {
43
- thead: 'sticky top-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10',
44
- tfoot: 'sticky bottom-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10'
43
+ thead: 'sticky top-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10 rounded-t-xl',
44
+ tfoot: 'sticky bottom-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10 rounded-b-xl'
45
45
  },
46
46
  header: {
47
- thead: 'sticky top-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10'
47
+ thead: 'sticky top-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10 rounded-t-xl'
48
48
  },
49
49
  footer: {
50
- tfoot: 'sticky bottom-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10'
50
+ tfoot: 'sticky bottom-0 inset-x-0 bg-surface-container-low/95 backdrop-blur-sm z-10 rounded-b-xl'
51
51
  }
52
52
  },
53
53
  striped: {
@@ -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';
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sv5ui",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "A modern Svelte 5 UI component library with Tailwind CSS",
5
5
  "author": "ndlabdev",
6
6
  "license": "MIT",
@@ -48,9 +48,27 @@
48
48
  }
49
49
  },
50
50
  "peerDependencies": {
51
+ "joi": "^17.0.0 || ^18.0.0",
51
52
  "mode-watcher": "^1.0.0",
52
53
  "svelte": "^5.0.0",
53
- "tailwindcss": "^4.0.0"
54
+ "tailwindcss": "^4.0.0",
55
+ "valibot": "^1.0.0",
56
+ "yup": "^1.7.0",
57
+ "zod": "^3.24.0 || ^4.0.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "joi": {
61
+ "optional": true
62
+ },
63
+ "valibot": {
64
+ "optional": true
65
+ },
66
+ "yup": {
67
+ "optional": true
68
+ },
69
+ "zod": {
70
+ "optional": true
71
+ }
54
72
  },
55
73
  "devDependencies": {
56
74
  "@eslint/compat": "^1.4.0",
@@ -66,6 +84,7 @@
66
84
  "eslint-config-prettier": "^10.1.8",
67
85
  "eslint-plugin-svelte": "^3.13.1",
68
86
  "globals": "^16.5.0",
87
+ "joi": "^18.1.2",
69
88
  "playwright": "^1.57.0",
70
89
  "prettier": "^3.7.4",
71
90
  "prettier-plugin-svelte": "^3.4.0",
@@ -76,9 +95,12 @@
76
95
  "tailwindcss": "^4.1.17",
77
96
  "typescript": "^5.9.3",
78
97
  "typescript-eslint": "^8.48.1",
98
+ "valibot": "^1.3.1",
79
99
  "vite": "^7.2.6",
80
100
  "vitest": "^4.0.15",
81
- "vitest-browser-svelte": "^2.0.1"
101
+ "vitest-browser-svelte": "^2.0.1",
102
+ "yup": "^1.7.1",
103
+ "zod": "^4.3.6"
82
104
  },
83
105
  "keywords": [
84
106
  "svelte",
@@ -98,6 +120,7 @@
98
120
  "dependencies": {
99
121
  "@iconify/svelte": "^5.2.1",
100
122
  "@internationalized/date": "^3.11.0",
123
+ "@standard-schema/spec": "^1.1.0",
101
124
  "bits-ui": "^2.15.4",
102
125
  "svelte-sonner": "^1.1.0",
103
126
  "tailwind-merge": "^3.4.0",