@r2digisolutions/components 0.6.0 → 0.6.1

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 (30) hide show
  1. package/dist/components/molecules/Combobox/Combobox.stories.d.ts +6 -0
  2. package/dist/components/molecules/Combobox/Combobox.stories.js +5 -2
  3. package/dist/components/molecules/Combobox/Combobox.svelte +155 -64
  4. package/dist/components/molecules/Combobox/Combobox.svelte.d.ts +15 -0
  5. package/dist/components/molecules/Combobox/ComboboxStory.svelte +25 -12
  6. package/dist/components/molecules/Combobox/ComboboxStory.svelte.d.ts +1 -0
  7. package/dist/components/molecules/Combobox/combobox-context.d.ts +12 -0
  8. package/dist/components/molecules/Combobox/combobox-context.js +10 -0
  9. package/dist/components/molecules/ComboboxItem/ComboboxItem.stories.d.ts +9 -0
  10. package/dist/components/molecules/ComboboxItem/ComboboxItem.stories.js +8 -0
  11. package/dist/components/molecules/ComboboxItem/ComboboxItem.svelte +121 -0
  12. package/dist/components/molecules/ComboboxItem/ComboboxItem.svelte.d.ts +33 -0
  13. package/dist/components/molecules/ComboboxItem/ComboboxItemStory.svelte +54 -0
  14. package/dist/components/molecules/ComboboxItem/ComboboxItemStory.svelte.d.ts +3 -0
  15. package/dist/components/molecules/Form/Form.svelte +36 -10
  16. package/dist/components/molecules/FormField/FormField.svelte +56 -19
  17. package/dist/components/molecules/FormField/FormField.svelte.d.ts +21 -6
  18. package/dist/components/molecules/FormPasswordInput/FormPasswordInput.svelte +38 -9
  19. package/dist/components/molecules/FormPasswordInput/FormPasswordInput.svelte.d.ts +8 -1
  20. package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte +1 -1
  21. package/dist/components/organisms/FileUploader/FileUploader.svelte +7 -3
  22. package/dist/components/organisms/InvoicePage/InvoicePage.svelte +1 -1
  23. package/dist/components/organisms/ProfileHeaderUploader/ProfileHeaderUploader.svelte +30 -10
  24. package/dist/components/organisms/ProfileHeaderUploader/ProfileHeaderUploader.svelte.d.ts +4 -0
  25. package/dist/index.d.ts +6 -3
  26. package/dist/index.js +3 -1
  27. package/dist/styles.css +6 -0
  28. package/dist/utils/formContext.d.ts +58 -0
  29. package/dist/utils/formContext.js +79 -1
  30. package/package.json +16 -16
package/dist/index.d.ts CHANGED
@@ -210,7 +210,10 @@ export type { TimeFormat } from './components/molecules/TimePicker/TimePicker.sv
210
210
  export { default as DateTimePicker } from './components/molecules/DateTimePicker/DateTimePicker.svelte';
211
211
  export { default as DateRangePicker } from './components/molecules/DateRangePicker/DateRangePicker.svelte';
212
212
  export { default as Combobox } from './components/molecules/Combobox/Combobox.svelte';
213
- export type { ComboboxOption } from './components/molecules/Combobox/Combobox.svelte';
213
+ export type { ComboboxOption, ComboboxItemState } from './components/molecules/Combobox/Combobox.svelte';
214
+ export { default as ComboboxItem } from './components/molecules/ComboboxItem/ComboboxItem.svelte';
215
+ export { COMBOBOX_CONTEXT, COMBOBOX_CREATE_KEY, getComboboxContext, setComboboxContext } from './components/molecules/Combobox/combobox-context.js';
216
+ export type { ComboboxContext } from './components/molecules/Combobox/combobox-context.js';
214
217
  export { default as MultiSelect } from './components/molecules/MultiSelect/MultiSelect.svelte';
215
218
  export type { MultiSelectOption } from './components/molecules/MultiSelect/MultiSelect.svelte';
216
219
  export { default as ConfirmDialog } from './components/molecules/ConfirmDialog/ConfirmDialog.svelte';
@@ -306,8 +309,8 @@ export type { FilterChip } from './components/molecules/FilterBar/FilterBar.svel
306
309
  export { default as SortableList } from './components/molecules/SortableList/SortableList.svelte';
307
310
  export type { SortableItem } from './components/molecules/SortableList/SortableList.svelte';
308
311
  export { default as Form } from './components/molecules/Form/Form.svelte';
309
- export { getFormContext, FORM_CONTEXT_KEY, remoteIssuesToErrors, isRemoteForm, fieldPath, resolveFormFieldState, applyFormDataSync, sameStringArray } from './utils/formContext.js';
310
- export type { FormErrors, FormContext, FormDataValues, FormFieldStatus, FormRemote,
312
+ export { getFormContext, FORM_CONTEXT_KEY, remoteIssuesToErrors, isRemoteForm, fieldPath, resolveFormFieldState, resolveRemoteInputProps, parseRemoteFieldName, remoteAsProps, getRemoteFormId, applyFormDataSync, sameStringArray } from './utils/formContext.js';
313
+ export type { FormErrors, FormContext, FormDataValues, FormFieldStatus, FormRemote, ResolvedRemoteInputProps, ParsedRemoteFieldName,
311
314
  /** @deprecated Use `FormRemote` */
312
315
  RemoteFormSpread, RemoteForm, RemoteFormInput, RemoteFormIssue } from './utils/formContext.js';
313
316
  export { validateForm, isFormDirty, firstFormError } from './utils/formValidate.js';
package/dist/index.js CHANGED
@@ -166,6 +166,8 @@ export { default as TimePicker } from './components/molecules/TimePicker/TimePic
166
166
  export { default as DateTimePicker } from './components/molecules/DateTimePicker/DateTimePicker.svelte';
167
167
  export { default as DateRangePicker } from './components/molecules/DateRangePicker/DateRangePicker.svelte';
168
168
  export { default as Combobox } from './components/molecules/Combobox/Combobox.svelte';
169
+ export { default as ComboboxItem } from './components/molecules/ComboboxItem/ComboboxItem.svelte';
170
+ export { COMBOBOX_CONTEXT, COMBOBOX_CREATE_KEY, getComboboxContext, setComboboxContext } from './components/molecules/Combobox/combobox-context.js';
169
171
  export { default as MultiSelect } from './components/molecules/MultiSelect/MultiSelect.svelte';
170
172
  export { default as ConfirmDialog } from './components/molecules/ConfirmDialog/ConfirmDialog.svelte';
171
173
  export { default as ContextMenu } from './components/molecules/ContextMenu/ContextMenu.svelte';
@@ -233,7 +235,7 @@ export { default as TableOfContents } from './components/molecules/TableOfConten
233
235
  export { default as FilterBar } from './components/molecules/FilterBar/FilterBar.svelte';
234
236
  export { default as SortableList } from './components/molecules/SortableList/SortableList.svelte';
235
237
  export { default as Form } from './components/molecules/Form/Form.svelte';
236
- export { getFormContext, FORM_CONTEXT_KEY, remoteIssuesToErrors, isRemoteForm, fieldPath, resolveFormFieldState, applyFormDataSync, sameStringArray } from './utils/formContext.js';
238
+ export { getFormContext, FORM_CONTEXT_KEY, remoteIssuesToErrors, isRemoteForm, fieldPath, resolveFormFieldState, resolveRemoteInputProps, parseRemoteFieldName, remoteAsProps, getRemoteFormId, applyFormDataSync, sameStringArray } from './utils/formContext.js';
237
239
  export { validateForm, isFormDirty, firstFormError } from './utils/formValidate.js';
238
240
  export { UI_DICTIONARIES, resolveLocaleTag, getDictionary, formatMessage } from './utils/i18n.js';
239
241
  export { i18n } from './utils/i18n.svelte.js';
package/dist/styles.css CHANGED
@@ -234,4 +234,10 @@
234
234
  .text-primary { color: var(--text-primary); }
235
235
  .text-secondary { color: var(--text-secondary); }
236
236
  .text-muted { color: var(--text-muted); }
237
+
238
+ /* SVG chart labels — Tailwind fill-* needs these (text-* alone does not paint <text>) */
239
+ .fill-primary { fill: var(--text-primary); }
240
+ .fill-secondary { fill: var(--text-secondary); }
241
+ .fill-muted { fill: var(--text-muted); }
242
+ .stroke-border { stroke: var(--border); }
237
243
  }
@@ -30,6 +30,13 @@ export interface FormContext<TData extends FormDataValues = FormDataValues, TRes
30
30
  disabled: boolean;
31
31
  /** Last result from a remote form / submit handler, if any */
32
32
  result: TResult | undefined;
33
+ /**
34
+ * Full Kit `RemoteForm` when `<Form remote={...}>` received one
35
+ * (not an `.enhance(...)` return). Used by fields to call `.as()`.
36
+ */
37
+ remote: RemoteForm<any, any> | null;
38
+ /** Stable Kit form id (`1p9kxmo/login_user`) for encoding input names */
39
+ remoteFormId: string | null;
33
40
  setError: (name: string, message: string) => void;
34
41
  clearError: (name: string) => void;
35
42
  clearErrors: () => void;
@@ -37,6 +44,57 @@ export interface FormContext<TData extends FormDataValues = FormDataValues, TRes
37
44
  getError: (name: string) => string | undefined;
38
45
  getData: <K extends keyof TData & string>(name: K) => TData[K] | undefined;
39
46
  }
47
+ /**
48
+ * Props safe to spread onto library `<Input>` / `<PasswordInput>` for Kit remote forms.
49
+ * Omits Kit `value` getters/setters so they do not fight `bind:value`.
50
+ */
51
+ export type ResolvedRemoteInputProps = {
52
+ name?: string;
53
+ };
54
+ /**
55
+ * Extract Kit remote form id from `remote.action` (`?/remote=<id>`).
56
+ * Pass `untrack(() => remote.action)` from callers inside effects/derived.
57
+ */
58
+ export declare function getRemoteFormId(action: string | undefined | null): string | null;
59
+ /**
60
+ * Resolve HTML input props for a field inside a Kit remote form.
61
+ * Prefer a precomputed `formId` string so fields never touch Kit proxies.
62
+ */
63
+ export declare function resolveRemoteInputProps(formId: string | null | undefined, logicalName: string | undefined, type?: string): ResolvedRemoteInputProps;
64
+ export type ParsedRemoteFieldName = {
65
+ /** Field path for errors / `form.data` (`email`, `items[0].name`) */
66
+ logicalName: string | undefined;
67
+ /** Exact HTML `name` to put on the input (Kit-encoded or plain) */
68
+ htmlName: string | undefined;
69
+ /** True when `name` already includes `/formId` (from Kit `.as()`) */
70
+ isEncoded: boolean;
71
+ };
72
+ /**
73
+ * Split a Kit-encoded HTML `name` (`email/1p9kxmo/login_user`, `n:age/...`)
74
+ * into logical + html parts. Plain names pass through unchanged.
75
+ *
76
+ * When `formId` is known, uses Kit’s suffix rule (`/${formId}`).
77
+ * Otherwise treats the first `/` after an optional `n:`/`b:` prefix as the form-id boundary
78
+ * (logical paths use `.` / `[n]`, never `/`).
79
+ */
80
+ export declare function parseRemoteFieldName(name: string | undefined, formId?: string | null): ParsedRemoteFieldName;
81
+ /**
82
+ * Safe props to spread onto `FormField` / `FormPasswordInput` from Kit `.as(...)`.
83
+ * Keeps `name` (+ `type`) and drops Kit `value` / `defaultValue` accessors that
84
+ * fight library `$bindable` and can cause `effect_update_depth_exceeded`.
85
+ *
86
+ * @example
87
+ * ```svelte
88
+ * <FormField {...remoteAsProps(login_user.fields.email.as('email'))} label="Email" />
89
+ * ```
90
+ */
91
+ export declare function remoteAsProps(asProps: {
92
+ name: string;
93
+ type?: string;
94
+ }): {
95
+ name: string;
96
+ type?: string;
97
+ };
40
98
  /**
41
99
  * Access the nearest `<Form>` context from a child component.
42
100
  * Returns `null` when used outside a Form.
@@ -7,6 +7,83 @@ export const FORM_CONTEXT_KEY = 'r2-form';
7
7
  export function fieldPath(...segments) {
8
8
  return segments.map(String).filter((s) => s.length > 0).join('.');
9
9
  }
10
+ /**
11
+ * Extract Kit remote form id from `remote.action` (`?/remote=<id>`).
12
+ * Pass `untrack(() => remote.action)` from callers inside effects/derived.
13
+ */
14
+ export function getRemoteFormId(action) {
15
+ if (!action)
16
+ return null;
17
+ const match = action.match(/\/remote=([^&]+)/);
18
+ return match ? decodeURIComponent(match[1]) : null;
19
+ }
20
+ /**
21
+ * Resolve HTML input props for a field inside a Kit remote form.
22
+ * Prefer a precomputed `formId` string so fields never touch Kit proxies.
23
+ */
24
+ export function resolveRemoteInputProps(formId, logicalName, type = 'text') {
25
+ if (!logicalName)
26
+ return {};
27
+ if (!formId)
28
+ return { name: logicalName };
29
+ let prefix = '';
30
+ if (type === 'number' || type === 'range')
31
+ prefix = 'n:';
32
+ else if (type === 'checkbox' || type === 'boolean')
33
+ prefix = 'b:';
34
+ return { name: `${prefix}${logicalName}/${formId}` };
35
+ }
36
+ /**
37
+ * Split a Kit-encoded HTML `name` (`email/1p9kxmo/login_user`, `n:age/...`)
38
+ * into logical + html parts. Plain names pass through unchanged.
39
+ *
40
+ * When `formId` is known, uses Kit’s suffix rule (`/${formId}`).
41
+ * Otherwise treats the first `/` after an optional `n:`/`b:` prefix as the form-id boundary
42
+ * (logical paths use `.` / `[n]`, never `/`).
43
+ */
44
+ export function parseRemoteFieldName(name, formId) {
45
+ if (!name) {
46
+ return { logicalName: undefined, htmlName: undefined, isEncoded: false };
47
+ }
48
+ const htmlName = name;
49
+ if (formId && name.endsWith(`/${formId}`)) {
50
+ let logical = name.slice(0, -(formId.length + 1));
51
+ if (logical.startsWith('n:') || logical.startsWith('b:'))
52
+ logical = logical.slice(2);
53
+ if (logical.endsWith('[]'))
54
+ logical = logical.slice(0, -2);
55
+ return { logicalName: logical, htmlName, isEncoded: true };
56
+ }
57
+ // Heuristic without formId: Kit-encoded names contain `/`
58
+ if (name.includes('/')) {
59
+ let rest = name;
60
+ if (rest.startsWith('n:') || rest.startsWith('b:'))
61
+ rest = rest.slice(2);
62
+ const slash = rest.indexOf('/');
63
+ if (slash !== -1) {
64
+ let logical = rest.slice(0, slash);
65
+ if (logical.endsWith('[]'))
66
+ logical = logical.slice(0, -2);
67
+ return { logicalName: logical, htmlName, isEncoded: true };
68
+ }
69
+ }
70
+ return { logicalName: name, htmlName: name, isEncoded: false };
71
+ }
72
+ /**
73
+ * Safe props to spread onto `FormField` / `FormPasswordInput` from Kit `.as(...)`.
74
+ * Keeps `name` (+ `type`) and drops Kit `value` / `defaultValue` accessors that
75
+ * fight library `$bindable` and can cause `effect_update_depth_exceeded`.
76
+ *
77
+ * @example
78
+ * ```svelte
79
+ * <FormField {...remoteAsProps(login_user.fields.email.as('email'))} label="Email" />
80
+ * ```
81
+ */
82
+ export function remoteAsProps(asProps) {
83
+ return asProps.type != null
84
+ ? { name: asProps.name, type: asProps.type }
85
+ : { name: asProps.name };
86
+ }
10
87
  /**
11
88
  * Access the nearest `<Form>` context from a child component.
12
89
  * Returns `null` when used outside a Form.
@@ -42,7 +119,8 @@ export function remoteIssuesToErrors(issues) {
42
119
  }
43
120
  /** True when the spread value is a full `RemoteForm` (has `fields` / `pending`). */
44
121
  export function isRemoteForm(remote) {
45
- return !!remote && 'fields' in remote && typeof remote.fields === 'object';
122
+ // Avoid reading `.fields` (client getter allocates a proxy and can loop in `$derived`).
123
+ return !!remote && 'fields' in remote;
46
124
  }
47
125
  /**
48
126
  * Shared resolution for form-aware controls (error / status / disabled).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",
@@ -66,37 +66,37 @@
66
66
  },
67
67
  "devDependencies": {
68
68
  "@eslint/js": "^10.0.1",
69
- "@lucide/svelte": "^1.26.0",
70
- "@playwright/test": "^1.61.1",
71
- "@storybook/addon-a11y": "^8.6.18",
69
+ "@lucide/svelte": "^1.27.0",
70
+ "@playwright/test": "^1.62.0",
71
+ "@storybook/addon-a11y": "^10.5.5",
72
72
  "@storybook/addon-essentials": "^8.6.18",
73
73
  "@storybook/addon-svelte-csf": "^5.1.2",
74
- "@storybook/addon-themes": "^8.6.18",
75
- "@storybook/svelte": "^8.6.18",
76
- "@storybook/sveltekit": "^8.6.18",
74
+ "@storybook/addon-themes": "^10.5.5",
75
+ "@storybook/svelte": "^10.5.5",
76
+ "@storybook/sveltekit": "^10.5.5",
77
77
  "@sveltejs/adapter-static": "next",
78
78
  "@sveltejs/kit": "next",
79
79
  "@sveltejs/package": "^2.5.8",
80
80
  "@sveltejs/vite-plugin-svelte": "^7.2.0",
81
81
  "@tailwindcss/vite": "^4.3.3",
82
- "@types/node": "^24.13.3",
82
+ "@types/node": "^26.1.2",
83
83
  "@vitest/browser-playwright": "^4.1.10",
84
- "bumpp": "^10.4.1",
85
- "eslint": "^10.7.0",
84
+ "bumpp": "^12.1.1",
85
+ "eslint": "^10.8.0",
86
86
  "eslint-config-prettier": "^10.1.8",
87
87
  "eslint-plugin-svelte": "^3.22.0",
88
- "globals": "^17.7.0",
88
+ "globals": "^17.8.0",
89
89
  "mdsvex": "^0.12.8",
90
- "playwright": "^1.61.1",
90
+ "playwright": "^1.62.0",
91
91
  "prettier": "^3.9.6",
92
92
  "prettier-plugin-svelte": "^4.1.1",
93
93
  "prettier-plugin-tailwindcss": "^0.8.1",
94
94
  "publint": "^0.3.22",
95
- "storybook": "^8.6.18",
96
- "svelte": "^5.56.7",
97
- "svelte-check": "^4.7.3",
95
+ "storybook": "^10.5.5",
96
+ "svelte": "^5.56.8",
97
+ "svelte-check": "^4.7.4",
98
98
  "tailwindcss": "^4.3.3",
99
- "typescript": "^6.0.3",
99
+ "typescript": "6.0.3",
100
100
  "typescript-eslint": "^8.65.0",
101
101
  "vite": "^8.1.5",
102
102
  "vitest": "^4.1.10",