@cohesivesystems/presentation-react 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +55 -0
- package/dist/collection-chrome-slot-renderer-registry.d.ts +12 -0
- package/dist/collection-chrome-slot-renderer-registry.d.ts.map +1 -0
- package/dist/collection-chrome-slot-renderer-registry.js +12 -0
- package/dist/collection-chrome-slot-renderer-registry.js.map +1 -0
- package/dist/collection-selection-state-context.d.ts +6 -0
- package/dist/collection-selection-state-context.d.ts.map +1 -0
- package/dist/collection-selection-state-context.js +25 -0
- package/dist/collection-selection-state-context.js.map +1 -0
- package/dist/collection-selection-state.d.ts +3 -0
- package/dist/collection-selection-state.d.ts.map +1 -0
- package/dist/collection-selection-state.js +52 -0
- package/dist/collection-selection-state.js.map +1 -0
- package/dist/document-workspace-runtime.d.ts +262 -0
- package/dist/document-workspace-runtime.d.ts.map +1 -0
- package/dist/document-workspace-runtime.js +699 -0
- package/dist/document-workspace-runtime.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/input-form-endpoint-runtime.d.ts +140 -0
- package/dist/input-form-endpoint-runtime.d.ts.map +1 -0
- package/dist/input-form-endpoint-runtime.js +280 -0
- package/dist/input-form-endpoint-runtime.js.map +1 -0
- package/dist/local-flow-state-data-source-projection.d.ts +21 -0
- package/dist/local-flow-state-data-source-projection.d.ts.map +1 -0
- package/dist/local-flow-state-data-source-projection.js +65 -0
- package/dist/local-flow-state-data-source-projection.js.map +1 -0
- package/dist/navigation-runtime-context.d.ts +22 -0
- package/dist/navigation-runtime-context.d.ts.map +1 -0
- package/dist/navigation-runtime-context.js +23 -0
- package/dist/navigation-runtime-context.js.map +1 -0
- package/dist/navigation-shell-frame-renderer-registry.d.ts +20 -0
- package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
- package/dist/navigation-shell-frame-renderer-registry.js +9 -0
- package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
- package/dist/navigation-shell-region-component-registry.d.ts +50 -0
- package/dist/navigation-shell-region-component-registry.d.ts.map +1 -0
- package/dist/navigation-shell-region-component-registry.js +44 -0
- package/dist/navigation-shell-region-component-registry.js.map +1 -0
- package/dist/navigation-shell-slot-renderer-registry.d.ts +22 -0
- package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
- package/dist/navigation-shell-slot-renderer-registry.js +9 -0
- package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
- package/dist/page-host-projection.d.ts +127 -0
- package/dist/page-host-projection.d.ts.map +1 -0
- package/dist/page-host-projection.js +59 -0
- package/dist/page-host-projection.js.map +1 -0
- package/dist/page-host-renderer-registry.d.ts +72 -0
- package/dist/page-host-renderer-registry.d.ts.map +1 -0
- package/dist/page-host-renderer-registry.js +63 -0
- package/dist/page-host-renderer-registry.js.map +1 -0
- package/dist/presentation-action-group-runtime-options.d.ts +36 -0
- package/dist/presentation-action-group-runtime-options.d.ts.map +1 -0
- package/dist/presentation-action-group-runtime-options.js +50 -0
- package/dist/presentation-action-group-runtime-options.js.map +1 -0
- package/dist/presentation-action-runtime.d.ts +52 -0
- package/dist/presentation-action-runtime.d.ts.map +1 -0
- package/dist/presentation-action-runtime.js +78 -0
- package/dist/presentation-action-runtime.js.map +1 -0
- package/dist/presentation-data-source-binding-runtime.d.ts +3 -0
- package/dist/presentation-data-source-binding-runtime.d.ts.map +1 -0
- package/dist/presentation-data-source-binding-runtime.js +65 -0
- package/dist/presentation-data-source-binding-runtime.js.map +1 -0
- package/dist/presentation-data-source-binding.d.ts +12 -0
- package/dist/presentation-data-source-binding.d.ts.map +1 -0
- package/dist/presentation-data-source-binding.js +13 -0
- package/dist/presentation-data-source-binding.js.map +1 -0
- package/dist/presentation-flow-runtime.d.ts +139 -0
- package/dist/presentation-flow-runtime.d.ts.map +1 -0
- package/dist/presentation-flow-runtime.js +514 -0
- package/dist/presentation-flow-runtime.js.map +1 -0
- package/dist/presentation-module-context.d.ts +4 -0
- package/dist/presentation-module-context.d.ts.map +1 -0
- package/dist/presentation-module-context.js +6 -0
- package/dist/presentation-module-context.js.map +1 -0
- package/dist/presentation-module-provider.d.ts +7 -0
- package/dist/presentation-module-provider.d.ts.map +1 -0
- package/dist/presentation-module-provider.js +7 -0
- package/dist/presentation-module-provider.js.map +1 -0
- package/dist/presentation-projection-diagnostics-context.d.ts +8 -0
- package/dist/presentation-projection-diagnostics-context.d.ts.map +1 -0
- package/dist/presentation-projection-diagnostics-context.js +3 -0
- package/dist/presentation-projection-diagnostics-context.js.map +1 -0
- package/dist/presentation-projection-diagnostics-hooks.d.ts +4 -0
- package/dist/presentation-projection-diagnostics-hooks.d.ts.map +1 -0
- package/dist/presentation-projection-diagnostics-hooks.js +17 -0
- package/dist/presentation-projection-diagnostics-hooks.js.map +1 -0
- package/dist/presentation-projection-diagnostics-runtime.d.ts +12 -0
- package/dist/presentation-projection-diagnostics-runtime.d.ts.map +1 -0
- package/dist/presentation-projection-diagnostics-runtime.js +54 -0
- package/dist/presentation-projection-diagnostics-runtime.js.map +1 -0
- package/dist/presentation-projection-trace.d.ts +27 -0
- package/dist/presentation-projection-trace.d.ts.map +1 -0
- package/dist/presentation-projection-trace.js +54 -0
- package/dist/presentation-projection-trace.js.map +1 -0
- package/dist/presentation-query-form-runtime.d.ts +30 -0
- package/dist/presentation-query-form-runtime.d.ts.map +1 -0
- package/dist/presentation-query-form-runtime.js +75 -0
- package/dist/presentation-query-form-runtime.js.map +1 -0
- package/dist/presentation-query-form-state-adapter-registry.d.ts +4 -0
- package/dist/presentation-query-form-state-adapter-registry.d.ts.map +1 -0
- package/dist/presentation-query-form-state-adapter-registry.js +13 -0
- package/dist/presentation-query-form-state-adapter-registry.js.map +1 -0
- package/dist/presentation-query-form-state-context.d.ts +20 -0
- package/dist/presentation-query-form-state-context.d.ts.map +1 -0
- package/dist/presentation-query-form-state-context.js +20 -0
- package/dist/presentation-query-form-state-context.js.map +1 -0
- package/dist/presentation-query-form-state.d.ts +29 -0
- package/dist/presentation-query-form-state.d.ts.map +1 -0
- package/dist/presentation-query-form-state.js +99 -0
- package/dist/presentation-query-form-state.js.map +1 -0
- package/dist/presentation-renderer-registry.d.ts +109 -0
- package/dist/presentation-renderer-registry.d.ts.map +1 -0
- package/dist/presentation-renderer-registry.js +25 -0
- package/dist/presentation-renderer-registry.js.map +1 -0
- package/dist/presentation-standards-context.d.ts +4 -0
- package/dist/presentation-standards-context.d.ts.map +1 -0
- package/dist/presentation-standards-context.js +12 -0
- package/dist/presentation-standards-context.js.map +1 -0
- package/dist/presentation-standards-provider.d.ts +14 -0
- package/dist/presentation-standards-provider.d.ts.map +1 -0
- package/dist/presentation-standards-provider.js +16 -0
- package/dist/presentation-standards-provider.js.map +1 -0
- package/dist/projected-document-action-runtime-bindings.d.ts +20 -0
- package/dist/projected-document-action-runtime-bindings.d.ts.map +1 -0
- package/dist/projected-document-action-runtime-bindings.js +88 -0
- package/dist/projected-document-action-runtime-bindings.js.map +1 -0
- package/dist/projected-document-action-runtime.d.ts +76 -0
- package/dist/projected-document-action-runtime.d.ts.map +1 -0
- package/dist/projected-document-action-runtime.js +224 -0
- package/dist/projected-document-action-runtime.js.map +1 -0
- package/dist/projected-document-process-preview-action-runtime-bindings.d.ts +26 -0
- package/dist/projected-document-process-preview-action-runtime-bindings.d.ts.map +1 -0
- package/dist/projected-document-process-preview-action-runtime-bindings.js +106 -0
- package/dist/projected-document-process-preview-action-runtime-bindings.js.map +1 -0
- package/dist/projected-document-process-preview-action-runtime.d.ts +321 -0
- package/dist/projected-document-process-preview-action-runtime.d.ts.map +1 -0
- package/dist/projected-document-process-preview-action-runtime.js +729 -0
- package/dist/projected-document-process-preview-action-runtime.js.map +1 -0
- package/dist/projected-document-process-preview-capability-runtime.d.ts +47 -0
- package/dist/projected-document-process-preview-capability-runtime.d.ts.map +1 -0
- package/dist/projected-document-process-preview-capability-runtime.js +49 -0
- package/dist/projected-document-process-preview-capability-runtime.js.map +1 -0
- package/dist/projected-document-workspace-action-runtime.d.ts +87 -0
- package/dist/projected-document-workspace-action-runtime.d.ts.map +1 -0
- package/dist/projected-document-workspace-action-runtime.js +150 -0
- package/dist/projected-document-workspace-action-runtime.js.map +1 -0
- package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts +18 -0
- package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts.map +1 -0
- package/dist/projected-document-workspace-projection-renderer-diagnostics.js +207 -0
- package/dist/projected-document-workspace-projection-renderer-diagnostics.js.map +1 -0
- package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts +19 -0
- package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts.map +1 -0
- package/dist/projected-document-workspace-runtime-binding-diagnostics.js +124 -0
- package/dist/projected-document-workspace-runtime-binding-diagnostics.js.map +1 -0
- package/dist/projected-navigation-provider.d.ts +16 -0
- package/dist/projected-navigation-provider.d.ts.map +1 -0
- package/dist/projected-navigation-provider.js +51 -0
- package/dist/projected-navigation-provider.js.map +1 -0
- package/dist/projected-presentation-flow-host.d.ts +16 -0
- package/dist/projected-presentation-flow-host.d.ts.map +1 -0
- package/dist/projected-presentation-flow-host.js +14 -0
- package/dist/projected-presentation-flow-host.js.map +1 -0
- package/dist/projected-query-form-state-adapter.d.ts +12 -0
- package/dist/projected-query-form-state-adapter.d.ts.map +1 -0
- package/dist/projected-query-form-state-adapter.js +361 -0
- package/dist/projected-query-form-state-adapter.js.map +1 -0
- package/dist/react-router-binding.d.ts +133 -0
- package/dist/react-router-binding.d.ts.map +1 -0
- package/dist/react-router-binding.js +76 -0
- package/dist/react-router-binding.js.map +1 -0
- package/dist/react-router-path.d.ts +4 -0
- package/dist/react-router-path.d.ts.map +1 -0
- package/dist/react-router-path.js +11 -0
- package/dist/react-router-path.js.map +1 -0
- package/dist/scope-selection-context.d.ts +28 -0
- package/dist/scope-selection-context.d.ts.map +1 -0
- package/dist/scope-selection-context.js +13 -0
- package/dist/scope-selection-context.js.map +1 -0
- package/dist/scope-selection-provider.d.ts +33 -0
- package/dist/scope-selection-provider.d.ts.map +1 -0
- package/dist/scope-selection-provider.js +162 -0
- package/dist/scope-selection-provider.js.map +1 -0
- package/dist/scope-selection-request.d.ts +6 -0
- package/dist/scope-selection-request.d.ts.map +1 -0
- package/dist/scope-selection-request.js +12 -0
- package/dist/scope-selection-request.js.map +1 -0
- package/dist/standard-navigation-shell-frame-renderers.d.ts +15 -0
- package/dist/standard-navigation-shell-frame-renderers.d.ts.map +1 -0
- package/dist/standard-navigation-shell-frame-renderers.js +22 -0
- package/dist/standard-navigation-shell-frame-renderers.js.map +1 -0
- package/dist/standard-navigation-shell-slot-renderers.d.ts +59 -0
- package/dist/standard-navigation-shell-slot-renderers.d.ts.map +1 -0
- package/dist/standard-navigation-shell-slot-renderers.js +67 -0
- package/dist/standard-navigation-shell-slot-renderers.js.map +1 -0
- package/dist/view-chrome-slot-renderer-registry.d.ts +12 -0
- package/dist/view-chrome-slot-renderer-registry.d.ts.map +1 -0
- package/dist/view-chrome-slot-renderer-registry.js +12 -0
- package/dist/view-chrome-slot-renderer-registry.js.map +1 -0
- package/package.json +60 -0
- package/src/collection-chrome-slot-renderer-registry.ts +66 -0
- package/src/collection-selection-state-context.ts +51 -0
- package/src/collection-selection-state.tsx +101 -0
- package/src/document-workspace-runtime.tsx +1505 -0
- package/src/index.ts +50 -0
- package/src/input-form-endpoint-runtime.ts +632 -0
- package/src/local-flow-state-data-source-projection.ts +133 -0
- package/src/navigation-runtime-context.ts +40 -0
- package/src/navigation-shell-frame-renderer-registry.ts +54 -0
- package/src/navigation-shell-region-component-registry.tsx +127 -0
- package/src/navigation-shell-slot-renderer-registry.ts +88 -0
- package/src/page-host-projection.ts +356 -0
- package/src/page-host-renderer-registry.tsx +353 -0
- package/src/presentation-action-group-runtime-options.ts +121 -0
- package/src/presentation-action-runtime.ts +190 -0
- package/src/presentation-data-source-binding-runtime.ts +109 -0
- package/src/presentation-data-source-binding.tsx +24 -0
- package/src/presentation-flow-runtime.tsx +835 -0
- package/src/presentation-module-context.ts +10 -0
- package/src/presentation-module-provider.tsx +19 -0
- package/src/presentation-projection-diagnostics-context.ts +17 -0
- package/src/presentation-projection-diagnostics-hooks.ts +29 -0
- package/src/presentation-projection-diagnostics-runtime.tsx +95 -0
- package/src/presentation-projection-trace.ts +117 -0
- package/src/presentation-query-form-runtime.ts +148 -0
- package/src/presentation-query-form-state-adapter-registry.ts +25 -0
- package/src/presentation-query-form-state-context.ts +56 -0
- package/src/presentation-query-form-state.tsx +250 -0
- package/src/presentation-renderer-registry.ts +214 -0
- package/src/presentation-standards-context.ts +21 -0
- package/src/presentation-standards-provider.tsx +44 -0
- package/src/projected-document-action-runtime-bindings.ts +132 -0
- package/src/projected-document-action-runtime.ts +414 -0
- package/src/projected-document-process-preview-action-runtime-bindings.ts +231 -0
- package/src/projected-document-process-preview-action-runtime.ts +1725 -0
- package/src/projected-document-process-preview-capability-runtime.ts +216 -0
- package/src/projected-document-workspace-action-runtime.ts +402 -0
- package/src/projected-document-workspace-projection-renderer-diagnostics.ts +342 -0
- package/src/projected-document-workspace-runtime-binding-diagnostics.ts +208 -0
- package/src/projected-navigation-provider.tsx +104 -0
- package/src/projected-presentation-flow-host.tsx +46 -0
- package/src/projected-query-form-state-adapter.ts +578 -0
- package/src/react-router-binding.tsx +417 -0
- package/src/react-router-path.ts +16 -0
- package/src/scope-selection-context.ts +58 -0
- package/src/scope-selection-provider.tsx +324 -0
- package/src/scope-selection-request.ts +21 -0
- package/src/standard-navigation-shell-frame-renderers.tsx +106 -0
- package/src/standard-navigation-shell-slot-renderers.tsx +289 -0
- package/src/view-chrome-slot-renderer-registry.ts +66 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
findPresentationAction,
|
|
5
|
+
findPresentationFlow,
|
|
6
|
+
findPresentationView,
|
|
7
|
+
readObjectPath,
|
|
8
|
+
type ActionDefinition,
|
|
9
|
+
type FlowDefinition,
|
|
10
|
+
type FlowStateDefinition,
|
|
11
|
+
type FlowTransitionDefinition,
|
|
12
|
+
type PresentationModuleDefinition,
|
|
13
|
+
type ViewDefinition,
|
|
14
|
+
} from '@cohesivesystems/presentation-core'
|
|
15
|
+
import {
|
|
16
|
+
flowStateKindLabels,
|
|
17
|
+
flowStateKinds,
|
|
18
|
+
preparationKinds,
|
|
19
|
+
} from '@cohesivesystems/presentation-contracts'
|
|
20
|
+
|
|
21
|
+
export interface PresentationFlowInstance {
|
|
22
|
+
readonly data: Readonly<Record<string, unknown>>
|
|
23
|
+
readonly flowId: string
|
|
24
|
+
readonly stateId: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PresentationFlowStartOptions {
|
|
28
|
+
readonly actionId?: string | null
|
|
29
|
+
readonly data?: Readonly<Record<string, unknown>>
|
|
30
|
+
readonly event?: string | null
|
|
31
|
+
readonly flowId: string
|
|
32
|
+
readonly stateId?: string | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PresentationFlowDispatchOptions {
|
|
36
|
+
readonly actionId?: string | null
|
|
37
|
+
readonly data?: Readonly<Record<string, unknown>>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PresentationFlowTransitionToOptions {
|
|
41
|
+
readonly data?: Readonly<Record<string, unknown>>
|
|
42
|
+
readonly flowId?: string | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PresentationFlowTransitionContext {
|
|
46
|
+
readonly flow: FlowDefinition
|
|
47
|
+
readonly fromState: FlowStateDefinition | null
|
|
48
|
+
readonly instance: PresentationFlowInstance
|
|
49
|
+
readonly toState: FlowStateDefinition
|
|
50
|
+
readonly transition: FlowTransitionDefinition | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PresentationFlowUnhandledEventContext {
|
|
54
|
+
readonly actionId: string | null
|
|
55
|
+
readonly event: string
|
|
56
|
+
readonly flow: FlowDefinition
|
|
57
|
+
readonly instance: PresentationFlowInstance
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface UsePresentationFlowRuntimeOptions {
|
|
61
|
+
readonly initialData?: Readonly<Record<string, unknown>>
|
|
62
|
+
readonly initialFlowId?: string | null
|
|
63
|
+
readonly initialStateId?: string | null
|
|
64
|
+
readonly module: PresentationModuleDefinition | null
|
|
65
|
+
readonly onTransition?: (context: PresentationFlowTransitionContext) => void
|
|
66
|
+
readonly onUnhandledEvent?: (context: PresentationFlowUnhandledEventContext) => void
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PresentationActionPreparedFlow {
|
|
70
|
+
readonly canCancel: boolean
|
|
71
|
+
readonly flowId: string
|
|
72
|
+
readonly promptViewId: string | null
|
|
73
|
+
readonly requiresExplicitCommit: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PresentationFlowRuntimeSnapshot {
|
|
77
|
+
readonly activeFlow: FlowDefinition | null
|
|
78
|
+
readonly activeInstance: PresentationFlowInstance | null
|
|
79
|
+
readonly activeState: FlowStateDefinition | null
|
|
80
|
+
readonly activeView: ViewDefinition | null
|
|
81
|
+
readonly clearFlow: () => void
|
|
82
|
+
readonly dispatchAction: (actionId: string, options?: PresentationFlowDispatchOptions) => boolean
|
|
83
|
+
readonly dispatchEvent: (event: string, options?: PresentationFlowDispatchOptions) => boolean
|
|
84
|
+
readonly dispatchTransition: (transitionId: string, options?: PresentationFlowDispatchOptions) => boolean
|
|
85
|
+
readonly isFlowActive: (flowId: string) => boolean
|
|
86
|
+
readonly isStateActive: (stateId: string) => boolean
|
|
87
|
+
readonly startFlow: (options: PresentationFlowStartOptions) => boolean
|
|
88
|
+
readonly transitionTo: (stateId: string, options?: PresentationFlowTransitionToOptions) => boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface PresentationFlowRuntimeEntry {
|
|
92
|
+
readonly flow: FlowDefinition
|
|
93
|
+
readonly instance: PresentationFlowInstance
|
|
94
|
+
readonly state: FlowStateDefinition
|
|
95
|
+
readonly view: ViewDefinition | null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface PresentationFlowRuntimeRegistrySnapshot {
|
|
99
|
+
readonly activeEntries: readonly PresentationFlowRuntimeEntry[]
|
|
100
|
+
readonly clearFlow: (flowId: string) => void
|
|
101
|
+
readonly getRuntime: (flowId: string) => PresentationFlowRuntimeSnapshot
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Tests flow-state kind values across generated numeric constants and backend
|
|
106
|
+
* string enum serialization.
|
|
107
|
+
*/
|
|
108
|
+
export function isPresentationFlowStateKind(
|
|
109
|
+
value: unknown,
|
|
110
|
+
...kinds: readonly (number | string)[]
|
|
111
|
+
) {
|
|
112
|
+
return kinds.some((kind) => matchesFlowStateKind(value, kind))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns true when a flow instance is active for a specific data key/value and
|
|
117
|
+
* the active state is a visible prompt-like state.
|
|
118
|
+
*/
|
|
119
|
+
export function isPresentationFlowSurfaceOpenForData({
|
|
120
|
+
dataKey,
|
|
121
|
+
dataValue,
|
|
122
|
+
flowId,
|
|
123
|
+
runtime,
|
|
124
|
+
}: {
|
|
125
|
+
readonly dataKey: string
|
|
126
|
+
readonly dataValue: string | null
|
|
127
|
+
readonly flowId: string
|
|
128
|
+
readonly runtime: PresentationFlowRuntimeSnapshot
|
|
129
|
+
}) {
|
|
130
|
+
if (!dataValue || !runtime.isFlowActive(flowId) || !runtime.activeState?.ViewId) {
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return runtime.activeInstance?.data[dataKey] === dataValue &&
|
|
135
|
+
isPresentationFlowStateKind(
|
|
136
|
+
runtime.activeState.Kind,
|
|
137
|
+
flowStateKinds.pending,
|
|
138
|
+
flowStateKinds.prompt,
|
|
139
|
+
flowStateKinds.error,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Finds the first visible prompt/pending/error state in a flow definition. */
|
|
144
|
+
export function findPresentationFlowSurfaceState(
|
|
145
|
+
flow: FlowDefinition | null | undefined,
|
|
146
|
+
) {
|
|
147
|
+
return flow?.States.find((state) =>
|
|
148
|
+
Boolean(state.ViewId) &&
|
|
149
|
+
isPresentationFlowStateKind(
|
|
150
|
+
state.Kind,
|
|
151
|
+
flowStateKinds.pending,
|
|
152
|
+
flowStateKinds.prompt,
|
|
153
|
+
flowStateKinds.error,
|
|
154
|
+
),
|
|
155
|
+
) ?? null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Finds the first visible error state in a flow definition. */
|
|
159
|
+
export function findPresentationFlowErrorSurfaceState(
|
|
160
|
+
flow: FlowDefinition | null | undefined,
|
|
161
|
+
) {
|
|
162
|
+
return flow?.States.find((state) =>
|
|
163
|
+
Boolean(state.ViewId) &&
|
|
164
|
+
isPresentationFlowStateKind(state.Kind, flowStateKinds.error),
|
|
165
|
+
) ?? null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Finds the first state matching one of the requested semantic flow kinds. */
|
|
169
|
+
export function findPresentationFlowStateByKind(
|
|
170
|
+
flow: FlowDefinition | null | undefined,
|
|
171
|
+
...kinds: readonly (number | string)[]
|
|
172
|
+
) {
|
|
173
|
+
return flow?.States.find((state) =>
|
|
174
|
+
isPresentationFlowStateKind(state.Kind, ...kinds),
|
|
175
|
+
) ?? null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Advances an active runtime instance to a state with the requested semantic
|
|
180
|
+
* kind, preferring a valid declared transition from the current state.
|
|
181
|
+
*/
|
|
182
|
+
export function advancePresentationFlowToStateKind({
|
|
183
|
+
allowStateFallback = true,
|
|
184
|
+
data,
|
|
185
|
+
flow,
|
|
186
|
+
runtime,
|
|
187
|
+
stateKinds,
|
|
188
|
+
}: {
|
|
189
|
+
readonly allowStateFallback?: boolean
|
|
190
|
+
readonly data?: Readonly<Record<string, unknown>>
|
|
191
|
+
readonly flow: FlowDefinition | null | undefined
|
|
192
|
+
readonly runtime: PresentationFlowRuntimeSnapshot
|
|
193
|
+
readonly stateKinds: readonly (number | string)[]
|
|
194
|
+
}) {
|
|
195
|
+
if (!flow || !runtime.activeInstance) {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const transition = findPresentationFlowTransitionToStateKind({
|
|
200
|
+
data: {
|
|
201
|
+
...runtime.activeInstance.data,
|
|
202
|
+
...(data ?? {}),
|
|
203
|
+
},
|
|
204
|
+
flow,
|
|
205
|
+
fromStateId: runtime.activeInstance.stateId,
|
|
206
|
+
stateKinds,
|
|
207
|
+
})
|
|
208
|
+
if (transition && runtime.dispatchTransition(transition.Id, { data })) {
|
|
209
|
+
return true
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!allowStateFallback) {
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const state = findPresentationFlowStateByKind(flow, ...stateKinds)
|
|
217
|
+
return state
|
|
218
|
+
? runtime.transitionTo(state.Id, { data, flowId: flow.Id })
|
|
219
|
+
: false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Finds a valid transition from a state to any state with the requested kind. */
|
|
223
|
+
export function findPresentationFlowTransitionToStateKind({
|
|
224
|
+
data,
|
|
225
|
+
flow,
|
|
226
|
+
fromStateId,
|
|
227
|
+
stateKinds,
|
|
228
|
+
}: {
|
|
229
|
+
readonly data: Readonly<Record<string, unknown>>
|
|
230
|
+
readonly flow: FlowDefinition
|
|
231
|
+
readonly fromStateId: string
|
|
232
|
+
readonly stateKinds: readonly (number | string)[]
|
|
233
|
+
}) {
|
|
234
|
+
const targetStateIds = new Set(
|
|
235
|
+
flow.States
|
|
236
|
+
.filter((state) => isPresentationFlowStateKind(state.Kind, ...stateKinds))
|
|
237
|
+
.map((state) => state.Id),
|
|
238
|
+
)
|
|
239
|
+
return flow.Transitions.find((transition) =>
|
|
240
|
+
transition.FromStateId === fromStateId &&
|
|
241
|
+
targetStateIds.has(transition.ToStateId) &&
|
|
242
|
+
matchesPresentationFlowTransitionGuard(transition, data),
|
|
243
|
+
) ?? null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Finds a valid action/event transition from the given flow state. */
|
|
247
|
+
export function findPresentationFlowTransition({
|
|
248
|
+
actionId,
|
|
249
|
+
data,
|
|
250
|
+
event,
|
|
251
|
+
flow,
|
|
252
|
+
fromStateId,
|
|
253
|
+
}: {
|
|
254
|
+
readonly actionId?: string | null
|
|
255
|
+
readonly data: Readonly<Record<string, unknown>>
|
|
256
|
+
readonly event?: string | null
|
|
257
|
+
readonly flow: FlowDefinition
|
|
258
|
+
readonly fromStateId: string
|
|
259
|
+
}) {
|
|
260
|
+
const candidates = flow.Transitions.filter((transition) =>
|
|
261
|
+
transition.FromStateId === fromStateId &&
|
|
262
|
+
(
|
|
263
|
+
(event ? transition.Event === event : false) ||
|
|
264
|
+
(actionId ? transition.ActionId === actionId : false)
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return candidates.find((transition) =>
|
|
269
|
+
matchesPresentationFlowTransitionGuard(transition, data),
|
|
270
|
+
) ?? null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Interprets client-resident Presentation IR flows into local React state.
|
|
275
|
+
*
|
|
276
|
+
* The runtime deliberately stays semantic: it tracks the active flow, resolves
|
|
277
|
+
* transitions by event or action id, exposes the active prompt view, and leaves
|
|
278
|
+
* endpoint execution, persistence, and custom rendering to host-specific escape
|
|
279
|
+
* hatches. That lets app code migrate from imperative conditionals toward IR
|
|
280
|
+
* projection without requiring every flow side effect to be generalized first.
|
|
281
|
+
*/
|
|
282
|
+
export function usePresentationFlowRuntime({
|
|
283
|
+
initialData,
|
|
284
|
+
initialFlowId,
|
|
285
|
+
initialStateId,
|
|
286
|
+
module,
|
|
287
|
+
onTransition,
|
|
288
|
+
onUnhandledEvent,
|
|
289
|
+
}: UsePresentationFlowRuntimeOptions): PresentationFlowRuntimeSnapshot {
|
|
290
|
+
const [instance, setInstance] = useState<PresentationFlowInstance | null>(() => {
|
|
291
|
+
if (!initialFlowId) {
|
|
292
|
+
return null
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const flow = findPresentationFlow<FlowDefinition>(module, initialFlowId)
|
|
296
|
+
if (!flow) {
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
data: initialData ?? {},
|
|
302
|
+
flowId: initialFlowId,
|
|
303
|
+
stateId: initialStateId ?? flow.InitialStateId,
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const activeFlow = useMemo(
|
|
308
|
+
() =>
|
|
309
|
+
instance
|
|
310
|
+
? findPresentationFlow<FlowDefinition>(module, instance.flowId)
|
|
311
|
+
: null,
|
|
312
|
+
[instance, module],
|
|
313
|
+
)
|
|
314
|
+
const activeState = useMemo(
|
|
315
|
+
() => activeFlow?.States.find((state) => state.Id === instance?.stateId) ?? null,
|
|
316
|
+
[activeFlow, instance],
|
|
317
|
+
)
|
|
318
|
+
const activeView = useMemo(
|
|
319
|
+
() =>
|
|
320
|
+
activeState?.ViewId
|
|
321
|
+
? findPresentationView<ViewDefinition>(module, activeState.ViewId)
|
|
322
|
+
: null,
|
|
323
|
+
[activeState, module],
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
function startFlow(options: PresentationFlowStartOptions) {
|
|
327
|
+
const flow = findPresentationFlow<FlowDefinition>(module, options.flowId)
|
|
328
|
+
if (!flow) {
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const data = options.data ?? {}
|
|
333
|
+
const fromStateId = options.stateId ?? flow.InitialStateId
|
|
334
|
+
const transition = options.stateId
|
|
335
|
+
? null
|
|
336
|
+
: resolveFlowTransition({
|
|
337
|
+
actionId: options.actionId,
|
|
338
|
+
data,
|
|
339
|
+
event: options.event,
|
|
340
|
+
flow,
|
|
341
|
+
fromStateId,
|
|
342
|
+
})
|
|
343
|
+
if (!options.stateId && (options.event || options.actionId) && !transition) {
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
const toStateId = transition?.ToStateId ?? fromStateId
|
|
347
|
+
const toState = flow.States.find((state) => state.Id === toStateId)
|
|
348
|
+
if (!toState) {
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const nextInstance = {
|
|
353
|
+
data,
|
|
354
|
+
flowId: flow.Id,
|
|
355
|
+
stateId: toState.Id,
|
|
356
|
+
} satisfies PresentationFlowInstance
|
|
357
|
+
setInstance(nextInstance)
|
|
358
|
+
onTransition?.({
|
|
359
|
+
flow,
|
|
360
|
+
fromState: flow.States.find((state) => state.Id === fromStateId) ?? null,
|
|
361
|
+
instance: nextInstance,
|
|
362
|
+
toState,
|
|
363
|
+
transition,
|
|
364
|
+
})
|
|
365
|
+
return true
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function dispatchEvent(event: string, options?: PresentationFlowDispatchOptions) {
|
|
369
|
+
if (!instance || !activeFlow) {
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const data = { ...instance.data, ...(options?.data ?? {}) }
|
|
374
|
+
const transition = resolveFlowTransition({
|
|
375
|
+
actionId: options?.actionId,
|
|
376
|
+
data,
|
|
377
|
+
event,
|
|
378
|
+
flow: activeFlow,
|
|
379
|
+
fromStateId: instance.stateId,
|
|
380
|
+
})
|
|
381
|
+
if (!transition) {
|
|
382
|
+
onUnhandledEvent?.({
|
|
383
|
+
actionId: options?.actionId ?? null,
|
|
384
|
+
event,
|
|
385
|
+
flow: activeFlow,
|
|
386
|
+
instance,
|
|
387
|
+
})
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return applyFlowState(activeFlow, transition.ToStateId, {
|
|
392
|
+
data,
|
|
393
|
+
transition,
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function dispatchAction(actionId: string, options?: PresentationFlowDispatchOptions) {
|
|
398
|
+
const action = findPresentationAction<ActionDefinition>(module, actionId)
|
|
399
|
+
return dispatchEvent(action?.Binding.Id ?? actionId, {
|
|
400
|
+
...options,
|
|
401
|
+
actionId,
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function dispatchTransition(transitionId: string, options?: PresentationFlowDispatchOptions) {
|
|
406
|
+
if (!instance || !activeFlow) {
|
|
407
|
+
return false
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const data = { ...instance.data, ...(options?.data ?? {}) }
|
|
411
|
+
const transition = activeFlow.Transitions.find((candidate) =>
|
|
412
|
+
candidate.Id === transitionId &&
|
|
413
|
+
candidate.FromStateId === instance.stateId &&
|
|
414
|
+
matchesPresentationFlowTransitionGuard(candidate, data),
|
|
415
|
+
)
|
|
416
|
+
if (!transition) {
|
|
417
|
+
return false
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return applyFlowState(activeFlow, transition.ToStateId, {
|
|
421
|
+
data,
|
|
422
|
+
transition,
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function transitionTo(stateId: string, options?: PresentationFlowTransitionToOptions) {
|
|
427
|
+
const flowId = options?.flowId ?? instance?.flowId
|
|
428
|
+
const flow = flowId ? findPresentationFlow<FlowDefinition>(module, flowId) : null
|
|
429
|
+
if (!flow) {
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return applyFlowState(flow, stateId, {
|
|
434
|
+
data: {
|
|
435
|
+
...(instance?.flowId === flow.Id ? instance.data : {}),
|
|
436
|
+
...(options?.data ?? {}),
|
|
437
|
+
},
|
|
438
|
+
transition: null,
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function applyFlowState(
|
|
443
|
+
flow: FlowDefinition,
|
|
444
|
+
stateId: string,
|
|
445
|
+
options: {
|
|
446
|
+
readonly data: Readonly<Record<string, unknown>>
|
|
447
|
+
readonly transition: FlowTransitionDefinition | null
|
|
448
|
+
},
|
|
449
|
+
) {
|
|
450
|
+
const toState = flow.States.find((state) => state.Id === stateId)
|
|
451
|
+
if (!toState) {
|
|
452
|
+
return false
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const nextInstance = {
|
|
456
|
+
data: options.data,
|
|
457
|
+
flowId: flow.Id,
|
|
458
|
+
stateId: toState.Id,
|
|
459
|
+
} satisfies PresentationFlowInstance
|
|
460
|
+
const fromState = instance?.flowId === flow.Id
|
|
461
|
+
? flow.States.find((state) => state.Id === instance.stateId) ?? null
|
|
462
|
+
: null
|
|
463
|
+
setInstance(nextInstance)
|
|
464
|
+
onTransition?.({
|
|
465
|
+
flow,
|
|
466
|
+
fromState,
|
|
467
|
+
instance: nextInstance,
|
|
468
|
+
toState,
|
|
469
|
+
transition: options.transition,
|
|
470
|
+
})
|
|
471
|
+
return true
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
activeFlow,
|
|
476
|
+
activeInstance: instance,
|
|
477
|
+
activeState,
|
|
478
|
+
activeView,
|
|
479
|
+
clearFlow: () => setInstance(null),
|
|
480
|
+
dispatchAction,
|
|
481
|
+
dispatchEvent,
|
|
482
|
+
dispatchTransition,
|
|
483
|
+
isFlowActive: (flowId) => instance?.flowId === flowId,
|
|
484
|
+
isStateActive: (stateId) => instance?.stateId === stateId,
|
|
485
|
+
startFlow,
|
|
486
|
+
transitionTo,
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Maintains the active instances for all client-resident Presentation IR flows
|
|
492
|
+
* in a host. Callers can still ask for a flow-scoped runtime, but prompt
|
|
493
|
+
* projection can iterate active entries instead of wiring each flow manually.
|
|
494
|
+
*/
|
|
495
|
+
export function usePresentationFlowRuntimeRegistry({
|
|
496
|
+
module,
|
|
497
|
+
onTransition,
|
|
498
|
+
onUnhandledEvent,
|
|
499
|
+
}: Pick<UsePresentationFlowRuntimeOptions, 'module' | 'onTransition' | 'onUnhandledEvent'>): PresentationFlowRuntimeRegistrySnapshot {
|
|
500
|
+
const [instances, setInstances] = useState<Readonly<Record<string, PresentationFlowInstance>>>({})
|
|
501
|
+
const instancesRef = useRef(instances)
|
|
502
|
+
|
|
503
|
+
useEffect(() => {
|
|
504
|
+
instancesRef.current = instances
|
|
505
|
+
}, [instances])
|
|
506
|
+
|
|
507
|
+
const activeEntries = useMemo(
|
|
508
|
+
() =>
|
|
509
|
+
Object.values(instances)
|
|
510
|
+
.map((instance) => resolvePresentationFlowRuntimeEntry(module, instance))
|
|
511
|
+
.filter((entry): entry is PresentationFlowRuntimeEntry => entry !== null),
|
|
512
|
+
[instances, module],
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
function setFlowInstance(instance: PresentationFlowInstance | null, flowId: string) {
|
|
516
|
+
const current = instancesRef.current
|
|
517
|
+
const next = { ...current }
|
|
518
|
+
if (instance) {
|
|
519
|
+
next[flowId] = instance
|
|
520
|
+
} else {
|
|
521
|
+
delete next[flowId]
|
|
522
|
+
}
|
|
523
|
+
instancesRef.current = next
|
|
524
|
+
setInstances(next)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function clearFlow(flowId: string) {
|
|
528
|
+
setFlowInstance(null, flowId)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function getRuntime(flowId: string): PresentationFlowRuntimeSnapshot {
|
|
532
|
+
const instance = instances[flowId] ?? null
|
|
533
|
+
const activeFlow = instance
|
|
534
|
+
? findPresentationFlow<FlowDefinition>(module, instance.flowId)
|
|
535
|
+
: null
|
|
536
|
+
const activeState =
|
|
537
|
+
activeFlow?.States.find((state) => state.Id === instance?.stateId) ?? null
|
|
538
|
+
const activeView =
|
|
539
|
+
activeState?.ViewId
|
|
540
|
+
? findPresentationView<ViewDefinition>(module, activeState.ViewId)
|
|
541
|
+
: null
|
|
542
|
+
|
|
543
|
+
function startFlow(options: PresentationFlowStartOptions) {
|
|
544
|
+
const flow = findPresentationFlow<FlowDefinition>(module, options.flowId)
|
|
545
|
+
if (!flow) {
|
|
546
|
+
return false
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const data = options.data ?? {}
|
|
550
|
+
const fromStateId = options.stateId ?? flow.InitialStateId
|
|
551
|
+
const transition = options.stateId
|
|
552
|
+
? null
|
|
553
|
+
: resolveFlowTransition({
|
|
554
|
+
actionId: options.actionId,
|
|
555
|
+
data,
|
|
556
|
+
event: options.event,
|
|
557
|
+
flow,
|
|
558
|
+
fromStateId,
|
|
559
|
+
})
|
|
560
|
+
if (!options.stateId && (options.event || options.actionId) && !transition) {
|
|
561
|
+
return false
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const toStateId = transition?.ToStateId ?? fromStateId
|
|
565
|
+
return applyRegistryFlowState(flow, toStateId, {
|
|
566
|
+
data,
|
|
567
|
+
previousInstance: instancesRef.current[flow.Id] ?? null,
|
|
568
|
+
transition,
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function dispatchEvent(event: string, options?: PresentationFlowDispatchOptions) {
|
|
573
|
+
const currentInstance = instancesRef.current[flowId] ?? null
|
|
574
|
+
const flow = currentInstance
|
|
575
|
+
? findPresentationFlow<FlowDefinition>(module, currentInstance.flowId)
|
|
576
|
+
: null
|
|
577
|
+
if (!currentInstance || !flow) {
|
|
578
|
+
return false
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const data = { ...currentInstance.data, ...(options?.data ?? {}) }
|
|
582
|
+
const transition = resolveFlowTransition({
|
|
583
|
+
actionId: options?.actionId,
|
|
584
|
+
data,
|
|
585
|
+
event,
|
|
586
|
+
flow,
|
|
587
|
+
fromStateId: currentInstance.stateId,
|
|
588
|
+
})
|
|
589
|
+
if (!transition) {
|
|
590
|
+
onUnhandledEvent?.({
|
|
591
|
+
actionId: options?.actionId ?? null,
|
|
592
|
+
event,
|
|
593
|
+
flow,
|
|
594
|
+
instance: currentInstance,
|
|
595
|
+
})
|
|
596
|
+
return false
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return applyRegistryFlowState(flow, transition.ToStateId, {
|
|
600
|
+
data,
|
|
601
|
+
previousInstance: currentInstance,
|
|
602
|
+
transition,
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function dispatchAction(actionId: string, options?: PresentationFlowDispatchOptions) {
|
|
607
|
+
const action = findPresentationAction<ActionDefinition>(module, actionId)
|
|
608
|
+
return dispatchEvent(action?.Binding.Id ?? actionId, {
|
|
609
|
+
...options,
|
|
610
|
+
actionId,
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function dispatchTransition(
|
|
615
|
+
transitionId: string,
|
|
616
|
+
options?: PresentationFlowDispatchOptions,
|
|
617
|
+
) {
|
|
618
|
+
const currentInstance = instancesRef.current[flowId] ?? null
|
|
619
|
+
const flow = currentInstance
|
|
620
|
+
? findPresentationFlow<FlowDefinition>(module, currentInstance.flowId)
|
|
621
|
+
: null
|
|
622
|
+
if (!currentInstance || !flow) {
|
|
623
|
+
return false
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const data = { ...currentInstance.data, ...(options?.data ?? {}) }
|
|
627
|
+
const transition = flow.Transitions.find((candidate) =>
|
|
628
|
+
candidate.Id === transitionId &&
|
|
629
|
+
candidate.FromStateId === currentInstance.stateId &&
|
|
630
|
+
matchesPresentationFlowTransitionGuard(candidate, data),
|
|
631
|
+
)
|
|
632
|
+
if (!transition) {
|
|
633
|
+
return false
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return applyRegistryFlowState(flow, transition.ToStateId, {
|
|
637
|
+
data,
|
|
638
|
+
previousInstance: currentInstance,
|
|
639
|
+
transition,
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function transitionTo(stateId: string, options?: PresentationFlowTransitionToOptions) {
|
|
644
|
+
const currentInstance = instancesRef.current[flowId] ?? null
|
|
645
|
+
const targetFlowId = options?.flowId ?? currentInstance?.flowId ?? flowId
|
|
646
|
+
const flow = findPresentationFlow<FlowDefinition>(module, targetFlowId)
|
|
647
|
+
if (!flow) {
|
|
648
|
+
return false
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return applyRegistryFlowState(flow, stateId, {
|
|
652
|
+
data: {
|
|
653
|
+
...(currentInstance?.flowId === flow.Id ? currentInstance.data : {}),
|
|
654
|
+
...(options?.data ?? {}),
|
|
655
|
+
},
|
|
656
|
+
previousInstance: currentInstance?.flowId === flow.Id ? currentInstance : null,
|
|
657
|
+
transition: null,
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function applyRegistryFlowState(
|
|
662
|
+
flow: FlowDefinition,
|
|
663
|
+
stateId: string,
|
|
664
|
+
options: {
|
|
665
|
+
readonly data: Readonly<Record<string, unknown>>
|
|
666
|
+
readonly previousInstance: PresentationFlowInstance | null
|
|
667
|
+
readonly transition: FlowTransitionDefinition | null
|
|
668
|
+
},
|
|
669
|
+
) {
|
|
670
|
+
const toState = flow.States.find((state) => state.Id === stateId)
|
|
671
|
+
if (!toState) {
|
|
672
|
+
return false
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const nextInstance = {
|
|
676
|
+
data: options.data,
|
|
677
|
+
flowId: flow.Id,
|
|
678
|
+
stateId: toState.Id,
|
|
679
|
+
} satisfies PresentationFlowInstance
|
|
680
|
+
setFlowInstance(nextInstance, flow.Id)
|
|
681
|
+
onTransition?.({
|
|
682
|
+
flow,
|
|
683
|
+
fromState: options.previousInstance
|
|
684
|
+
? flow.States.find((state) => state.Id === options.previousInstance?.stateId) ?? null
|
|
685
|
+
: null,
|
|
686
|
+
instance: nextInstance,
|
|
687
|
+
toState,
|
|
688
|
+
transition: options.transition,
|
|
689
|
+
})
|
|
690
|
+
return true
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
activeFlow,
|
|
695
|
+
activeInstance: instance,
|
|
696
|
+
activeState,
|
|
697
|
+
activeView,
|
|
698
|
+
clearFlow: () => clearFlow(flowId),
|
|
699
|
+
dispatchAction,
|
|
700
|
+
dispatchEvent,
|
|
701
|
+
dispatchTransition,
|
|
702
|
+
isFlowActive: (candidateFlowId) =>
|
|
703
|
+
instancesRef.current[flowId]?.flowId === candidateFlowId,
|
|
704
|
+
isStateActive: (stateId) => instancesRef.current[flowId]?.stateId === stateId,
|
|
705
|
+
startFlow,
|
|
706
|
+
transitionTo,
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
activeEntries,
|
|
712
|
+
clearFlow,
|
|
713
|
+
getRuntime,
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function resolvePresentationActionPreparedFlow(
|
|
718
|
+
action: ActionDefinition | null,
|
|
719
|
+
): PresentationActionPreparedFlow | null {
|
|
720
|
+
const preparation = action?.Preparation
|
|
721
|
+
if (!preparation?.FlowId || !isFlowPreparationKind(preparation.Kind)) {
|
|
722
|
+
return null
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
canCancel: preparation.CanCancel,
|
|
727
|
+
flowId: preparation.FlowId,
|
|
728
|
+
promptViewId: preparation.PromptViewId ?? null,
|
|
729
|
+
requiresExplicitCommit: preparation.RequiresExplicitCommit,
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function resolvePresentationFlowRuntimeEntry(
|
|
734
|
+
module: PresentationModuleDefinition | null,
|
|
735
|
+
instance: PresentationFlowInstance,
|
|
736
|
+
): PresentationFlowRuntimeEntry | null {
|
|
737
|
+
const flow = findPresentationFlow<FlowDefinition>(module, instance.flowId)
|
|
738
|
+
if (!flow) {
|
|
739
|
+
return null
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const state = flow.States.find((candidate) => candidate.Id === instance.stateId)
|
|
743
|
+
if (!state) {
|
|
744
|
+
return null
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
flow,
|
|
749
|
+
instance,
|
|
750
|
+
state,
|
|
751
|
+
view: state.ViewId
|
|
752
|
+
? findPresentationView<ViewDefinition>(module, state.ViewId)
|
|
753
|
+
: null,
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function resolveFlowTransition({
|
|
758
|
+
actionId,
|
|
759
|
+
data,
|
|
760
|
+
event,
|
|
761
|
+
flow,
|
|
762
|
+
fromStateId,
|
|
763
|
+
}: {
|
|
764
|
+
readonly actionId?: string | null
|
|
765
|
+
readonly data: Readonly<Record<string, unknown>>
|
|
766
|
+
readonly event?: string | null
|
|
767
|
+
readonly flow: FlowDefinition
|
|
768
|
+
readonly fromStateId: string
|
|
769
|
+
}) {
|
|
770
|
+
return findPresentationFlowTransition({
|
|
771
|
+
actionId,
|
|
772
|
+
data,
|
|
773
|
+
event,
|
|
774
|
+
flow,
|
|
775
|
+
fromStateId,
|
|
776
|
+
})
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function matchesPresentationFlowTransitionGuard(
|
|
780
|
+
transition: FlowTransitionDefinition,
|
|
781
|
+
data: Readonly<Record<string, unknown>>,
|
|
782
|
+
) {
|
|
783
|
+
if (!transition.Guard) {
|
|
784
|
+
return true
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const match = transition.Guard.match(
|
|
788
|
+
/^\s*([A-Za-z0-9_.]+)\s*==\s*(true|false|null|'[^']*'|"[^"]*"|-?\d+(?:\.\d+)?)\s*$/,
|
|
789
|
+
)
|
|
790
|
+
if (!match) {
|
|
791
|
+
return false
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return readObjectPath(data, match[1]) === parseGuardLiteral(match[2])
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function parseGuardLiteral(value: string) {
|
|
798
|
+
if (value === 'true') {
|
|
799
|
+
return true
|
|
800
|
+
}
|
|
801
|
+
if (value === 'false') {
|
|
802
|
+
return false
|
|
803
|
+
}
|
|
804
|
+
if (value === 'null') {
|
|
805
|
+
return null
|
|
806
|
+
}
|
|
807
|
+
if (
|
|
808
|
+
(value.startsWith("'") && value.endsWith("'")) ||
|
|
809
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
810
|
+
) {
|
|
811
|
+
return value.slice(1, -1)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const numberValue = Number(value)
|
|
815
|
+
return Number.isFinite(numberValue) ? numberValue : value
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function isFlowPreparationKind(value: unknown) {
|
|
819
|
+
return value === preparationKinds.prompt ||
|
|
820
|
+
value === preparationKinds.previewFlow ||
|
|
821
|
+
String(value).toLowerCase() === 'prompt' ||
|
|
822
|
+
String(value).toLowerCase() === 'previewflow'
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function matchesFlowStateKind(value: unknown, kind: string | number) {
|
|
826
|
+
const label = typeof kind === 'number'
|
|
827
|
+
? flowStateKindLabels[kind as keyof typeof flowStateKindLabels]
|
|
828
|
+
: kind
|
|
829
|
+
const normalizedValue = String(value).toLocaleLowerCase()
|
|
830
|
+
return (
|
|
831
|
+
value === kind ||
|
|
832
|
+
normalizedValue === String(kind).toLocaleLowerCase() ||
|
|
833
|
+
normalizedValue === label?.toLocaleLowerCase()
|
|
834
|
+
)
|
|
835
|
+
}
|