@cohesivesystems/presentation-react-shadcn 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/document-workspace-surface-slot-renderers.d.ts +58 -0
- package/dist/document-workspace-surface-slot-renderers.d.ts.map +1 -0
- package/dist/document-workspace-surface-slot-renderers.js +206 -0
- package/dist/document-workspace-surface-slot-renderers.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation-shell-frame-renderer-registry.d.ts +8 -0
- package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
- package/dist/navigation-shell-frame-renderer-registry.js +2 -0
- package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
- package/dist/navigation-shell-slot-renderer-registry.d.ts +9 -0
- package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
- package/dist/navigation-shell-slot-renderer-registry.js +2 -0
- package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
- package/dist/page-host-unavailable-renderer.d.ts +10 -0
- package/dist/page-host-unavailable-renderer.d.ts.map +1 -0
- package/dist/page-host-unavailable-renderer.js +27 -0
- package/dist/page-host-unavailable-renderer.js.map +1 -0
- package/dist/presentation-action-group.d.ts +99 -0
- package/dist/presentation-action-group.d.ts.map +1 -0
- package/dist/presentation-action-group.js +214 -0
- package/dist/presentation-action-group.js.map +1 -0
- package/dist/presentation-badge-target-interpreter.d.ts +26 -0
- package/dist/presentation-badge-target-interpreter.d.ts.map +1 -0
- package/dist/presentation-badge-target-interpreter.js +17 -0
- package/dist/presentation-badge-target-interpreter.js.map +1 -0
- package/dist/presentation-component-groups.d.ts +968 -0
- package/dist/presentation-component-groups.d.ts.map +1 -0
- package/dist/presentation-component-groups.js +505 -0
- package/dist/presentation-component-groups.js.map +1 -0
- package/dist/presentation-component-system-diagnostics.d.ts +27 -0
- package/dist/presentation-component-system-diagnostics.d.ts.map +1 -0
- package/dist/presentation-component-system-diagnostics.js +399 -0
- package/dist/presentation-component-system-diagnostics.js.map +1 -0
- package/dist/presentation-component-system.d.ts +31 -0
- package/dist/presentation-component-system.d.ts.map +1 -0
- package/dist/presentation-component-system.js +61 -0
- package/dist/presentation-component-system.js.map +1 -0
- package/dist/presentation-icon-diagnostics.d.ts +12 -0
- package/dist/presentation-icon-diagnostics.d.ts.map +1 -0
- package/dist/presentation-icon-diagnostics.js +17 -0
- package/dist/presentation-icon-diagnostics.js.map +1 -0
- package/dist/presentation-icon-registry.d.ts +28 -0
- package/dist/presentation-icon-registry.d.ts.map +1 -0
- package/dist/presentation-icon-registry.js +80 -0
- package/dist/presentation-icon-registry.js.map +1 -0
- package/dist/presentation-projection-trace-panel.d.ts +10 -0
- package/dist/presentation-projection-trace-panel.d.ts.map +1 -0
- package/dist/presentation-projection-trace-panel.js +48 -0
- package/dist/presentation-projection-trace-panel.js.map +1 -0
- package/dist/presentation-routed-surface-host.d.ts +19 -0
- package/dist/presentation-routed-surface-host.d.ts.map +1 -0
- package/dist/presentation-routed-surface-host.js +12 -0
- package/dist/presentation-routed-surface-host.js.map +1 -0
- package/dist/presentation-shadcn-component-system.d.ts +22 -0
- package/dist/presentation-shadcn-component-system.d.ts.map +1 -0
- package/dist/presentation-shadcn-component-system.js +2 -0
- package/dist/presentation-shadcn-component-system.js.map +1 -0
- package/dist/presentation-standard-renderers.d.ts +168 -0
- package/dist/presentation-standard-renderers.d.ts.map +1 -0
- package/dist/presentation-standard-renderers.js +440 -0
- package/dist/presentation-standard-renderers.js.map +1 -0
- package/dist/presentation-surface-renderer.d.ts +23 -0
- package/dist/presentation-surface-renderer.d.ts.map +1 -0
- package/dist/presentation-surface-renderer.js +119 -0
- package/dist/presentation-surface-renderer.js.map +1 -0
- package/dist/presentation-value-renderers.d.ts +10 -0
- package/dist/presentation-value-renderers.d.ts.map +1 -0
- package/dist/presentation-value-renderers.js +14 -0
- package/dist/presentation-value-renderers.js.map +1 -0
- package/dist/process-task-icons.d.ts +49 -0
- package/dist/process-task-icons.d.ts.map +1 -0
- package/dist/process-task-icons.js +154 -0
- package/dist/process-task-icons.js.map +1 -0
- package/dist/projected-activity-state.d.ts +17 -0
- package/dist/projected-activity-state.d.ts.map +1 -0
- package/dist/projected-activity-state.js +22 -0
- package/dist/projected-activity-state.js.map +1 -0
- package/dist/projected-collection-action-runtime-diagnostics.d.ts +4 -0
- package/dist/projected-collection-action-runtime-diagnostics.d.ts.map +1 -0
- package/dist/projected-collection-action-runtime-diagnostics.js +9 -0
- package/dist/projected-collection-action-runtime-diagnostics.js.map +1 -0
- package/dist/projected-collection-view.d.ts +90 -0
- package/dist/projected-collection-view.d.ts.map +1 -0
- package/dist/projected-collection-view.js +532 -0
- package/dist/projected-collection-view.js.map +1 -0
- package/dist/projected-document-action-status-notices.d.ts +15 -0
- package/dist/projected-document-action-status-notices.d.ts.map +1 -0
- package/dist/projected-document-action-status-notices.js +33 -0
- package/dist/projected-document-action-status-notices.js.map +1 -0
- package/dist/projected-document-badges.d.ts +24 -0
- package/dist/projected-document-badges.d.ts.map +1 -0
- package/dist/projected-document-badges.js +53 -0
- package/dist/projected-document-badges.js.map +1 -0
- package/dist/projected-document-editor-surface.d.ts +46 -0
- package/dist/projected-document-editor-surface.d.ts.map +1 -0
- package/dist/projected-document-editor-surface.js +399 -0
- package/dist/projected-document-editor-surface.js.map +1 -0
- package/dist/projected-document-workspace-notices.d.ts +24 -0
- package/dist/projected-document-workspace-notices.d.ts.map +1 -0
- package/dist/projected-document-workspace-notices.js +14 -0
- package/dist/projected-document-workspace-notices.js.map +1 -0
- package/dist/projected-document-workspace-prompt-child-view-registry.d.ts +25 -0
- package/dist/projected-document-workspace-prompt-child-view-registry.d.ts.map +1 -0
- package/dist/projected-document-workspace-prompt-child-view-registry.js +41 -0
- package/dist/projected-document-workspace-prompt-child-view-registry.js.map +1 -0
- package/dist/projected-document-workspace-prompt-child-view-renderers.d.ts +32 -0
- package/dist/projected-document-workspace-prompt-child-view-renderers.d.ts.map +1 -0
- package/dist/projected-document-workspace-prompt-child-view-renderers.js +77 -0
- package/dist/projected-document-workspace-prompt-child-view-renderers.js.map +1 -0
- package/dist/projected-document-workspace-shell.d.ts +100 -0
- package/dist/projected-document-workspace-shell.d.ts.map +1 -0
- package/dist/projected-document-workspace-shell.js +153 -0
- package/dist/projected-document-workspace-shell.js.map +1 -0
- package/dist/projected-document-workspace.d.ts +39 -0
- package/dist/projected-document-workspace.d.ts.map +1 -0
- package/dist/projected-document-workspace.js +63 -0
- package/dist/projected-document-workspace.js.map +1 -0
- package/dist/projected-field-value-rendering.d.ts +94 -0
- package/dist/projected-field-value-rendering.d.ts.map +1 -0
- package/dist/projected-field-value-rendering.js +377 -0
- package/dist/projected-field-value-rendering.js.map +1 -0
- package/dist/projected-inline-list.d.ts +20 -0
- package/dist/projected-inline-list.d.ts.map +1 -0
- package/dist/projected-inline-list.js +149 -0
- package/dist/projected-inline-list.js.map +1 -0
- package/dist/projected-input-form.d.ts +143 -0
- package/dist/projected-input-form.d.ts.map +1 -0
- package/dist/projected-input-form.js +612 -0
- package/dist/projected-input-form.js.map +1 -0
- package/dist/projected-metric-dashboard.d.ts +26 -0
- package/dist/projected-metric-dashboard.d.ts.map +1 -0
- package/dist/projected-metric-dashboard.js +20 -0
- package/dist/projected-metric-dashboard.js.map +1 -0
- package/dist/projected-metric-strip.d.ts +20 -0
- package/dist/projected-metric-strip.d.ts.map +1 -0
- package/dist/projected-metric-strip.js +131 -0
- package/dist/projected-metric-strip.js.map +1 -0
- package/dist/projected-navigation-shell-view-host.d.ts +34 -0
- package/dist/projected-navigation-shell-view-host.d.ts.map +1 -0
- package/dist/projected-navigation-shell-view-host.js +75 -0
- package/dist/projected-navigation-shell-view-host.js.map +1 -0
- package/dist/projected-navigation-shell.d.ts +33 -0
- package/dist/projected-navigation-shell.d.ts.map +1 -0
- package/dist/projected-navigation-shell.js +112 -0
- package/dist/projected-navigation-shell.js.map +1 -0
- package/dist/projected-presentation-badges.d.ts +34 -0
- package/dist/projected-presentation-badges.d.ts.map +1 -0
- package/dist/projected-presentation-badges.js +22 -0
- package/dist/projected-presentation-badges.js.map +1 -0
- package/dist/projected-presentation-diagnostics.d.ts +10 -0
- package/dist/projected-presentation-diagnostics.d.ts.map +1 -0
- package/dist/projected-presentation-diagnostics.js +40 -0
- package/dist/projected-presentation-diagnostics.js.map +1 -0
- package/dist/projected-presentation-flow-layer.d.ts +33 -0
- package/dist/projected-presentation-flow-layer.d.ts.map +1 -0
- package/dist/projected-presentation-flow-layer.js +71 -0
- package/dist/projected-presentation-flow-layer.js.map +1 -0
- package/dist/projected-process-task-notices.d.ts +37 -0
- package/dist/projected-process-task-notices.d.ts.map +1 -0
- package/dist/projected-process-task-notices.js +225 -0
- package/dist/projected-process-task-notices.js.map +1 -0
- package/dist/projected-prompt-child-view-renderer.d.ts +101 -0
- package/dist/projected-prompt-child-view-renderer.d.ts.map +1 -0
- package/dist/projected-prompt-child-view-renderer.js +442 -0
- package/dist/projected-prompt-child-view-renderer.js.map +1 -0
- package/dist/projected-prompt-document-preview.d.ts +39 -0
- package/dist/projected-prompt-document-preview.d.ts.map +1 -0
- package/dist/projected-prompt-document-preview.js +50 -0
- package/dist/projected-prompt-document-preview.js.map +1 -0
- package/dist/projected-prompt-view.d.ts +35 -0
- package/dist/projected-prompt-view.d.ts.map +1 -0
- package/dist/projected-prompt-view.js +292 -0
- package/dist/projected-prompt-view.js.map +1 -0
- package/dist/projected-query-form.d.ts +56 -0
- package/dist/projected-query-form.d.ts.map +1 -0
- package/dist/projected-query-form.js +100 -0
- package/dist/projected-query-form.js.map +1 -0
- package/dist/projected-record-details.d.ts +20 -0
- package/dist/projected-record-details.d.ts.map +1 -0
- package/dist/projected-record-details.js +60 -0
- package/dist/projected-record-details.js.map +1 -0
- package/dist/projected-routed-surface-runtime.d.ts +86 -0
- package/dist/projected-routed-surface-runtime.d.ts.map +1 -0
- package/dist/projected-routed-surface-runtime.js +332 -0
- package/dist/projected-routed-surface-runtime.js.map +1 -0
- package/dist/projected-tabs-view.d.ts +21 -0
- package/dist/projected-tabs-view.d.ts.map +1 -0
- package/dist/projected-tabs-view.js +140 -0
- package/dist/projected-tabs-view.js.map +1 -0
- package/dist/projected-view-chrome.d.ts +15 -0
- package/dist/projected-view-chrome.d.ts.map +1 -0
- package/dist/projected-view-chrome.js +67 -0
- package/dist/projected-view-chrome.js.map +1 -0
- package/dist/projected-view-surface.d.ts +34 -0
- package/dist/projected-view-surface.d.ts.map +1 -0
- package/dist/projected-view-surface.js +40 -0
- package/dist/projected-view-surface.js.map +1 -0
- package/dist/standard-collection-chrome-slot-renderers.d.ts +26 -0
- package/dist/standard-collection-chrome-slot-renderers.d.ts.map +1 -0
- package/dist/standard-collection-chrome-slot-renderers.js +250 -0
- package/dist/standard-collection-chrome-slot-renderers.js.map +1 -0
- package/dist/standard-view-chrome-slot-renderers.d.ts +57 -0
- package/dist/standard-view-chrome-slot-renderers.d.ts.map +1 -0
- package/dist/standard-view-chrome-slot-renderers.js +614 -0
- package/dist/standard-view-chrome-slot-renderers.js.map +1 -0
- package/package.json +68 -0
- package/src/document-workspace-surface-slot-renderers.tsx +395 -0
- package/src/index.ts +53 -0
- package/src/navigation-shell-frame-renderer-registry.ts +29 -0
- package/src/navigation-shell-slot-renderer-registry.ts +46 -0
- package/src/page-host-unavailable-renderer.tsx +79 -0
- package/src/presentation-action-group.tsx +475 -0
- package/src/presentation-badge-target-interpreter.ts +54 -0
- package/src/presentation-component-groups.ts +2505 -0
- package/src/presentation-component-system-diagnostics.ts +848 -0
- package/src/presentation-component-system.ts +365 -0
- package/src/presentation-icon-diagnostics.ts +53 -0
- package/src/presentation-icon-registry.tsx +181 -0
- package/src/presentation-projection-trace-panel.tsx +140 -0
- package/src/presentation-routed-surface-host.tsx +48 -0
- package/src/presentation-shadcn-component-system.ts +102 -0
- package/src/presentation-standard-renderers.tsx +1290 -0
- package/src/presentation-surface-renderer.tsx +263 -0
- package/src/presentation-value-renderers.tsx +40 -0
- package/src/process-task-icons.tsx +258 -0
- package/src/projected-activity-state.tsx +63 -0
- package/src/projected-collection-action-runtime-diagnostics.ts +22 -0
- package/src/projected-collection-view.tsx +1273 -0
- package/src/projected-document-action-status-notices.tsx +66 -0
- package/src/projected-document-badges.tsx +134 -0
- package/src/projected-document-editor-surface.tsx +808 -0
- package/src/projected-document-workspace-notices.tsx +77 -0
- package/src/projected-document-workspace-prompt-child-view-registry.ts +99 -0
- package/src/projected-document-workspace-prompt-child-view-renderers.tsx +205 -0
- package/src/projected-document-workspace-shell.tsx +427 -0
- package/src/projected-document-workspace.tsx +216 -0
- package/src/projected-field-value-rendering.tsx +704 -0
- package/src/projected-inline-list.tsx +434 -0
- package/src/projected-input-form.tsx +1218 -0
- package/src/projected-metric-dashboard.tsx +106 -0
- package/src/projected-metric-strip.tsx +244 -0
- package/src/projected-navigation-shell-view-host.tsx +177 -0
- package/src/projected-navigation-shell.tsx +219 -0
- package/src/projected-presentation-badges.tsx +95 -0
- package/src/projected-presentation-diagnostics.tsx +120 -0
- package/src/projected-presentation-flow-layer.tsx +186 -0
- package/src/projected-process-task-notices.tsx +501 -0
- package/src/projected-prompt-child-view-renderer.tsx +698 -0
- package/src/projected-prompt-document-preview.tsx +197 -0
- package/src/projected-prompt-view.tsx +592 -0
- package/src/projected-query-form.tsx +286 -0
- package/src/projected-record-details.tsx +133 -0
- package/src/projected-routed-surface-runtime.tsx +713 -0
- package/src/projected-tabs-view.tsx +308 -0
- package/src/projected-view-chrome.tsx +148 -0
- package/src/projected-view-surface.tsx +131 -0
- package/src/standard-collection-chrome-slot-renderers.tsx +391 -0
- package/src/standard-view-chrome-slot-renderers.tsx +1064 -0
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
import { Fragment, useMemo, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createDefaultDateTimeFilter,
|
|
5
|
+
createPresentationEnumDiscriminator,
|
|
6
|
+
createPresentationProjectionDiagnostic,
|
|
7
|
+
defaultPresentationComponentSet,
|
|
8
|
+
findPresentationAction,
|
|
9
|
+
findPresentationField,
|
|
10
|
+
isViewChromeSlotKind,
|
|
11
|
+
matchesPresentationEnum,
|
|
12
|
+
normalizeDateTimeFilter,
|
|
13
|
+
readObjectPath,
|
|
14
|
+
resolvePresentationFieldValueLabel,
|
|
15
|
+
resolvePresentationFieldValueTone,
|
|
16
|
+
type ActionPlacementDefinition,
|
|
17
|
+
type DataSourceQueryDefinition,
|
|
18
|
+
type DataSourceQueryFieldDefinition,
|
|
19
|
+
type DateTimeFilterValue,
|
|
20
|
+
type FieldPresentationDefinition,
|
|
21
|
+
type InputFormDefinition,
|
|
22
|
+
type InputFormFieldDefinition,
|
|
23
|
+
type PresentationModuleDefinition,
|
|
24
|
+
type PresentationDataSourceResolver,
|
|
25
|
+
type PresentationProjectionDiagnostic,
|
|
26
|
+
type ProjectedInputFormRuntime,
|
|
27
|
+
type ProjectedInputFormTargetContext,
|
|
28
|
+
type QueryFormDefinition,
|
|
29
|
+
type ViewChromeSlotDefinition,
|
|
30
|
+
type ViewDefinition,
|
|
31
|
+
} from '@cohesivesystems/presentation-core'
|
|
32
|
+
import type {
|
|
33
|
+
PresentationShadcnComponentSystem as PresentationComponentSystem,
|
|
34
|
+
} from './presentation-shadcn-component-system'
|
|
35
|
+
import type { PresentationDesignSystem } from '@cohesivesystems/presentation-tailwind'
|
|
36
|
+
import {
|
|
37
|
+
projectPresentationActionIconDiagnostics,
|
|
38
|
+
} from './presentation-icon-diagnostics'
|
|
39
|
+
import {
|
|
40
|
+
renderPresentationIcon,
|
|
41
|
+
} from './presentation-icon-registry'
|
|
42
|
+
import { ProjectedStatusBlock } from './projected-activity-state'
|
|
43
|
+
import {
|
|
44
|
+
ProjectedViewSurface,
|
|
45
|
+
type ProjectedViewSurfaceChromeSlotRenderer,
|
|
46
|
+
} from './projected-view-surface'
|
|
47
|
+
import {
|
|
48
|
+
useRegisterPresentationProjectionDiagnostics,
|
|
49
|
+
} from '@cohesivesystems/presentation-react'
|
|
50
|
+
import {
|
|
51
|
+
inputFormChoiceDefaultSelections,
|
|
52
|
+
inputFormFieldControlKinds,
|
|
53
|
+
inputFormFieldKinds,
|
|
54
|
+
viewChromeSlotKinds,
|
|
55
|
+
} from '@cohesivesystems/presentation-contracts'
|
|
56
|
+
|
|
57
|
+
export type {
|
|
58
|
+
ProjectedInputFormActionContext,
|
|
59
|
+
ProjectedInputFormRuntime,
|
|
60
|
+
ProjectedInputFormTargetContext,
|
|
61
|
+
ProjectedInputFormValueChangeContext,
|
|
62
|
+
} from '@cohesivesystems/presentation-core'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Context supplied to a domain-specific input-form field renderer.
|
|
66
|
+
*/
|
|
67
|
+
export interface ProjectedInputFormFieldRenderContext {
|
|
68
|
+
/** Optional resolver that maps a choice and tone to toggle styling. */
|
|
69
|
+
readonly choiceToggleClassName?: ProjectedInputFormChoiceClassNameResolver
|
|
70
|
+
|
|
71
|
+
/** Optional resolver that normalizes domain choice metadata into a presentation tone. */
|
|
72
|
+
readonly choiceTone?: ProjectedInputFormChoiceToneResolver
|
|
73
|
+
|
|
74
|
+
/** Choices resolved from the field's semantic choice source. */
|
|
75
|
+
readonly choices: readonly ProjectedInputFormChoice[]
|
|
76
|
+
|
|
77
|
+
/** Component implementation set used for projected form controls. */
|
|
78
|
+
readonly componentSystem: PresentationComponentSystem
|
|
79
|
+
|
|
80
|
+
/** Design-system class and semantic resolvers used for projected form layout. */
|
|
81
|
+
readonly designSystem: PresentationDesignSystem
|
|
82
|
+
|
|
83
|
+
/** Input-form field being rendered. */
|
|
84
|
+
readonly field: InputFormFieldDefinition
|
|
85
|
+
|
|
86
|
+
/** Input form that owns the field. */
|
|
87
|
+
readonly inputForm: InputFormDefinition
|
|
88
|
+
|
|
89
|
+
/** Presentation field metadata referenced by the input-form field, when available. */
|
|
90
|
+
readonly presentationField: FieldPresentationDefinition | null
|
|
91
|
+
|
|
92
|
+
/** Data-source query field supplied when a relation-query target specializes the input form. */
|
|
93
|
+
readonly queryField: DataSourceQueryFieldDefinition | null
|
|
94
|
+
|
|
95
|
+
/** Query-form binding supplied when a relation-query target specializes the input form. */
|
|
96
|
+
readonly queryForm: QueryFormDefinition | null
|
|
97
|
+
|
|
98
|
+
/** Shared or target state id affected by the input form. */
|
|
99
|
+
readonly stateId: string
|
|
100
|
+
|
|
101
|
+
/** Target interpretation attached to this form instance. */
|
|
102
|
+
readonly target: ProjectedInputFormTargetContext
|
|
103
|
+
|
|
104
|
+
/** Updates the field value at its declared value path. */
|
|
105
|
+
readonly setFieldValue: (value: unknown) => void
|
|
106
|
+
|
|
107
|
+
/** Current value read from the caller-owned form value at the field value path. */
|
|
108
|
+
readonly value: unknown
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Override renderer for an input-form field.
|
|
113
|
+
*/
|
|
114
|
+
export type ProjectedInputFormFieldRenderer = (
|
|
115
|
+
context: ProjectedInputFormFieldRenderContext,
|
|
116
|
+
) => ReactNode
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Normalized choice projected from an input-form choice source.
|
|
120
|
+
*/
|
|
121
|
+
export interface ProjectedInputFormChoice {
|
|
122
|
+
/** User-facing choice label. */
|
|
123
|
+
readonly label: string
|
|
124
|
+
|
|
125
|
+
/** Optional semantic tone supplied by the choice source or caller resolver. */
|
|
126
|
+
readonly tone?: string | null
|
|
127
|
+
|
|
128
|
+
/** Stable choice value written into input-form state. */
|
|
129
|
+
readonly value: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Context supplied when resolving choice tone or styling.
|
|
134
|
+
*/
|
|
135
|
+
export interface ProjectedInputFormChoiceRenderContext {
|
|
136
|
+
/** Choice being rendered. */
|
|
137
|
+
readonly choice: ProjectedInputFormChoice
|
|
138
|
+
|
|
139
|
+
/** Input-form field that owns the choice. */
|
|
140
|
+
readonly field: InputFormFieldDefinition
|
|
141
|
+
|
|
142
|
+
/** Input form that owns the field. */
|
|
143
|
+
readonly inputForm: InputFormDefinition
|
|
144
|
+
|
|
145
|
+
/** Presentation field metadata referenced by the input-form field, when available. */
|
|
146
|
+
readonly presentationField: FieldPresentationDefinition | null
|
|
147
|
+
|
|
148
|
+
/** Data-source query field supplied when a relation-query target specializes the input form. */
|
|
149
|
+
readonly queryField: DataSourceQueryFieldDefinition | null
|
|
150
|
+
|
|
151
|
+
/** Query-form binding supplied when a relation-query target specializes the input form. */
|
|
152
|
+
readonly queryForm: QueryFormDefinition | null
|
|
153
|
+
|
|
154
|
+
/** Shared or target state id affected by the input form. */
|
|
155
|
+
readonly stateId: string
|
|
156
|
+
|
|
157
|
+
/** Target interpretation attached to this form instance. */
|
|
158
|
+
readonly target: ProjectedInputFormTargetContext
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Choice styling context after tone resolution has completed.
|
|
163
|
+
*/
|
|
164
|
+
export interface ProjectedInputFormChoiceClassNameContext
|
|
165
|
+
extends ProjectedInputFormChoiceRenderContext {
|
|
166
|
+
/** Resolved presentation tone for the choice, or null when no tone applies. */
|
|
167
|
+
readonly tone: string | null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolves a semantic choice into a presentation tone understood by the local design system.
|
|
172
|
+
*/
|
|
173
|
+
export type ProjectedInputFormChoiceToneResolver = (
|
|
174
|
+
context: ProjectedInputFormChoiceRenderContext,
|
|
175
|
+
) => string | null | undefined
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolves final toggle classes for a choice after tone resolution.
|
|
179
|
+
*/
|
|
180
|
+
export type ProjectedInputFormChoiceClassNameResolver = (
|
|
181
|
+
context: ProjectedInputFormChoiceClassNameContext,
|
|
182
|
+
) => string | undefined
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Props required to project a backend-declared input form into React controls.
|
|
186
|
+
*
|
|
187
|
+
* @typeParam TValue - Shape of the caller-owned form state object.
|
|
188
|
+
*/
|
|
189
|
+
export interface ProjectedInputFormProps<TValue extends object = object> {
|
|
190
|
+
/** Optional resolver that maps a choice and tone to toggle styling. */
|
|
191
|
+
readonly choiceToggleClassName?: ProjectedInputFormChoiceClassNameResolver
|
|
192
|
+
|
|
193
|
+
/** Optional resolver that normalizes domain choice metadata into a presentation tone. */
|
|
194
|
+
readonly choiceTone?: ProjectedInputFormChoiceToneResolver
|
|
195
|
+
|
|
196
|
+
/** Class name applied to the projected view surface. */
|
|
197
|
+
readonly className?: string
|
|
198
|
+
|
|
199
|
+
/** Class name applied to projected view chrome after the form body. */
|
|
200
|
+
readonly chromeAfterContentClassName?: string
|
|
201
|
+
|
|
202
|
+
/** Class name applied to projected view chrome before the form body. */
|
|
203
|
+
readonly chromeBeforeContentClassName?: string
|
|
204
|
+
|
|
205
|
+
/** Class name applied to projected view chrome in the surface footer. */
|
|
206
|
+
readonly chromeFooterClassName?: string
|
|
207
|
+
|
|
208
|
+
/** Class name applied to projected view chrome in the surface header. */
|
|
209
|
+
readonly chromeHeaderClassName?: string
|
|
210
|
+
|
|
211
|
+
/** Class name applied to the projected view surface content area. */
|
|
212
|
+
readonly contentClassName?: string
|
|
213
|
+
|
|
214
|
+
/** Component implementation set used for projected form controls. */
|
|
215
|
+
readonly componentSystem: PresentationComponentSystem
|
|
216
|
+
|
|
217
|
+
/** Presentation target component set used to resolve declared icons. */
|
|
218
|
+
readonly componentSet?: string
|
|
219
|
+
|
|
220
|
+
/** Design-system class and semantic resolvers used for projected form layout. */
|
|
221
|
+
readonly designSystem: PresentationDesignSystem
|
|
222
|
+
|
|
223
|
+
/** Resolver for data sources used by field choice sources. */
|
|
224
|
+
readonly dataSourceResolver: PresentationDataSourceResolver
|
|
225
|
+
|
|
226
|
+
/** Field renderer overrides keyed by input-form field id or presentation field id. */
|
|
227
|
+
readonly fieldRenderers?: Readonly<Record<string, ProjectedInputFormFieldRenderer>>
|
|
228
|
+
|
|
229
|
+
/** Input form to project; null renders a diagnostic status block. */
|
|
230
|
+
readonly inputForm: InputFormDefinition | null
|
|
231
|
+
|
|
232
|
+
/** Presentation module that owns the input form, fields, and actions. */
|
|
233
|
+
readonly module: PresentationModuleDefinition
|
|
234
|
+
|
|
235
|
+
/** Runtime state and action handlers; null renders a diagnostic status block. */
|
|
236
|
+
readonly runtime: ProjectedInputFormRuntime<TValue> | null
|
|
237
|
+
|
|
238
|
+
/** Interprets non-form view chrome slots declared by the hosting view. */
|
|
239
|
+
readonly renderChromeSlot?: ProjectedViewSurfaceChromeSlotRenderer
|
|
240
|
+
|
|
241
|
+
/** Optional target specialization attached to this input form instance. */
|
|
242
|
+
readonly target?: ProjectedInputFormTargetContext | null
|
|
243
|
+
|
|
244
|
+
/** View that hosts the input form and supplies surface identity. */
|
|
245
|
+
readonly view: ViewDefinition
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface ProjectedInputFormFieldProjection {
|
|
249
|
+
readonly inputField: InputFormFieldDefinition
|
|
250
|
+
readonly queryField: DataSourceQueryFieldDefinition | null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Projects a semantic input-form definition into concrete React form controls.
|
|
255
|
+
*
|
|
256
|
+
* Generic input kinds are handled locally; domain-specific fields, choice tones,
|
|
257
|
+
* styling, and action behavior stay injectable through renderer props and runtime.
|
|
258
|
+
*/
|
|
259
|
+
export function ProjectedInputForm<TValue extends object = object>({
|
|
260
|
+
choiceToggleClassName,
|
|
261
|
+
choiceTone,
|
|
262
|
+
className,
|
|
263
|
+
chromeAfterContentClassName,
|
|
264
|
+
chromeBeforeContentClassName,
|
|
265
|
+
chromeFooterClassName,
|
|
266
|
+
chromeHeaderClassName,
|
|
267
|
+
componentSet = defaultPresentationComponentSet,
|
|
268
|
+
componentSystem,
|
|
269
|
+
contentClassName,
|
|
270
|
+
dataSourceResolver,
|
|
271
|
+
designSystem,
|
|
272
|
+
fieldRenderers,
|
|
273
|
+
inputForm,
|
|
274
|
+
module,
|
|
275
|
+
renderChromeSlot,
|
|
276
|
+
runtime,
|
|
277
|
+
target,
|
|
278
|
+
view,
|
|
279
|
+
}: ProjectedInputFormProps<TValue>) {
|
|
280
|
+
const actionIconPlacements = useMemo(
|
|
281
|
+
() => resolveInputFormActionIconPlacements(inputForm, view),
|
|
282
|
+
[inputForm, view],
|
|
283
|
+
)
|
|
284
|
+
const actionIconDiagnostics = useMemo(
|
|
285
|
+
() => projectPresentationActionIconDiagnostics({
|
|
286
|
+
actionPlacements: actionIconPlacements,
|
|
287
|
+
componentSet,
|
|
288
|
+
module,
|
|
289
|
+
source: `projected-input-form-icons:${view.Id}:${inputForm?.Id ?? 'missing'}`,
|
|
290
|
+
surfaceId: view.Id,
|
|
291
|
+
surfaceName: inputForm?.Name ?? view.Name,
|
|
292
|
+
}),
|
|
293
|
+
[
|
|
294
|
+
actionIconPlacements,
|
|
295
|
+
componentSet,
|
|
296
|
+
inputForm?.Id,
|
|
297
|
+
inputForm?.Name,
|
|
298
|
+
module,
|
|
299
|
+
view.Id,
|
|
300
|
+
view.Name,
|
|
301
|
+
],
|
|
302
|
+
)
|
|
303
|
+
useRegisterPresentationProjectionDiagnostics(
|
|
304
|
+
`projected-input-form-icons:${view.Id}:${inputForm?.Id ?? 'missing'}`,
|
|
305
|
+
actionIconDiagnostics,
|
|
306
|
+
)
|
|
307
|
+
const runtimeDiagnosticsSource =
|
|
308
|
+
`projected-input-form-runtime:${view.Id}:${inputForm?.Id ?? 'missing'}`
|
|
309
|
+
const runtimeDiagnostics = useMemo(
|
|
310
|
+
() => createInputFormRuntimeDiagnostics({
|
|
311
|
+
hasRuntime: runtime !== null,
|
|
312
|
+
inputForm,
|
|
313
|
+
source: runtimeDiagnosticsSource,
|
|
314
|
+
view,
|
|
315
|
+
}),
|
|
316
|
+
[inputForm, runtime, runtimeDiagnosticsSource, view],
|
|
317
|
+
)
|
|
318
|
+
useRegisterPresentationProjectionDiagnostics(
|
|
319
|
+
runtimeDiagnosticsSource,
|
|
320
|
+
runtimeDiagnostics,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
if (!inputForm) {
|
|
324
|
+
return <ProjectedStatusBlock label={`Presentation view '${view.Name}' has no input form binding.`} />
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!runtime) {
|
|
328
|
+
return null
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const formTarget = target ?? createDefaultInputFormTarget(inputForm)
|
|
332
|
+
const projectedFields = projectInputFormFields(inputForm, formTarget.queryDefinition ?? null)
|
|
333
|
+
const projectedFieldsById = new Map(
|
|
334
|
+
projectedFields.map((field) => [field.inputField.Id, field] as const),
|
|
335
|
+
)
|
|
336
|
+
const choiceValuesByFieldId = Object.fromEntries(
|
|
337
|
+
projectedFields.map(({ inputField, queryField }) => [
|
|
338
|
+
inputField.Id,
|
|
339
|
+
readInputFormChoices(
|
|
340
|
+
dataSourceResolver,
|
|
341
|
+
inputField,
|
|
342
|
+
queryField,
|
|
343
|
+
findPresentationField<FieldPresentationDefinition>(
|
|
344
|
+
module,
|
|
345
|
+
queryField?.FieldId ?? inputField.FieldId,
|
|
346
|
+
) ?? null,
|
|
347
|
+
).map((choice) => choice.value),
|
|
348
|
+
]),
|
|
349
|
+
)
|
|
350
|
+
const renderFormChromeSlot = (slot: ViewChromeSlotDefinition, chromeView: ViewDefinition | null) => {
|
|
351
|
+
if (isViewChromeSlotKind(slot, viewChromeSlotKinds.actions)) {
|
|
352
|
+
return renderInputFormActionRow({
|
|
353
|
+
choiceValuesByFieldId,
|
|
354
|
+
componentSet,
|
|
355
|
+
componentSystem,
|
|
356
|
+
designSystem,
|
|
357
|
+
inputForm,
|
|
358
|
+
module,
|
|
359
|
+
placements: slot.Actions.length > 0 ? slot.Actions : inputForm.Actions,
|
|
360
|
+
runtime,
|
|
361
|
+
target: formTarget,
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return renderChromeSlot?.(slot, chromeView) ?? null
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<ProjectedViewSurface
|
|
370
|
+
className={className}
|
|
371
|
+
chromeAfterContentClassName={chromeAfterContentClassName}
|
|
372
|
+
chromeBeforeContentClassName={chromeBeforeContentClassName}
|
|
373
|
+
chromeFooterClassName={chromeFooterClassName}
|
|
374
|
+
chromeHeaderClassName={chromeHeaderClassName}
|
|
375
|
+
componentSystem={componentSystem}
|
|
376
|
+
contentClassName={cn(designSystem.classNames.formSurface.content({}), contentClassName)}
|
|
377
|
+
collapsible
|
|
378
|
+
renderChromeSlot={renderFormChromeSlot}
|
|
379
|
+
title={view.Name}
|
|
380
|
+
view={view}
|
|
381
|
+
>
|
|
382
|
+
{componentSystem.forms.InputForm({
|
|
383
|
+
form: inputForm,
|
|
384
|
+
viewId: view.Id,
|
|
385
|
+
children: (
|
|
386
|
+
<>
|
|
387
|
+
{componentSystem.forms.InputFormGroups({
|
|
388
|
+
className: designSystem.classNames.formSurface.groups({}),
|
|
389
|
+
form: inputForm,
|
|
390
|
+
children: inputForm.Groups.map((group) => {
|
|
391
|
+
const groupFields = group.FieldIds.flatMap((fieldId) => {
|
|
392
|
+
const field = projectedFieldsById.get(fieldId)
|
|
393
|
+
return field ? [field] : []
|
|
394
|
+
})
|
|
395
|
+
if (groupFields.length === 0) {
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<Fragment key={group.Id}>
|
|
401
|
+
{componentSystem.forms.InputFormGroup({
|
|
402
|
+
className: designSystem.classNames.formSurface.group({ group }),
|
|
403
|
+
group,
|
|
404
|
+
children: groupFields.map((field) => (
|
|
405
|
+
<Fragment key={field.inputField.Id}>
|
|
406
|
+
{renderInputFormField({
|
|
407
|
+
choiceToggleClassName,
|
|
408
|
+
choiceTone,
|
|
409
|
+
componentSystem,
|
|
410
|
+
dataSourceResolver,
|
|
411
|
+
designSystem,
|
|
412
|
+
fieldRenderers,
|
|
413
|
+
choiceValuesByFieldId,
|
|
414
|
+
inputField: field.inputField,
|
|
415
|
+
inputForm,
|
|
416
|
+
module,
|
|
417
|
+
queryField: field.queryField,
|
|
418
|
+
runtime,
|
|
419
|
+
target: formTarget,
|
|
420
|
+
})}
|
|
421
|
+
</Fragment>
|
|
422
|
+
)),
|
|
423
|
+
})}
|
|
424
|
+
</Fragment>
|
|
425
|
+
)
|
|
426
|
+
}),
|
|
427
|
+
})}
|
|
428
|
+
|
|
429
|
+
{hasActionsChromeSlot(view) ? null : renderInputFormActionRow({
|
|
430
|
+
choiceValuesByFieldId,
|
|
431
|
+
componentSet,
|
|
432
|
+
componentSystem,
|
|
433
|
+
designSystem,
|
|
434
|
+
inputForm,
|
|
435
|
+
module,
|
|
436
|
+
placements: inputForm.Actions,
|
|
437
|
+
runtime,
|
|
438
|
+
target: formTarget,
|
|
439
|
+
})}
|
|
440
|
+
</>
|
|
441
|
+
),
|
|
442
|
+
})}
|
|
443
|
+
</ProjectedViewSurface>
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function createInputFormRuntimeDiagnostics({
|
|
448
|
+
hasRuntime,
|
|
449
|
+
inputForm,
|
|
450
|
+
source,
|
|
451
|
+
view,
|
|
452
|
+
}: {
|
|
453
|
+
readonly hasRuntime: boolean
|
|
454
|
+
readonly inputForm: InputFormDefinition | null
|
|
455
|
+
readonly source: string
|
|
456
|
+
readonly view: ViewDefinition
|
|
457
|
+
}): readonly PresentationProjectionDiagnostic[] {
|
|
458
|
+
if (!inputForm || hasRuntime) {
|
|
459
|
+
return []
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return [
|
|
463
|
+
createPresentationProjectionDiagnostic({
|
|
464
|
+
category: 'missing-binding',
|
|
465
|
+
id: `input-form.${inputForm.Id}.runtime.missing-binding`,
|
|
466
|
+
interpretation: {
|
|
467
|
+
status: 'unbound',
|
|
468
|
+
target: 'input-form-runtime-binding',
|
|
469
|
+
},
|
|
470
|
+
message: `Input form '${inputForm.Name}' has no runtime binding.`,
|
|
471
|
+
severity: 'warning',
|
|
472
|
+
source,
|
|
473
|
+
subject: {
|
|
474
|
+
id: inputForm.Id,
|
|
475
|
+
kind: 'input-form',
|
|
476
|
+
name: inputForm.Name,
|
|
477
|
+
},
|
|
478
|
+
suggestedNextStep:
|
|
479
|
+
`Bind input form '${inputForm.Id}' to a frontend runtime adapter or remove it from view '${view.Id}'.`,
|
|
480
|
+
}),
|
|
481
|
+
]
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function renderInputFormField<TValue extends object>({
|
|
485
|
+
choiceToggleClassName,
|
|
486
|
+
choiceTone,
|
|
487
|
+
componentSystem,
|
|
488
|
+
dataSourceResolver,
|
|
489
|
+
designSystem,
|
|
490
|
+
fieldRenderers,
|
|
491
|
+
choiceValuesByFieldId,
|
|
492
|
+
inputField,
|
|
493
|
+
inputForm,
|
|
494
|
+
module,
|
|
495
|
+
queryField,
|
|
496
|
+
runtime,
|
|
497
|
+
target,
|
|
498
|
+
}: {
|
|
499
|
+
readonly choiceToggleClassName?: ProjectedInputFormChoiceClassNameResolver
|
|
500
|
+
readonly choiceTone?: ProjectedInputFormChoiceToneResolver
|
|
501
|
+
readonly choiceValuesByFieldId: Readonly<Record<string, readonly string[]>>
|
|
502
|
+
readonly componentSystem: PresentationComponentSystem
|
|
503
|
+
readonly dataSourceResolver: PresentationDataSourceResolver
|
|
504
|
+
readonly designSystem: PresentationDesignSystem
|
|
505
|
+
readonly fieldRenderers?: Readonly<Record<string, ProjectedInputFormFieldRenderer>>
|
|
506
|
+
readonly inputField: InputFormFieldDefinition
|
|
507
|
+
readonly inputForm: InputFormDefinition
|
|
508
|
+
readonly module: PresentationModuleDefinition
|
|
509
|
+
readonly queryField: DataSourceQueryFieldDefinition | null
|
|
510
|
+
readonly runtime: ProjectedInputFormRuntime<TValue>
|
|
511
|
+
readonly target: ProjectedInputFormTargetContext
|
|
512
|
+
}) {
|
|
513
|
+
const fieldId = queryField?.FieldId ?? inputField.FieldId
|
|
514
|
+
const valuePath = queryField?.ValuePath ?? inputField.ValuePath
|
|
515
|
+
const presentationField = findPresentationField<FieldPresentationDefinition>(module, fieldId) ?? null
|
|
516
|
+
const value = readFormValue(runtime.value, valuePath)
|
|
517
|
+
const choices = readInputFormChoices(dataSourceResolver, inputField, queryField, presentationField)
|
|
518
|
+
const setFieldValue = (nextValue: unknown) => {
|
|
519
|
+
runtime.setValue(
|
|
520
|
+
(current) => setFormValue(current, valuePath, nextValue),
|
|
521
|
+
{
|
|
522
|
+
choiceValuesByFieldId,
|
|
523
|
+
inputForm,
|
|
524
|
+
target,
|
|
525
|
+
value: runtime.value,
|
|
526
|
+
},
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
const renderOverride = findInputFieldRenderer(fieldRenderers, inputField, queryField)
|
|
530
|
+
const fieldContext = {
|
|
531
|
+
choiceToggleClassName,
|
|
532
|
+
choiceTone,
|
|
533
|
+
choices,
|
|
534
|
+
componentSystem,
|
|
535
|
+
designSystem,
|
|
536
|
+
field: inputField,
|
|
537
|
+
inputForm,
|
|
538
|
+
presentationField,
|
|
539
|
+
queryField,
|
|
540
|
+
queryForm: target.queryForm ?? null,
|
|
541
|
+
setFieldValue,
|
|
542
|
+
stateId: target.stateId,
|
|
543
|
+
target,
|
|
544
|
+
value,
|
|
545
|
+
} satisfies ProjectedInputFormFieldRenderContext
|
|
546
|
+
const control = renderOverride
|
|
547
|
+
? renderOverride(fieldContext)
|
|
548
|
+
: renderDefaultFieldControl({
|
|
549
|
+
...fieldContext,
|
|
550
|
+
label: presentationField?.Label ?? queryField?.Name ?? inputField.Name,
|
|
551
|
+
})
|
|
552
|
+
const label = presentationField?.Label ?? queryField?.Name ?? inputField.Name
|
|
553
|
+
|
|
554
|
+
return componentSystem.forms.InputFormField({
|
|
555
|
+
className: designSystem.classNames.formSurface.field({}),
|
|
556
|
+
control: componentSystem.forms.InputFormControlSlot({
|
|
557
|
+
field: inputField,
|
|
558
|
+
children: control,
|
|
559
|
+
}),
|
|
560
|
+
field: inputField,
|
|
561
|
+
label: componentSystem.forms.FormFieldLabel({
|
|
562
|
+
children: label,
|
|
563
|
+
}),
|
|
564
|
+
})
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function renderDefaultFieldControl({
|
|
568
|
+
choiceToggleClassName,
|
|
569
|
+
choiceTone,
|
|
570
|
+
choices,
|
|
571
|
+
componentSystem,
|
|
572
|
+
designSystem,
|
|
573
|
+
field,
|
|
574
|
+
inputForm,
|
|
575
|
+
label,
|
|
576
|
+
presentationField,
|
|
577
|
+
queryField,
|
|
578
|
+
queryForm,
|
|
579
|
+
setFieldValue,
|
|
580
|
+
stateId,
|
|
581
|
+
target,
|
|
582
|
+
value,
|
|
583
|
+
}: ProjectedInputFormFieldRenderContext & { readonly label: string }) {
|
|
584
|
+
if (isMultiSelectInputField(field, queryField)) {
|
|
585
|
+
const selected = readSelectedChoiceValues(field, queryField, choices, value)
|
|
586
|
+
return choices.length > 0 ? (
|
|
587
|
+
componentSystem.forms.ChoiceToggleGroup({
|
|
588
|
+
'aria-label': label,
|
|
589
|
+
onValueChange: setFieldValue,
|
|
590
|
+
value: selected,
|
|
591
|
+
children: choices.map((choice) => (
|
|
592
|
+
<Fragment key={choice.value}>
|
|
593
|
+
{componentSystem.forms.ChoiceToggleItem({
|
|
594
|
+
'aria-label': choice.label,
|
|
595
|
+
className: resolveChoiceToggleClassName({
|
|
596
|
+
choice,
|
|
597
|
+
choiceToggleClassName,
|
|
598
|
+
choiceTone,
|
|
599
|
+
field,
|
|
600
|
+
inputForm,
|
|
601
|
+
presentationField,
|
|
602
|
+
queryField,
|
|
603
|
+
queryForm,
|
|
604
|
+
stateId,
|
|
605
|
+
target,
|
|
606
|
+
}),
|
|
607
|
+
value: choice.value,
|
|
608
|
+
children: choice.label,
|
|
609
|
+
})}
|
|
610
|
+
</Fragment>
|
|
611
|
+
)),
|
|
612
|
+
})
|
|
613
|
+
) : (
|
|
614
|
+
componentSystem.forms.InputFormFieldMessage({
|
|
615
|
+
field,
|
|
616
|
+
children: `No ${label.toLocaleLowerCase()} choices are available.`,
|
|
617
|
+
})
|
|
618
|
+
)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (isInputFormFieldKind(field, 'select', 'Select')) {
|
|
622
|
+
return componentSystem.forms.SelectControl({
|
|
623
|
+
'aria-label': label,
|
|
624
|
+
onValueChange: setFieldValue,
|
|
625
|
+
options: [
|
|
626
|
+
{ label: 'Any', value: '' },
|
|
627
|
+
...choices.map((choice) => ({
|
|
628
|
+
label: choice.label,
|
|
629
|
+
value: choice.value,
|
|
630
|
+
})),
|
|
631
|
+
],
|
|
632
|
+
value: typeof value === 'string' ? value : '',
|
|
633
|
+
})
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (isInputFormFieldKind(field, 'dateTimeRange', 'DateTimeRange')) {
|
|
637
|
+
if (isDateTimeFilterControl(field)) {
|
|
638
|
+
return componentSystem.forms.DateTimeFilterControl({
|
|
639
|
+
emptyLabel: field.Display?.EmptyValueLabel ?? undefined,
|
|
640
|
+
incrementMinutes: field.Display?.IncrementMinutes ?? undefined,
|
|
641
|
+
label,
|
|
642
|
+
onValueChange: setFieldValue,
|
|
643
|
+
showTimezone: field.Display?.ShowTimezone ?? undefined,
|
|
644
|
+
value: coerceDateTimeFilterValue(value),
|
|
645
|
+
})
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const range = value && typeof value === 'object'
|
|
649
|
+
? value as Record<string, unknown>
|
|
650
|
+
: {}
|
|
651
|
+
return componentSystem.forms.InputFormControlGroup({
|
|
652
|
+
className: designSystem.classNames.formSurface.rangeGroup({}),
|
|
653
|
+
field,
|
|
654
|
+
children: (
|
|
655
|
+
<>
|
|
656
|
+
{componentSystem.forms.TextInputControl({
|
|
657
|
+
'aria-label': `${label} after`,
|
|
658
|
+
onValueChange: (nextValue) =>
|
|
659
|
+
setFieldValue({ ...range, after: localDateTimeToIso(nextValue) }),
|
|
660
|
+
type: 'datetime-local',
|
|
661
|
+
value: isoToLocalDateTime(readOptionalString(range.after)),
|
|
662
|
+
})}
|
|
663
|
+
{componentSystem.forms.TextInputControl({
|
|
664
|
+
'aria-label': `${label} before`,
|
|
665
|
+
onValueChange: (nextValue) =>
|
|
666
|
+
setFieldValue({ ...range, before: localDateTimeToIso(nextValue) }),
|
|
667
|
+
type: 'datetime-local',
|
|
668
|
+
value: isoToLocalDateTime(readOptionalString(range.before)),
|
|
669
|
+
})}
|
|
670
|
+
</>
|
|
671
|
+
),
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (isInputFormFieldKind(field, 'dateTime', 'DateTime')) {
|
|
676
|
+
return componentSystem.forms.TextInputControl({
|
|
677
|
+
'aria-label': label,
|
|
678
|
+
onValueChange: (nextValue) => setFieldValue(localDateTimeToIso(nextValue)),
|
|
679
|
+
type: 'datetime-local',
|
|
680
|
+
value: isoToLocalDateTime(readOptionalString(value)),
|
|
681
|
+
})
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (isInputFormFieldKind(field, 'date', 'Date')) {
|
|
685
|
+
return componentSystem.forms.TextInputControl({
|
|
686
|
+
'aria-label': label,
|
|
687
|
+
onValueChange: (nextValue) => setFieldValue(nextValue || null),
|
|
688
|
+
type: 'date',
|
|
689
|
+
value: typeof value === 'string' ? value.slice(0, 10) : '',
|
|
690
|
+
})
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (isInputFormFieldKind(field, 'number', 'Number')) {
|
|
694
|
+
return componentSystem.forms.TextInputControl({
|
|
695
|
+
'aria-label': label,
|
|
696
|
+
onValueChange: (nextValue) =>
|
|
697
|
+
setFieldValue(nextValue === '' ? null : Number.parseFloat(nextValue)),
|
|
698
|
+
type: 'number',
|
|
699
|
+
value: typeof value === 'number' && Number.isFinite(value) ? String(value) : '',
|
|
700
|
+
})
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (isInputFormFieldKind(field, 'numberRange', 'NumberRange')) {
|
|
704
|
+
const range = value && typeof value === 'object'
|
|
705
|
+
? value as Record<string, unknown>
|
|
706
|
+
: {}
|
|
707
|
+
return componentSystem.forms.InputFormControlGroup({
|
|
708
|
+
className: designSystem.classNames.formSurface.rangeGroup({}),
|
|
709
|
+
field,
|
|
710
|
+
children: (
|
|
711
|
+
<>
|
|
712
|
+
{componentSystem.forms.TextInputControl({
|
|
713
|
+
'aria-label': `${label} minimum`,
|
|
714
|
+
onValueChange: (nextValue) =>
|
|
715
|
+
setFieldValue({ ...range, minimum: readOptionalNumber(nextValue) }),
|
|
716
|
+
type: 'number',
|
|
717
|
+
value: typeof range.minimum === 'number' ? String(range.minimum) : '',
|
|
718
|
+
})}
|
|
719
|
+
{componentSystem.forms.TextInputControl({
|
|
720
|
+
'aria-label': `${label} maximum`,
|
|
721
|
+
onValueChange: (nextValue) =>
|
|
722
|
+
setFieldValue({ ...range, maximum: readOptionalNumber(nextValue) }),
|
|
723
|
+
type: 'number',
|
|
724
|
+
value: typeof range.maximum === 'number' ? String(range.maximum) : '',
|
|
725
|
+
})}
|
|
726
|
+
</>
|
|
727
|
+
),
|
|
728
|
+
})
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (isInputFormFieldKind(field, 'boolean', 'Boolean')) {
|
|
732
|
+
return componentSystem.forms.CheckboxControl({
|
|
733
|
+
'aria-label': label,
|
|
734
|
+
checked: value === true,
|
|
735
|
+
onCheckedChange: setFieldValue,
|
|
736
|
+
})
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return componentSystem.forms.TextInputControl({
|
|
740
|
+
'aria-label': label,
|
|
741
|
+
onValueChange: setFieldValue,
|
|
742
|
+
placeholder: field.Placeholder ?? undefined,
|
|
743
|
+
value: typeof value === 'string' ? value : '',
|
|
744
|
+
})
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function renderInputFormActionRow<TValue extends object>({
|
|
748
|
+
choiceValuesByFieldId,
|
|
749
|
+
componentSet,
|
|
750
|
+
componentSystem,
|
|
751
|
+
designSystem,
|
|
752
|
+
inputForm,
|
|
753
|
+
module,
|
|
754
|
+
placements,
|
|
755
|
+
runtime,
|
|
756
|
+
target,
|
|
757
|
+
}: {
|
|
758
|
+
readonly choiceValuesByFieldId: Readonly<Record<string, readonly string[]>>
|
|
759
|
+
readonly componentSet: string
|
|
760
|
+
readonly componentSystem: PresentationComponentSystem
|
|
761
|
+
readonly designSystem: PresentationDesignSystem
|
|
762
|
+
readonly inputForm: InputFormDefinition
|
|
763
|
+
readonly module: PresentationModuleDefinition
|
|
764
|
+
readonly placements: readonly ActionPlacementDefinition[]
|
|
765
|
+
readonly runtime: ProjectedInputFormRuntime<TValue>
|
|
766
|
+
readonly target: ProjectedInputFormTargetContext
|
|
767
|
+
}) {
|
|
768
|
+
if (placements.length === 0) {
|
|
769
|
+
return null
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return componentSystem.forms.InputFormActionRow({
|
|
773
|
+
className: designSystem.classNames.formSurface.actionRow({}),
|
|
774
|
+
form: inputForm,
|
|
775
|
+
children: placements.map((placement) => (
|
|
776
|
+
<Fragment key={`${placement.Region}:${placement.ActionId}`}>
|
|
777
|
+
{renderInputFormAction({
|
|
778
|
+
choiceValuesByFieldId,
|
|
779
|
+
componentSet,
|
|
780
|
+
componentSystem,
|
|
781
|
+
inputForm,
|
|
782
|
+
module,
|
|
783
|
+
placement,
|
|
784
|
+
runtime,
|
|
785
|
+
target,
|
|
786
|
+
})}
|
|
787
|
+
</Fragment>
|
|
788
|
+
)),
|
|
789
|
+
})
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function renderInputFormAction<TValue extends object>({
|
|
793
|
+
choiceValuesByFieldId,
|
|
794
|
+
componentSystem,
|
|
795
|
+
componentSet,
|
|
796
|
+
inputForm,
|
|
797
|
+
module,
|
|
798
|
+
placement,
|
|
799
|
+
runtime,
|
|
800
|
+
target,
|
|
801
|
+
}: {
|
|
802
|
+
readonly choiceValuesByFieldId: Readonly<Record<string, readonly string[]>>
|
|
803
|
+
readonly componentSet: string
|
|
804
|
+
readonly componentSystem: PresentationComponentSystem
|
|
805
|
+
readonly inputForm: InputFormDefinition
|
|
806
|
+
readonly module: PresentationModuleDefinition
|
|
807
|
+
readonly placement: ActionPlacementDefinition
|
|
808
|
+
readonly runtime: ProjectedInputFormRuntime<TValue>
|
|
809
|
+
readonly target: ProjectedInputFormTargetContext
|
|
810
|
+
}) {
|
|
811
|
+
const action = findPresentationAction(module, placement.ActionId)
|
|
812
|
+
const label = placement.Label ?? action?.Name ?? placement.ActionId
|
|
813
|
+
return componentSystem.forms.FormActionButton({
|
|
814
|
+
'data-presentation-action-id': placement.ActionId,
|
|
815
|
+
'data-presentation-view-id': target.queryForm?.Id ?? inputForm.Id,
|
|
816
|
+
onClick: () =>
|
|
817
|
+
runtime.invokeAction({
|
|
818
|
+
action,
|
|
819
|
+
choiceValuesByFieldId,
|
|
820
|
+
inputForm,
|
|
821
|
+
placement,
|
|
822
|
+
target,
|
|
823
|
+
value: runtime.value,
|
|
824
|
+
}),
|
|
825
|
+
size: 'sm',
|
|
826
|
+
type: 'button',
|
|
827
|
+
variant: placement.Intent === 'primary' ? 'default' : 'outline',
|
|
828
|
+
children: (
|
|
829
|
+
<>
|
|
830
|
+
{renderPresentationIcon({
|
|
831
|
+
componentSet,
|
|
832
|
+
icon: placement.Icon,
|
|
833
|
+
module,
|
|
834
|
+
subject: placement,
|
|
835
|
+
})}
|
|
836
|
+
{label}
|
|
837
|
+
</>
|
|
838
|
+
),
|
|
839
|
+
})
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function hasActionsChromeSlot(view: ViewDefinition) {
|
|
843
|
+
return view.Chrome?.Slots.some((slot) =>
|
|
844
|
+
isViewChromeSlotKind(slot, viewChromeSlotKinds.actions)) ?? false
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function resolveInputFormActionIconPlacements(
|
|
848
|
+
inputForm: InputFormDefinition | null,
|
|
849
|
+
view: ViewDefinition,
|
|
850
|
+
) {
|
|
851
|
+
if (!inputForm) {
|
|
852
|
+
return []
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const actionSlots = view.Chrome?.Slots.filter((slot) =>
|
|
856
|
+
isViewChromeSlotKind(slot, viewChromeSlotKinds.actions)) ?? []
|
|
857
|
+
if (actionSlots.length === 0) {
|
|
858
|
+
return inputForm.Actions
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return actionSlots.flatMap((slot) =>
|
|
862
|
+
slot.Actions.length > 0 ? slot.Actions : inputForm.Actions)
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function createDefaultInputFormTarget(inputForm: InputFormDefinition): ProjectedInputFormTargetContext {
|
|
866
|
+
return {
|
|
867
|
+
queryDefinition: null,
|
|
868
|
+
queryForm: null,
|
|
869
|
+
stateId: inputForm.SharedStateId ?? inputForm.Target.Id ?? inputForm.StateDataSourceId,
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function projectInputFormFields(
|
|
874
|
+
inputForm: InputFormDefinition,
|
|
875
|
+
query: DataSourceQueryDefinition | null,
|
|
876
|
+
): ProjectedInputFormFieldProjection[] {
|
|
877
|
+
if (!query) {
|
|
878
|
+
return inputForm.Fields.map((field): ProjectedInputFormFieldProjection => ({
|
|
879
|
+
inputField: field,
|
|
880
|
+
queryField: null,
|
|
881
|
+
}))
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const queryFieldsById = new Map(query.Fields.map((field) => [field.Id, field]))
|
|
885
|
+
const queryFieldsByFieldId = new Map(
|
|
886
|
+
query.Fields.flatMap((field) => field.FieldId ? [[field.FieldId, field]] : []),
|
|
887
|
+
)
|
|
888
|
+
const queryFieldsByValuePath = new Map(
|
|
889
|
+
query.Fields.flatMap((field) => field.ValuePath ? [[field.ValuePath, field]] : []),
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
return inputForm.Fields.map((field): ProjectedInputFormFieldProjection => {
|
|
893
|
+
const queryField =
|
|
894
|
+
queryFieldsById.get(field.Id) ??
|
|
895
|
+
queryFieldsByFieldId.get(field.FieldId) ??
|
|
896
|
+
queryFieldsByValuePath.get(field.ValuePath)
|
|
897
|
+
return {
|
|
898
|
+
inputField: field,
|
|
899
|
+
queryField: queryField ?? null,
|
|
900
|
+
}
|
|
901
|
+
})
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function findInputFieldRenderer(
|
|
905
|
+
renderers: Readonly<Record<string, ProjectedInputFormFieldRenderer>> | undefined,
|
|
906
|
+
field: InputFormFieldDefinition,
|
|
907
|
+
queryField: DataSourceQueryFieldDefinition | null,
|
|
908
|
+
) {
|
|
909
|
+
if (!renderers) {
|
|
910
|
+
return undefined
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return (
|
|
914
|
+
(queryField?.Id ? renderers[queryField.Id] : undefined) ??
|
|
915
|
+
renderers[field.Id] ??
|
|
916
|
+
(queryField?.FieldId ? renderers[queryField.FieldId] : undefined) ??
|
|
917
|
+
renderers[field.FieldId] ??
|
|
918
|
+
(queryField?.ValuePath ? renderers[queryField.ValuePath] : undefined) ??
|
|
919
|
+
renderers[field.ValuePath]
|
|
920
|
+
)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function readInputFormChoices(
|
|
924
|
+
dataSourceResolver: PresentationDataSourceResolver,
|
|
925
|
+
field: InputFormFieldDefinition,
|
|
926
|
+
queryField: DataSourceQueryFieldDefinition | null,
|
|
927
|
+
presentationField: FieldPresentationDefinition | null,
|
|
928
|
+
): readonly ProjectedInputFormChoice[] {
|
|
929
|
+
if (queryField?.ChoiceDataSourceId) {
|
|
930
|
+
const value = dataSourceResolver.readPath(
|
|
931
|
+
queryField.ChoiceDataSourceId,
|
|
932
|
+
queryField.ChoiceItemsPath,
|
|
933
|
+
)
|
|
934
|
+
return readChoiceItems(value, {
|
|
935
|
+
labelPath: 'Label',
|
|
936
|
+
presentationField,
|
|
937
|
+
tonePath: 'Tone',
|
|
938
|
+
valuePath: 'Id',
|
|
939
|
+
})
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const source = field.ChoiceSource
|
|
943
|
+
if (!source) {
|
|
944
|
+
return []
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const sourcePath = source.CollectionPath ?? source.FactsPath
|
|
948
|
+
const value = source.DataSourceId
|
|
949
|
+
? dataSourceResolver.readPath(source.DataSourceId, sourcePath)
|
|
950
|
+
: []
|
|
951
|
+
if (!Array.isArray(value)) {
|
|
952
|
+
return []
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return readChoiceItems(value, {
|
|
956
|
+
labelPath: source.LabelPath,
|
|
957
|
+
presentationField,
|
|
958
|
+
tonePath: source.TonePath,
|
|
959
|
+
valuePath: source.ValuePath,
|
|
960
|
+
})
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function readChoiceItems(
|
|
964
|
+
value: unknown,
|
|
965
|
+
paths: {
|
|
966
|
+
readonly labelPath: string
|
|
967
|
+
readonly presentationField: FieldPresentationDefinition | null
|
|
968
|
+
readonly tonePath?: string | null
|
|
969
|
+
readonly valuePath: string
|
|
970
|
+
},
|
|
971
|
+
): readonly ProjectedInputFormChoice[] {
|
|
972
|
+
if (!Array.isArray(value)) {
|
|
973
|
+
return []
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return value.flatMap((item): ProjectedInputFormChoice[] => {
|
|
977
|
+
const choiceValue = readObjectPath(item, paths.valuePath)
|
|
978
|
+
if (choiceValue === null || choiceValue === undefined || choiceValue === '') {
|
|
979
|
+
return []
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const label = readObjectPath(item, paths.labelPath)
|
|
983
|
+
const tone = paths.tonePath
|
|
984
|
+
? readObjectPath(item, paths.tonePath)
|
|
985
|
+
: null
|
|
986
|
+
const resolvedLabel = label === null || label === undefined || label === ''
|
|
987
|
+
? resolvePresentationFieldValueLabel(paths.presentationField, choiceValue)
|
|
988
|
+
: String(label)
|
|
989
|
+
return [{
|
|
990
|
+
label: resolvedLabel ?? String(choiceValue),
|
|
991
|
+
tone: tone === null || tone === undefined
|
|
992
|
+
? resolvePresentationFieldValueTone(paths.presentationField, choiceValue)
|
|
993
|
+
: String(tone),
|
|
994
|
+
value: String(choiceValue),
|
|
995
|
+
}]
|
|
996
|
+
})
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function readSelectedChoiceValues(
|
|
1000
|
+
field: InputFormFieldDefinition,
|
|
1001
|
+
queryField: DataSourceQueryFieldDefinition | null,
|
|
1002
|
+
choices: readonly ProjectedInputFormChoice[],
|
|
1003
|
+
value: unknown,
|
|
1004
|
+
) {
|
|
1005
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1006
|
+
return value.map(String)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (isInputFormChoiceDefaultSelection(field, 'all', 'All')) {
|
|
1010
|
+
return choices.map((choice) => choice.value)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (isInputFormChoiceDefaultSelection(field, 'first', 'First')) {
|
|
1014
|
+
return choices[0] ? [choices[0].value] : []
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (queryField?.ChoiceDataSourceId && isMultiSelectInputField(field, queryField)) {
|
|
1018
|
+
return choices.map((choice) => choice.value)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return []
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function resolveChoiceToggleClassName({
|
|
1025
|
+
choice,
|
|
1026
|
+
choiceToggleClassName,
|
|
1027
|
+
choiceTone,
|
|
1028
|
+
field,
|
|
1029
|
+
inputForm,
|
|
1030
|
+
presentationField,
|
|
1031
|
+
queryField,
|
|
1032
|
+
queryForm,
|
|
1033
|
+
stateId,
|
|
1034
|
+
target,
|
|
1035
|
+
}: {
|
|
1036
|
+
readonly choice: ProjectedInputFormChoice
|
|
1037
|
+
readonly choiceToggleClassName?: ProjectedInputFormChoiceClassNameResolver
|
|
1038
|
+
readonly choiceTone?: ProjectedInputFormChoiceToneResolver
|
|
1039
|
+
readonly field: InputFormFieldDefinition
|
|
1040
|
+
readonly inputForm: InputFormDefinition
|
|
1041
|
+
readonly presentationField: FieldPresentationDefinition | null
|
|
1042
|
+
readonly queryField: DataSourceQueryFieldDefinition | null
|
|
1043
|
+
readonly queryForm: QueryFormDefinition | null
|
|
1044
|
+
readonly stateId: string
|
|
1045
|
+
readonly target: ProjectedInputFormTargetContext
|
|
1046
|
+
}) {
|
|
1047
|
+
const choiceContext = {
|
|
1048
|
+
choice,
|
|
1049
|
+
field,
|
|
1050
|
+
inputForm,
|
|
1051
|
+
presentationField,
|
|
1052
|
+
queryField,
|
|
1053
|
+
queryForm,
|
|
1054
|
+
stateId,
|
|
1055
|
+
target,
|
|
1056
|
+
} satisfies ProjectedInputFormChoiceRenderContext
|
|
1057
|
+
const tone =
|
|
1058
|
+
choiceTone?.(choiceContext) ??
|
|
1059
|
+
choice.tone ??
|
|
1060
|
+
resolvePresentationFieldValueTone(presentationField, choice.value) ??
|
|
1061
|
+
null
|
|
1062
|
+
return choiceToggleClassName?.({ ...choiceContext, tone }) ??
|
|
1063
|
+
getDefaultChoiceClassName(tone)
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function readFormValue(value: object, path: string) {
|
|
1067
|
+
return readObjectPath(value, path)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function setFormValue<TValue extends object>(
|
|
1071
|
+
value: TValue,
|
|
1072
|
+
path: string,
|
|
1073
|
+
nextValue: unknown,
|
|
1074
|
+
): TValue {
|
|
1075
|
+
const segments = path.split('.').filter(Boolean)
|
|
1076
|
+
if (segments.length === 0) {
|
|
1077
|
+
return value
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return setObjectPath(value, segments, nextValue) as TValue
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function setObjectPath(value: unknown, segments: readonly string[], nextValue: unknown): unknown {
|
|
1084
|
+
const [head, ...tail] = segments
|
|
1085
|
+
if (!head) {
|
|
1086
|
+
return nextValue
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const record = value && typeof value === 'object'
|
|
1090
|
+
? value as Record<string, unknown>
|
|
1091
|
+
: {}
|
|
1092
|
+
|
|
1093
|
+
return {
|
|
1094
|
+
...record,
|
|
1095
|
+
[head]: tail.length === 0
|
|
1096
|
+
? nextValue
|
|
1097
|
+
: setObjectPath(record[head], tail, nextValue),
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function isMultiSelectInputField(
|
|
1102
|
+
field: InputFormFieldDefinition,
|
|
1103
|
+
queryField: DataSourceQueryFieldDefinition | null,
|
|
1104
|
+
) {
|
|
1105
|
+
return isInputFormFieldKind(field, 'multiSelect', 'MultiSelect') ||
|
|
1106
|
+
queryField?.Operators.some((operator) => operator.toLocaleLowerCase() === 'in') === true
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function isInputFormFieldKind(
|
|
1110
|
+
field: InputFormFieldDefinition,
|
|
1111
|
+
kind: keyof typeof inputFormFieldKinds,
|
|
1112
|
+
label: string,
|
|
1113
|
+
) {
|
|
1114
|
+
return matchesPresentationEnum(
|
|
1115
|
+
field.Kind,
|
|
1116
|
+
createPresentationEnumDiscriminator(inputFormFieldKinds, kind, label),
|
|
1117
|
+
)
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function isInputFormChoiceDefaultSelection(
|
|
1121
|
+
field: InputFormFieldDefinition,
|
|
1122
|
+
selection: keyof typeof inputFormChoiceDefaultSelections,
|
|
1123
|
+
label: string,
|
|
1124
|
+
) {
|
|
1125
|
+
const defaultSelection = field.ChoiceSource?.DefaultSelection
|
|
1126
|
+
return defaultSelection !== null &&
|
|
1127
|
+
defaultSelection !== undefined &&
|
|
1128
|
+
matchesPresentationEnum(
|
|
1129
|
+
defaultSelection,
|
|
1130
|
+
createPresentationEnumDiscriminator(inputFormChoiceDefaultSelections, selection, label),
|
|
1131
|
+
)
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function getDefaultChoiceClassName(tone: string | null) {
|
|
1135
|
+
if (tone === 'success') {
|
|
1136
|
+
return 'data-[state=off]:border-teal-700/15 data-[state=off]:bg-white data-[state=off]:text-slate-500 data-[state=on]:border-teal-700/15 data-[state=on]:bg-teal-50 data-[state=on]:text-teal-700'
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (tone === 'danger') {
|
|
1140
|
+
return 'data-[state=off]:border-red-700/15 data-[state=off]:bg-white data-[state=off]:text-slate-500 data-[state=on]:border-red-700/15 data-[state=on]:bg-red-50 data-[state=on]:text-red-700'
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (tone === 'warning') {
|
|
1144
|
+
return 'data-[state=off]:border-amber-700/15 data-[state=off]:bg-white data-[state=off]:text-slate-500 data-[state=on]:border-amber-700/15 data-[state=on]:bg-amber-50 data-[state=on]:text-amber-700'
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (tone === 'accent') {
|
|
1148
|
+
return 'data-[state=off]:border-fuchsia-700/15 data-[state=off]:bg-white data-[state=off]:text-slate-500 data-[state=on]:border-fuchsia-700/15 data-[state=on]:bg-fuchsia-50 data-[state=on]:text-fuchsia-700'
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return 'data-[state=off]:border-sky-700/15 data-[state=off]:bg-white data-[state=off]:text-slate-500 data-[state=on]:border-sky-700/15 data-[state=on]:bg-sky-50 data-[state=on]:text-sky-700'
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function isDateTimeFilterControl(field: InputFormFieldDefinition) {
|
|
1155
|
+
const control = field.Display?.Control
|
|
1156
|
+
return control === inputFormFieldControlKinds.dateTimeFilter ||
|
|
1157
|
+
control?.toString().toLocaleLowerCase() === 'datetimefilter'
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function coerceDateTimeFilterValue(value: unknown): DateTimeFilterValue {
|
|
1161
|
+
const fallback = createDefaultDateTimeFilter()
|
|
1162
|
+
if (!value || typeof value !== 'object') {
|
|
1163
|
+
return fallback
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const record = value as Record<string, unknown>
|
|
1167
|
+
return normalizeDateTimeFilter({
|
|
1168
|
+
afterLocal: readOptionalString(record.afterLocal) || fallback.afterLocal,
|
|
1169
|
+
beforeLocal: readOptionalString(record.beforeLocal) || fallback.beforeLocal,
|
|
1170
|
+
mode: record.mode === 'range' ? 'range' : 'preset',
|
|
1171
|
+
preset: readOptionalString(record.preset) as DateTimeFilterValue['preset'],
|
|
1172
|
+
timezone: readOptionalString(record.timezone) || fallback.timezone,
|
|
1173
|
+
})
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function readOptionalString(value: unknown) {
|
|
1177
|
+
return typeof value === 'string' ? value : ''
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function readOptionalNumber(value: string) {
|
|
1181
|
+
if (!value) {
|
|
1182
|
+
return null
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const numberValue = Number(value)
|
|
1186
|
+
return Number.isFinite(numberValue) ? numberValue : null
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function isoToLocalDateTime(value: string) {
|
|
1190
|
+
if (!value) {
|
|
1191
|
+
return ''
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const parsed = new Date(value)
|
|
1195
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
1196
|
+
return ''
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const year = parsed.getFullYear()
|
|
1200
|
+
const month = `${parsed.getMonth() + 1}`.padStart(2, '0')
|
|
1201
|
+
const day = `${parsed.getDate()}`.padStart(2, '0')
|
|
1202
|
+
const hours = `${parsed.getHours()}`.padStart(2, '0')
|
|
1203
|
+
const minutes = `${parsed.getMinutes()}`.padStart(2, '0')
|
|
1204
|
+
return `${year}-${month}-${day}T${hours}:${minutes}`
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function localDateTimeToIso(value: string) {
|
|
1208
|
+
if (!value) {
|
|
1209
|
+
return null
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const parsed = new Date(value)
|
|
1213
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function cn(...values: readonly (false | null | string | undefined)[]) {
|
|
1217
|
+
return values.filter(Boolean).join(' ')
|
|
1218
|
+
}
|