vanilla-jui 1.4.5 → 1.4.7
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/README.md +6 -0
- package/dist/index.d.ts +1273 -1896
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +21 -2
- package/src/components/accordion.ts +297 -216
- package/src/components/flow.ts +222 -258
- package/src/components/form.ts +44 -102
- package/src/components/menu.ts +268 -356
- package/src/components/modal.ts +181 -641
- package/src/components/offcanvas.ts +225 -106
- package/src/components/pagination.ts +296 -326
- package/src/components/sticky.ts +55 -54
- package/src/components/swiper.ts +237 -247
- package/src/components/tabs.ts +278 -271
- package/src/components/toc.ts +42 -46
- package/src/core/Component.ts +69 -47
- package/src/css/accordion.css +2 -2
- package/src/css/animation.css +1 -5
- package/src/css/avatar.css +1 -1
- package/src/css/badge.css +1 -1
- package/src/css/breadcrumb.css +9 -10
- package/src/css/button.css +1 -1
- package/src/css/card.css +2 -2
- package/src/css/color.css +974 -1002
- package/src/css/color_p3.css +974 -1004
- package/src/css/components.css +1 -1
- package/src/css/content.css +72 -63
- package/src/css/drop.css +1 -1
- package/src/css/editor.css +14 -14
- package/src/css/flow.css +4 -14
- package/src/css/font.css +5 -4
- package/src/css/form.css +76 -52
- package/src/css/gradient.css +299 -222
- package/src/css/icon.css +57 -57
- package/src/css/index.css +1 -1
- package/src/css/layout.css +13 -10
- package/src/css/loading.css +60 -12
- package/src/css/menu.css +262 -188
- package/src/css/modal.css +1 -1
- package/src/css/offcanvas.css +9 -13
- package/src/css/pagination.css +5 -1
- package/src/css/popup.css +1 -1
- package/src/css/skeleton.css +3 -2
- package/src/css/status.css +2 -3
- package/src/css/swiper.css +1 -1
- package/src/css/table.css +1 -1
- package/src/css/tabs.css +12 -11
- package/src/css/tag.css +1 -1
- package/src/css/theme-palette.css +19 -5
- package/src/css/themes/default/accordion.css +61 -61
- package/src/css/themes/default/avatar.css +54 -54
- package/src/css/themes/default/badge.css +62 -62
- package/src/css/themes/default/breadcrumb.css +23 -23
- package/src/css/themes/default/button.css +203 -203
- package/src/css/themes/default/content.css +53 -53
- package/src/css/themes/default/drop.css +10 -10
- package/src/css/themes/default/editor.css +9 -9
- package/src/css/themes/default/index.css +1 -1
- package/src/css/themes/default/loading.css +26 -20
- package/src/css/themes/default/menu.css +75 -11
- package/src/css/themes/default/modal.css +32 -36
- package/src/css/themes/default/pagination.css +7 -7
- package/src/css/themes/default/popup.css +6 -6
- package/src/css/themes/default/swiper.css +32 -32
- package/src/css/themes/default/table.css +16 -16
- package/src/css/themes/default/tabs.css +30 -30
- package/src/css/themes/default/tag.css +71 -71
- package/src/css/themes/default/theme-palette.css +11 -11
- package/src/css/themes/default/tip.css +42 -42
- package/src/css/themes/default/toast.css +60 -60
- package/src/css/themes/default/toolbar.css +23 -29
- package/src/css/themes/default/tooltip.css +43 -13
- package/src/css/themes/index.css +1 -1
- package/src/css/tip.css +2 -2
- package/src/css/toast.css +3 -3
- package/src/css/tokens.css +12 -17
- package/src/css/toolbar.css +9 -10
- package/src/css/tooltip.css +3 -2
- package/src/index.ts +22 -19
- package/src/{components → primitives}/drop.ts +58 -49
- package/src/{components → primitives}/icons.ts +1 -1
- package/src/primitives/loading.ts +23 -0
- package/src/primitives/parabola.ts +388 -0
- package/src/primitives/popup.ts +61 -0
- package/src/{components → primitives}/theme.ts +89 -57
- package/src/{components → primitives}/toast.ts +18 -25
- package/src/{components → primitives}/tooltip.ts +100 -25
- package/src/utilities/browser.ts +23 -49
- package/src/utilities/class-name.ts +5 -0
- package/src/utilities/dom.ts +14 -32
- package/src/utilities/function.ts +4 -0
- package/src/utilities/http.ts +7 -7
- package/src/utilities/id.ts +57 -0
- package/src/utilities/object.ts +5 -0
- package/src/utilities/timer.ts +38 -0
- package/src/utilities/types.ts +420 -0
- package/src/{components → validation}/validator.ts +64 -53
- package/src/components/parabola.ts +0 -335
- package/src/utilities/cache.ts +0 -57
- package/src/utilities/core.ts +0 -614
- package/src/utilities/storage.ts +0 -46
package/src/components/flow.ts
CHANGED
|
@@ -7,30 +7,21 @@ import {
|
|
|
7
7
|
render,
|
|
8
8
|
} from 'vanilla-signal';
|
|
9
9
|
|
|
10
|
+
import { type RenderableContent } from '../utilities/dom.ts';
|
|
11
|
+
import { randomId } from '../utilities/id.ts';
|
|
12
|
+
import { isPlainObject } from '../utilities/object.ts';
|
|
10
13
|
import {
|
|
11
14
|
type ResolveSchema,
|
|
12
|
-
isPlainObject,
|
|
13
|
-
randomId,
|
|
14
15
|
resolveProps,
|
|
15
16
|
validateParam,
|
|
16
|
-
} from '../utilities/
|
|
17
|
-
import {
|
|
18
|
-
type DOMReference,
|
|
19
|
-
type RenderableContent,
|
|
20
|
-
isRenderableContent,
|
|
21
|
-
requireContainer,
|
|
22
|
-
} from '../utilities/dom.ts';
|
|
17
|
+
} from '../utilities/types.ts';
|
|
23
18
|
|
|
24
19
|
export type FlowData = Record<string, unknown>;
|
|
25
20
|
export type FlowPayload = FlowData | null;
|
|
26
21
|
export type FlowAction = 'next' | 'back' | 'goTo' | 'finish';
|
|
27
22
|
export type FlowBusyStrategy = 'ignore' | 'throw';
|
|
28
23
|
export type FlowDirection = string;
|
|
29
|
-
export type FlowSlotName =
|
|
30
|
-
| 'renderHeader'
|
|
31
|
-
| 'renderSteps'
|
|
32
|
-
| 'renderBody'
|
|
33
|
-
| 'renderFooter';
|
|
24
|
+
export type FlowSlotName = 'renderHeader' | 'renderBody' | 'renderFooter';
|
|
34
25
|
export type FlowCleanup = () => void;
|
|
35
26
|
export type FlowStepResult =
|
|
36
27
|
| string
|
|
@@ -42,8 +33,6 @@ export type FlowStepResult =
|
|
|
42
33
|
export interface FlowClassNames {
|
|
43
34
|
root: string;
|
|
44
35
|
header: string;
|
|
45
|
-
title: string;
|
|
46
|
-
description: string;
|
|
47
36
|
steps: string;
|
|
48
37
|
step: string;
|
|
49
38
|
active: string;
|
|
@@ -54,8 +43,6 @@ export interface FlowClassNames {
|
|
|
54
43
|
body: string;
|
|
55
44
|
footer: string;
|
|
56
45
|
button: string;
|
|
57
|
-
buttonGhost: string;
|
|
58
|
-
buttonPrimary: string;
|
|
59
46
|
reset: string;
|
|
60
47
|
back: string;
|
|
61
48
|
next: string;
|
|
@@ -66,11 +53,9 @@ export type FlowClassNameConfig = Partial<FlowClassNames>;
|
|
|
66
53
|
export interface FlowStep {
|
|
67
54
|
id: string;
|
|
68
55
|
title?: string;
|
|
69
|
-
description?: string;
|
|
70
56
|
content?: RenderableContent<FlowContext>;
|
|
71
57
|
data?: FlowData;
|
|
72
58
|
modal?: FlowData | ((context: FlowContext) => FlowData | null) | null;
|
|
73
|
-
view?: FlowData;
|
|
74
59
|
onEnter?: FlowLifecycleHook;
|
|
75
60
|
onLeave?: FlowLifecycleHook;
|
|
76
61
|
onNext?: FlowMoveHook;
|
|
@@ -199,7 +184,7 @@ export interface FlowText {
|
|
|
199
184
|
[key: string]: string;
|
|
200
185
|
}
|
|
201
186
|
|
|
202
|
-
export interface
|
|
187
|
+
export interface FlowProps extends Record<string, unknown> {
|
|
203
188
|
id?: string | null;
|
|
204
189
|
steps?: FlowStep[];
|
|
205
190
|
initial?: string | number | null;
|
|
@@ -208,16 +193,12 @@ export interface FlowOptions extends Record<string, unknown> {
|
|
|
208
193
|
render?: boolean;
|
|
209
194
|
rollbackOnError?: boolean;
|
|
210
195
|
busyStrategy?: FlowBusyStrategy;
|
|
211
|
-
showHeader?: boolean;
|
|
212
|
-
showFooter?: boolean;
|
|
213
|
-
showSteps?: boolean;
|
|
214
196
|
showBack?: boolean;
|
|
215
197
|
showNext?: boolean;
|
|
216
198
|
showReset?: boolean;
|
|
217
199
|
text?: Partial<FlowText>;
|
|
218
200
|
className?: FlowClassNameConfig | string;
|
|
219
201
|
renderHeader?: FlowSlot;
|
|
220
|
-
renderSteps?: FlowSlot;
|
|
221
202
|
renderBody?: FlowSlot;
|
|
222
203
|
renderFooter?: FlowSlot;
|
|
223
204
|
onChange?: FlowChangeHook | null;
|
|
@@ -228,7 +209,7 @@ export interface FlowOptions extends Record<string, unknown> {
|
|
|
228
209
|
onBusy?: FlowBusyHook | null;
|
|
229
210
|
}
|
|
230
211
|
|
|
231
|
-
interface
|
|
212
|
+
interface ResolvedFlowProps extends Record<string, unknown> {
|
|
232
213
|
id: string;
|
|
233
214
|
steps: FlowStep[];
|
|
234
215
|
initial: string | number | null;
|
|
@@ -237,16 +218,12 @@ interface ResolvedFlowOptions extends Record<string, unknown> {
|
|
|
237
218
|
render: boolean;
|
|
238
219
|
rollbackOnError: boolean;
|
|
239
220
|
busyStrategy: FlowBusyStrategy;
|
|
240
|
-
showHeader: boolean;
|
|
241
|
-
showFooter: boolean;
|
|
242
|
-
showSteps: boolean;
|
|
243
221
|
showBack: boolean;
|
|
244
222
|
showNext: boolean;
|
|
245
223
|
showReset: boolean;
|
|
246
224
|
text: FlowText;
|
|
247
225
|
className: FlowClassNames;
|
|
248
226
|
renderHeader: FlowSlot;
|
|
249
|
-
renderSteps: FlowSlot;
|
|
250
227
|
renderBody: FlowSlot;
|
|
251
228
|
renderFooter: FlowSlot;
|
|
252
229
|
onChange: FlowChangeHook | null;
|
|
@@ -278,8 +255,18 @@ interface InitialFlowData {
|
|
|
278
255
|
global: FlowData;
|
|
279
256
|
}
|
|
280
257
|
|
|
281
|
-
interface
|
|
282
|
-
root
|
|
258
|
+
interface FlowDOM {
|
|
259
|
+
root: HTMLElement | null;
|
|
260
|
+
header: HTMLElement | null;
|
|
261
|
+
body: HTMLElement | null;
|
|
262
|
+
footer: HTMLElement | null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface FlowRuntime {
|
|
266
|
+
built: boolean;
|
|
267
|
+
destroyed: boolean;
|
|
268
|
+
activeAction: FlowAction | null;
|
|
269
|
+
actionController: AbortController | null;
|
|
283
270
|
}
|
|
284
271
|
|
|
285
272
|
interface FlowGoToOptions {
|
|
@@ -327,8 +314,6 @@ interface FlowError extends Error {
|
|
|
327
314
|
const DEFAULT_CLASS_NAMES: FlowClassNames = {
|
|
328
315
|
root: 'j-flow',
|
|
329
316
|
header: 'flow-header',
|
|
330
|
-
title: 'flow-title',
|
|
331
|
-
description: 'flow-description',
|
|
332
317
|
steps: 'flow-steps',
|
|
333
318
|
step: 'flow-step',
|
|
334
319
|
active: 'is-active',
|
|
@@ -339,27 +324,17 @@ const DEFAULT_CLASS_NAMES: FlowClassNames = {
|
|
|
339
324
|
body: 'flow-body',
|
|
340
325
|
footer: 'flow-footer',
|
|
341
326
|
button: 'j-button',
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
back: 'flow-back',
|
|
346
|
-
next: 'flow-next',
|
|
327
|
+
reset: 'is-ghost flow-reset',
|
|
328
|
+
back: 'is-ghost flow-back',
|
|
329
|
+
next: 'is-primary flow-next',
|
|
347
330
|
};
|
|
348
331
|
|
|
349
|
-
function isFlowStep(step: unknown): step is FlowStep {
|
|
350
|
-
return (
|
|
351
|
-
!!step &&
|
|
352
|
-
typeof step === 'object' &&
|
|
353
|
-
typeof (step as FlowStep).id === 'string'
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
332
|
function isFlowRenderSlot(slot: unknown): slot is FlowSlot {
|
|
358
333
|
return slot == null || slot === false || typeof slot === 'function';
|
|
359
334
|
}
|
|
360
335
|
|
|
361
336
|
function clonePlainObject(value: unknown): FlowData {
|
|
362
|
-
return isPlainObject(value) ? { ...value } : {};
|
|
337
|
+
return isPlainObject(value) ? { ...(value as FlowData) } : {};
|
|
363
338
|
}
|
|
364
339
|
|
|
365
340
|
function cloneArray<T>(value: unknown): T[] {
|
|
@@ -369,7 +344,10 @@ function cloneArray<T>(value: unknown): T[] {
|
|
|
369
344
|
function cloneSteps(steps: unknown): FlowStep[] {
|
|
370
345
|
if (!Array.isArray(steps)) return [];
|
|
371
346
|
return steps.map((step) => {
|
|
372
|
-
const source = step as FlowStep;
|
|
347
|
+
const source = { ...(step as FlowStep & Record<string, unknown>) };
|
|
348
|
+
delete source.description;
|
|
349
|
+
delete source.view;
|
|
350
|
+
|
|
373
351
|
return {
|
|
374
352
|
...source,
|
|
375
353
|
data: clonePlainObject(source.data),
|
|
@@ -377,7 +355,6 @@ function cloneSteps(steps: unknown): FlowStep[] {
|
|
|
377
355
|
typeof source.modal === 'function' || source.modal == null
|
|
378
356
|
? source.modal
|
|
379
357
|
: clonePlainObject(source.modal),
|
|
380
|
-
view: clonePlainObject(source.view),
|
|
381
358
|
};
|
|
382
359
|
});
|
|
383
360
|
}
|
|
@@ -387,13 +364,17 @@ function normalizeStepResult(
|
|
|
387
364
|
fallbackId: string
|
|
388
365
|
): { id: string; data?: FlowPayload } {
|
|
389
366
|
if (typeof result === 'string') return { id: result };
|
|
390
|
-
if (isPlainObject(result)
|
|
367
|
+
if (isPlainObject(result)) {
|
|
368
|
+
const source = result as { id?: unknown; data?: unknown };
|
|
369
|
+
if (typeof source.id !== 'string') return { id: fallbackId };
|
|
370
|
+
const data: FlowPayload | undefined = isPlainObject(source.data)
|
|
371
|
+
? (source.data as FlowData)
|
|
372
|
+
: source.data == null
|
|
373
|
+
? source.data
|
|
374
|
+
: undefined;
|
|
391
375
|
return {
|
|
392
|
-
id:
|
|
393
|
-
data
|
|
394
|
-
isPlainObject(result.data) || result.data == null
|
|
395
|
-
? result.data
|
|
396
|
-
: undefined,
|
|
376
|
+
id: source.id,
|
|
377
|
+
data,
|
|
397
378
|
};
|
|
398
379
|
}
|
|
399
380
|
return { id: fallbackId };
|
|
@@ -412,7 +393,7 @@ function normalizeClassNames(value: unknown): FlowClassNames {
|
|
|
412
393
|
|
|
413
394
|
return {
|
|
414
395
|
...DEFAULT_CLASS_NAMES,
|
|
415
|
-
...(isPlainObject(value) ? value : {}),
|
|
396
|
+
...(isPlainObject(value) ? (value as Partial<FlowClassNames>) : {}),
|
|
416
397
|
};
|
|
417
398
|
}
|
|
418
399
|
|
|
@@ -423,23 +404,23 @@ function joinClasses(
|
|
|
423
404
|
}
|
|
424
405
|
|
|
425
406
|
const FLOW_STEP_RULE = {
|
|
426
|
-
|
|
427
|
-
|
|
407
|
+
type: 'plainObject',
|
|
408
|
+
shape: {
|
|
409
|
+
id: { type: 'string', nonEmpty: true },
|
|
410
|
+
},
|
|
428
411
|
};
|
|
429
412
|
|
|
430
413
|
const FLOW_CONTENT_RULE = {
|
|
431
|
-
|
|
432
|
-
message: 'expects string, Node, array, function or null.',
|
|
414
|
+
type: 'renderable',
|
|
433
415
|
};
|
|
434
416
|
|
|
435
417
|
const FLOW_STEPS_RULE = {
|
|
436
|
-
|
|
437
|
-
|
|
418
|
+
type: 'array',
|
|
419
|
+
nonEmpty: true,
|
|
438
420
|
};
|
|
439
421
|
|
|
440
422
|
const FLOW_PAYLOAD_RULE = {
|
|
441
|
-
|
|
442
|
-
message: 'expects an object or null.',
|
|
423
|
+
types: ['plainObject', 'null', 'undefined'],
|
|
443
424
|
};
|
|
444
425
|
|
|
445
426
|
const FLOW_RENDER_SLOT_RULE = {
|
|
@@ -449,10 +430,10 @@ const FLOW_RENDER_SLOT_RULE = {
|
|
|
449
430
|
|
|
450
431
|
const FLOW_TEXT_RULE = {
|
|
451
432
|
default: {},
|
|
452
|
-
|
|
453
|
-
message: 'expects an object with text fields.',
|
|
433
|
+
type: 'plainObject',
|
|
454
434
|
normalize: (value: unknown): FlowText => {
|
|
455
|
-
const text = isPlainObject(value) ? value : {}
|
|
435
|
+
const text = (isPlainObject(value) ? value : {}) as Partial<FlowText> &
|
|
436
|
+
Record<string, unknown>;
|
|
456
437
|
return {
|
|
457
438
|
...Object.fromEntries(
|
|
458
439
|
Object.entries(text).filter(([, item]) => typeof item === 'string')
|
|
@@ -465,7 +446,7 @@ const FLOW_TEXT_RULE = {
|
|
|
465
446
|
},
|
|
466
447
|
};
|
|
467
448
|
|
|
468
|
-
const
|
|
449
|
+
const FLOW_PROPS_SCHEMA = {
|
|
469
450
|
id: {
|
|
470
451
|
default: null,
|
|
471
452
|
types: ['string', 'null'],
|
|
@@ -489,9 +470,6 @@ const FLOW_OPTIONS_SCHEMA = {
|
|
|
489
470
|
type: 'string',
|
|
490
471
|
enum: ['ignore', 'throw'],
|
|
491
472
|
},
|
|
492
|
-
showHeader: { default: true, type: 'boolean' },
|
|
493
|
-
showFooter: { default: true, type: 'boolean' },
|
|
494
|
-
showSteps: { default: true, type: 'boolean' },
|
|
495
473
|
showBack: { default: true, type: 'boolean' },
|
|
496
474
|
showNext: { default: true, type: 'boolean' },
|
|
497
475
|
showReset: { default: false, type: 'boolean' },
|
|
@@ -502,7 +480,6 @@ const FLOW_OPTIONS_SCHEMA = {
|
|
|
502
480
|
normalize: normalizeClassNames,
|
|
503
481
|
},
|
|
504
482
|
renderHeader: { default: null, ...FLOW_RENDER_SLOT_RULE },
|
|
505
|
-
renderSteps: { default: null, ...FLOW_RENDER_SLOT_RULE },
|
|
506
483
|
renderBody: { default: null, ...FLOW_RENDER_SLOT_RULE },
|
|
507
484
|
renderFooter: { default: null, ...FLOW_RENDER_SLOT_RULE },
|
|
508
485
|
onChange: { default: null, types: ['function', 'null'] },
|
|
@@ -511,37 +488,33 @@ const FLOW_OPTIONS_SCHEMA = {
|
|
|
511
488
|
onFinish: { default: null, types: ['function', 'null'] },
|
|
512
489
|
onError: { default: null, types: ['function', 'null'] },
|
|
513
490
|
onBusy: { default: null, types: ['function', 'null'] },
|
|
514
|
-
} satisfies ResolveSchema<
|
|
491
|
+
} satisfies ResolveSchema<FlowProps>;
|
|
515
492
|
|
|
516
|
-
function
|
|
517
|
-
const
|
|
493
|
+
function normalizeProps(input: FlowProps): ResolvedFlowProps {
|
|
494
|
+
const props = resolveProps(input, FLOW_PROPS_SCHEMA, 'Flow.props');
|
|
518
495
|
return {
|
|
519
|
-
id:
|
|
520
|
-
steps: cloneSteps(
|
|
521
|
-
initial:
|
|
522
|
-
cache:
|
|
523
|
-
linear:
|
|
524
|
-
render:
|
|
525
|
-
rollbackOnError:
|
|
526
|
-
busyStrategy:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
onBack: options.onBack as FlowMoveHook | null,
|
|
542
|
-
onFinish: options.onFinish as FlowFinishHook | null,
|
|
543
|
-
onError: options.onError as FlowErrorHook | null,
|
|
544
|
-
onBusy: options.onBusy as FlowBusyHook | null,
|
|
496
|
+
id: props.id as string,
|
|
497
|
+
steps: cloneSteps(props.steps),
|
|
498
|
+
initial: props.initial as ResolvedFlowProps['initial'],
|
|
499
|
+
cache: props.cache as boolean,
|
|
500
|
+
linear: props.linear as boolean,
|
|
501
|
+
render: props.render as boolean,
|
|
502
|
+
rollbackOnError: props.rollbackOnError as boolean,
|
|
503
|
+
busyStrategy: props.busyStrategy as FlowBusyStrategy,
|
|
504
|
+
showBack: props.showBack as boolean,
|
|
505
|
+
showNext: props.showNext as boolean,
|
|
506
|
+
showReset: props.showReset as boolean,
|
|
507
|
+
text: props.text as FlowText,
|
|
508
|
+
className: props.className as FlowClassNames,
|
|
509
|
+
renderHeader: props.renderHeader as FlowSlot,
|
|
510
|
+
renderBody: props.renderBody as FlowSlot,
|
|
511
|
+
renderFooter: props.renderFooter as FlowSlot,
|
|
512
|
+
onChange: props.onChange as FlowChangeHook | null,
|
|
513
|
+
onNext: props.onNext as FlowMoveHook | null,
|
|
514
|
+
onBack: props.onBack as FlowMoveHook | null,
|
|
515
|
+
onFinish: props.onFinish as FlowFinishHook | null,
|
|
516
|
+
onError: props.onError as FlowErrorHook | null,
|
|
517
|
+
onBusy: props.onBusy as FlowBusyHook | null,
|
|
545
518
|
};
|
|
546
519
|
}
|
|
547
520
|
|
|
@@ -550,44 +523,48 @@ function normalizeOptions(input: FlowOptions): ResolvedFlowOptions {
|
|
|
550
523
|
*
|
|
551
524
|
* 适合在 Modal、Offcanvas、页面表单或任意业务组件中复用 next/back/goTo、步骤缓存和生命周期。
|
|
552
525
|
*/
|
|
553
|
-
|
|
554
|
-
|
|
526
|
+
class Flow {
|
|
527
|
+
props: ResolvedFlowProps;
|
|
555
528
|
steps: FlowStep[];
|
|
556
529
|
state: FlowState;
|
|
557
|
-
|
|
530
|
+
dom: FlowDOM;
|
|
531
|
+
runtime: FlowRuntime;
|
|
558
532
|
private stepMap: Map<string, number>;
|
|
559
533
|
private initialStepId: string;
|
|
560
534
|
private initialData: InitialFlowData;
|
|
561
535
|
private subscribers: Set<FlowSubscriber>;
|
|
562
536
|
private renderDispose: FlowCleanup | null;
|
|
563
537
|
private cleanupTasks: Set<FlowCleanup>;
|
|
564
|
-
private nodes: FlowNodes;
|
|
565
|
-
private destroyed: boolean;
|
|
566
|
-
private activeAction: FlowAction | null;
|
|
567
|
-
private actionController: AbortController | null;
|
|
568
538
|
|
|
569
539
|
/**
|
|
570
540
|
* 创建 Flow 实例。
|
|
571
|
-
* @param {
|
|
541
|
+
* @param {FlowProps} [props={}] Flow 配置。
|
|
572
542
|
*/
|
|
573
|
-
constructor(
|
|
574
|
-
this.
|
|
575
|
-
this.steps = this.
|
|
543
|
+
constructor(props: FlowProps = {}) {
|
|
544
|
+
this.props = normalizeProps(props);
|
|
545
|
+
this.steps = this.props.steps;
|
|
576
546
|
this.validateSteps(this.steps);
|
|
577
547
|
this.stepMap = new Map(this.steps.map((step, index) => [step.id, index]));
|
|
578
|
-
this.initialStepId = this.resolveInitialStepId(this.
|
|
548
|
+
this.initialStepId = this.resolveInitialStepId(this.props.initial);
|
|
579
549
|
this.initialData = this.createInitialStepData();
|
|
580
550
|
this.subscribers = new Set();
|
|
581
551
|
this.renderDispose = null;
|
|
582
552
|
this.cleanupTasks = new Set();
|
|
583
|
-
this.
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
553
|
+
this.dom = {
|
|
554
|
+
root: null,
|
|
555
|
+
header: null,
|
|
556
|
+
body: null,
|
|
557
|
+
footer: null,
|
|
558
|
+
};
|
|
559
|
+
this.runtime = {
|
|
560
|
+
built: false,
|
|
561
|
+
destroyed: false,
|
|
562
|
+
activeAction: null,
|
|
563
|
+
actionController: null,
|
|
564
|
+
};
|
|
588
565
|
|
|
589
566
|
this.state = createDeepStore({
|
|
590
|
-
id: this.
|
|
567
|
+
id: this.props.id,
|
|
591
568
|
currentId: this.initialStepId,
|
|
592
569
|
currentIndex: this.stepMap.get(this.initialStepId) ?? 0,
|
|
593
570
|
previousId: null,
|
|
@@ -694,38 +671,30 @@ export class Flow {
|
|
|
694
671
|
}
|
|
695
672
|
|
|
696
673
|
/**
|
|
697
|
-
*
|
|
698
|
-
* @param {Element|Node|string|Array} container DOM 容器、选择器或 JSX/h 返回节点。
|
|
674
|
+
* 构建默认 Flow UI。
|
|
699
675
|
* @returns {Flow}
|
|
700
676
|
*/
|
|
701
|
-
|
|
702
|
-
this.assertActive('
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
'Flow.mount.container',
|
|
708
|
-
'element'
|
|
709
|
-
);
|
|
677
|
+
build(): this {
|
|
678
|
+
this.assertActive('build');
|
|
679
|
+
if (this.runtime.built) return this;
|
|
680
|
+
|
|
681
|
+
this.runtime.built = true;
|
|
682
|
+
if (!this.props.render) return this;
|
|
710
683
|
|
|
711
|
-
this.
|
|
712
|
-
this.root = this.buildRoot();
|
|
713
|
-
target.appendChild(this.root);
|
|
684
|
+
this.dom.root = this.buildRoot();
|
|
714
685
|
this.mountView();
|
|
715
686
|
return this;
|
|
716
687
|
}
|
|
717
688
|
|
|
718
|
-
|
|
719
|
-
* 卸载默认 UI。
|
|
720
|
-
* @returns {Flow}
|
|
721
|
-
*/
|
|
722
|
-
unmount(): this {
|
|
689
|
+
private teardownView(): void {
|
|
723
690
|
this.renderDispose?.();
|
|
724
691
|
this.renderDispose = null;
|
|
725
|
-
|
|
726
|
-
this.root = null;
|
|
727
|
-
this.
|
|
728
|
-
|
|
692
|
+
this.dom.root?.remove();
|
|
693
|
+
this.dom.root = null;
|
|
694
|
+
this.dom.header = null;
|
|
695
|
+
this.dom.body = null;
|
|
696
|
+
this.dom.footer = null;
|
|
697
|
+
this.runtime.built = false;
|
|
729
698
|
}
|
|
730
699
|
|
|
731
700
|
/**
|
|
@@ -868,7 +837,7 @@ export class Flow {
|
|
|
868
837
|
...clonePlainObject(this.state.stepData[stepId]),
|
|
869
838
|
...data,
|
|
870
839
|
};
|
|
871
|
-
if (this.
|
|
840
|
+
if (this.props.cache) Object.assign(this.state.data, data);
|
|
872
841
|
this.state.version += 1;
|
|
873
842
|
});
|
|
874
843
|
if (!options.silent) this.emitChange();
|
|
@@ -936,7 +905,7 @@ export class Flow {
|
|
|
936
905
|
'finish',
|
|
937
906
|
async () => {
|
|
938
907
|
if (payload) this.setStepData(this.state.currentId, payload);
|
|
939
|
-
await this.callHook(this.
|
|
908
|
+
await this.callHook(this.props.onFinish, [this.snapshot(), this]);
|
|
940
909
|
return this.snapshot();
|
|
941
910
|
},
|
|
942
911
|
{ internal: options.internal }
|
|
@@ -948,10 +917,10 @@ export class Flow {
|
|
|
948
917
|
* @returns {void}
|
|
949
918
|
*/
|
|
950
919
|
destroy(): void {
|
|
951
|
-
if (this.destroyed) return;
|
|
952
|
-
this.destroyed = true;
|
|
920
|
+
if (this.runtime.destroyed) return;
|
|
921
|
+
this.runtime.destroyed = true;
|
|
953
922
|
this.abortActiveAction();
|
|
954
|
-
this.
|
|
923
|
+
this.teardownView();
|
|
955
924
|
for (const cleanup of Array.from(this.cleanupTasks)) {
|
|
956
925
|
cleanup();
|
|
957
926
|
}
|
|
@@ -959,21 +928,19 @@ export class Flow {
|
|
|
959
928
|
this.subscribers.clear();
|
|
960
929
|
this.steps = [];
|
|
961
930
|
this.stepMap.clear();
|
|
962
|
-
this.activeAction = null;
|
|
963
|
-
this.actionController = null;
|
|
931
|
+
this.runtime.activeAction = null;
|
|
932
|
+
this.runtime.actionController = null;
|
|
964
933
|
}
|
|
965
934
|
|
|
966
935
|
private resolveInitialStepId(initial: string | number | null): string {
|
|
967
936
|
if (typeof initial === 'number') {
|
|
968
937
|
const step = this.steps[initial];
|
|
969
|
-
if (!step) throw new Error('Flow.
|
|
938
|
+
if (!step) throw new Error('Flow.props.initial index is out of range.');
|
|
970
939
|
return step.id;
|
|
971
940
|
}
|
|
972
941
|
if (typeof initial === 'string') {
|
|
973
942
|
if (!this.stepMap.has(initial)) {
|
|
974
|
-
throw new Error(
|
|
975
|
-
`Flow.options.initial step "${initial}" does not exist.`
|
|
976
|
-
);
|
|
943
|
+
throw new Error(`Flow.props.initial step "${initial}" does not exist.`);
|
|
977
944
|
}
|
|
978
945
|
return initial;
|
|
979
946
|
}
|
|
@@ -983,15 +950,15 @@ export class Flow {
|
|
|
983
950
|
private validateSteps(steps: FlowStep[]): void {
|
|
984
951
|
const ids = new Set<string>();
|
|
985
952
|
for (const [index, step] of steps.entries()) {
|
|
986
|
-
validateParam(String(index), step, FLOW_STEP_RULE, 'Flow.
|
|
953
|
+
validateParam(String(index), step, FLOW_STEP_RULE, 'Flow.props.steps');
|
|
987
954
|
validateParam(
|
|
988
955
|
'content',
|
|
989
956
|
step.content ?? null,
|
|
990
957
|
FLOW_CONTENT_RULE,
|
|
991
|
-
`Flow.
|
|
958
|
+
`Flow.props.steps.${index}`
|
|
992
959
|
);
|
|
993
960
|
if (ids.has(step.id)) {
|
|
994
|
-
throw new Error(`Flow.
|
|
961
|
+
throw new Error(`Flow.props.steps: duplicated step id "${step.id}".`);
|
|
995
962
|
}
|
|
996
963
|
ids.add(step.id);
|
|
997
964
|
}
|
|
@@ -1029,8 +996,7 @@ export class Flow {
|
|
|
1029
996
|
payload: FlowPayload,
|
|
1030
997
|
fallbackId: string
|
|
1031
998
|
): Promise<FlowStepResult | FlowPayload | void> {
|
|
1032
|
-
const globalHook =
|
|
1033
|
-
type === 'back' ? this.options.onBack : this.options.onNext;
|
|
999
|
+
const globalHook = type === 'back' ? this.props.onBack : this.props.onNext;
|
|
1034
1000
|
const stepHook = type === 'back' ? step.onBack : step.onNext;
|
|
1035
1001
|
const context = this.createContext({ payload, targetId: fallbackId });
|
|
1036
1002
|
|
|
@@ -1098,8 +1064,8 @@ export class Flow {
|
|
|
1098
1064
|
|
|
1099
1065
|
this.emitChange(fromSnapshot);
|
|
1100
1066
|
} catch (error) {
|
|
1101
|
-
if (this.destroyed) throw error;
|
|
1102
|
-
if (this.
|
|
1067
|
+
if (this.runtime.destroyed) throw error;
|
|
1068
|
+
if (this.props.rollbackOnError) {
|
|
1103
1069
|
this.restoreState(rollbackState, { keepLoading: true });
|
|
1104
1070
|
}
|
|
1105
1071
|
this.handleError(error, fromSnapshot);
|
|
@@ -1143,7 +1109,7 @@ export class Flow {
|
|
|
1143
1109
|
flow: this,
|
|
1144
1110
|
step,
|
|
1145
1111
|
state: this.state,
|
|
1146
|
-
signal: this.actionController?.signal || null,
|
|
1112
|
+
signal: this.runtime.actionController?.signal || null,
|
|
1147
1113
|
snapshot,
|
|
1148
1114
|
data: clonePlainObject(this.state.data),
|
|
1149
1115
|
currentData: this.getStepData(step.id),
|
|
@@ -1164,23 +1130,23 @@ export class Flow {
|
|
|
1164
1130
|
): Promise<FlowSnapshot | null> {
|
|
1165
1131
|
const isOuterAction = !options.internal && !this.state.loading;
|
|
1166
1132
|
if (isOuterAction) {
|
|
1167
|
-
this.actionController =
|
|
1133
|
+
this.runtime.actionController =
|
|
1168
1134
|
typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
1169
|
-
this.activeAction = action;
|
|
1135
|
+
this.runtime.activeAction = action;
|
|
1170
1136
|
this.setLoading(true, action);
|
|
1171
1137
|
}
|
|
1172
1138
|
|
|
1173
1139
|
try {
|
|
1174
1140
|
return await task();
|
|
1175
1141
|
} catch (error) {
|
|
1176
|
-
if (this.destroyed) return null;
|
|
1142
|
+
if (this.runtime.destroyed) return null;
|
|
1177
1143
|
if (this.state.error !== error) this.handleError(error);
|
|
1178
1144
|
throw error;
|
|
1179
1145
|
} finally {
|
|
1180
|
-
if (isOuterAction && !this.destroyed) {
|
|
1146
|
+
if (isOuterAction && !this.runtime.destroyed) {
|
|
1181
1147
|
this.setLoading(false, null);
|
|
1182
|
-
this.activeAction = null;
|
|
1183
|
-
this.actionController = null;
|
|
1148
|
+
this.runtime.activeAction = null;
|
|
1149
|
+
this.runtime.actionController = null;
|
|
1184
1150
|
}
|
|
1185
1151
|
}
|
|
1186
1152
|
}
|
|
@@ -1193,19 +1159,19 @@ export class Flow {
|
|
|
1193
1159
|
) as FlowError;
|
|
1194
1160
|
error.code = 'FLOW_BUSY';
|
|
1195
1161
|
|
|
1196
|
-
if (typeof this.
|
|
1197
|
-
this.
|
|
1162
|
+
if (typeof this.props.onBusy === 'function') {
|
|
1163
|
+
this.props.onBusy(action, this.snapshot(), this);
|
|
1198
1164
|
}
|
|
1199
1165
|
|
|
1200
|
-
if (this.
|
|
1166
|
+
if (this.props.busyStrategy === 'throw') throw error;
|
|
1201
1167
|
|
|
1202
1168
|
return this.snapshot();
|
|
1203
1169
|
}
|
|
1204
1170
|
|
|
1205
1171
|
private abortActiveAction(): void {
|
|
1206
|
-
this.actionController?.abort();
|
|
1207
|
-
this.actionController = null;
|
|
1208
|
-
this.activeAction = null;
|
|
1172
|
+
this.runtime.actionController?.abort();
|
|
1173
|
+
this.runtime.actionController = null;
|
|
1174
|
+
this.runtime.activeAction = null;
|
|
1209
1175
|
}
|
|
1210
1176
|
|
|
1211
1177
|
private async callHook<TResult>(
|
|
@@ -1216,7 +1182,7 @@ export class Flow {
|
|
|
1216
1182
|
const result = await (
|
|
1217
1183
|
hook as (...args: unknown[]) => TResult | Promise<TResult>
|
|
1218
1184
|
)(...args);
|
|
1219
|
-
if (this.destroyed) throw this.createAbortError();
|
|
1185
|
+
if (this.runtime.destroyed) throw this.createAbortError();
|
|
1220
1186
|
return result;
|
|
1221
1187
|
}
|
|
1222
1188
|
|
|
@@ -1230,10 +1196,17 @@ export class Flow {
|
|
|
1230
1196
|
value: boolean,
|
|
1231
1197
|
action: FlowAction | null = this.state.busyAction
|
|
1232
1198
|
): void {
|
|
1199
|
+
const previous = this.snapshot();
|
|
1200
|
+
const busyAction = value ? action : null;
|
|
1201
|
+
if (this.state.loading === value && this.state.busyAction === busyAction)
|
|
1202
|
+
return;
|
|
1203
|
+
|
|
1233
1204
|
flushSync(() => {
|
|
1234
1205
|
this.state.loading = value;
|
|
1235
|
-
this.state.busyAction =
|
|
1206
|
+
this.state.busyAction = busyAction;
|
|
1207
|
+
this.state.version += 1;
|
|
1236
1208
|
});
|
|
1209
|
+
this.emitChange(previous);
|
|
1237
1210
|
}
|
|
1238
1211
|
|
|
1239
1212
|
private handleError(
|
|
@@ -1244,8 +1217,8 @@ export class Flow {
|
|
|
1244
1217
|
this.state.error = error;
|
|
1245
1218
|
this.state.version += 1;
|
|
1246
1219
|
});
|
|
1247
|
-
if (typeof this.
|
|
1248
|
-
this.
|
|
1220
|
+
if (typeof this.props.onError === 'function') {
|
|
1221
|
+
this.props.onError(error, this.snapshot(), this, previous);
|
|
1249
1222
|
}
|
|
1250
1223
|
}
|
|
1251
1224
|
|
|
@@ -1254,8 +1227,8 @@ export class Flow {
|
|
|
1254
1227
|
for (const handler of Array.from(this.subscribers)) {
|
|
1255
1228
|
handler(next, this, previous);
|
|
1256
1229
|
}
|
|
1257
|
-
if (typeof this.
|
|
1258
|
-
this.
|
|
1230
|
+
if (typeof this.props.onChange === 'function') {
|
|
1231
|
+
this.props.onChange(next, this, previous);
|
|
1259
1232
|
}
|
|
1260
1233
|
}
|
|
1261
1234
|
|
|
@@ -1312,7 +1285,7 @@ export class Flow {
|
|
|
1312
1285
|
if (typeof cleanup !== 'function') {
|
|
1313
1286
|
throw new Error('Flow.addCleanup: cleanup expects a function.');
|
|
1314
1287
|
}
|
|
1315
|
-
if (this.destroyed) {
|
|
1288
|
+
if (this.runtime.destroyed) {
|
|
1316
1289
|
cleanup();
|
|
1317
1290
|
return () => {};
|
|
1318
1291
|
}
|
|
@@ -1321,7 +1294,7 @@ export class Flow {
|
|
|
1321
1294
|
}
|
|
1322
1295
|
|
|
1323
1296
|
private assertActive(method: string): void {
|
|
1324
|
-
if (this.destroyed) {
|
|
1297
|
+
if (this.runtime.destroyed) {
|
|
1325
1298
|
throw new Error(`Flow.${method}: instance has been destroyed.`);
|
|
1326
1299
|
}
|
|
1327
1300
|
}
|
|
@@ -1342,22 +1315,21 @@ export class Flow {
|
|
|
1342
1315
|
|
|
1343
1316
|
private buildRoot(): HTMLElement {
|
|
1344
1317
|
return jsx('div', {
|
|
1345
|
-
className: this.
|
|
1346
|
-
id: this.
|
|
1318
|
+
className: this.props.className.root,
|
|
1319
|
+
id: this.props.id,
|
|
1347
1320
|
role: 'group',
|
|
1348
1321
|
'data-flow': 'root',
|
|
1349
1322
|
'aria-busy': () => (this.state.loading ? 'true' : 'false'),
|
|
1350
|
-
'aria-labelledby': `${this.options.id}-title`,
|
|
1351
|
-
ref: (element: HTMLElement) => {
|
|
1352
|
-
this.nodes.root = element;
|
|
1353
|
-
},
|
|
1354
1323
|
}) as HTMLElement;
|
|
1355
1324
|
}
|
|
1356
1325
|
|
|
1357
1326
|
private mountView(): void {
|
|
1358
|
-
if (this.renderDispose || !this.root) return;
|
|
1327
|
+
if (this.renderDispose || !this.dom.root) return;
|
|
1359
1328
|
this.renderDispose = createRoot((dispose: FlowCleanup) => {
|
|
1360
|
-
const viewDispose = render(
|
|
1329
|
+
const viewDispose = render(
|
|
1330
|
+
() => this.view(),
|
|
1331
|
+
this.dom.root as HTMLElement
|
|
1332
|
+
);
|
|
1361
1333
|
onCleanup(viewDispose);
|
|
1362
1334
|
return dispose;
|
|
1363
1335
|
});
|
|
@@ -1366,16 +1338,9 @@ export class Flow {
|
|
|
1366
1338
|
private view(): RenderableContent<FlowRenderContext> {
|
|
1367
1339
|
const snapshot = this.snapshot();
|
|
1368
1340
|
return [
|
|
1369
|
-
this.
|
|
1370
|
-
|
|
1371
|
-
),
|
|
1372
|
-
this.renderSlot('renderSteps', snapshot, () =>
|
|
1373
|
-
this.options.showSteps ? this.stepsView(snapshot) : null
|
|
1374
|
-
),
|
|
1375
|
-
this.renderSlot('renderBody', snapshot, () => this.bodyView(snapshot)),
|
|
1376
|
-
this.renderSlot('renderFooter', snapshot, () =>
|
|
1377
|
-
this.options.showFooter ? this.footerView(snapshot) : null
|
|
1378
|
-
),
|
|
1341
|
+
this.headerView(snapshot),
|
|
1342
|
+
this.bodyView(snapshot),
|
|
1343
|
+
this.footerView(snapshot),
|
|
1379
1344
|
];
|
|
1380
1345
|
}
|
|
1381
1346
|
|
|
@@ -1384,7 +1349,7 @@ export class Flow {
|
|
|
1384
1349
|
snapshot: FlowSnapshot,
|
|
1385
1350
|
fallback: () => RenderableContent<FlowRenderContext>
|
|
1386
1351
|
): RenderableContent<FlowRenderContext> {
|
|
1387
|
-
const slot = this.
|
|
1352
|
+
const slot = this.props[name];
|
|
1388
1353
|
if (slot === false) return null;
|
|
1389
1354
|
if (typeof slot === 'function') {
|
|
1390
1355
|
return slot(this.createRenderContext(snapshot, fallback));
|
|
@@ -1414,30 +1379,20 @@ export class Flow {
|
|
|
1414
1379
|
|
|
1415
1380
|
private headerView(snapshot: FlowSnapshot): HTMLElement {
|
|
1416
1381
|
return jsx('div', {
|
|
1417
|
-
className: this.
|
|
1382
|
+
className: this.props.className.header,
|
|
1418
1383
|
'data-flow-header': '',
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
snapshot.currentStep?.title || snapshot.currentStep?.id || '',
|
|
1426
|
-
}),
|
|
1427
|
-
snapshot.currentStep?.description
|
|
1428
|
-
? jsx('div', {
|
|
1429
|
-
className: this.options.className.description,
|
|
1430
|
-
'data-flow-description': '',
|
|
1431
|
-
children: snapshot.currentStep.description,
|
|
1432
|
-
})
|
|
1433
|
-
: null,
|
|
1434
|
-
],
|
|
1384
|
+
ref: (element: HTMLElement) => {
|
|
1385
|
+
this.dom.header = element;
|
|
1386
|
+
},
|
|
1387
|
+
children: this.renderSlot('renderHeader', snapshot, () =>
|
|
1388
|
+
this.stepsView(snapshot)
|
|
1389
|
+
),
|
|
1435
1390
|
}) as HTMLElement;
|
|
1436
1391
|
}
|
|
1437
1392
|
|
|
1438
1393
|
private stepsView(snapshot: FlowSnapshot): HTMLOListElement {
|
|
1439
1394
|
return jsx('ol', {
|
|
1440
|
-
className: this.
|
|
1395
|
+
className: this.props.className.steps,
|
|
1441
1396
|
'data-flow-steps': '',
|
|
1442
1397
|
role: 'list',
|
|
1443
1398
|
'aria-label': 'Flow steps',
|
|
@@ -1450,12 +1405,12 @@ export class Flow {
|
|
|
1450
1405
|
children: [
|
|
1451
1406
|
jsx('button', {
|
|
1452
1407
|
type: 'button',
|
|
1453
|
-
className: this.
|
|
1454
|
-
disabled: this.
|
|
1408
|
+
className: this.props.className.stepButton,
|
|
1409
|
+
disabled: this.props.linear && index > snapshot.currentIndex,
|
|
1455
1410
|
'aria-current':
|
|
1456
1411
|
index === snapshot.currentIndex ? 'step' : undefined,
|
|
1457
1412
|
'aria-disabled':
|
|
1458
|
-
this.
|
|
1413
|
+
this.props.linear && index > snapshot.currentIndex
|
|
1459
1414
|
? 'true'
|
|
1460
1415
|
: 'false',
|
|
1461
1416
|
'aria-label': `${index + 1}. ${step.title || step.id}`,
|
|
@@ -1466,12 +1421,12 @@ export class Flow {
|
|
|
1466
1421
|
},
|
|
1467
1422
|
children: [
|
|
1468
1423
|
jsx('span', {
|
|
1469
|
-
className: this.
|
|
1424
|
+
className: this.props.className.stepIndex,
|
|
1470
1425
|
'data-flow-step-number': '',
|
|
1471
1426
|
children: index + 1,
|
|
1472
1427
|
}),
|
|
1473
1428
|
jsx('span', {
|
|
1474
|
-
className: this.
|
|
1429
|
+
className: this.props.className.stepTitle,
|
|
1475
1430
|
'data-flow-step-title': '',
|
|
1476
1431
|
children: step.title || step.id,
|
|
1477
1432
|
}),
|
|
@@ -1485,76 +1440,85 @@ export class Flow {
|
|
|
1485
1440
|
|
|
1486
1441
|
private bodyView(snapshot: FlowSnapshot): HTMLElement {
|
|
1487
1442
|
return jsx('div', {
|
|
1488
|
-
className: this.
|
|
1443
|
+
className: this.props.className.body,
|
|
1489
1444
|
'data-flow-body': '',
|
|
1490
1445
|
role: 'region',
|
|
1491
1446
|
'aria-live': 'polite',
|
|
1492
1447
|
'aria-busy': snapshot.loading ? 'true' : 'false',
|
|
1493
|
-
|
|
1448
|
+
ref: (element: HTMLElement) => {
|
|
1449
|
+
this.dom.body = element;
|
|
1450
|
+
},
|
|
1451
|
+
children: this.renderSlot('renderBody', snapshot, () =>
|
|
1452
|
+
this.contentView(this.currentStep.content)
|
|
1453
|
+
),
|
|
1494
1454
|
}) as HTMLElement;
|
|
1495
1455
|
}
|
|
1496
1456
|
|
|
1497
1457
|
private footerView(snapshot: FlowSnapshot): HTMLElement {
|
|
1498
|
-
const ghostButton = joinClasses(
|
|
1499
|
-
this.options.className.button,
|
|
1500
|
-
this.options.className.buttonGhost
|
|
1501
|
-
);
|
|
1502
|
-
const primaryButton = joinClasses(
|
|
1503
|
-
this.options.className.button,
|
|
1504
|
-
this.options.className.buttonPrimary
|
|
1505
|
-
);
|
|
1506
|
-
|
|
1507
1458
|
return jsx('div', {
|
|
1508
|
-
className: this.
|
|
1459
|
+
className: this.props.className.footer,
|
|
1509
1460
|
'data-flow-footer': '',
|
|
1510
|
-
|
|
1511
|
-
this.
|
|
1461
|
+
ref: (element: HTMLElement) => {
|
|
1462
|
+
this.dom.footer = element;
|
|
1463
|
+
},
|
|
1464
|
+
children: this.renderSlot('renderFooter', snapshot, () => [
|
|
1465
|
+
this.props.showReset
|
|
1512
1466
|
? jsx('button', {
|
|
1513
1467
|
type: 'button',
|
|
1514
|
-
className: joinClasses(
|
|
1468
|
+
className: joinClasses(
|
|
1469
|
+
this.props.className.button,
|
|
1470
|
+
this.props.className.reset
|
|
1471
|
+
),
|
|
1515
1472
|
'data-action': 'reset',
|
|
1516
1473
|
onClick: () => this.reset(),
|
|
1517
1474
|
disabled: snapshot.loading,
|
|
1518
1475
|
'aria-disabled': snapshot.loading ? 'true' : 'false',
|
|
1519
|
-
children: this.
|
|
1476
|
+
children: this.props.text.reset,
|
|
1520
1477
|
})
|
|
1521
1478
|
: null,
|
|
1522
|
-
this.
|
|
1479
|
+
this.props.showBack
|
|
1523
1480
|
? jsx('button', {
|
|
1524
1481
|
type: 'button',
|
|
1525
|
-
className: joinClasses(
|
|
1482
|
+
className: joinClasses(
|
|
1483
|
+
this.props.className.button,
|
|
1484
|
+
this.props.className.back
|
|
1485
|
+
),
|
|
1526
1486
|
'data-action': 'back',
|
|
1527
1487
|
onClick: () => void this.back(),
|
|
1528
1488
|
disabled: !snapshot.canBack,
|
|
1529
1489
|
'aria-disabled': !snapshot.canBack ? 'true' : 'false',
|
|
1530
|
-
children: this.
|
|
1490
|
+
children: this.props.text.back,
|
|
1531
1491
|
})
|
|
1532
1492
|
: null,
|
|
1533
|
-
this.
|
|
1493
|
+
this.props.showNext
|
|
1534
1494
|
? jsx('button', {
|
|
1535
1495
|
type: 'button',
|
|
1536
1496
|
className: joinClasses(
|
|
1537
|
-
|
|
1538
|
-
this.
|
|
1497
|
+
this.props.className.button,
|
|
1498
|
+
this.props.className.next
|
|
1539
1499
|
),
|
|
1540
1500
|
'data-action': 'next',
|
|
1541
1501
|
onClick: () => void this.next(),
|
|
1542
1502
|
disabled: snapshot.loading,
|
|
1543
1503
|
'aria-disabled': snapshot.loading ? 'true' : 'false',
|
|
1544
1504
|
children: snapshot.isLast
|
|
1545
|
-
? this.
|
|
1546
|
-
: this.
|
|
1505
|
+
? this.props.text.finish
|
|
1506
|
+
: this.props.text.next,
|
|
1547
1507
|
})
|
|
1548
1508
|
: null,
|
|
1549
|
-
],
|
|
1509
|
+
]),
|
|
1550
1510
|
}) as HTMLElement;
|
|
1551
1511
|
}
|
|
1552
1512
|
|
|
1553
1513
|
private contentView(
|
|
1554
1514
|
content: FlowStep['content']
|
|
1555
|
-
): RenderableContent<
|
|
1556
|
-
if (typeof content === 'function')
|
|
1557
|
-
|
|
1515
|
+
): RenderableContent<FlowRenderContext> {
|
|
1516
|
+
if (typeof content === 'function') {
|
|
1517
|
+
return content(
|
|
1518
|
+
this.createContext()
|
|
1519
|
+
) as RenderableContent<FlowRenderContext>;
|
|
1520
|
+
}
|
|
1521
|
+
return (content ?? '') as RenderableContent<FlowRenderContext>;
|
|
1558
1522
|
}
|
|
1559
1523
|
|
|
1560
1524
|
private stepClass(
|
|
@@ -1562,18 +1526,18 @@ export class Flow {
|
|
|
1562
1526
|
currentIndex = this.state.currentIndex
|
|
1563
1527
|
): string {
|
|
1564
1528
|
return joinClasses(
|
|
1565
|
-
this.
|
|
1566
|
-
index === currentIndex && this.
|
|
1567
|
-
index < currentIndex && this.
|
|
1529
|
+
this.props.className.step,
|
|
1530
|
+
index === currentIndex && this.props.className.active,
|
|
1531
|
+
index < currentIndex && this.props.className.complete
|
|
1568
1532
|
);
|
|
1569
1533
|
}
|
|
1570
1534
|
}
|
|
1571
1535
|
|
|
1572
1536
|
/**
|
|
1573
1537
|
* 创建 Flow 实例。
|
|
1574
|
-
* @param {
|
|
1538
|
+
* @param {FlowProps} props Flow 配置。
|
|
1575
1539
|
* @returns {Flow}
|
|
1576
1540
|
*/
|
|
1577
|
-
export function createFlow(
|
|
1578
|
-
return new Flow(
|
|
1541
|
+
export function createFlow(props: FlowProps = {}): Flow {
|
|
1542
|
+
return new Flow(props);
|
|
1579
1543
|
}
|