@r2digisolutions/components 0.5.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.
- package/dist/components/atoms/Button/Button.stories.d.ts +6 -0
- package/dist/components/atoms/Button/Button.stories.js +7 -0
- package/dist/components/atoms/Button/Button.svelte +15 -17
- package/dist/components/atoms/Button/Button.svelte.d.ts +1 -0
- package/dist/components/atoms/Button/ButtonStory.svelte +2 -0
- package/dist/components/atoms/Button/ButtonStory.svelte.d.ts +1 -0
- package/dist/components/molecules/Combobox/Combobox.stories.d.ts +6 -0
- package/dist/components/molecules/Combobox/Combobox.stories.js +5 -2
- package/dist/components/molecules/Combobox/Combobox.svelte +155 -64
- package/dist/components/molecules/Combobox/Combobox.svelte.d.ts +15 -0
- package/dist/components/molecules/Combobox/ComboboxStory.svelte +25 -12
- package/dist/components/molecules/Combobox/ComboboxStory.svelte.d.ts +1 -0
- package/dist/components/molecules/Combobox/combobox-context.d.ts +12 -0
- package/dist/components/molecules/Combobox/combobox-context.js +10 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItem.stories.d.ts +9 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItem.stories.js +8 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItem.svelte +121 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItem.svelte.d.ts +33 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItemStory.svelte +54 -0
- package/dist/components/molecules/ComboboxItem/ComboboxItemStory.svelte.d.ts +3 -0
- package/dist/components/molecules/Form/Form.svelte +36 -10
- package/dist/components/molecules/FormField/FormField.svelte +56 -19
- package/dist/components/molecules/FormField/FormField.svelte.d.ts +21 -6
- package/dist/components/molecules/FormPasswordInput/FormPasswordInput.svelte +38 -9
- package/dist/components/molecules/FormPasswordInput/FormPasswordInput.svelte.d.ts +8 -1
- package/dist/components/organisms/BlogEditor/BlogEditorStory.svelte +1 -1
- package/dist/components/organisms/FileUploader/FileUploader.svelte +7 -3
- package/dist/components/organisms/InvoicePage/InvoicePage.svelte +1 -1
- package/dist/components/organisms/ProfileHeaderUploader/ProfileHeaderUploader.svelte +30 -10
- package/dist/components/organisms/ProfileHeaderUploader/ProfileHeaderUploader.svelte.d.ts +4 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +3 -1
- package/dist/styles.css +6 -0
- package/dist/utils/formContext.d.ts +58 -0
- package/dist/utils/formContext.js +79 -1
- package/package.json +16 -16
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
label?: string;
|
|
18
18
|
coverHelperText?: string;
|
|
19
19
|
avatarHelperText?: string;
|
|
20
|
+
/** Existing remote cover URL (shown when no local File is selected). */
|
|
21
|
+
coverSrc?: string;
|
|
22
|
+
/** Existing remote avatar URL (shown when no local File is selected). */
|
|
23
|
+
avatarSrc?: string;
|
|
20
24
|
accept?: string;
|
|
21
25
|
maxCoverMb?: number;
|
|
22
26
|
maxAvatarMb?: number;
|
|
@@ -30,6 +34,8 @@
|
|
|
30
34
|
label = 'Profile media',
|
|
31
35
|
coverHelperText = 'Cover · PNG or JPG · max 10MB',
|
|
32
36
|
avatarHelperText = 'Avatar · square image recommended',
|
|
37
|
+
coverSrc = '',
|
|
38
|
+
avatarSrc = '',
|
|
33
39
|
accept = 'image/*',
|
|
34
40
|
maxCoverMb = 10,
|
|
35
41
|
maxAvatarMb = 5,
|
|
@@ -52,6 +58,12 @@
|
|
|
52
58
|
const coverInputId = $derived(`${id}-cover`);
|
|
53
59
|
const avatarInputId = $derived(`${id}-avatar`);
|
|
54
60
|
|
|
61
|
+
const coverDisplayUrl = $derived(cover?.previewUrl || coverSrc || '');
|
|
62
|
+
const avatarDisplayUrl = $derived(avatar?.previewUrl || avatarSrc || '');
|
|
63
|
+
const hasCoverMedia = $derived(Boolean(coverDisplayUrl));
|
|
64
|
+
const hasAvatarMedia = $derived(Boolean(avatarDisplayUrl));
|
|
65
|
+
const hasLocalMedia = $derived(Boolean(cover || avatar));
|
|
66
|
+
|
|
55
67
|
function revoke(media: ProfileMedia | null) {
|
|
56
68
|
if (media?.previewUrl) URL.revokeObjectURL(media.previewUrl);
|
|
57
69
|
}
|
|
@@ -171,7 +183,7 @@
|
|
|
171
183
|
/>
|
|
172
184
|
|
|
173
185
|
<div
|
|
174
|
-
class="relative
|
|
186
|
+
class="relative rounded-xl border border-border bg-surface-elevated shadow-sm"
|
|
175
187
|
aria-labelledby={label ? labelId : undefined}
|
|
176
188
|
aria-describedby={[coverHelperText || avatarHelperText ? helperId : '', errorMessage ? errorId : '']
|
|
177
189
|
.filter(Boolean)
|
|
@@ -182,7 +194,11 @@
|
|
|
182
194
|
<div
|
|
183
195
|
role="button"
|
|
184
196
|
tabindex={disabled ? -1 : 0}
|
|
185
|
-
aria-label={
|
|
197
|
+
aria-label={hasCoverMedia
|
|
198
|
+
? cover
|
|
199
|
+
? `Change cover photo, ${cover.name}`
|
|
200
|
+
: 'Change cover photo'
|
|
201
|
+
: 'Upload cover photo'}
|
|
186
202
|
aria-disabled={disabled || undefined}
|
|
187
203
|
ondragenter={(e) => {
|
|
188
204
|
e.preventDefault();
|
|
@@ -200,14 +216,14 @@
|
|
|
200
216
|
}}
|
|
201
217
|
onclick={() => !disabled && coverInput?.click()}
|
|
202
218
|
class={[
|
|
203
|
-
'group relative aspect-3/1 min-h-36 w-full overflow-hidden outline-none transition-colors',
|
|
219
|
+
'group relative aspect-3/1 min-h-36 w-full overflow-hidden rounded-t-xl outline-none transition-colors',
|
|
204
220
|
'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500/40',
|
|
205
221
|
coverDragging ? 'bg-brand-500/10' : 'bg-surface-overlay',
|
|
206
222
|
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'
|
|
207
223
|
]}
|
|
208
224
|
>
|
|
209
|
-
{#if
|
|
210
|
-
<img src={
|
|
225
|
+
{#if hasCoverMedia}
|
|
226
|
+
<img src={coverDisplayUrl} alt="" class="h-full w-full object-cover" />
|
|
211
227
|
{#if !disabled}
|
|
212
228
|
<div
|
|
213
229
|
class="absolute inset-0 flex items-start justify-end bg-linear-to-b from-black/35 via-transparent to-transparent p-3 opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
|
|
@@ -267,7 +283,11 @@
|
|
|
267
283
|
<div
|
|
268
284
|
role="button"
|
|
269
285
|
tabindex={disabled ? -1 : 0}
|
|
270
|
-
aria-label={
|
|
286
|
+
aria-label={hasAvatarMedia
|
|
287
|
+
? avatar
|
|
288
|
+
? `Change profile photo, ${avatar.name}`
|
|
289
|
+
: 'Change profile photo'
|
|
290
|
+
: 'Upload profile photo'}
|
|
271
291
|
aria-disabled={disabled || undefined}
|
|
272
292
|
ondragenter={(e) => {
|
|
273
293
|
e.preventDefault();
|
|
@@ -298,14 +318,14 @@
|
|
|
298
318
|
if (!disabled) avatarInput?.click();
|
|
299
319
|
}}
|
|
300
320
|
class={[
|
|
301
|
-
'group relative h-20 w-20 shrink-0 overflow-hidden rounded-full border-4 border-surface-elevated bg-surface-overlay shadow-md outline-none transition sm:h-24 sm:w-24',
|
|
321
|
+
'group relative z-10 h-20 w-20 shrink-0 overflow-hidden rounded-full border-4 border-surface-elevated bg-surface-overlay shadow-md outline-none transition sm:h-24 sm:w-24',
|
|
302
322
|
'focus-visible:ring-2 focus-visible:ring-brand-500/40 focus-visible:ring-offset-2 focus-visible:ring-offset-surface-elevated',
|
|
303
323
|
avatarDragging ? 'ring-2 ring-brand-500' : '',
|
|
304
324
|
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'
|
|
305
325
|
]}
|
|
306
326
|
>
|
|
307
|
-
{#if
|
|
308
|
-
<img src={
|
|
327
|
+
{#if hasAvatarMedia}
|
|
328
|
+
<img src={avatarDisplayUrl} alt="" class="h-full w-full object-cover" />
|
|
309
329
|
{#if !disabled}
|
|
310
330
|
<div
|
|
311
331
|
class="absolute inset-0 flex items-center justify-center bg-black/45 opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
|
|
@@ -339,7 +359,7 @@
|
|
|
339
359
|
</div>
|
|
340
360
|
|
|
341
361
|
<div class="min-w-0 flex-1 pb-1">
|
|
342
|
-
{#if
|
|
362
|
+
{#if hasLocalMedia}
|
|
343
363
|
<div class="flex flex-wrap items-center gap-2">
|
|
344
364
|
{#if cover && !disabled}
|
|
345
365
|
<button
|
|
@@ -7,6 +7,10 @@ interface ProfileHeaderUploaderProps {
|
|
|
7
7
|
label?: string;
|
|
8
8
|
coverHelperText?: string;
|
|
9
9
|
avatarHelperText?: string;
|
|
10
|
+
/** Existing remote cover URL (shown when no local File is selected). */
|
|
11
|
+
coverSrc?: string;
|
|
12
|
+
/** Existing remote avatar URL (shown when no local File is selected). */
|
|
13
|
+
avatarSrc?: string;
|
|
10
14
|
accept?: string;
|
|
11
15
|
maxCoverMb?: number;
|
|
12
16
|
maxAvatarMb?: number;
|
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
|
-
|
|
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.
|
|
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.
|
|
70
|
-
"@playwright/test": "^1.
|
|
71
|
-
"@storybook/addon-a11y": "^
|
|
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": "^
|
|
75
|
-
"@storybook/svelte": "^
|
|
76
|
-
"@storybook/sveltekit": "^
|
|
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": "^
|
|
82
|
+
"@types/node": "^26.1.2",
|
|
83
83
|
"@vitest/browser-playwright": "^4.1.10",
|
|
84
|
-
"bumpp": "^
|
|
85
|
-
"eslint": "^10.
|
|
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.
|
|
88
|
+
"globals": "^17.8.0",
|
|
89
89
|
"mdsvex": "^0.12.8",
|
|
90
|
-
"playwright": "^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": "^
|
|
96
|
-
"svelte": "^5.56.
|
|
97
|
-
"svelte-check": "^4.7.
|
|
95
|
+
"storybook": "^10.5.5",
|
|
96
|
+
"svelte": "^5.56.8",
|
|
97
|
+
"svelte-check": "^4.7.4",
|
|
98
98
|
"tailwindcss": "^4.3.3",
|
|
99
|
-
"typescript": "
|
|
99
|
+
"typescript": "6.0.3",
|
|
100
100
|
"typescript-eslint": "^8.65.0",
|
|
101
101
|
"vite": "^8.1.5",
|
|
102
102
|
"vitest": "^4.1.10",
|