@streamscloud/kit 0.9.4 → 0.9.6

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.
@@ -72,6 +72,8 @@ load / error / stub state machine.
72
72
  | `--sc-kit--avatar--badge--size` | Badge mini-avatar box | 45% of the avatar diameter |
73
73
  | `--sc-kit--avatar--badge--ring-color` | Badge separation ring (override on active/hover row backgrounds to blend) | `--sc-kit--color--bg--panel` |
74
74
  | `--sc-kit--avatar--badge--ring-width` | Badge separation ring thickness | `1.5px` |
75
+ | `--sc-kit--avatar--badge--initials--background` | Badge initials chip background (contrast element of org-with-user) | `--sc-kit--color--accent` |
76
+ | `--sc-kit--avatar--badge--initials--color` | Badge initials chip text color | `--sc-kit--color--text--on-accent` |
75
77
  -->
76
78
 
77
79
  <style>.avatar {
@@ -86,6 +88,8 @@ load / error / stub state machine.
86
88
  --_avatar--badge-size: var(--sc-kit--avatar--badge--size, calc(var(--_avatar--size) * 0.45));
87
89
  --_avatar--badge-ring-color: var(--sc-kit--avatar--badge--ring-color, var(--sc-kit--color--bg--panel));
88
90
  --_avatar--badge-ring-width: var(--sc-kit--avatar--badge--ring-width, 1.5px);
91
+ --_avatar--badge-initials-background: var(--sc-kit--avatar--badge--initials--background, var(--sc-kit--color--accent));
92
+ --_avatar--badge-initials-color: var(--sc-kit--avatar--badge--initials--color, var(--sc-kit--color--text--on-accent));
89
93
  --_avatar--border-radius: var(--sc-kit--avatar--border-radius, 50%);
90
94
  --sc-kit--image--border-radius: var(--_avatar--border-radius);
91
95
  --sc-kit--image--background: var(--_avatar--background);
@@ -133,6 +137,8 @@ load / error / stub state machine.
133
137
  .avatar__initials--badge {
134
138
  border-radius: var(--sc-kit--radius--sm);
135
139
  font-size: calc(var(--_avatar--badge-size) * 0.5);
140
+ background: var(--_avatar--badge-initials-background);
141
+ color: var(--_avatar--badge-initials-color);
136
142
  }
137
143
  .avatar__badge {
138
144
  position: absolute;
@@ -35,6 +35,8 @@ type Props = {
35
35
  * | `--sc-kit--avatar--badge--size` | Badge mini-avatar box | 45% of the avatar diameter |
36
36
  * | `--sc-kit--avatar--badge--ring-color` | Badge separation ring (override on active/hover row backgrounds to blend) | `--sc-kit--color--bg--panel` |
37
37
  * | `--sc-kit--avatar--badge--ring-width` | Badge separation ring thickness | `1.5px` |
38
+ * | `--sc-kit--avatar--badge--initials--background` | Badge initials chip background (contrast element of org-with-user) | `--sc-kit--color--accent` |
39
+ * | `--sc-kit--avatar--badge--initials--color` | Badge initials chip text color | `--sc-kit--color--text--on-accent` |
38
40
  */
39
41
  declare const Cmp: import("svelte").Component<Props, {}, "">;
40
42
  type Cmp = ReturnType<typeof Cmp>;
@@ -156,7 +156,8 @@ $effect(() => {
156
156
  {/if}
157
157
 
158
158
  {#if open}
159
- <div bind:this={panelEl} class="date-picker__panel">
159
+ <!-- preventDefault cancels FormField's <label> activation — a dead-space click inside the panel would forward to the trigger and close the calendar. -->
160
+ <div bind:this={panelEl} class="date-picker__panel" role="presentation" onclick={(e) => e.preventDefault()} onkeydown={() => undefined}>
160
161
  <DatePickerCalendar
161
162
  selectedDate={selectedDate}
162
163
  minDate={minDate}
@@ -74,6 +74,7 @@ const showClear = $derived(mode.clearable && hasValue && isInteractive && !core.
74
74
  class:singleselect--inert={inert}
75
75
  class:singleselect--borderless={borderless}
76
76
  onclick={core.handleClick}
77
+ onmousedown={core.handleMousedown}
77
78
  onkeydown={() => undefined}
78
79
  role="presentation">
79
80
  {#if icon}
@@ -185,6 +185,7 @@ const handleDndFinalize = (e) => {
185
185
  class:multiselect-trigger--inert={inert}
186
186
  class:multiselect-trigger--borderless={borderless}
187
187
  onclick={core.handleClick}
188
+ onmousedown={core.handleMousedown}
188
189
  onkeydown={() => undefined}
189
190
  role="presentation">
190
191
  {#if icon}
@@ -73,6 +73,7 @@ export type SelectCore<T> = {
73
73
  readonly interactionLocked: boolean;
74
74
  handleFocus: () => void;
75
75
  handleClick: (e: MouseEvent) => void;
76
+ handleMousedown: (e: MouseEvent) => void;
76
77
  handleInput: (e: Event) => void;
77
78
  handleKeydown: (e: KeyboardEvent) => void;
78
79
  openPopover: () => void;
@@ -250,6 +250,8 @@ export function createSelectCore(config) {
250
250
  };
251
251
  const handleFocus = () => undefined;
252
252
  const handleClick = (e) => {
253
+ // Cancels FormField's <label> activation — else its synthetic click re-toggles the popover on every pick.
254
+ e.preventDefault();
253
255
  if (!config.getInteractive() || isFiltering) {
254
256
  return;
255
257
  }
@@ -268,6 +270,12 @@ export function createSelectCore(config) {
268
270
  openPopover();
269
271
  }
270
272
  };
273
+ const handleMousedown = (e) => {
274
+ // Keeps the inner input focused — default mousedown on the chevron/padding blurs it (focus-ring flicker).
275
+ if (e.target !== config.getInputEl()) {
276
+ e.preventDefault();
277
+ }
278
+ };
271
279
  const handleInput = (e) => {
272
280
  if (!config.getInteractive()) {
273
281
  return;
@@ -357,6 +365,7 @@ export function createSelectCore(config) {
357
365
  },
358
366
  handleFocus,
359
367
  handleClick,
368
+ handleMousedown,
360
369
  handleInput,
361
370
  handleKeydown,
362
371
  openPopover,
@@ -116,7 +116,11 @@ $effect(() => {
116
116
  role="listbox"
117
117
  id={listboxId}
118
118
  tabindex="-1"
119
- onclick={(e) => e.stopPropagation()}
119
+ onclick={(e) => {
120
+ // preventDefault cancels FormField's <label> activation — stopPropagation alone doesn't.
121
+ e.preventDefault();
122
+ e.stopPropagation();
123
+ }}
120
124
  onmousedown={(e) => e.stopPropagation()}
121
125
  onkeydown={() => undefined}>
122
126
  {#if headerSnippet}
@@ -0,0 +1,197 @@
1
+ <script lang="ts" generics="TResult = void, TCancelResult = void">import { Button } from '../button';
2
+ import { Dialog, DialogCloseButton } from '../dialog';
3
+ import { IconSlot } from '../icon';
4
+ import { Stepper } from '../stepper';
5
+ import { StepperDialogLayoutLocalization } from './stepper-dialog-layout-localization';
6
+ let { controller, icon, title, subtitle, steps, canAdvance = () => true, labels, children, on } = $props();
7
+ const localization = new StepperDialogLayoutLocalization();
8
+ let step = $state(0);
9
+ let submitting = $state(false);
10
+ const isFirstStep = $derived(step === 0);
11
+ const isLastStep = $derived(step === steps.length - 1);
12
+ const stepAllowed = $derived(canAdvance(step));
13
+ const showCloseButton = $derived(!controller.settings.nonCancelable);
14
+ const setStep = (next) => {
15
+ step = next;
16
+ on.stepChange?.(next);
17
+ };
18
+ const handleNext = async () => {
19
+ if (!stepAllowed) {
20
+ return;
21
+ }
22
+ if (on.beforeNext && (await on.beforeNext(step)) === false) {
23
+ return;
24
+ }
25
+ setStep(step + 1);
26
+ };
27
+ const handleBack = () => {
28
+ if (step > 0) {
29
+ setStep(step - 1);
30
+ }
31
+ };
32
+ const handleFinish = async () => {
33
+ if (!stepAllowed || submitting) {
34
+ return;
35
+ }
36
+ submitting = true;
37
+ try {
38
+ await on.finish();
39
+ }
40
+ catch (error) {
41
+ console.error('StepperDialogLayout: on.finish threw — surface errors inside the handler', error);
42
+ }
43
+ finally {
44
+ submitting = false;
45
+ }
46
+ };
47
+ const handleStepClick = (clicked) => {
48
+ if (clicked < step) {
49
+ setStep(clicked);
50
+ }
51
+ };
52
+ </script>
53
+
54
+ <div class="stepper-dialog-layout">
55
+ <Dialog controller={controller}>
56
+ {#snippet headerSection()}
57
+ <div class="stepper-dialog-layout__header">
58
+ {#if icon}
59
+ <span class="stepper-dialog-layout__icon" aria-hidden="true"><IconSlot icon={icon} /></span>
60
+ {/if}
61
+ <div class="stepper-dialog-layout__title-block">
62
+ <div class="stepper-dialog-layout__title">{title}</div>
63
+ {#if subtitle}
64
+ <div class="stepper-dialog-layout__subtitle">{subtitle}</div>
65
+ {/if}
66
+ </div>
67
+ {#if showCloseButton}
68
+ <DialogCloseButton controller={controller} />
69
+ {/if}
70
+ </div>
71
+ {/snippet}
72
+
73
+ {#snippet bodySection()}
74
+ <div class="stepper-dialog-layout__layout">
75
+ <div class="stepper-dialog-layout__sidebar">
76
+ <Stepper orientation="vertical" completed="checkmark" current={step} steps={steps} on={{ stepClick: handleStepClick }} />
77
+ </div>
78
+ <div class="stepper-dialog-layout__main">
79
+ <div class="stepper-dialog-layout__body">{@render children(step)}</div>
80
+ </div>
81
+ </div>
82
+ {/snippet}
83
+
84
+ {#snippet footer()}
85
+ <Button type="button" variant="secondary" on={{ click: () => controller.cancel() }}>{labels?.cancel ?? localization.cancel}</Button>
86
+ {#if !isFirstStep}
87
+ <Button type="button" variant="ghost" on={{ click: handleBack }}>{labels?.back ?? localization.back}</Button>
88
+ {/if}
89
+ {#if isLastStep}
90
+ <Button type="button" variant="primary" loading={submitting} disabled={!stepAllowed || submitting} on={{ click: () => void handleFinish() }}
91
+ >{labels?.finish ?? localization.finish}</Button>
92
+ {:else}
93
+ <Button type="button" variant="primary" disabled={!stepAllowed} on={{ click: () => void handleNext() }}>{labels?.next ?? localization.next}</Button>
94
+ {/if}
95
+ {/snippet}
96
+ </Dialog>
97
+ </div>
98
+
99
+ <!--
100
+ @component
101
+ StepperDialogLayout — layout scaffold for multi-step wizard dialog views: header (icon / title /
102
+ subtitle / close), vertical Stepper sidebar, scrollable body, and a Cancel / Back / Next / Finish
103
+ footer. Render it inside your own dialog view opened via `Dialogs.open()` — the layout owns step
104
+ navigation and the submitting state; your `on.finish` owns closing (`controller.ok(...)`).
105
+ Completed sidebar steps are clickable to go back; `canAdvance(step)` gates Next / Finish.
106
+
107
+ ### CSS Custom Properties
108
+ | Property | Description | Default |
109
+ |---|---|---|
110
+ | `--sc-kit--stepper-dialog-layout--sidebar--width` | Stepper sidebar width | `20rem` (320px) |
111
+ | `--sc-kit--stepper-dialog-layout--sidebar--background` | Sidebar background | `--sc-kit--color--bg--field-alt` |
112
+ | `--sc-kit--stepper-dialog-layout--height` | Dialog height (forwarded to `--sc-kit--dialog--height`) | `auto` |
113
+ -->
114
+
115
+ <style>.stepper-dialog-layout {
116
+ --_sdl--sidebar-width: var(--sc-kit--stepper-dialog-layout--sidebar--width, functions.to-rem(320));
117
+ --_sdl--sidebar-background: var(--sc-kit--stepper-dialog-layout--sidebar--background, var(--sc-kit--color--bg--field-alt));
118
+ --sc-kit--dialog--height: var(--sc-kit--stepper-dialog-layout--height, auto);
119
+ --sc-kit--dialog--body--overflow-y: auto;
120
+ display: contents;
121
+ }
122
+ .stepper-dialog-layout__header {
123
+ display: flex;
124
+ align-items: flex-start;
125
+ gap: var(--sc-kit--space--3);
126
+ padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
127
+ border-block-end: 1px solid var(--sc-kit--color--border);
128
+ }
129
+ .stepper-dialog-layout__icon {
130
+ --sc-kit--icon--size: 1.5rem;
131
+ display: inline-flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ flex-shrink: 0;
135
+ inline-size: 2.5rem;
136
+ block-size: 2.5rem;
137
+ background: var(--sc-kit--color--bg--field-alt);
138
+ color: var(--sc-kit--color--text--primary);
139
+ border-radius: var(--sc-kit--radius--md);
140
+ }
141
+ .stepper-dialog-layout__title-block {
142
+ flex: 1;
143
+ min-inline-size: 0;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 0.125rem;
147
+ }
148
+ .stepper-dialog-layout__title {
149
+ font-size: var(--sc-kit--font-size--lg);
150
+ font-weight: var(--sc-kit--font-weight--semibold);
151
+ line-height: var(--sc-kit--leading--tight);
152
+ color: var(--sc-kit--color--text--primary);
153
+ }
154
+ .stepper-dialog-layout__subtitle {
155
+ font-size: var(--sc-kit--font-size--sm);
156
+ color: var(--sc-kit--color--text--muted);
157
+ line-height: var(--sc-kit--leading--normal);
158
+ }
159
+ .stepper-dialog-layout__layout {
160
+ display: flex;
161
+ flex: 1;
162
+ min-block-size: 0;
163
+ block-size: 100%;
164
+ }
165
+ @media screen and (max-width: 768px) {
166
+ .stepper-dialog-layout__layout {
167
+ flex-direction: column;
168
+ }
169
+ }
170
+ .stepper-dialog-layout__sidebar {
171
+ flex: 0 0 var(--_sdl--sidebar-width);
172
+ padding: var(--sc-kit--space--5) var(--sc-kit--space--4);
173
+ background: var(--_sdl--sidebar-background);
174
+ border-inline-end: 1px solid var(--sc-kit--color--border);
175
+ overflow-y: auto;
176
+ min-block-size: 0;
177
+ }
178
+ @media screen and (max-width: 768px) {
179
+ .stepper-dialog-layout__sidebar {
180
+ flex: 0 0 auto;
181
+ border-inline-end: none;
182
+ border-block-end: 1px solid var(--sc-kit--color--border);
183
+ }
184
+ }
185
+ .stepper-dialog-layout__main {
186
+ flex: 1 1 auto;
187
+ display: flex;
188
+ flex-direction: column;
189
+ min-inline-size: 0;
190
+ min-block-size: 0;
191
+ }
192
+ .stepper-dialog-layout__body {
193
+ flex: 1;
194
+ min-block-size: 0;
195
+ overflow-y: auto;
196
+ padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
197
+ }</style>
@@ -0,0 +1,66 @@
1
+ import { type DialogController } from '../dialog';
2
+ import { type IconProp } from '../icon';
3
+ import { type Step } from '../stepper';
4
+ import type { Snippet } from 'svelte';
5
+ declare function $$render<TResult = void, TCancelResult = void>(): {
6
+ props: {
7
+ controller: DialogController<TResult, TCancelResult>;
8
+ /** Leading header icon — string SVG source, `{ src, color?, size? }` object, or custom snippet. */
9
+ icon?: IconProp;
10
+ title: string;
11
+ subtitle?: string;
12
+ steps: Step[];
13
+ /** Reactive gate for Next / Finish per step index. @default () => true */
14
+ canAdvance?: (step: number) => boolean;
15
+ /** Per-button overrides of the localized footer labels. */
16
+ labels?: {
17
+ cancel?: string;
18
+ back?: string;
19
+ next?: string;
20
+ finish?: string;
21
+ };
22
+ /** Body content; receives the current step index. */
23
+ children: Snippet<[number]>;
24
+ on: {
25
+ /** Owns closing: call `controller.ok(...)` on success; return without closing to keep the dialog open. Surface errors inside the handler — rejections are absorbed (logged), not rethrown. */
26
+ finish: () => void | Promise<void>;
27
+ beforeNext?: (step: number) => boolean | void | Promise<boolean | void>;
28
+ stepChange?: (step: number) => void;
29
+ };
30
+ };
31
+ exports: {};
32
+ bindings: "";
33
+ slots: {};
34
+ events: {};
35
+ };
36
+ declare class __sveltets_Render<TResult = void, TCancelResult = void> {
37
+ props(): ReturnType<typeof $$render<TResult, TCancelResult>>['props'];
38
+ events(): ReturnType<typeof $$render<TResult, TCancelResult>>['events'];
39
+ slots(): ReturnType<typeof $$render<TResult, TCancelResult>>['slots'];
40
+ bindings(): "";
41
+ exports(): {};
42
+ }
43
+ interface $$IsomorphicComponent {
44
+ new <TResult = void, TCancelResult = void>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TResult, TCancelResult>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TResult, TCancelResult>['props']>, ReturnType<__sveltets_Render<TResult, TCancelResult>['events']>, ReturnType<__sveltets_Render<TResult, TCancelResult>['slots']>> & {
45
+ $$bindings?: ReturnType<__sveltets_Render<TResult, TCancelResult>['bindings']>;
46
+ } & ReturnType<__sveltets_Render<TResult, TCancelResult>['exports']>;
47
+ <TResult = void, TCancelResult = void>(internal: unknown, props: ReturnType<__sveltets_Render<TResult, TCancelResult>['props']> & {}): ReturnType<__sveltets_Render<TResult, TCancelResult>['exports']>;
48
+ z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
49
+ }
50
+ /**
51
+ * StepperDialogLayout — layout scaffold for multi-step wizard dialog views: header (icon / title /
52
+ * subtitle / close), vertical Stepper sidebar, scrollable body, and a Cancel / Back / Next / Finish
53
+ * footer. Render it inside your own dialog view opened via `Dialogs.open()` — the layout owns step
54
+ * navigation and the submitting state; your `on.finish` owns closing (`controller.ok(...)`).
55
+ * Completed sidebar steps are clickable to go back; `canAdvance(step)` gates Next / Finish.
56
+ *
57
+ * ### CSS Custom Properties
58
+ * | Property | Description | Default |
59
+ * |---|---|---|
60
+ * | `--sc-kit--stepper-dialog-layout--sidebar--width` | Stepper sidebar width | `20rem` (320px) |
61
+ * | `--sc-kit--stepper-dialog-layout--sidebar--background` | Sidebar background | `--sc-kit--color--bg--field-alt` |
62
+ * | `--sc-kit--stepper-dialog-layout--height` | Dialog height (forwarded to `--sc-kit--dialog--height`) | `auto` |
63
+ */
64
+ declare const Cmp: $$IsomorphicComponent;
65
+ type Cmp<TResult = void, TCancelResult = void> = InstanceType<typeof Cmp<TResult, TCancelResult>>;
66
+ export default Cmp;
@@ -0,0 +1 @@
1
+ export { default as StepperDialogLayout } from './cmp.stepper-dialog-layout.svelte';
@@ -0,0 +1 @@
1
+ export { default as StepperDialogLayout } from './cmp.stepper-dialog-layout.svelte';
@@ -1,4 +1,4 @@
1
- export declare class StepperDialogLocalization {
1
+ export declare class StepperDialogLayoutLocalization {
2
2
  get cancel(): string;
3
3
  get back(): string;
4
4
  get next(): string;
@@ -1,18 +1,4 @@
1
1
  import { AppLocale } from '../../core/locale';
2
- export class StepperDialogLocalization {
3
- get cancel() {
4
- return loc.cancel[AppLocale.current];
5
- }
6
- get back() {
7
- return loc.back[AppLocale.current];
8
- }
9
- get next() {
10
- return loc.next[AppLocale.current];
11
- }
12
- get finish() {
13
- return loc.finish[AppLocale.current];
14
- }
15
- }
16
2
  const loc = {
17
3
  cancel: {
18
4
  en: 'Cancel',
@@ -31,3 +17,17 @@ const loc = {
31
17
  no: 'Fullfør'
32
18
  }
33
19
  };
20
+ export class StepperDialogLayoutLocalization {
21
+ get cancel() {
22
+ return loc.cancel[AppLocale.current];
23
+ }
24
+ get back() {
25
+ return loc.back[AppLocale.current];
26
+ }
27
+ get next() {
28
+ return loc.next[AppLocale.current];
29
+ }
30
+ get finish() {
31
+ return loc.finish[AppLocale.current];
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -359,9 +359,9 @@
359
359
  "types": "./dist/ui/stepper/index.d.ts",
360
360
  "svelte": "./dist/ui/stepper/index.js"
361
361
  },
362
- "./ui/stepper-dialog": {
363
- "types": "./dist/ui/stepper-dialog/index.d.ts",
364
- "svelte": "./dist/ui/stepper-dialog/index.js"
362
+ "./ui/stepper-dialog-layout": {
363
+ "types": "./dist/ui/stepper-dialog-layout/index.d.ts",
364
+ "svelte": "./dist/ui/stepper-dialog-layout/index.js"
365
365
  },
366
366
  "./ui/swipe-indicator": {
367
367
  "types": "./dist/ui/swipe-indicator/index.d.ts",
@@ -1,221 +0,0 @@
1
- <script lang="ts">import { Button } from '../button';
2
- import { Dialog, DialogCloseButton } from '../dialog';
3
- import { Icon } from '../icon';
4
- import { Stepper } from '../stepper';
5
- import { StepperDialogLocalization } from './stepper-dialog-localization';
6
- let { controller, data } = $props();
7
- const localization = new StepperDialogLocalization();
8
- let step = $state(0);
9
- let submitting = $state(false);
10
- const isFirstStep = $derived(step === 0);
11
- const isLastStep = $derived(step === data.steps.length - 1);
12
- const canAdvance = $derived(data.canAdvance(step));
13
- const showCloseButton = $derived(!controller.settings.nonCancelable);
14
- const setStep = (i) => {
15
- step = i;
16
- data.on.stepChange?.(i);
17
- };
18
- const handleNext = async () => {
19
- if (!canAdvance) {
20
- return;
21
- }
22
- if (data.on.beforeNext) {
23
- const result = await data.on.beforeNext(step);
24
- if (result === false) {
25
- return;
26
- }
27
- }
28
- setStep(step + 1);
29
- };
30
- const handleBack = () => {
31
- if (step > 0) {
32
- setStep(step - 1);
33
- }
34
- };
35
- const handleFinish = async () => {
36
- if (!canAdvance || submitting) {
37
- return;
38
- }
39
- submitting = true;
40
- try {
41
- await data.on.finish();
42
- controller.ok();
43
- }
44
- finally {
45
- submitting = false;
46
- }
47
- };
48
- const handleStepClick = (i) => {
49
- if (i < step) {
50
- setStep(i);
51
- }
52
- };
53
- </script>
54
-
55
- {#snippet footerButtons()}
56
- <Button type="button" variant="secondary" on={{ click: () => controller.cancel() }}>{data.cancelLabel ?? localization.cancel}</Button>
57
- {#if !isFirstStep}
58
- <Button type="button" variant="ghost" on={{ click: handleBack }}>{data.backLabel ?? localization.back}</Button>
59
- {/if}
60
- {#if isLastStep}
61
- <Button type="button" variant="primary" loading={submitting} disabled={!canAdvance || submitting} on={{ click: handleFinish }}
62
- >{data.finishLabel ?? localization.finish}</Button>
63
- {:else}
64
- <Button type="button" variant="primary" disabled={!canAdvance} on={{ click: handleNext }}>{data.nextLabel ?? localization.next}</Button>
65
- {/if}
66
- {/snippet}
67
-
68
- {#snippet titleRow()}
69
- <div class="stepper-dialog__title-row">
70
- {#if data.icon}<span class="stepper-dialog__icon" aria-hidden="true"><Icon src={data.icon} /></span>{/if}
71
- <div class="stepper-dialog__title-block">
72
- <div class="stepper-dialog__title">{data.title}</div>
73
- {#if data.subtitle}<div class="stepper-dialog__subtitle">{data.subtitle}</div>{/if}
74
- </div>
75
- {#if showCloseButton}<DialogCloseButton controller={controller} />{/if}
76
- </div>
77
- {/snippet}
78
-
79
- <div
80
- class="stepper-dialog stepper-dialog--{data.layout}"
81
- style:--sc-kit--dialog--height={data.height}
82
- style:--sc-kit--dialog--body--overflow-y={data.height ? 'auto' : null}
83
- style:--_stepper-dialog--sidebar-background={data.sidebarBackground}>
84
- {#if data.layout === 'side'}
85
- <Dialog controller={controller}>
86
- {#snippet headerSection()}
87
- <div class="stepper-dialog__header stepper-dialog__header--side">
88
- {@render titleRow()}
89
- </div>
90
- {/snippet}
91
- {#snippet bodySection()}
92
- <div class="stepper-dialog__layout">
93
- <div class="stepper-dialog__sidebar">
94
- <Stepper orientation="vertical" completed="checkmark" current={step} steps={data.steps} on={{ stepClick: handleStepClick }} />
95
- </div>
96
- <div class="stepper-dialog__main">
97
- <div class="stepper-dialog__body">{@render data.content(step)}</div>
98
- </div>
99
- </div>
100
- {/snippet}
101
- {#snippet footer()}{@render footerButtons()}{/snippet}
102
- </Dialog>
103
- {:else}
104
- <Dialog controller={controller}>
105
- {#snippet headerSection()}
106
- <div class="stepper-dialog__header stepper-dialog__header--top">
107
- {@render titleRow()}
108
- <div class="stepper-dialog__stepper">
109
- <Stepper orientation="horizontal-inline" size="sm" completed="checkmark" current={step} steps={data.steps} on={{ stepClick: handleStepClick }} />
110
- </div>
111
- </div>
112
- {/snippet}
113
- {#snippet body()}{@render data.content(step)}{/snippet}
114
- {#snippet footer()}{@render footerButtons()}{/snippet}
115
- </Dialog>
116
- {/if}
117
- </div>
118
-
119
- <!--
120
- @component
121
- Internal view for `openStepperDialog`. Renders a multi-step dialog: header (icon + title + subtitle + close),
122
- a stepper band (top layout) or vertical sidebar (side layout), the current step's body, and a Cancel / Back /
123
- Next / Finish footer. The dialog owns step navigation and the submitting spinner; the consumer owns step
124
- content (via `content` snippet) and the finish handler.
125
-
126
- Never render this view directly — use the imperative `openStepperDialog(...)` from the module barrel.
127
- -->
128
-
129
- <style>.stepper-dialog {
130
- --_stepper-dialog--sidebar-width: 20rem;
131
- --_stepper-dialog--sidebar-background: var(--sc-kit--color--bg--field-alt);
132
- --_stepper-dialog--header-background: var(--sc-kit--color--bg--field-alt);
133
- --_stepper-dialog--header-border-color: var(--sc-kit--color--border);
134
- display: contents;
135
- }
136
- .stepper-dialog__header {
137
- display: flex;
138
- flex-direction: column;
139
- }
140
- .stepper-dialog__header--side {
141
- border-bottom: 1px solid var(--_stepper-dialog--header-border-color);
142
- }
143
- .stepper-dialog__title-row {
144
- display: flex;
145
- align-items: flex-start;
146
- gap: var(--sc-kit--space--3);
147
- padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
148
- }
149
- .stepper-dialog__icon {
150
- display: inline-flex;
151
- align-items: center;
152
- justify-content: center;
153
- flex-shrink: 0;
154
- width: 2.5rem;
155
- height: 2.5rem;
156
- background: var(--sc-kit--color--bg--field-alt);
157
- color: var(--sc-kit--color--text--primary);
158
- border-radius: var(--sc-kit--radius--md);
159
- --sc-kit--icon--size: 1.5rem;
160
- }
161
- .stepper-dialog__title-block {
162
- flex: 1;
163
- min-width: 0;
164
- display: flex;
165
- flex-direction: column;
166
- gap: 0.125rem;
167
- }
168
- .stepper-dialog__title {
169
- font-size: var(--sc-kit--font-size--lg);
170
- font-weight: var(--sc-kit--font-weight--semibold);
171
- line-height: var(--sc-kit--leading--tight);
172
- color: var(--sc-kit--color--text--primary);
173
- }
174
- .stepper-dialog__subtitle {
175
- font-size: var(--sc-kit--font-size--sm);
176
- color: var(--sc-kit--color--text--muted);
177
- line-height: var(--sc-kit--leading--snug);
178
- }
179
- .stepper-dialog__stepper {
180
- padding: var(--sc-kit--space--2) var(--sc-kit--space--6);
181
- background: var(--_stepper-dialog--header-background);
182
- }
183
- .stepper-dialog__layout {
184
- display: flex;
185
- flex: 1;
186
- min-height: 0;
187
- height: 100%;
188
- }
189
- @media screen and (max-width: 768px) {
190
- .stepper-dialog__layout {
191
- flex-direction: column;
192
- }
193
- }
194
- .stepper-dialog__sidebar {
195
- flex: 0 0 var(--_stepper-dialog--sidebar-width);
196
- padding: var(--sc-kit--space--5) var(--sc-kit--space--4);
197
- background: var(--_stepper-dialog--sidebar-background);
198
- border-right: 1px solid var(--sc-kit--color--border);
199
- overflow-y: auto;
200
- min-height: 0;
201
- }
202
- @media screen and (max-width: 768px) {
203
- .stepper-dialog__sidebar {
204
- flex: 0 0 auto;
205
- border-right: none;
206
- border-bottom: 1px solid var(--sc-kit--color--border);
207
- }
208
- }
209
- .stepper-dialog__main {
210
- flex: 1 1 auto;
211
- display: flex;
212
- flex-direction: column;
213
- min-width: 0;
214
- min-height: 0;
215
- }
216
- .stepper-dialog__body {
217
- flex: 1;
218
- min-height: 0;
219
- overflow-y: auto;
220
- padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
221
- }</style>
@@ -1,17 +0,0 @@
1
- import { type DialogController } from '../dialog';
2
- import type { StepperDialogData } from './types';
3
- type Props = {
4
- controller: DialogController;
5
- data: StepperDialogData;
6
- };
7
- /**
8
- * Internal view for `openStepperDialog`. Renders a multi-step dialog: header (icon + title + subtitle + close),
9
- * a stepper band (top layout) or vertical sidebar (side layout), the current step's body, and a Cancel / Back /
10
- * Next / Finish footer. The dialog owns step navigation and the submitting spinner; the consumer owns step
11
- * content (via `content` snippet) and the finish handler.
12
- *
13
- * Never render this view directly — use the imperative `openStepperDialog(...)` from the module barrel.
14
- */
15
- declare const Cmp: import("svelte").Component<Props, {}, "">;
16
- type Cmp = ReturnType<typeof Cmp>;
17
- export default Cmp;
@@ -1,2 +0,0 @@
1
- export { openStepperDialog } from './stepper-dialog';
2
- export type { StepperDialogData, StepperDialogLayout } from './types';
@@ -1 +0,0 @@
1
- export { openStepperDialog } from './stepper-dialog';
@@ -1,22 +0,0 @@
1
- import { type DialogResult } from '../dialog';
2
- import type { Step } from '../stepper';
3
- import type { StepperDialogData, StepperDialogLayout } from './types';
4
- import type { Snippet } from 'svelte';
5
- type StepperDialogInit = {
6
- icon?: string;
7
- title: string;
8
- subtitle?: string;
9
- steps: Step[];
10
- layout?: StepperDialogLayout;
11
- sidebarBackground?: string;
12
- canAdvance?: (step: number) => boolean;
13
- cancelLabel?: string;
14
- backLabel?: string;
15
- nextLabel?: string;
16
- finishLabel?: string;
17
- height?: string;
18
- content: Snippet<[number]>;
19
- on: StepperDialogData['on'];
20
- };
21
- export declare const openStepperDialog: (init: StepperDialogInit) => Promise<DialogResult<void>>;
22
- export {};
@@ -1,26 +0,0 @@
1
- import { Dialogs } from '../dialog';
2
- import StepperDialogView from './cmp.stepper-dialog.svelte';
3
- export const openStepperDialog = (init) => {
4
- const data = {
5
- icon: init.icon,
6
- title: init.title,
7
- subtitle: init.subtitle,
8
- steps: init.steps,
9
- layout: init.layout ?? 'top',
10
- sidebarBackground: init.sidebarBackground,
11
- canAdvance: init.canAdvance ?? (() => true),
12
- cancelLabel: init.cancelLabel,
13
- backLabel: init.backLabel,
14
- nextLabel: init.nextLabel,
15
- finishLabel: init.finishLabel,
16
- height: init.height,
17
- content: init.content,
18
- on: init.on
19
- };
20
- return Dialogs.open({
21
- view: StepperDialogView,
22
- data,
23
- explicitSettings: { closeOnEsc: true, closeOnClickOutside: false },
24
- containerSettings: { size: data.layout === 'side' ? 'large' : 'medium' }
25
- });
26
- };
@@ -1,34 +0,0 @@
1
- import type { Step } from '../stepper';
2
- import type { Snippet } from 'svelte';
3
- export type StepperDialogLayout = 'top' | 'side';
4
- export type StepperDialogData = {
5
- icon?: string;
6
- title: string;
7
- subtitle?: string;
8
- steps: Step[];
9
- layout: StepperDialogLayout;
10
- /** Side-layout sidebar background — any valid CSS `background` shorthand. No-op in `'top'` layout. */
11
- sidebarBackground?: string;
12
- /** Reactive: close over reactive state to enable/disable Next / Finish per step. @default () => true */
13
- canAdvance: (step: number) => boolean;
14
- /** Override the localized Cancel label. */
15
- cancelLabel?: string;
16
- /** Override the localized Back label. */
17
- backLabel?: string;
18
- /** Override the localized Next label. */
19
- nextLabel?: string;
20
- /** Override the localized Finish label. */
21
- finishLabel?: string;
22
- /** Fixed dialog height (any CSS length). When set, body scrolls inside the fixed frame. */
23
- height?: string;
24
- /** Body content — receives the current step index. */
25
- content: Snippet<[number]>;
26
- on: {
27
- /** Called when the user clicks Finish from the last step. Dialog auto-closes with `controller.ok()` after this resolves; throw to keep it open. */
28
- finish: () => void | Promise<void>;
29
- /** Pre-advance hook. Return `false` to block Next. */
30
- beforeNext?: (step: number) => boolean | void | Promise<boolean | void>;
31
- /** Fires whenever the internal step index changes. */
32
- stepChange?: (step: number) => void;
33
- };
34
- };
@@ -1 +0,0 @@
1
- export {};