@mrintel/villain-ui 0.7.8 → 0.8.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.
- package/dist/components/buttons/Button.svelte.d.ts +1 -1
- package/dist/components/buttons/IconButton.svelte.d.ts +1 -1
- package/dist/components/buttons/LinkButton.svelte.d.ts +1 -1
- package/dist/components/buttons/buttonClasses.d.ts +2 -0
- package/dist/components/buttons/buttonClasses.js +2 -1
- package/dist/components/cards/SectionHeader.svelte +8 -3
- package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
- package/dist/components/data/Data.types.d.ts +10 -0
- package/dist/components/data/EmptyState.svelte +42 -0
- package/dist/components/data/EmptyState.svelte.d.ts +13 -0
- package/dist/components/data/Stat.svelte +30 -2
- package/dist/components/data/Stat.svelte.d.ts +9 -0
- package/dist/components/data/WeekHeatmap.svelte +150 -0
- package/dist/components/data/WeekHeatmap.svelte.d.ts +47 -0
- package/dist/components/data/index.d.ts +3 -1
- package/dist/components/data/index.js +2 -0
- package/dist/components/forms/Checkbox.svelte +27 -21
- package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/forms/DatePicker.svelte +7 -2
- package/dist/components/forms/DatePicker.svelte.d.ts +1 -0
- package/dist/components/forms/DateTimePicker.svelte +7 -2
- package/dist/components/forms/DateTimePicker.svelte.d.ts +1 -0
- package/dist/components/forms/FileUpload.svelte +7 -2
- package/dist/components/forms/FileUpload.svelte.d.ts +2 -0
- package/dist/components/forms/Input.svelte +95 -10
- package/dist/components/forms/Input.svelte.d.ts +8 -0
- package/dist/components/forms/RadioGroup.svelte +35 -25
- package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/forms/RangeSlider.svelte +6 -2
- package/dist/components/forms/RangeSlider.svelte.d.ts +2 -0
- package/dist/components/forms/Select.svelte +39 -30
- package/dist/components/forms/Select.svelte.d.ts +1 -0
- package/dist/components/forms/SelectMenu.svelte +7 -2
- package/dist/components/forms/SelectMenu.svelte.d.ts +1 -0
- package/dist/components/forms/Switch.svelte +31 -23
- package/dist/components/forms/Switch.svelte.d.ts +2 -0
- package/dist/components/forms/Textarea.svelte +28 -19
- package/dist/components/forms/Textarea.svelte.d.ts +1 -0
- package/dist/components/forms/TimePicker.svelte +7 -2
- package/dist/components/forms/TimePicker.svelte.d.ts +1 -0
- package/dist/components/overlays/ToastHost.svelte +19 -0
- package/dist/components/overlays/ToastHost.svelte.d.ts +8 -0
- package/dist/components/overlays/index.d.ts +3 -0
- package/dist/components/overlays/index.js +2 -0
- package/dist/components/overlays/toast.store.d.ts +32 -0
- package/dist/components/overlays/toast.store.js +34 -0
- package/dist/components/typography/Heading.svelte +3 -3
- package/dist/components/typography/Heading.svelte.d.ts +1 -0
- package/dist/components/typography/Text.svelte +2 -2
- package/dist/components/typography/Text.svelte.d.ts +1 -0
- package/dist/components/wizard/StepForm.svelte +22 -1
- package/dist/components/wizard/StepForm.svelte.d.ts +8 -1
- package/dist/components/wizard/step.service.js +89 -21
- package/dist/components/wizard/step.types.d.ts +32 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +3 -3
- package/dist/theme.css +125 -0
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">import Toast from './Toast.svelte';
|
|
2
|
+
import { toasts, dismissToast } from './toast.store';
|
|
3
|
+
let { position = 'bottom-right', maxVisible = 4 } = $props();
|
|
4
|
+
let items = $state(toasts.get());
|
|
5
|
+
$effect(() => toasts.subscribe((value) => (items = value)));
|
|
6
|
+
const visible = $derived(items.slice(0, maxVisible));
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
{#each visible as item, index (item.id)}
|
|
10
|
+
<Toast
|
|
11
|
+
message={item.message}
|
|
12
|
+
variant={item.variant}
|
|
13
|
+
duration={item.duration}
|
|
14
|
+
dismissible={item.dismissible}
|
|
15
|
+
{position}
|
|
16
|
+
{index}
|
|
17
|
+
onClose={() => dismissToast(item.id)}
|
|
18
|
+
/>
|
|
19
|
+
{/each}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
position?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
3
|
+
/** Newest toasts beyond this count are queued until older ones dismiss. */
|
|
4
|
+
maxVisible?: number;
|
|
5
|
+
}
|
|
6
|
+
declare const ToastHost: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ToastHost = ReturnType<typeof ToastHost>;
|
|
8
|
+
export default ToastHost;
|
|
@@ -5,6 +5,9 @@ export { default as Tooltip } from './Tooltip.svelte';
|
|
|
5
5
|
export { default as ProgressBar } from './ProgressBar.svelte';
|
|
6
6
|
export { default as SkeletonLoader } from './SkeletonLoader.svelte';
|
|
7
7
|
export { default as Toast } from './Toast.svelte';
|
|
8
|
+
export { default as ToastHost } from './ToastHost.svelte';
|
|
9
|
+
export { toast, toasts, dismissToast } from './toast.store';
|
|
10
|
+
export type { ToastItem, ToastOptions, ToastVariant } from './toast.store';
|
|
8
11
|
export { default as Drawer } from './Drawer.svelte';
|
|
9
12
|
export { default as Popover } from './Popover.svelte';
|
|
10
13
|
export { default as Dropdown } from './Dropdown.svelte';
|
|
@@ -5,6 +5,8 @@ export { default as Tooltip } from './Tooltip.svelte';
|
|
|
5
5
|
export { default as ProgressBar } from './ProgressBar.svelte';
|
|
6
6
|
export { default as SkeletonLoader } from './SkeletonLoader.svelte';
|
|
7
7
|
export { default as Toast } from './Toast.svelte';
|
|
8
|
+
export { default as ToastHost } from './ToastHost.svelte';
|
|
9
|
+
export { toast, toasts, dismissToast } from './toast.store';
|
|
8
10
|
export { default as Drawer } from './Drawer.svelte';
|
|
9
11
|
export { default as Popover } from './Popover.svelte';
|
|
10
12
|
export { default as Dropdown } from './Dropdown.svelte';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type ToastVariant = 'info' | 'success' | 'warning' | 'error';
|
|
2
|
+
export interface ToastOptions {
|
|
3
|
+
variant?: ToastVariant;
|
|
4
|
+
/** Auto-dismiss delay in ms. 0 disables auto-dismiss. Default 3000. */
|
|
5
|
+
duration?: number;
|
|
6
|
+
dismissible?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ToastItem extends Required<Pick<ToastOptions, 'variant' | 'duration' | 'dismissible'>> {
|
|
9
|
+
id: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/** Active toasts, oldest first. Rendered by `<ToastHost />`. */
|
|
13
|
+
export declare const toasts: import("nanostores").PreinitializedWritableAtom<ToastItem[]> & object;
|
|
14
|
+
export declare function dismissToast(id: string): void;
|
|
15
|
+
declare function push(message: string, options?: ToastOptions): string;
|
|
16
|
+
/**
|
|
17
|
+
* Show a toast. Requires a single `<ToastHost />` mounted in the app's root
|
|
18
|
+
* layout. Returns the toast id (usable with `dismissToast`).
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* toast('Saved');
|
|
22
|
+
* toast.success('Added to cart');
|
|
23
|
+
* toast.error('Could not save', { duration: 0 });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const toast: typeof push & {
|
|
27
|
+
info: (message: string, options?: Omit<ToastOptions, "variant">) => string;
|
|
28
|
+
success: (message: string, options?: Omit<ToastOptions, "variant">) => string;
|
|
29
|
+
warning: (message: string, options?: Omit<ToastOptions, "variant">) => string;
|
|
30
|
+
error: (message: string, options?: Omit<ToastOptions, "variant">) => string;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { atom } from 'nanostores';
|
|
2
|
+
import { createId } from '../../lib/internal/id';
|
|
3
|
+
/** Active toasts, oldest first. Rendered by `<ToastHost />`. */
|
|
4
|
+
export const toasts = atom([]);
|
|
5
|
+
export function dismissToast(id) {
|
|
6
|
+
toasts.set(toasts.get().filter((t) => t.id !== id));
|
|
7
|
+
}
|
|
8
|
+
function push(message, options = {}) {
|
|
9
|
+
const item = {
|
|
10
|
+
id: createId('toast'),
|
|
11
|
+
message,
|
|
12
|
+
variant: options.variant ?? 'info',
|
|
13
|
+
duration: options.duration ?? 3000,
|
|
14
|
+
dismissible: options.dismissible ?? true
|
|
15
|
+
};
|
|
16
|
+
toasts.set([...toasts.get(), item]);
|
|
17
|
+
return item.id;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Show a toast. Requires a single `<ToastHost />` mounted in the app's root
|
|
21
|
+
* layout. Returns the toast id (usable with `dismissToast`).
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* toast('Saved');
|
|
25
|
+
* toast.success('Added to cart');
|
|
26
|
+
* toast.error('Could not save', { duration: 0 });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const toast = Object.assign(push, {
|
|
30
|
+
info: (message, options) => push(message, { ...options, variant: 'info' }),
|
|
31
|
+
success: (message, options) => push(message, { ...options, variant: 'success' }),
|
|
32
|
+
warning: (message, options) => push(message, { ...options, variant: 'warning' }),
|
|
33
|
+
error: (message, options) => push(message, { ...options, variant: 'error' })
|
|
34
|
+
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<script lang="ts">"use strict";
|
|
2
|
-
let { level = 1, glow = false, variant = 'default', as, children } = $props();
|
|
2
|
+
let { level = 1, glow = false, variant = 'default', as, children, class: className = '', ...restProps } = $props();
|
|
3
3
|
const element = $derived(as ?? `h${level}`);
|
|
4
4
|
const variantClasses = {
|
|
5
5
|
default: '',
|
|
6
6
|
accent: 'text-accent',
|
|
7
7
|
gradient: 'text-gradient'
|
|
8
8
|
};
|
|
9
|
-
const baseClasses = $derived(`transition-all duration-300 ${glow ? 'text-glow' : ''} ${variantClasses[variant]}`);
|
|
9
|
+
const baseClasses = $derived(`transition-all duration-300 ${glow ? 'text-glow' : ''} ${variantClasses[variant]} ${className}`);
|
|
10
10
|
const styles = $derived(`font-size: var(--text-h${level}-size); line-height: var(--text-h${level}-line-height); font-weight: var(--text-h${level}-weight); letter-spacing: var(--text-h${level}-letter-spacing); font-family: var(--font-heading); color: var(--color-text);`);
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
-
<svelte:element this={element} class={baseClasses} style={styles}>
|
|
13
|
+
<svelte:element this={element} class={baseClasses} style={styles} {...restProps}>
|
|
14
14
|
{@render children?.()}
|
|
15
15
|
</svelte:element>
|
|
@@ -4,6 +4,7 @@ interface Props {
|
|
|
4
4
|
variant?: 'default' | 'accent' | 'gradient';
|
|
5
5
|
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
6
|
children?: import('svelte').Snippet;
|
|
7
|
+
class?: string;
|
|
7
8
|
}
|
|
8
9
|
declare const Heading: import("svelte").Component<Props, {}, "">;
|
|
9
10
|
type Heading = ReturnType<typeof Heading>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">"use strict";
|
|
2
|
-
let { variant = 'body', color = 'default', weight = 'normal', as = 'p', children } = $props();
|
|
2
|
+
let { variant = 'body', color = 'default', weight = 'normal', as = 'p', children, class: className = '', ...restProps } = $props();
|
|
3
3
|
const colorMap = {
|
|
4
4
|
default: 'var(--color-text)',
|
|
5
5
|
soft: 'var(--color-text-soft)',
|
|
@@ -16,6 +16,6 @@ const weightMap = {
|
|
|
16
16
|
const styles = $derived(`font-size: var(--text-${variant}-size); line-height: var(--text-${variant}-line-height); font-weight: ${weightMap[weight] || '400'}; letter-spacing: var(--text-${variant}-letter-spacing); font-family: var(--font-body); color: ${colorMap[color]};`);
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
|
-
<svelte:element this={as} class="transition-colors duration-300" style={styles}>
|
|
19
|
+
<svelte:element this={as} class="transition-colors duration-300 {className}" style={styles} {...restProps}>
|
|
20
20
|
{@render children?.()}
|
|
21
21
|
</svelte:element>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import { createStepController } from './step.service';
|
|
2
2
|
// No-op action fallback when enhance is not provided
|
|
3
3
|
const noop = () => { };
|
|
4
|
-
let { steps: stepDefs, controller: externalController, orientation = 'horizontal', initialStepId, controllerOptions = {}, enhance = noop, method = 'POST', action: formAction, rail, step: stepSnippet, actions, class: className = '', ...restProps } = $props();
|
|
4
|
+
let { steps: stepDefs, controller: externalController, orientation = 'horizontal', initialStepId, controllerOptions = {}, enhance = noop, method = 'POST', action: formAction, rail, step: stepSnippet, actions, error: errorSnippet, showErrors = true, class: className = '', ...restProps } = $props();
|
|
5
5
|
// Create or use external controller (captured once at init)
|
|
6
6
|
const ctrl = externalController ??
|
|
7
7
|
createStepController({
|
|
@@ -14,10 +14,12 @@ const ctrl = externalController ??
|
|
|
14
14
|
const stepsStore = ctrl.steps;
|
|
15
15
|
const currentStore = ctrl.current;
|
|
16
16
|
const indexStore = ctrl.currentIndex;
|
|
17
|
+
const errorsStore = ctrl.stepErrors;
|
|
17
18
|
// Use $ prefix to subscribe to nanostores (Svelte store contract)
|
|
18
19
|
const runtimeSteps = $derived($stepsStore);
|
|
19
20
|
const currentStep = $derived($currentStore);
|
|
20
21
|
const currentIndex = $derived($indexStore);
|
|
22
|
+
const currentErrors = $derived($errorsStore[currentStep.id] ?? []);
|
|
21
23
|
// Computed loading state
|
|
22
24
|
const isLoading = $derived($currentStore.state === 'loading');
|
|
23
25
|
// Check if on first/last step
|
|
@@ -35,6 +37,8 @@ const railContext = $derived({
|
|
|
35
37
|
});
|
|
36
38
|
const stepContext = $derived({
|
|
37
39
|
step: currentStep,
|
|
40
|
+
isLoading: isLoading,
|
|
41
|
+
errors: currentErrors,
|
|
38
42
|
});
|
|
39
43
|
const actionsContext = $derived({
|
|
40
44
|
loading: isLoading,
|
|
@@ -79,6 +83,23 @@ const layoutClasses = $derived(() => {
|
|
|
79
83
|
</div>
|
|
80
84
|
{/if}
|
|
81
85
|
|
|
86
|
+
<!-- Error Section -->
|
|
87
|
+
{#if currentErrors.length > 0}
|
|
88
|
+
{#if errorSnippet}
|
|
89
|
+
{@render errorSnippet({ step: currentStep, errors: currentErrors })}
|
|
90
|
+
{:else if showErrors}
|
|
91
|
+
<div
|
|
92
|
+
class="mt-4 rounded-[var(--radius-lg)] border-l-4 border-[var(--color-error)] bg-[var(--color-error-overlay-10)] px-4 py-3"
|
|
93
|
+
role="alert"
|
|
94
|
+
aria-live="polite"
|
|
95
|
+
>
|
|
96
|
+
{#each currentErrors as message (message)}
|
|
97
|
+
<p class="text-sm text-[var(--color-error)]">{message}</p>
|
|
98
|
+
{/each}
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
{/if}
|
|
102
|
+
|
|
82
103
|
<!-- Actions Section -->
|
|
83
104
|
{#if actions}
|
|
84
105
|
<div class="flex justify-between items-center gap-4 mt-6 pt-6 border-t border-[var(--color-border)]">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { Action } from 'svelte/action';
|
|
3
|
-
import type { StepDefinition, StepOrientation, StepController, StepControllerOptions, StepRailSnippetContext, StepPanelSnippetContext, StepActionsSnippetContext } from './step.types';
|
|
3
|
+
import type { StepDefinition, StepOrientation, StepController, StepControllerOptions, StepRailSnippetContext, StepPanelSnippetContext, StepActionsSnippetContext, StepRuntime } from './step.types';
|
|
4
4
|
type TId = string;
|
|
5
5
|
interface Props {
|
|
6
6
|
/** Step definitions */
|
|
@@ -25,6 +25,13 @@ interface Props {
|
|
|
25
25
|
step?: Snippet<[StepPanelSnippetContext<TId>]>;
|
|
26
26
|
/** Actions snippet */
|
|
27
27
|
actions?: Snippet<[StepActionsSnippetContext<TId>]>;
|
|
28
|
+
/** Custom error rendering between step content and actions. */
|
|
29
|
+
error?: Snippet<[{
|
|
30
|
+
step: StepRuntime<TId>;
|
|
31
|
+
errors: string[];
|
|
32
|
+
}]>;
|
|
33
|
+
/** Render the default error block when no `error` snippet given. Default true. */
|
|
34
|
+
showErrors?: boolean;
|
|
28
35
|
/** Additional CSS classes */
|
|
29
36
|
class?: string;
|
|
30
37
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { atom, computed } from 'nanostores';
|
|
2
|
-
import { arrayHasValueInArray } from '../../lib/internal/util';
|
|
3
2
|
/**
|
|
4
3
|
* Creates a headless step controller using nanostores.
|
|
5
4
|
* This service owns all step state, transitions, and guards.
|
|
@@ -18,6 +17,10 @@ export function createStepController(options) {
|
|
|
18
17
|
* Internal loading state.
|
|
19
18
|
*/
|
|
20
19
|
const $loading = atom(false);
|
|
20
|
+
/**
|
|
21
|
+
* Per-step error messages, keyed by step id.
|
|
22
|
+
*/
|
|
23
|
+
const $stepErrors = atom({});
|
|
21
24
|
/**
|
|
22
25
|
* Base step store (authoritative mutable data).
|
|
23
26
|
*/
|
|
@@ -102,6 +105,20 @@ export function createStepController(options) {
|
|
|
102
105
|
* @param step Runtime step
|
|
103
106
|
* @returns Whether validation passed
|
|
104
107
|
*/
|
|
108
|
+
/**
|
|
109
|
+
* Assigns a step's validator to the attached form's options.validators.
|
|
110
|
+
* Steps without a validator leave whatever is currently set — matches the
|
|
111
|
+
* behavior for wizards that configure a single schema up front.
|
|
112
|
+
*
|
|
113
|
+
* @param step Step whose validator should become active
|
|
114
|
+
*/
|
|
115
|
+
function applyStepValidator(step) {
|
|
116
|
+
if (!formIntegration?.options)
|
|
117
|
+
return;
|
|
118
|
+
if (step.validator !== undefined) {
|
|
119
|
+
formIntegration.options.validators = step.validator;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
105
122
|
async function validateStep(step) {
|
|
106
123
|
setLoading(true);
|
|
107
124
|
try {
|
|
@@ -155,6 +172,7 @@ export function createStepController(options) {
|
|
|
155
172
|
.get()
|
|
156
173
|
.map((s) => s.index === current.index ? { ...s, visited: true } : s));
|
|
157
174
|
$currentIndex.set(targetIndex);
|
|
175
|
+
applyStepValidator(target);
|
|
158
176
|
onStepChange?.(target);
|
|
159
177
|
return true;
|
|
160
178
|
}
|
|
@@ -173,7 +191,16 @@ export function createStepController(options) {
|
|
|
173
191
|
}
|
|
174
192
|
// Validate current step before proceeding only if not a submit step
|
|
175
193
|
// Submit steps are expected to handle validation via form submission (user handled but we react to it)
|
|
176
|
-
|
|
194
|
+
// Validate against the step being LEFT, with its own validator active.
|
|
195
|
+
const shouldValidate = current.validateOnNext ?? current.validator !== undefined;
|
|
196
|
+
if (shouldValidate) {
|
|
197
|
+
applyStepValidator(current);
|
|
198
|
+
if (!(await validateStep(current))) {
|
|
199
|
+
onError?.(current);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (!(await validateStep(current))) {
|
|
177
204
|
onError?.(current);
|
|
178
205
|
return false;
|
|
179
206
|
}
|
|
@@ -254,6 +281,33 @@ export function createStepController(options) {
|
|
|
254
281
|
.get()
|
|
255
282
|
.map((s) => s.id === id ? { ...s, stateOverride: undefined } : s));
|
|
256
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Sets human-readable error messages on a step and flips its rail state.
|
|
286
|
+
*
|
|
287
|
+
* @param id Step identifier
|
|
288
|
+
* @param error Message or list of messages
|
|
289
|
+
*/
|
|
290
|
+
function setStepError(id, error) {
|
|
291
|
+
$stepErrors.set({
|
|
292
|
+
...$stepErrors.get(),
|
|
293
|
+
[id]: Array.isArray(error) ? error : [error],
|
|
294
|
+
});
|
|
295
|
+
setStepState(id, 'error');
|
|
296
|
+
const step = $runtimeSteps.get().find((s) => s.id === id);
|
|
297
|
+
if (step)
|
|
298
|
+
onError?.(step, $stepErrors.get()[id]);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Clears a step's error messages and its rail error state.
|
|
302
|
+
*
|
|
303
|
+
* @param id Step identifier
|
|
304
|
+
*/
|
|
305
|
+
function clearStepError(id) {
|
|
306
|
+
const next = { ...$stepErrors.get() };
|
|
307
|
+
delete next[id];
|
|
308
|
+
$stepErrors.set(next);
|
|
309
|
+
clearStepState(id);
|
|
310
|
+
}
|
|
257
311
|
/**
|
|
258
312
|
* Marks a step as completed.
|
|
259
313
|
*
|
|
@@ -277,6 +331,7 @@ export function createStepController(options) {
|
|
|
277
331
|
completed: false,
|
|
278
332
|
active: false,
|
|
279
333
|
})));
|
|
334
|
+
$stepErrors.set({});
|
|
280
335
|
if (toStepId) {
|
|
281
336
|
const idx = stepDefs.findIndex((s) => s.id === toStepId);
|
|
282
337
|
$currentIndex.set(idx >= 0 ? idx : 0);
|
|
@@ -296,41 +351,51 @@ export function createStepController(options) {
|
|
|
296
351
|
current,
|
|
297
352
|
steps,
|
|
298
353
|
}));
|
|
299
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Flattens a superforms nested error object into dot-path keys → messages.
|
|
356
|
+
* e.g. { user: { email: ['Invalid'] } } → { 'user.email': ['Invalid'] }.
|
|
357
|
+
*/
|
|
358
|
+
const flattenErrorMessages = (value, prefix = '') => {
|
|
300
359
|
if (Array.isArray(value)) {
|
|
301
|
-
return value.length > 0;
|
|
360
|
+
return value.length > 0 && prefix ? { [prefix]: value } : {};
|
|
302
361
|
}
|
|
303
362
|
if (value && typeof value === 'object') {
|
|
304
|
-
return Object.
|
|
363
|
+
return Object.entries(value).reduce((acc, [key, child]) => {
|
|
364
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
365
|
+
return { ...acc, ...flattenErrorMessages(child, path) };
|
|
366
|
+
}, {});
|
|
305
367
|
}
|
|
306
|
-
return
|
|
368
|
+
return {};
|
|
307
369
|
};
|
|
308
370
|
const attachFormIntegration = (form) => {
|
|
309
371
|
// Implementation for attaching form integration
|
|
310
372
|
formIntegration = form;
|
|
373
|
+
// Apply the initial step's validator so the first next() validates
|
|
374
|
+
// against the right schema even before any step change fires.
|
|
375
|
+
applyStepValidator($currentStep.get());
|
|
311
376
|
// Subscribe to form submitting state
|
|
312
377
|
unsubSubmitting = formIntegration.submitting.subscribe((submitting) => {
|
|
313
|
-
// Clear any error
|
|
378
|
+
// Clear any error on submit start (user is fixing things)
|
|
314
379
|
if (submitting) {
|
|
315
|
-
|
|
380
|
+
clearStepError($currentStep.get().id);
|
|
316
381
|
}
|
|
317
382
|
setLoading(submitting);
|
|
318
383
|
});
|
|
319
384
|
unsubErrors = formIntegration.errors.subscribe((errors) => {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
clearStepState(step.id);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
stepDefs.forEach((step) => {
|
|
329
|
-
if (arrayHasValueInArray(keys, step.data || [])) {
|
|
330
|
-
setStepState(step.id, 'error');
|
|
331
|
-
}
|
|
332
|
-
});
|
|
385
|
+
const flat = flattenErrorMessages(errors);
|
|
386
|
+
if (Object.keys(flat).length === 0) {
|
|
387
|
+
stepDefs.forEach((step) => clearStepError(step.id));
|
|
388
|
+
return;
|
|
333
389
|
}
|
|
390
|
+
stepDefs.forEach((step) => {
|
|
391
|
+
const messages = Object.entries(flat)
|
|
392
|
+
.filter(([key]) => (step.data ?? []).includes(key.split('.')[0]))
|
|
393
|
+
.flatMap(([, msgs]) => msgs);
|
|
394
|
+
if (messages.length > 0)
|
|
395
|
+
setStepError(step.id, messages);
|
|
396
|
+
else
|
|
397
|
+
clearStepError(step.id);
|
|
398
|
+
});
|
|
334
399
|
});
|
|
335
400
|
};
|
|
336
401
|
return {
|
|
@@ -339,6 +404,7 @@ export function createStepController(options) {
|
|
|
339
404
|
current: $currentStep,
|
|
340
405
|
currentIndex: $currentIndex,
|
|
341
406
|
isLastStep: $isLastStep,
|
|
407
|
+
stepErrors: $stepErrors,
|
|
342
408
|
debug: $debug,
|
|
343
409
|
/** Navigation */
|
|
344
410
|
next,
|
|
@@ -350,6 +416,8 @@ export function createStepController(options) {
|
|
|
350
416
|
/** State control */
|
|
351
417
|
setStepState,
|
|
352
418
|
clearStepState,
|
|
419
|
+
setStepError,
|
|
420
|
+
clearStepError,
|
|
353
421
|
markCompleted,
|
|
354
422
|
setLoading,
|
|
355
423
|
reset,
|
|
@@ -46,6 +46,15 @@ export interface StepDefinition<TId extends string = string> {
|
|
|
46
46
|
data?: string[];
|
|
47
47
|
/** Handles jump policy logic */
|
|
48
48
|
jumpPolicy?: JumpPolicy;
|
|
49
|
+
/**
|
|
50
|
+
* Validation adapter for this step (e.g. zod4(step1Schema)).
|
|
51
|
+
* When set, the controller assigns it to the attached superForm's
|
|
52
|
+
* options.validators on entering the step, and next() validates the
|
|
53
|
+
* CURRENT step against it before navigating.
|
|
54
|
+
*/
|
|
55
|
+
validator?: unknown;
|
|
56
|
+
/** Validate on next(). Default true when `validator` is set. */
|
|
57
|
+
validateOnNext?: boolean;
|
|
49
58
|
}
|
|
50
59
|
/**
|
|
51
60
|
* Derived runtime step containing both definition and computed state.
|
|
@@ -114,6 +123,9 @@ export interface StepCallbacks<TId extends string = string> {
|
|
|
114
123
|
/**
|
|
115
124
|
* Validator configuration for a step.
|
|
116
125
|
* Used for Superforms integration.
|
|
126
|
+
*
|
|
127
|
+
* @deprecated Use {@link StepDefinition.validator} / {@link StepDefinition.validateOnNext}
|
|
128
|
+
* on the step definition instead; the controller wires per-step validators automatically.
|
|
117
129
|
*/
|
|
118
130
|
export interface StepValidator {
|
|
119
131
|
/** Adapter-specific validator (zod4, valibot, etc.) */
|
|
@@ -149,6 +161,8 @@ export interface StepController<TId extends string = string> {
|
|
|
149
161
|
currentIndex: ReadableAtom<number>;
|
|
150
162
|
/** Whether the current step is the last step */
|
|
151
163
|
isLastStep: ReadableAtom<boolean>;
|
|
164
|
+
/** Reactive map of step id → error messages */
|
|
165
|
+
stepErrors: ReadableAtom<Partial<Record<TId, string[]>>>;
|
|
152
166
|
/** Debug info store */
|
|
153
167
|
debug: ReadableAtom<any>;
|
|
154
168
|
/**
|
|
@@ -190,6 +204,17 @@ export interface StepController<TId extends string = string> {
|
|
|
190
204
|
* @param id - The step ID
|
|
191
205
|
*/
|
|
192
206
|
clearStepState(id: TId): void;
|
|
207
|
+
/**
|
|
208
|
+
* Set a human-readable error on a step (also flips its rail state to 'error').
|
|
209
|
+
* @param id - The step ID
|
|
210
|
+
* @param error - A message or list of messages
|
|
211
|
+
*/
|
|
212
|
+
setStepError(id: TId, error: string | string[]): void;
|
|
213
|
+
/**
|
|
214
|
+
* Clear a step's error messages and rail error state.
|
|
215
|
+
* @param id - The step ID
|
|
216
|
+
*/
|
|
217
|
+
clearStepError(id: TId): void;
|
|
193
218
|
/**
|
|
194
219
|
* Mark a step as completed.
|
|
195
220
|
* @param id - The step ID to mark completed
|
|
@@ -237,6 +262,9 @@ export interface StepRailSnippetContext<TId extends string = string> {
|
|
|
237
262
|
export interface StepPanelSnippetContext<TId extends string = string> {
|
|
238
263
|
/** The current step being displayed */
|
|
239
264
|
step: StepRuntime<TId>;
|
|
265
|
+
isLoading: boolean;
|
|
266
|
+
/** Current step's error messages ([] when none) */
|
|
267
|
+
errors: string[];
|
|
240
268
|
}
|
|
241
269
|
/**
|
|
242
270
|
* Context passed to step actions snippet for custom navigation controls.
|
|
@@ -279,6 +307,10 @@ export type SuperFormLike<T = any, M = any> = {
|
|
|
279
307
|
}) => Promise<{
|
|
280
308
|
valid: boolean;
|
|
281
309
|
}>;
|
|
310
|
+
/** superforms options bag; the controller only touches .validators */
|
|
311
|
+
options?: {
|
|
312
|
+
validators?: unknown;
|
|
313
|
+
} & Record<string, unknown>;
|
|
282
314
|
};
|
|
283
315
|
type Nested<T, V> = T extends object ? {
|
|
284
316
|
[K in keyof T]: Nested<T[K], V>;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
* This is the main entry point for the component library.
|
|
5
5
|
*/
|
|
6
6
|
import './theme.css';
|
|
7
|
-
export declare const version = "0.7.
|
|
7
|
+
export declare const version = "0.7.9";
|
|
8
8
|
export { Button, IconButton, ButtonGroup, LinkButton, FloatingActionButton } from './components/buttons';
|
|
9
9
|
export { Input, Textarea, Select, SelectMenu, Checkbox, Switch, RadioGroup, RangeSlider, FileUpload, InputGroup, DatePicker, TimePicker, DateTimePicker } from './components/forms';
|
|
10
10
|
export { Card, Panel, Grid, Container, SectionHeader, Divider } from './components/cards';
|
|
11
11
|
export { Navbar, Sidebar, Tabs, Breadcrumbs, Menu, DropdownMenu, ContextMenu } from './components/navigation';
|
|
12
|
-
export { Modal, Alert, Spinner, Tooltip, ProgressBar, SkeletonLoader, Toast, Drawer, Popover, Dropdown, CommandPalette } from './components/overlays';
|
|
12
|
+
export { Modal, Alert, Spinner, Tooltip, ProgressBar, SkeletonLoader, Toast, ToastHost, toast, toasts, dismissToast, Drawer, Popover, Dropdown, CommandPalette } from './components/overlays';
|
|
13
|
+
export type { ToastItem, ToastOptions, ToastVariant } from './components/overlays';
|
|
13
14
|
export { Heading, Text, Code } from './components/typography';
|
|
14
|
-
export { Table, Pagination, Badge, Tag, List, Avatar, CodeBlock, Stat, CalendarGrid, Sparkline } from './components/data';
|
|
15
|
-
export type { TableColumn, SortDirection, RowKey, SelectionState, CalendarEvent, ListItem } from './components/data';
|
|
15
|
+
export { Table, Pagination, Badge, Tag, List, Avatar, CodeBlock, Stat, CalendarGrid, Sparkline, EmptyState, WeekHeatmap } from './components/data';
|
|
16
|
+
export type { TableColumn, SortDirection, RowKey, SelectionState, CalendarEvent, ListItem, WeekHeatmapCell } from './components/data';
|
|
16
17
|
export { Portal, Collapse, Accordion, Carousel, ScrollArea, Hero, SystemConsole, SystemInterface } from './components/utilities';
|
|
17
18
|
export type { ConsoleMessage, SystemMessage, MessageContentType } from './components/utilities';
|
|
18
19
|
export { StepForm, StepRail, createStepController, } from './components/wizard';
|
|
@@ -40,6 +41,7 @@ import type Alert from './components/overlays/Alert.svelte';
|
|
|
40
41
|
import type Tooltip from './components/overlays/Tooltip.svelte';
|
|
41
42
|
import type Accordion from './components/utilities/Accordion.svelte';
|
|
42
43
|
import type Sparkline from './components/data/Sparkline.svelte';
|
|
44
|
+
import type WeekHeatmap from './components/data/WeekHeatmap.svelte';
|
|
43
45
|
import type StepForm from './components/wizard/StepForm.svelte';
|
|
44
46
|
import type StepRail from './components/wizard/StepRail.svelte';
|
|
45
47
|
export type ButtonProps = ComponentProps<Button>;
|
|
@@ -64,5 +66,6 @@ export type AlertProps = ComponentProps<Alert>;
|
|
|
64
66
|
export type TooltipProps = ComponentProps<Tooltip>;
|
|
65
67
|
export type AccordionProps = ComponentProps<Accordion>;
|
|
66
68
|
export type SparklineProps = ComponentProps<Sparkline>;
|
|
69
|
+
export type WeekHeatmapProps = ComponentProps<WeekHeatmap>;
|
|
67
70
|
export type StepFormProps = ComponentProps<StepForm>;
|
|
68
71
|
export type StepRailProps = ComponentProps<StepRail>;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
// Import theme CSS to ensure it's bundled
|
|
7
7
|
import './theme.css';
|
|
8
|
-
export const version = '0.7.
|
|
8
|
+
export const version = '0.7.9';
|
|
9
9
|
// ===== Button Components =====
|
|
10
10
|
export { Button, IconButton, ButtonGroup, LinkButton, FloatingActionButton } from './components/buttons';
|
|
11
11
|
// ===== Form Components =====
|
|
@@ -15,11 +15,11 @@ export { Card, Panel, Grid, Container, SectionHeader, Divider } from './componen
|
|
|
15
15
|
// ===== Navigation Components =====
|
|
16
16
|
export { Navbar, Sidebar, Tabs, Breadcrumbs, Menu, DropdownMenu, ContextMenu } from './components/navigation';
|
|
17
17
|
// ===== Overlay & Feedback Components =====
|
|
18
|
-
export { Modal, Alert, Spinner, Tooltip, ProgressBar, SkeletonLoader, Toast, Drawer, Popover, Dropdown, CommandPalette } from './components/overlays';
|
|
18
|
+
export { Modal, Alert, Spinner, Tooltip, ProgressBar, SkeletonLoader, Toast, ToastHost, toast, toasts, dismissToast, Drawer, Popover, Dropdown, CommandPalette } from './components/overlays';
|
|
19
19
|
// ===== Typography Components =====
|
|
20
20
|
export { Heading, Text, Code } from './components/typography';
|
|
21
21
|
// ===== Data Display Components =====
|
|
22
|
-
export { Table, Pagination, Badge, Tag, List, Avatar, CodeBlock, Stat, CalendarGrid, Sparkline } from './components/data';
|
|
22
|
+
export { Table, Pagination, Badge, Tag, List, Avatar, CodeBlock, Stat, CalendarGrid, Sparkline, EmptyState, WeekHeatmap } from './components/data';
|
|
23
23
|
// ===== Utility Components =====
|
|
24
24
|
export { Portal, Collapse, Accordion, Carousel, ScrollArea, Hero, SystemConsole, SystemInterface } from './components/utilities';
|
|
25
25
|
// ===== Wizard Components =====
|