svelora 3.0.0 → 3.0.2

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 (109) hide show
  1. package/dist/Accordion/Accordion.svelte +66 -97
  2. package/dist/Alert/Alert.svelte +39 -64
  3. package/dist/Alert/Alert.svelte.d.ts +1 -1
  4. package/dist/Avatar/Avatar.svelte +35 -75
  5. package/dist/AvatarGroup/AvatarGroup.svelte +38 -55
  6. package/dist/Badge/Badge.svelte +28 -50
  7. package/dist/Banner/Banner.svelte +46 -41
  8. package/dist/Banner/Banner.svelte.d.ts +1 -1
  9. package/dist/Breadcrumb/Breadcrumb.svelte +32 -26
  10. package/dist/Button/Button.svelte +70 -138
  11. package/dist/Calendar/Calendar.svelte +94 -157
  12. package/dist/Calendar/Calendar.svelte.d.ts +1 -1
  13. package/dist/Card/Card.svelte +18 -31
  14. package/dist/Carousel/Carousel.svelte +118 -173
  15. package/dist/Checkbox/Checkbox.svelte +52 -97
  16. package/dist/CheckboxGroup/CheckboxGroup.svelte +62 -107
  17. package/dist/CheckboxGroup/CheckboxGroup.svelte.d.ts +1 -1
  18. package/dist/Chip/Chip.svelte +22 -34
  19. package/dist/CodeBlock/CodeBlock.svelte +42 -59
  20. package/dist/Collapsible/Collapsible.svelte +22 -38
  21. package/dist/Collapsible/Collapsible.svelte.d.ts +1 -1
  22. package/dist/Collapsible/CollapsibleTestWrapper.svelte +2 -5
  23. package/dist/Collapsible/CollapsibleTestWrapper.svelte.d.ts +1 -1
  24. package/dist/Command/Command.svelte +40 -77
  25. package/dist/Command/Command.svelte.d.ts +1 -1
  26. package/dist/Command/CommandTestWrapper.svelte +2 -10
  27. package/dist/Command/CommandTestWrapper.svelte.d.ts +1 -1
  28. package/dist/Container/Container.svelte +11 -14
  29. package/dist/ContextMenu/ContextMenu.svelte +51 -114
  30. package/dist/ContextMenu/ContextMenu.svelte.d.ts +1 -1
  31. package/dist/Drawer/Drawer.svelte +72 -110
  32. package/dist/Drawer/DrawerTriggerTestWrapper.svelte +1 -2
  33. package/dist/DropdownMenu/DropdownMenu.svelte +63 -124
  34. package/dist/DropdownMenu/DropdownMenu.svelte.d.ts +1 -1
  35. package/dist/DropdownMenu/DropdownMenuTriggerTestWrapper.svelte +2 -5
  36. package/dist/Editor/Editor.svelte +441 -576
  37. package/dist/Editor/Editor.svelte.d.ts +1 -1
  38. package/dist/Editor/EditorUrlPrompt.svelte +40 -53
  39. package/dist/Editor/SlashPopup.svelte +12 -24
  40. package/dist/Empty/Empty.svelte +32 -63
  41. package/dist/FieldGroup/FieldGroup.svelte +23 -38
  42. package/dist/FileUpload/FileUpload.svelte +242 -320
  43. package/dist/FileUpload/FileUpload.svelte.d.ts +1 -1
  44. package/dist/Fonts/Fonts.svelte +15 -37
  45. package/dist/Form/Form.svelte +112 -170
  46. package/dist/FormField/FormField.svelte +102 -135
  47. package/dist/Icon/Icon.svelte +7 -32
  48. package/dist/Input/Input.svelte +71 -141
  49. package/dist/Input/Input.svelte.d.ts +2 -2
  50. package/dist/Kbd/Kbd.svelte +18 -34
  51. package/dist/Link/Link.svelte +129 -196
  52. package/dist/LocaleButton/LocaleButton.svelte +165 -0
  53. package/dist/LocaleButton/LocaleButton.svelte.d.ts +5 -0
  54. package/dist/LocaleButton/index.d.ts +2 -0
  55. package/dist/LocaleButton/index.js +1 -0
  56. package/dist/LocaleButton/locale-button.types.d.ts +182 -0
  57. package/dist/LocaleButton/locale-button.types.js +1 -0
  58. package/dist/LocaleButton/locale-button.variants.d.ts +61 -0
  59. package/dist/LocaleButton/locale-button.variants.js +34 -0
  60. package/dist/Modal/Modal.svelte +52 -106
  61. package/dist/Modal/ModalTriggerTestWrapper.svelte +1 -2
  62. package/dist/Pagination/Pagination.svelte +48 -92
  63. package/dist/Pagination/pagination.variants.d.ts +1 -1
  64. package/dist/PinInput/PinInput.svelte +57 -111
  65. package/dist/PinInput/PinInput.svelte.d.ts +1 -1
  66. package/dist/Popover/Popover.svelte +28 -61
  67. package/dist/Popover/Popover.svelte.d.ts +1 -1
  68. package/dist/Progress/Progress.svelte +75 -94
  69. package/dist/RadioGroup/RadioGroup.svelte +54 -99
  70. package/dist/RadioGroup/RadioGroup.svelte.d.ts +1 -1
  71. package/dist/Select/Select.svelte +112 -269
  72. package/dist/Select/Select.svelte.d.ts +1 -1
  73. package/dist/SelectMenu/SelectMenu.svelte +211 -409
  74. package/dist/SelectMenu/SelectMenu.svelte.d.ts +1 -1
  75. package/dist/SelectMenu/SelectMenuFormFieldTestWrapper.svelte +3 -6
  76. package/dist/Separator/Separator.svelte +29 -44
  77. package/dist/Skeleton/Skeleton.svelte +11 -23
  78. package/dist/Slideover/Slideover.svelte +52 -106
  79. package/dist/Slideover/SlideoverTriggerTestWrapper.svelte +1 -2
  80. package/dist/Slider/Slider.svelte +48 -84
  81. package/dist/Slider/Slider.svelte.d.ts +1 -1
  82. package/dist/Stepper/Stepper.svelte +139 -132
  83. package/dist/Stepper/Stepper.svelte.d.ts +1 -1
  84. package/dist/Switch/Switch.svelte +62 -98
  85. package/dist/Table/Table.svelte +232 -283
  86. package/dist/Table/table.variants.d.ts +1 -1
  87. package/dist/Tabs/Tabs.svelte +96 -129
  88. package/dist/Tabs/Tabs.svelte.d.ts +1 -1
  89. package/dist/Textarea/Textarea.svelte +90 -173
  90. package/dist/Textarea/Textarea.svelte.d.ts +1 -1
  91. package/dist/ThemeModeButton/ThemeModeButton.svelte +16 -38
  92. package/dist/Timeline/Timeline.svelte +75 -54
  93. package/dist/Toast/Toaster.svelte +8 -25
  94. package/dist/Tooltip/Tooltip.svelte +34 -66
  95. package/dist/Tooltip/Tooltip.svelte.d.ts +1 -1
  96. package/dist/Tooltip/TooltipTestWrapper.svelte +2 -5
  97. package/dist/User/User.svelte +33 -49
  98. package/dist/docs/navigation.d.ts +1 -1
  99. package/dist/docs/navigation.js +8 -1
  100. package/dist/hooks/HookContextProbe.svelte +2 -4
  101. package/dist/hooks/HookContextProvider.svelte +8 -6
  102. package/dist/hooks/HookEmitProbe.svelte +8 -11
  103. package/dist/i18n.d.ts +2 -0
  104. package/dist/i18n.js +19 -0
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.js +1 -0
  107. package/dist/mcp/svelora-docs.data.json +4 -2
  108. package/dist/theme.css +1 -1
  109. package/package.json +16 -8
@@ -1,178 +1,120 @@
1
- <script lang="ts" module>
2
- import type { FormProps, FormSchema } from './form.types.js'
3
-
4
- export type Props<
5
- S extends FormSchema | undefined = FormSchema | undefined,
6
- T extends boolean = true,
7
- N extends boolean = false
8
- > = FormProps<S, T, N>
1
+ <script lang="ts" module>export {};
9
2
  </script>
10
3
 
11
4
  <script
12
5
  lang="ts"
13
6
  generics="S extends FormSchema | undefined = FormSchema | undefined, T extends boolean = true, N extends boolean = false"
14
- >
15
- import { onDestroy, onMount, setContext, untrack } from 'svelte'
16
- import type { HTMLAttributes } from 'svelte/elements'
17
- import { getComponentConfig } from '../config.js'
18
- import { FORM_CONTEXT_KEY, FormContext, getFormContext } from './form.context.svelte.js'
19
- import type { FormApi, FormErrorWithId } from './form.types.js'
20
- import { formDefaults, formVariants } from './form.variants.js'
21
-
22
- const config = getComponentConfig('form', formDefaults)
23
-
24
- let {
25
- ref = $bindable(null),
26
- api = $bindable<FormApi<unknown> | undefined>(),
27
- id,
28
- schema,
29
- state = $bindable({} as never),
30
- validate: customValidate,
31
- validateOn = ['input', 'blur', 'change'],
32
- validateOnInputDelay = 300,
33
- disabled = false,
34
- loadingAuto = true,
35
- transform = true as T,
36
- nested = false as N,
37
- name,
38
- onsubmit,
39
- onerror,
40
- class: className,
41
- ui,
42
- children,
43
- ...restProps
44
- }: Props<S, T, N> = $props()
45
-
46
- // Generate a stable form id. Both `id` and `nested` are effectively init-only
47
- // (they should not change over a form instance's lifetime), so `untrack`
48
- // silences Svelte's state_referenced_locally warning.
49
- const formId: string | number = untrack(
50
- () =>
51
- (id as string | number | undefined) ??
52
- (typeof crypto !== 'undefined' && 'randomUUID' in crypto
53
- ? crypto.randomUUID()
54
- : `svelora-form-${Math.random().toString(36).slice(2)}`)
55
- )
56
-
57
- // Parent context captured once at init — `nested` is effectively immutable
58
- // over a form instance's lifetime. We warn (dev-only) if it's mutated later.
59
- const parentCtx = untrack(() => (nested ? getFormContext() : undefined))
60
- const initialNested = untrack(() => nested)
61
- $effect(() => {
62
- if (nested !== initialNested) {
63
- }
64
- })
65
-
66
- // Create the reactive form context. All config is passed via getter closures
67
- // so that reactive prop changes are visible inside the class.
68
- const ctx = new FormContext(
69
- {
70
- getState: () => state as unknown,
71
- getSchema: () => schema as FormSchema | undefined,
72
- getCustomValidate: () =>
73
- customValidate as
74
- | ((s: unknown) => ReturnType<NonNullable<typeof customValidate>>)
75
- | undefined,
76
- getValidateOn: () => validateOn,
77
- getValidateOnInputDelay: () => validateOnInputDelay,
78
- getDisabled: () => disabled,
79
- getLoadingAuto: () => loadingAuto,
80
- getTransform: () => transform,
81
- getName: () => name as string | undefined,
82
- getOnSubmit: () =>
83
- onsubmit as
84
- | ((
85
- e: import('./form.types.js').FormSubmitEvent<unknown>
86
- ) => void | Promise<void>)
87
- | undefined,
88
- getOnError: () => onerror
89
- },
90
- formId,
91
- parentCtx
92
- )
93
-
94
- setContext(FORM_CONTEXT_KEY, ctx)
95
-
96
- // Build the bindable API object. Getters ensure reactive state reads stay live.
97
- const apiObject: FormApi<unknown> = {
98
- validate: (opts) => ctx.validate(opts) as Promise<unknown | false>,
99
- submit: () => ctx.submit(),
100
- clear: (name) => ctx.clear(name),
101
- getErrors: (name) => ctx.getErrors(name),
102
- setErrors: (errs, name) => ctx.setErrors(errs, name),
103
- reset: () => ctx.reset(),
104
- get errors() {
105
- return ctx.errors
106
- },
107
- get loading() {
108
- return ctx.loading
109
- },
110
- get disabled() {
111
- return ctx.disabled
112
- },
113
- get dirty() {
114
- return ctx.dirty
115
- },
116
- get dirtyFields() {
117
- return ctx.dirtyFields as ReadonlySet<string>
118
- },
119
- get touchedFields() {
120
- return ctx.touchedFields as ReadonlySet<string>
121
- },
122
- get blurredFields() {
123
- return ctx.blurredFields as ReadonlySet<string>
124
- },
125
- get submitCount() {
126
- return ctx.submitCount
127
- }
128
- }
129
-
130
- // Sync the api object to the bindable prop once at setup time. `apiObject`
131
- // is a stable reference built from getters, so there's nothing reactive to
132
- // track — direct assignment is sufficient.
133
- api = apiObject as typeof api
134
-
135
- // Nested form attach/detach lifecycle.
136
- onMount(() => {
137
- if (parentCtx && nested) {
138
- parentCtx.attachChild(formId, {
139
- formId,
140
- name: name as string | undefined,
141
- validate: (opts) => ctx.validate(opts) as Promise<unknown | false>,
142
- clear: (n) => ctx.clear(n),
143
- reset: () => ctx.reset(),
144
- setErrors: (errs, n) => ctx.setErrors(errs, n)
145
- })
146
- }
147
- })
148
-
149
- onDestroy(() => {
150
- if (parentCtx && nested) parentCtx.detachChild(formId)
151
- ctx.dispose()
152
- })
153
-
154
- async function handleSubmit(event: SubmitEvent) {
155
- event.preventDefault()
156
- // Forward the real SubmitEvent so user handlers can read `submitter`,
157
- // `target`, etc. When `api.submit()` is called programmatically, ctx.submit
158
- // synthesizes a fresh event instead.
159
- await ctx.submit(event)
160
- }
161
-
162
- // `formVariants` has no variants (only a single `root` slot), so its
163
- // output is stable — compute once at setup rather than via $derived.
164
- const variantSlots = formVariants()
165
- const classes = $derived({
166
- root: variantSlots.root({
167
- class: [config.slots.root, className, ui?.root]
168
- })
169
- })
170
-
171
- // Slot props for children
172
- const slotProps = $derived({
173
- errors: ctx.errors as FormErrorWithId[],
174
- loading: ctx.loading
175
- })
7
+ >import { onDestroy, onMount, setContext, untrack } from "svelte";
8
+ import { getComponentConfig } from "../config.js";
9
+ import { FORM_CONTEXT_KEY, FormContext, getFormContext } from "./form.context.svelte.js";
10
+ import { formDefaults, formVariants } from "./form.variants.js";
11
+ const config = getComponentConfig("form", formDefaults);
12
+ let { ref = $bindable(null), api = $bindable(), id, schema, state = $bindable({}), validate: customValidate, validateOn = [
13
+ "input",
14
+ "blur",
15
+ "change"
16
+ ], validateOnInputDelay = 300, disabled = false, loadingAuto = true, transform = true, nested = false, name, onsubmit, onerror, class: className, ui, children, ...restProps } = $props();
17
+ // Generate a stable form id. Both `id` and `nested` are effectively init-only
18
+ // (they should not change over a form instance's lifetime), so `untrack`
19
+ // silences Svelte's state_referenced_locally warning.
20
+ const formId = untrack(() => id ?? (typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `svelora-form-${Math.random().toString(36).slice(2)}`));
21
+ // Parent context captured once at init — `nested` is effectively immutable
22
+ // over a form instance's lifetime. We warn (dev-only) if it's mutated later.
23
+ const parentCtx = untrack(() => nested ? getFormContext() : undefined);
24
+ const initialNested = untrack(() => nested);
25
+ $effect(() => {
26
+ if (nested !== initialNested) {}
27
+ });
28
+ // Create the reactive form context. All config is passed via getter closures
29
+ // so that reactive prop changes are visible inside the class.
30
+ const ctx = new FormContext({
31
+ getState: () => state,
32
+ getSchema: () => schema,
33
+ getCustomValidate: () => customValidate,
34
+ getValidateOn: () => validateOn,
35
+ getValidateOnInputDelay: () => validateOnInputDelay,
36
+ getDisabled: () => disabled,
37
+ getLoadingAuto: () => loadingAuto,
38
+ getTransform: () => transform,
39
+ getName: () => name,
40
+ getOnSubmit: () => onsubmit,
41
+ getOnError: () => onerror
42
+ }, formId, parentCtx);
43
+ setContext(FORM_CONTEXT_KEY, ctx);
44
+ // Build the bindable API object. Getters ensure reactive state reads stay live.
45
+ const apiObject = {
46
+ validate: (opts) => ctx.validate(opts),
47
+ submit: () => ctx.submit(),
48
+ clear: (name) => ctx.clear(name),
49
+ getErrors: (name) => ctx.getErrors(name),
50
+ setErrors: (errs, name) => ctx.setErrors(errs, name),
51
+ reset: () => ctx.reset(),
52
+ get errors() {
53
+ return ctx.errors;
54
+ },
55
+ get loading() {
56
+ return ctx.loading;
57
+ },
58
+ get disabled() {
59
+ return ctx.disabled;
60
+ },
61
+ get dirty() {
62
+ return ctx.dirty;
63
+ },
64
+ get dirtyFields() {
65
+ return ctx.dirtyFields;
66
+ },
67
+ get touchedFields() {
68
+ return ctx.touchedFields;
69
+ },
70
+ get blurredFields() {
71
+ return ctx.blurredFields;
72
+ },
73
+ get submitCount() {
74
+ return ctx.submitCount;
75
+ }
76
+ };
77
+ // Sync the api object to the bindable prop once at setup time. `apiObject`
78
+ // is a stable reference built from getters, so there's nothing reactive to
79
+ // track direct assignment is sufficient.
80
+ api = apiObject;
81
+ // Nested form attach/detach lifecycle.
82
+ onMount(() => {
83
+ if (parentCtx && nested) {
84
+ parentCtx.attachChild(formId, {
85
+ formId,
86
+ name,
87
+ validate: (opts) => ctx.validate(opts),
88
+ clear: (n) => ctx.clear(n),
89
+ reset: () => ctx.reset(),
90
+ setErrors: (errs, n) => ctx.setErrors(errs, n)
91
+ });
92
+ }
93
+ });
94
+ onDestroy(() => {
95
+ if (parentCtx && nested) parentCtx.detachChild(formId);
96
+ ctx.dispose();
97
+ });
98
+ async function handleSubmit(event) {
99
+ event.preventDefault();
100
+ // Forward the real SubmitEvent so user handlers can read `submitter`,
101
+ // `target`, etc. When `api.submit()` is called programmatically, ctx.submit
102
+ // synthesizes a fresh event instead.
103
+ await ctx.submit(event);
104
+ }
105
+ // `formVariants` has no variants (only a single `root` slot), so its
106
+ // output is stable — compute once at setup rather than via $derived.
107
+ const variantSlots = formVariants();
108
+ const classes = $derived({ root: variantSlots.root({ class: [
109
+ config.slots.root,
110
+ className,
111
+ ui?.root
112
+ ] }) });
113
+ // Slot props for children
114
+ const slotProps = $derived({
115
+ errors: ctx.errors,
116
+ loading: ctx.loading
117
+ });
176
118
  </script>
177
119
 
178
120
  {#if nested}
@@ -1,140 +1,107 @@
1
- <script lang="ts" module>
2
- import type { FormFieldProps } from './form-field.types.js'
3
-
4
- export type Props = FormFieldProps
1
+ <script lang="ts" module>export {};
5
2
  </script>
6
3
 
7
- <script lang="ts">
8
- import { Label, useId } from 'bits-ui'
9
- import { setContext, untrack } from 'svelte'
10
- import { getComponentConfig } from '../config.js'
11
- import { getFormContext } from '../Form/form.context.svelte.js'
12
- import { FORM_FIELD_CONTEXT_KEY, type FormFieldContext } from '../hooks/useFormField.svelte.js'
13
- import { formFieldDefaults, formFieldVariants } from './form-field.variants.js'
14
-
15
- const config = getComponentConfig('formField', formFieldDefaults)
16
-
17
- let {
18
- ref = $bindable(null),
19
- ui,
20
- name,
21
- label,
22
- description,
23
- hint,
24
- help,
25
- error,
26
- size = config.defaultVariants.size ?? 'md',
27
- required = false,
28
- orientation = config.defaultVariants.orientation ?? 'vertical',
29
- eagerValidation,
30
- validateOnInputDelay,
31
- errorPattern,
32
- class: className,
33
- children,
34
- labelSlot,
35
- hintSlot,
36
- descriptionSlot,
37
- helpSlot,
38
- errorSlot,
39
- ...restProps
40
- }: Props = $props()
41
-
42
- const id = useId()
43
- const ariaId = $derived(name ? `form-field-${name}` : id)
44
-
45
- // Form-level context (undefined when FormField is used standalone).
46
- const formCtx = getFormContext()
47
-
48
- // Resolve error: explicit `error` prop always wins; otherwise ask the Form for
49
- // errors matching this field's name (or errorPattern regex, if provided).
50
- const resolvedError = $derived.by<string | boolean | undefined>(() => {
51
- if (error !== undefined) return error
52
- if (!formCtx || !name) return undefined
53
- const errs = errorPattern ? formCtx.getErrors(errorPattern) : formCtx.getErrors(name)
54
- return errs[0]?.message
55
- })
56
-
57
- // Register this field with the form. Split into two effects:
58
- //
59
- // 1. Lifecycle effect — tracks only `name`. Handles the initial registration
60
- // and cleanup on unmount / rename. Reads other entry fields via `untrack`
61
- // so they don't retrigger the lifecycle.
62
- // 2. Update effect — tracks ariaId / errorPattern / eagerValidation /
63
- // validateOnInputDelay and rewrites the entry via `.set()`. No cleanup —
64
- // a single atomic overwrite, so no window where the registry is empty.
65
- //
66
- // Without this split, changing an unrelated prop (e.g. errorPattern) would
67
- // cause cleanup→re-register, briefly leaving `#fieldRegistry.get(name)`
68
- // undefined for any concurrent `#resolveErrorIds` call.
69
- $effect(() => {
70
- if (!formCtx || !name) return
71
- const registeredName = name
72
- untrack(() => {
73
- formCtx.registerField(registeredName, {
74
- id: ariaId,
75
- pattern: errorPattern,
76
- eagerValidation,
77
- validateOnInputDelay
78
- })
79
- })
80
- return () => {
81
- formCtx.unregisterField(registeredName)
82
- }
83
- })
84
-
85
- $effect(() => {
86
- if (!formCtx || !name) return
87
- formCtx.registerField(name, {
88
- id: ariaId,
89
- pattern: errorPattern,
90
- eagerValidation,
91
- validateOnInputDelay
92
- })
93
- })
94
-
95
- const variantSlots = $derived(formFieldVariants({ size, required, orientation }))
96
- const classes = $derived({
97
- root: variantSlots.root({ class: [config.slots.root, className, ui?.root] }),
98
- wrapper: variantSlots.wrapper({ class: [config.slots.wrapper, ui?.wrapper] }),
99
- labelWrapper: variantSlots.labelWrapper({
100
- class: [config.slots.labelWrapper, ui?.labelWrapper]
101
- }),
102
- label: variantSlots.label({ class: [config.slots.label, ui?.label] }),
103
- container: variantSlots.container({ class: [config.slots.container, ui?.container] }),
104
- description: variantSlots.description({
105
- class: [config.slots.description, ui?.description]
106
- }),
107
- error: variantSlots.error({ class: [config.slots.error, ui?.error] }),
108
- hint: variantSlots.hint({ class: [config.slots.hint, ui?.hint] }),
109
- help: variantSlots.help({ class: [config.slots.help, ui?.help] })
110
- })
111
-
112
- const hasError = $derived(resolvedError !== undefined && resolvedError !== false)
113
- const errorMessage = $derived(typeof resolvedError === 'string' ? resolvedError : undefined)
114
-
115
- setContext<FormFieldContext>(FORM_FIELD_CONTEXT_KEY, {
116
- get name() {
117
- return name
118
- },
119
- get size() {
120
- return size
121
- },
122
- get error() {
123
- return resolvedError
124
- },
125
- get ariaId() {
126
- return ariaId
127
- },
128
- get eagerValidation() {
129
- return eagerValidation
130
- },
131
- get validateOnInputDelay() {
132
- return validateOnInputDelay
133
- },
134
- get errorPattern() {
135
- return errorPattern
136
- }
137
- })
4
+ <script lang="ts">import { Label, useId } from "bits-ui";
5
+ import { setContext, untrack } from "svelte";
6
+ import { getComponentConfig } from "../config.js";
7
+ import { getFormContext } from "../Form/form.context.svelte.js";
8
+ import { FORM_FIELD_CONTEXT_KEY } from "../hooks/useFormField.svelte.js";
9
+ import { formFieldDefaults, formFieldVariants } from "./form-field.variants.js";
10
+ const config = getComponentConfig("formField", formFieldDefaults);
11
+ let { ref = $bindable(null), ui, name, label, description, hint, help, error, size = config.defaultVariants.size ?? "md", required = false, orientation = config.defaultVariants.orientation ?? "vertical", eagerValidation, validateOnInputDelay, errorPattern, class: className, children, labelSlot, hintSlot, descriptionSlot, helpSlot, errorSlot, ...restProps } = $props();
12
+ const id = useId();
13
+ const ariaId = $derived(name ? `form-field-${name}` : id);
14
+ // Form-level context (undefined when FormField is used standalone).
15
+ const formCtx = getFormContext();
16
+ // Resolve error: explicit `error` prop always wins; otherwise ask the Form for
17
+ // errors matching this field's name (or errorPattern regex, if provided).
18
+ const resolvedError = $derived.by(() => {
19
+ if (error !== undefined) return error;
20
+ if (!formCtx || !name) return undefined;
21
+ const errs = errorPattern ? formCtx.getErrors(errorPattern) : formCtx.getErrors(name);
22
+ return errs[0]?.message;
23
+ });
24
+ // Register this field with the form. Split into two effects:
25
+ //
26
+ // 1. Lifecycle effect — tracks only `name`. Handles the initial registration
27
+ // and cleanup on unmount / rename. Reads other entry fields via `untrack`
28
+ // so they don't retrigger the lifecycle.
29
+ // 2. Update effect — tracks ariaId / errorPattern / eagerValidation /
30
+ // validateOnInputDelay and rewrites the entry via `.set()`. No cleanup —
31
+ // a single atomic overwrite, so no window where the registry is empty.
32
+ //
33
+ // Without this split, changing an unrelated prop (e.g. errorPattern) would
34
+ // cause cleanup→re-register, briefly leaving `#fieldRegistry.get(name)`
35
+ // undefined for any concurrent `#resolveErrorIds` call.
36
+ $effect(() => {
37
+ if (!formCtx || !name) return;
38
+ const registeredName = name;
39
+ untrack(() => {
40
+ formCtx.registerField(registeredName, {
41
+ id: ariaId,
42
+ pattern: errorPattern,
43
+ eagerValidation,
44
+ validateOnInputDelay
45
+ });
46
+ });
47
+ return () => {
48
+ formCtx.unregisterField(registeredName);
49
+ };
50
+ });
51
+ $effect(() => {
52
+ if (!formCtx || !name) return;
53
+ formCtx.registerField(name, {
54
+ id: ariaId,
55
+ pattern: errorPattern,
56
+ eagerValidation,
57
+ validateOnInputDelay
58
+ });
59
+ });
60
+ const variantSlots = $derived(formFieldVariants({
61
+ size,
62
+ required,
63
+ orientation
64
+ }));
65
+ const classes = $derived({
66
+ root: variantSlots.root({ class: [
67
+ config.slots.root,
68
+ className,
69
+ ui?.root
70
+ ] }),
71
+ wrapper: variantSlots.wrapper({ class: [config.slots.wrapper, ui?.wrapper] }),
72
+ labelWrapper: variantSlots.labelWrapper({ class: [config.slots.labelWrapper, ui?.labelWrapper] }),
73
+ label: variantSlots.label({ class: [config.slots.label, ui?.label] }),
74
+ container: variantSlots.container({ class: [config.slots.container, ui?.container] }),
75
+ description: variantSlots.description({ class: [config.slots.description, ui?.description] }),
76
+ error: variantSlots.error({ class: [config.slots.error, ui?.error] }),
77
+ hint: variantSlots.hint({ class: [config.slots.hint, ui?.hint] }),
78
+ help: variantSlots.help({ class: [config.slots.help, ui?.help] })
79
+ });
80
+ const hasError = $derived(resolvedError !== undefined && resolvedError !== false);
81
+ const errorMessage = $derived(typeof resolvedError === "string" ? resolvedError : undefined);
82
+ setContext(FORM_FIELD_CONTEXT_KEY, {
83
+ get name() {
84
+ return name;
85
+ },
86
+ get size() {
87
+ return size;
88
+ },
89
+ get error() {
90
+ return resolvedError;
91
+ },
92
+ get ariaId() {
93
+ return ariaId;
94
+ },
95
+ get eagerValidation() {
96
+ return eagerValidation;
97
+ },
98
+ get validateOnInputDelay() {
99
+ return validateOnInputDelay;
100
+ },
101
+ get errorPattern() {
102
+ return errorPattern;
103
+ }
104
+ });
138
105
  </script>
139
106
 
140
107
  <div bind:this={ref} class={classes.root} {...restProps}>
@@ -1,37 +1,12 @@
1
- <script lang="ts" module>
2
- import type { IconProps } from './icon.types.js'
3
-
4
- export type Props = IconProps
1
+ <script lang="ts" module>export {};
5
2
  </script>
6
3
 
7
- <script lang="ts">
8
- import Icon from '@iconify/svelte'
9
- import { twMerge } from 'tailwind-merge'
10
-
11
- let {
12
- name,
13
- size = 24,
14
- color,
15
- flipH = false,
16
- flipV = false,
17
- rotate = 0,
18
- class: className,
19
- ...restProps
20
- }: Props = $props()
21
-
22
- const flip = $derived(
23
- flipH && flipV
24
- ? 'horizontal,vertical'
25
- : flipH
26
- ? 'horizontal'
27
- : flipV
28
- ? 'vertical'
29
- : undefined
30
- )
31
-
32
- const rotateValue = $derived(rotate ? rotate / 90 : undefined)
33
-
34
- const iconClass = $derived(twMerge('shrink-0', className))
4
+ <script lang="ts">import Icon from "@iconify/svelte";
5
+ import { twMerge } from "tailwind-merge";
6
+ let { name, size = 24, color, flipH = false, flipV = false, rotate = 0, class: className, ...restProps } = $props();
7
+ const flip = $derived(flipH && flipV ? "horizontal,vertical" : flipH ? "horizontal" : flipV ? "vertical" : undefined);
8
+ const rotateValue = $derived(rotate ? rotate / 90 : undefined);
9
+ const iconClass = $derived(twMerge("shrink-0", className));
35
10
  </script>
36
11
 
37
12
  <Icon