@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.
Files changed (252) hide show
  1. package/LICENSE +55 -0
  2. package/dist/collection-chrome-slot-renderer-registry.d.ts +12 -0
  3. package/dist/collection-chrome-slot-renderer-registry.d.ts.map +1 -0
  4. package/dist/collection-chrome-slot-renderer-registry.js +12 -0
  5. package/dist/collection-chrome-slot-renderer-registry.js.map +1 -0
  6. package/dist/collection-selection-state-context.d.ts +6 -0
  7. package/dist/collection-selection-state-context.d.ts.map +1 -0
  8. package/dist/collection-selection-state-context.js +25 -0
  9. package/dist/collection-selection-state-context.js.map +1 -0
  10. package/dist/collection-selection-state.d.ts +3 -0
  11. package/dist/collection-selection-state.d.ts.map +1 -0
  12. package/dist/collection-selection-state.js +52 -0
  13. package/dist/collection-selection-state.js.map +1 -0
  14. package/dist/document-workspace-runtime.d.ts +262 -0
  15. package/dist/document-workspace-runtime.d.ts.map +1 -0
  16. package/dist/document-workspace-runtime.js +699 -0
  17. package/dist/document-workspace-runtime.js.map +1 -0
  18. package/dist/index.d.ts +51 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +51 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/input-form-endpoint-runtime.d.ts +140 -0
  23. package/dist/input-form-endpoint-runtime.d.ts.map +1 -0
  24. package/dist/input-form-endpoint-runtime.js +280 -0
  25. package/dist/input-form-endpoint-runtime.js.map +1 -0
  26. package/dist/local-flow-state-data-source-projection.d.ts +21 -0
  27. package/dist/local-flow-state-data-source-projection.d.ts.map +1 -0
  28. package/dist/local-flow-state-data-source-projection.js +65 -0
  29. package/dist/local-flow-state-data-source-projection.js.map +1 -0
  30. package/dist/navigation-runtime-context.d.ts +22 -0
  31. package/dist/navigation-runtime-context.d.ts.map +1 -0
  32. package/dist/navigation-runtime-context.js +23 -0
  33. package/dist/navigation-runtime-context.js.map +1 -0
  34. package/dist/navigation-shell-frame-renderer-registry.d.ts +20 -0
  35. package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
  36. package/dist/navigation-shell-frame-renderer-registry.js +9 -0
  37. package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
  38. package/dist/navigation-shell-region-component-registry.d.ts +50 -0
  39. package/dist/navigation-shell-region-component-registry.d.ts.map +1 -0
  40. package/dist/navigation-shell-region-component-registry.js +44 -0
  41. package/dist/navigation-shell-region-component-registry.js.map +1 -0
  42. package/dist/navigation-shell-slot-renderer-registry.d.ts +22 -0
  43. package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
  44. package/dist/navigation-shell-slot-renderer-registry.js +9 -0
  45. package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
  46. package/dist/page-host-projection.d.ts +127 -0
  47. package/dist/page-host-projection.d.ts.map +1 -0
  48. package/dist/page-host-projection.js +59 -0
  49. package/dist/page-host-projection.js.map +1 -0
  50. package/dist/page-host-renderer-registry.d.ts +72 -0
  51. package/dist/page-host-renderer-registry.d.ts.map +1 -0
  52. package/dist/page-host-renderer-registry.js +63 -0
  53. package/dist/page-host-renderer-registry.js.map +1 -0
  54. package/dist/presentation-action-group-runtime-options.d.ts +36 -0
  55. package/dist/presentation-action-group-runtime-options.d.ts.map +1 -0
  56. package/dist/presentation-action-group-runtime-options.js +50 -0
  57. package/dist/presentation-action-group-runtime-options.js.map +1 -0
  58. package/dist/presentation-action-runtime.d.ts +52 -0
  59. package/dist/presentation-action-runtime.d.ts.map +1 -0
  60. package/dist/presentation-action-runtime.js +78 -0
  61. package/dist/presentation-action-runtime.js.map +1 -0
  62. package/dist/presentation-data-source-binding-runtime.d.ts +3 -0
  63. package/dist/presentation-data-source-binding-runtime.d.ts.map +1 -0
  64. package/dist/presentation-data-source-binding-runtime.js +65 -0
  65. package/dist/presentation-data-source-binding-runtime.js.map +1 -0
  66. package/dist/presentation-data-source-binding.d.ts +12 -0
  67. package/dist/presentation-data-source-binding.d.ts.map +1 -0
  68. package/dist/presentation-data-source-binding.js +13 -0
  69. package/dist/presentation-data-source-binding.js.map +1 -0
  70. package/dist/presentation-flow-runtime.d.ts +139 -0
  71. package/dist/presentation-flow-runtime.d.ts.map +1 -0
  72. package/dist/presentation-flow-runtime.js +514 -0
  73. package/dist/presentation-flow-runtime.js.map +1 -0
  74. package/dist/presentation-module-context.d.ts +4 -0
  75. package/dist/presentation-module-context.d.ts.map +1 -0
  76. package/dist/presentation-module-context.js +6 -0
  77. package/dist/presentation-module-context.js.map +1 -0
  78. package/dist/presentation-module-provider.d.ts +7 -0
  79. package/dist/presentation-module-provider.d.ts.map +1 -0
  80. package/dist/presentation-module-provider.js +7 -0
  81. package/dist/presentation-module-provider.js.map +1 -0
  82. package/dist/presentation-projection-diagnostics-context.d.ts +8 -0
  83. package/dist/presentation-projection-diagnostics-context.d.ts.map +1 -0
  84. package/dist/presentation-projection-diagnostics-context.js +3 -0
  85. package/dist/presentation-projection-diagnostics-context.js.map +1 -0
  86. package/dist/presentation-projection-diagnostics-hooks.d.ts +4 -0
  87. package/dist/presentation-projection-diagnostics-hooks.d.ts.map +1 -0
  88. package/dist/presentation-projection-diagnostics-hooks.js +17 -0
  89. package/dist/presentation-projection-diagnostics-hooks.js.map +1 -0
  90. package/dist/presentation-projection-diagnostics-runtime.d.ts +12 -0
  91. package/dist/presentation-projection-diagnostics-runtime.d.ts.map +1 -0
  92. package/dist/presentation-projection-diagnostics-runtime.js +54 -0
  93. package/dist/presentation-projection-diagnostics-runtime.js.map +1 -0
  94. package/dist/presentation-projection-trace.d.ts +27 -0
  95. package/dist/presentation-projection-trace.d.ts.map +1 -0
  96. package/dist/presentation-projection-trace.js +54 -0
  97. package/dist/presentation-projection-trace.js.map +1 -0
  98. package/dist/presentation-query-form-runtime.d.ts +30 -0
  99. package/dist/presentation-query-form-runtime.d.ts.map +1 -0
  100. package/dist/presentation-query-form-runtime.js +75 -0
  101. package/dist/presentation-query-form-runtime.js.map +1 -0
  102. package/dist/presentation-query-form-state-adapter-registry.d.ts +4 -0
  103. package/dist/presentation-query-form-state-adapter-registry.d.ts.map +1 -0
  104. package/dist/presentation-query-form-state-adapter-registry.js +13 -0
  105. package/dist/presentation-query-form-state-adapter-registry.js.map +1 -0
  106. package/dist/presentation-query-form-state-context.d.ts +20 -0
  107. package/dist/presentation-query-form-state-context.d.ts.map +1 -0
  108. package/dist/presentation-query-form-state-context.js +20 -0
  109. package/dist/presentation-query-form-state-context.js.map +1 -0
  110. package/dist/presentation-query-form-state.d.ts +29 -0
  111. package/dist/presentation-query-form-state.d.ts.map +1 -0
  112. package/dist/presentation-query-form-state.js +99 -0
  113. package/dist/presentation-query-form-state.js.map +1 -0
  114. package/dist/presentation-renderer-registry.d.ts +109 -0
  115. package/dist/presentation-renderer-registry.d.ts.map +1 -0
  116. package/dist/presentation-renderer-registry.js +25 -0
  117. package/dist/presentation-renderer-registry.js.map +1 -0
  118. package/dist/presentation-standards-context.d.ts +4 -0
  119. package/dist/presentation-standards-context.d.ts.map +1 -0
  120. package/dist/presentation-standards-context.js +12 -0
  121. package/dist/presentation-standards-context.js.map +1 -0
  122. package/dist/presentation-standards-provider.d.ts +14 -0
  123. package/dist/presentation-standards-provider.d.ts.map +1 -0
  124. package/dist/presentation-standards-provider.js +16 -0
  125. package/dist/presentation-standards-provider.js.map +1 -0
  126. package/dist/projected-document-action-runtime-bindings.d.ts +20 -0
  127. package/dist/projected-document-action-runtime-bindings.d.ts.map +1 -0
  128. package/dist/projected-document-action-runtime-bindings.js +88 -0
  129. package/dist/projected-document-action-runtime-bindings.js.map +1 -0
  130. package/dist/projected-document-action-runtime.d.ts +76 -0
  131. package/dist/projected-document-action-runtime.d.ts.map +1 -0
  132. package/dist/projected-document-action-runtime.js +224 -0
  133. package/dist/projected-document-action-runtime.js.map +1 -0
  134. package/dist/projected-document-process-preview-action-runtime-bindings.d.ts +26 -0
  135. package/dist/projected-document-process-preview-action-runtime-bindings.d.ts.map +1 -0
  136. package/dist/projected-document-process-preview-action-runtime-bindings.js +106 -0
  137. package/dist/projected-document-process-preview-action-runtime-bindings.js.map +1 -0
  138. package/dist/projected-document-process-preview-action-runtime.d.ts +321 -0
  139. package/dist/projected-document-process-preview-action-runtime.d.ts.map +1 -0
  140. package/dist/projected-document-process-preview-action-runtime.js +729 -0
  141. package/dist/projected-document-process-preview-action-runtime.js.map +1 -0
  142. package/dist/projected-document-process-preview-capability-runtime.d.ts +47 -0
  143. package/dist/projected-document-process-preview-capability-runtime.d.ts.map +1 -0
  144. package/dist/projected-document-process-preview-capability-runtime.js +49 -0
  145. package/dist/projected-document-process-preview-capability-runtime.js.map +1 -0
  146. package/dist/projected-document-workspace-action-runtime.d.ts +87 -0
  147. package/dist/projected-document-workspace-action-runtime.d.ts.map +1 -0
  148. package/dist/projected-document-workspace-action-runtime.js +150 -0
  149. package/dist/projected-document-workspace-action-runtime.js.map +1 -0
  150. package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts +18 -0
  151. package/dist/projected-document-workspace-projection-renderer-diagnostics.d.ts.map +1 -0
  152. package/dist/projected-document-workspace-projection-renderer-diagnostics.js +207 -0
  153. package/dist/projected-document-workspace-projection-renderer-diagnostics.js.map +1 -0
  154. package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts +19 -0
  155. package/dist/projected-document-workspace-runtime-binding-diagnostics.d.ts.map +1 -0
  156. package/dist/projected-document-workspace-runtime-binding-diagnostics.js +124 -0
  157. package/dist/projected-document-workspace-runtime-binding-diagnostics.js.map +1 -0
  158. package/dist/projected-navigation-provider.d.ts +16 -0
  159. package/dist/projected-navigation-provider.d.ts.map +1 -0
  160. package/dist/projected-navigation-provider.js +51 -0
  161. package/dist/projected-navigation-provider.js.map +1 -0
  162. package/dist/projected-presentation-flow-host.d.ts +16 -0
  163. package/dist/projected-presentation-flow-host.d.ts.map +1 -0
  164. package/dist/projected-presentation-flow-host.js +14 -0
  165. package/dist/projected-presentation-flow-host.js.map +1 -0
  166. package/dist/projected-query-form-state-adapter.d.ts +12 -0
  167. package/dist/projected-query-form-state-adapter.d.ts.map +1 -0
  168. package/dist/projected-query-form-state-adapter.js +361 -0
  169. package/dist/projected-query-form-state-adapter.js.map +1 -0
  170. package/dist/react-router-binding.d.ts +133 -0
  171. package/dist/react-router-binding.d.ts.map +1 -0
  172. package/dist/react-router-binding.js +76 -0
  173. package/dist/react-router-binding.js.map +1 -0
  174. package/dist/react-router-path.d.ts +4 -0
  175. package/dist/react-router-path.d.ts.map +1 -0
  176. package/dist/react-router-path.js +11 -0
  177. package/dist/react-router-path.js.map +1 -0
  178. package/dist/scope-selection-context.d.ts +28 -0
  179. package/dist/scope-selection-context.d.ts.map +1 -0
  180. package/dist/scope-selection-context.js +13 -0
  181. package/dist/scope-selection-context.js.map +1 -0
  182. package/dist/scope-selection-provider.d.ts +33 -0
  183. package/dist/scope-selection-provider.d.ts.map +1 -0
  184. package/dist/scope-selection-provider.js +162 -0
  185. package/dist/scope-selection-provider.js.map +1 -0
  186. package/dist/scope-selection-request.d.ts +6 -0
  187. package/dist/scope-selection-request.d.ts.map +1 -0
  188. package/dist/scope-selection-request.js +12 -0
  189. package/dist/scope-selection-request.js.map +1 -0
  190. package/dist/standard-navigation-shell-frame-renderers.d.ts +15 -0
  191. package/dist/standard-navigation-shell-frame-renderers.d.ts.map +1 -0
  192. package/dist/standard-navigation-shell-frame-renderers.js +22 -0
  193. package/dist/standard-navigation-shell-frame-renderers.js.map +1 -0
  194. package/dist/standard-navigation-shell-slot-renderers.d.ts +59 -0
  195. package/dist/standard-navigation-shell-slot-renderers.d.ts.map +1 -0
  196. package/dist/standard-navigation-shell-slot-renderers.js +67 -0
  197. package/dist/standard-navigation-shell-slot-renderers.js.map +1 -0
  198. package/dist/view-chrome-slot-renderer-registry.d.ts +12 -0
  199. package/dist/view-chrome-slot-renderer-registry.d.ts.map +1 -0
  200. package/dist/view-chrome-slot-renderer-registry.js +12 -0
  201. package/dist/view-chrome-slot-renderer-registry.js.map +1 -0
  202. package/package.json +60 -0
  203. package/src/collection-chrome-slot-renderer-registry.ts +66 -0
  204. package/src/collection-selection-state-context.ts +51 -0
  205. package/src/collection-selection-state.tsx +101 -0
  206. package/src/document-workspace-runtime.tsx +1505 -0
  207. package/src/index.ts +50 -0
  208. package/src/input-form-endpoint-runtime.ts +632 -0
  209. package/src/local-flow-state-data-source-projection.ts +133 -0
  210. package/src/navigation-runtime-context.ts +40 -0
  211. package/src/navigation-shell-frame-renderer-registry.ts +54 -0
  212. package/src/navigation-shell-region-component-registry.tsx +127 -0
  213. package/src/navigation-shell-slot-renderer-registry.ts +88 -0
  214. package/src/page-host-projection.ts +356 -0
  215. package/src/page-host-renderer-registry.tsx +353 -0
  216. package/src/presentation-action-group-runtime-options.ts +121 -0
  217. package/src/presentation-action-runtime.ts +190 -0
  218. package/src/presentation-data-source-binding-runtime.ts +109 -0
  219. package/src/presentation-data-source-binding.tsx +24 -0
  220. package/src/presentation-flow-runtime.tsx +835 -0
  221. package/src/presentation-module-context.ts +10 -0
  222. package/src/presentation-module-provider.tsx +19 -0
  223. package/src/presentation-projection-diagnostics-context.ts +17 -0
  224. package/src/presentation-projection-diagnostics-hooks.ts +29 -0
  225. package/src/presentation-projection-diagnostics-runtime.tsx +95 -0
  226. package/src/presentation-projection-trace.ts +117 -0
  227. package/src/presentation-query-form-runtime.ts +148 -0
  228. package/src/presentation-query-form-state-adapter-registry.ts +25 -0
  229. package/src/presentation-query-form-state-context.ts +56 -0
  230. package/src/presentation-query-form-state.tsx +250 -0
  231. package/src/presentation-renderer-registry.ts +214 -0
  232. package/src/presentation-standards-context.ts +21 -0
  233. package/src/presentation-standards-provider.tsx +44 -0
  234. package/src/projected-document-action-runtime-bindings.ts +132 -0
  235. package/src/projected-document-action-runtime.ts +414 -0
  236. package/src/projected-document-process-preview-action-runtime-bindings.ts +231 -0
  237. package/src/projected-document-process-preview-action-runtime.ts +1725 -0
  238. package/src/projected-document-process-preview-capability-runtime.ts +216 -0
  239. package/src/projected-document-workspace-action-runtime.ts +402 -0
  240. package/src/projected-document-workspace-projection-renderer-diagnostics.ts +342 -0
  241. package/src/projected-document-workspace-runtime-binding-diagnostics.ts +208 -0
  242. package/src/projected-navigation-provider.tsx +104 -0
  243. package/src/projected-presentation-flow-host.tsx +46 -0
  244. package/src/projected-query-form-state-adapter.ts +578 -0
  245. package/src/react-router-binding.tsx +417 -0
  246. package/src/react-router-path.ts +16 -0
  247. package/src/scope-selection-context.ts +58 -0
  248. package/src/scope-selection-provider.tsx +324 -0
  249. package/src/scope-selection-request.ts +21 -0
  250. package/src/standard-navigation-shell-frame-renderers.tsx +106 -0
  251. package/src/standard-navigation-shell-slot-renderers.tsx +289 -0
  252. package/src/view-chrome-slot-renderer-registry.ts +66 -0
@@ -0,0 +1,578 @@
1
+ import type {
2
+ InputFormDefinition,
3
+ InputFormFieldDefinition,
4
+ PresentationValueDefinition,
5
+ } from '@cohesivesystems/presentation-core'
6
+ import type {
7
+ PresentationQueryFormStateAdapter,
8
+ PresentationQueryFormStateAdapterContext,
9
+ } from './presentation-query-form-state'
10
+ import {
11
+ createQueryFormUrlFields,
12
+ createQueryFormUrlSearch,
13
+ createQueryFormUrlSearchKey,
14
+ readObjectPath,
15
+ readQueryFormUrlValue,
16
+ type QueryFormUrlFieldCodecs,
17
+ writeObjectPath,
18
+ } from '@cohesivesystems/presentation-core'
19
+ import {
20
+ createPresentationEnumDiscriminator,
21
+ matchesPresentationEnum,
22
+ } from '@cohesivesystems/presentation-core'
23
+ import {
24
+ inputFormChoiceDefaultSelections,
25
+ inputFormFieldControlKinds,
26
+ inputFormFieldKinds,
27
+ presentationValueKinds,
28
+ } from '@cohesivesystems/presentation-contracts'
29
+ import {
30
+ createDefaultDateTimeFilter,
31
+ isDateTimePreset,
32
+ normalizeDateTimeFilter,
33
+ type DateTimeFilterValue,
34
+ } from '@cohesivesystems/presentation-core'
35
+
36
+ type ProjectedQueryFormValue = Record<string, unknown>
37
+
38
+ const dateTimeRangeParamValue = 'range'
39
+
40
+ /**
41
+ * Creates the default frontend interpretation for an IR-declared query form.
42
+ *
43
+ * The adapter derives draft/applied state shape, normalization, and URL state
44
+ * from the query form's backing input-form fields. Targets can still override a
45
+ * query form by registering an adapter with the same query form id.
46
+ */
47
+ export function createProjectedQueryFormStateAdapter(
48
+ queryFormId: string,
49
+ ): PresentationQueryFormStateAdapter<ProjectedQueryFormValue> {
50
+ return {
51
+ createAppliedSearch: ({
52
+ choiceValuesByFieldId,
53
+ inputForm,
54
+ queryForm,
55
+ search,
56
+ value,
57
+ }) =>
58
+ createQueryFormUrlSearch({
59
+ allValuesByFieldId: choiceValuesByFieldId,
60
+ defaultValue: createProjectedQueryFormDefaultValue({
61
+ choiceValuesByFieldId,
62
+ inputForm,
63
+ }),
64
+ fieldCodecs: createProjectedQueryFormUrlFieldCodecs(inputForm),
65
+ fields: createQueryFormUrlFields(inputForm, queryForm),
66
+ queryForm,
67
+ search,
68
+ value: normalizeProjectedQueryFormValue({
69
+ choiceValuesByFieldId,
70
+ inputForm,
71
+ value,
72
+ }),
73
+ }),
74
+ createDefaultValue: (context) =>
75
+ createProjectedQueryFormDefaultValue(context),
76
+ createSearchKey: ({
77
+ choiceValuesByFieldId = {},
78
+ inputForm,
79
+ queryForm,
80
+ search,
81
+ }) =>
82
+ createQueryFormUrlSearchKey({
83
+ allValuesByFieldId: choiceValuesByFieldId,
84
+ defaultValue: createProjectedQueryFormDefaultValue({
85
+ choiceValuesByFieldId,
86
+ inputForm,
87
+ }),
88
+ fieldCodecs: createProjectedQueryFormUrlFieldCodecs(inputForm),
89
+ fields: createQueryFormUrlFields(inputForm, queryForm),
90
+ queryForm,
91
+ search,
92
+ }),
93
+ normalizeValue: ({ choiceValuesByFieldId, inputForm, value }) =>
94
+ normalizeProjectedQueryFormValue({
95
+ choiceValuesByFieldId,
96
+ inputForm,
97
+ value,
98
+ }),
99
+ queryFormId,
100
+ readValueFromSearch: ({
101
+ choiceValuesByFieldId = {},
102
+ inputForm,
103
+ queryForm,
104
+ search,
105
+ }) =>
106
+ normalizeProjectedQueryFormValue({
107
+ choiceValuesByFieldId,
108
+ inputForm,
109
+ value: readQueryFormUrlValue({
110
+ defaultValue: createProjectedQueryFormDefaultValue({
111
+ choiceValuesByFieldId,
112
+ inputForm,
113
+ }),
114
+ fieldCodecs: createProjectedQueryFormUrlFieldCodecs(inputForm),
115
+ fields: createQueryFormUrlFields(inputForm, queryForm),
116
+ queryForm,
117
+ search,
118
+ }),
119
+ }),
120
+ }
121
+ }
122
+
123
+ function createProjectedQueryFormDefaultValue({
124
+ choiceValuesByFieldId = {},
125
+ inputForm,
126
+ }: Pick<
127
+ PresentationQueryFormStateAdapterContext,
128
+ 'choiceValuesByFieldId' | 'inputForm'
129
+ >): ProjectedQueryFormValue {
130
+ if (!inputForm) {
131
+ return {}
132
+ }
133
+
134
+ const value: ProjectedQueryFormValue = {}
135
+ for (const field of inputForm.Fields) {
136
+ writeObjectPath(
137
+ value,
138
+ field.ValuePath,
139
+ createDefaultInputFormFieldValue(
140
+ field,
141
+ readChoiceValues(choiceValuesByFieldId, field),
142
+ ),
143
+ )
144
+ }
145
+
146
+ return applyLiteralInputFormDefaultValues(inputForm, value)
147
+ }
148
+
149
+ function normalizeProjectedQueryFormValue({
150
+ choiceValuesByFieldId,
151
+ inputForm,
152
+ value,
153
+ }: {
154
+ readonly choiceValuesByFieldId: Readonly<Record<string, readonly string[]>>
155
+ readonly inputForm: InputFormDefinition | null
156
+ readonly value: ProjectedQueryFormValue
157
+ }) {
158
+ if (!inputForm) {
159
+ return value
160
+ }
161
+
162
+ const nextValue = clonePlainObject(value)
163
+ for (const field of inputForm.Fields) {
164
+ const choiceValues = readChoiceValues(choiceValuesByFieldId, field)
165
+ const currentValue = readObjectPath(nextValue, field.ValuePath)
166
+ writeObjectPath(
167
+ nextValue,
168
+ field.ValuePath,
169
+ normalizeInputFormFieldValue(field, currentValue, choiceValues),
170
+ )
171
+ }
172
+
173
+ return nextValue
174
+ }
175
+
176
+ function createDefaultInputFormFieldValue(
177
+ field: InputFormFieldDefinition,
178
+ choiceValues: readonly string[],
179
+ ): unknown {
180
+ if (isInputFormFieldKind(field, 'multiSelect', 'MultiSelect')) {
181
+ return createDefaultChoiceSelection(field, choiceValues)
182
+ }
183
+
184
+ if (isInputFormFieldKind(field, 'select', 'Select')) {
185
+ return createDefaultSingleChoice(field, choiceValues)
186
+ }
187
+
188
+ if (isInputFormFieldKind(field, 'dateTimeRange', 'DateTimeRange')) {
189
+ return isDateTimeFilterControl(field)
190
+ ? createDefaultDateTimeFilter()
191
+ : { after: null, before: null }
192
+ }
193
+
194
+ if (isInputFormFieldKind(field, 'number', 'Number')) {
195
+ return readDefaultNumber(field.DefaultValue)
196
+ }
197
+
198
+ if (isInputFormFieldKind(field, 'numberRange', 'NumberRange')) {
199
+ return { maximum: null, minimum: null }
200
+ }
201
+
202
+ if (isInputFormFieldKind(field, 'boolean', 'Boolean')) {
203
+ return field.DefaultValue === 'true'
204
+ }
205
+
206
+ return field.DefaultValue ?? ''
207
+ }
208
+
209
+ function normalizeInputFormFieldValue(
210
+ field: InputFormFieldDefinition,
211
+ value: unknown,
212
+ choiceValues: readonly string[],
213
+ ): unknown {
214
+ if (isInputFormFieldKind(field, 'multiSelect', 'MultiSelect')) {
215
+ const selectedValues = normalizeStringSet(value)
216
+ const availableValues = normalizeStringSet(choiceValues)
217
+ const availableSet = new Set(availableValues)
218
+ const visibleSelectedValues = availableSet.size > 0
219
+ ? selectedValues.filter((selectedValue) => availableSet.has(selectedValue))
220
+ : selectedValues
221
+
222
+ return visibleSelectedValues.length > 0
223
+ ? visibleSelectedValues
224
+ : createDefaultChoiceSelection(field, availableValues)
225
+ }
226
+
227
+ if (isInputFormFieldKind(field, 'select', 'Select')) {
228
+ const selectedValue = typeof value === 'string' ? value.trim() : ''
229
+ const availableValues = normalizeStringSet(choiceValues)
230
+ return selectedValue && (availableValues.length === 0 || availableValues.includes(selectedValue))
231
+ ? selectedValue
232
+ : createDefaultSingleChoice(field, availableValues)
233
+ }
234
+
235
+ if (isInputFormFieldKind(field, 'dateTimeRange', 'DateTimeRange')) {
236
+ return isDateTimeFilterControl(field)
237
+ ? coerceDateTimeFilterValue(value)
238
+ : normalizePlainDateTimeRange(value)
239
+ }
240
+
241
+ if (isInputFormFieldKind(field, 'dateTime', 'DateTime') ||
242
+ isInputFormFieldKind(field, 'date', 'Date')) {
243
+ return typeof value === 'string' ? value.trim() : ''
244
+ }
245
+
246
+ if (isInputFormFieldKind(field, 'number', 'Number')) {
247
+ return typeof value === 'number' && Number.isFinite(value)
248
+ ? value
249
+ : readDefaultNumber(field.DefaultValue)
250
+ }
251
+
252
+ if (isInputFormFieldKind(field, 'numberRange', 'NumberRange')) {
253
+ return normalizeNumberRange(value)
254
+ }
255
+
256
+ if (isInputFormFieldKind(field, 'boolean', 'Boolean')) {
257
+ return value === true
258
+ }
259
+
260
+ return typeof value === 'string' ? value.trim() : field.DefaultValue ?? ''
261
+ }
262
+
263
+ function createProjectedQueryFormUrlFieldCodecs(
264
+ inputForm: InputFormDefinition | null,
265
+ ): QueryFormUrlFieldCodecs<ProjectedQueryFormValue> {
266
+ if (!inputForm) {
267
+ return {}
268
+ }
269
+
270
+ return Object.fromEntries(
271
+ inputForm.Fields
272
+ .filter((field) =>
273
+ isInputFormFieldKind(field, 'dateTimeRange', 'DateTimeRange') &&
274
+ isDateTimeFilterControl(field)
275
+ )
276
+ .map((field) => [
277
+ field.Id,
278
+ {
279
+ deleteParams: ({ key, params }) =>
280
+ deleteDateTimeFilterParams(params, key),
281
+ read: ({ defaultFieldValue, key, params }) =>
282
+ readDateTimeFilterParams(
283
+ params,
284
+ key,
285
+ coerceDateTimeFilterValue(defaultFieldValue),
286
+ ),
287
+ write: ({ fieldValue, key, params }) =>
288
+ writeDateTimeFilterParams(
289
+ params,
290
+ key,
291
+ coerceDateTimeFilterValue(fieldValue),
292
+ ),
293
+ },
294
+ ]),
295
+ ) satisfies QueryFormUrlFieldCodecs<ProjectedQueryFormValue>
296
+ }
297
+
298
+ function readDateTimeFilterParams(
299
+ params: URLSearchParams,
300
+ key: string,
301
+ defaults: DateTimeFilterValue,
302
+ ): DateTimeFilterValue {
303
+ const value = params.get(key)?.trim() ?? ''
304
+ const timezone = params.get(createDateTimeFilterParam(key, 'tz'))?.trim() || defaults.timezone
305
+
306
+ if (value === dateTimeRangeParamValue) {
307
+ return normalizeDateTimeFilter({
308
+ ...defaults,
309
+ afterLocal: params.get(createDateTimeFilterParam(key, 'after'))?.trim() ?? '',
310
+ beforeLocal: params.get(createDateTimeFilterParam(key, 'before'))?.trim() ?? '',
311
+ mode: 'range',
312
+ timezone,
313
+ })
314
+ }
315
+
316
+ if (value && isDateTimePreset(value)) {
317
+ return normalizeDateTimeFilter({
318
+ ...defaults,
319
+ mode: 'preset',
320
+ preset: value,
321
+ timezone,
322
+ })
323
+ }
324
+
325
+ return defaults
326
+ }
327
+
328
+ function writeDateTimeFilterParams(
329
+ params: URLSearchParams,
330
+ key: string,
331
+ value: DateTimeFilterValue,
332
+ ) {
333
+ const normalizedValue = normalizeDateTimeFilter(value)
334
+
335
+ if (normalizedValue.mode === 'range') {
336
+ params.set(key, dateTimeRangeParamValue)
337
+ writeStringParam(params, createDateTimeFilterParam(key, 'after'), normalizedValue.afterLocal)
338
+ writeStringParam(params, createDateTimeFilterParam(key, 'before'), normalizedValue.beforeLocal)
339
+ writeStringParam(params, createDateTimeFilterParam(key, 'tz'), normalizedValue.timezone)
340
+ return
341
+ }
342
+
343
+ if (normalizedValue.preset !== 'today') {
344
+ params.set(key, normalizedValue.preset)
345
+ }
346
+ }
347
+
348
+ function deleteDateTimeFilterParams(params: URLSearchParams, key: string) {
349
+ params.delete(key)
350
+ params.delete(createDateTimeFilterParam(key, 'after'))
351
+ params.delete(createDateTimeFilterParam(key, 'before'))
352
+ params.delete(createDateTimeFilterParam(key, 'tz'))
353
+ }
354
+
355
+ function createDateTimeFilterParam(key: string, suffix: 'after' | 'before' | 'tz') {
356
+ return `${key}_${suffix}`
357
+ }
358
+
359
+ function writeStringParam(params: URLSearchParams, key: string, value: string) {
360
+ const trimmedValue = value.trim()
361
+ if (trimmedValue) {
362
+ params.set(key, trimmedValue)
363
+ }
364
+ }
365
+
366
+ function createDefaultChoiceSelection(
367
+ field: InputFormFieldDefinition,
368
+ choiceValues: readonly string[],
369
+ ) {
370
+ if (isInputFormChoiceDefaultSelection(field, 'all', 'All')) {
371
+ return [...choiceValues]
372
+ }
373
+
374
+ if (isInputFormChoiceDefaultSelection(field, 'first', 'First')) {
375
+ return choiceValues[0] ? [choiceValues[0]] : []
376
+ }
377
+
378
+ return normalizeStringSet(field.DefaultValue)
379
+ }
380
+
381
+ function createDefaultSingleChoice(
382
+ field: InputFormFieldDefinition,
383
+ choiceValues: readonly string[],
384
+ ) {
385
+ if (isInputFormChoiceDefaultSelection(field, 'first', 'First')) {
386
+ return choiceValues[0] ?? ''
387
+ }
388
+
389
+ return field.DefaultValue ?? ''
390
+ }
391
+
392
+ function readChoiceValues(
393
+ choiceValuesByFieldId: Readonly<Record<string, readonly string[]>>,
394
+ field: InputFormFieldDefinition,
395
+ ) {
396
+ return choiceValuesByFieldId[field.Id] ??
397
+ choiceValuesByFieldId[field.FieldId] ??
398
+ []
399
+ }
400
+
401
+ function coerceDateTimeFilterValue(value: unknown): DateTimeFilterValue {
402
+ const fallback = createDefaultDateTimeFilter()
403
+ if (!value || typeof value !== 'object') {
404
+ return fallback
405
+ }
406
+
407
+ const record = value as Record<string, unknown>
408
+ return normalizeDateTimeFilter({
409
+ afterLocal: readOptionalString(record.afterLocal) || fallback.afterLocal,
410
+ beforeLocal: readOptionalString(record.beforeLocal) || fallback.beforeLocal,
411
+ mode: record.mode === 'range' ? 'range' : 'preset',
412
+ preset: readOptionalString(record.preset) as DateTimeFilterValue['preset'],
413
+ timezone: readOptionalString(record.timezone) || fallback.timezone,
414
+ })
415
+ }
416
+
417
+ function normalizePlainDateTimeRange(value: unknown) {
418
+ const range = value && typeof value === 'object'
419
+ ? value as Record<string, unknown>
420
+ : {}
421
+ return {
422
+ after: readOptionalString(range.after) || null,
423
+ before: readOptionalString(range.before) || null,
424
+ }
425
+ }
426
+
427
+ function normalizeNumberRange(value: unknown) {
428
+ const range = value && typeof value === 'object'
429
+ ? value as Record<string, unknown>
430
+ : {}
431
+ return {
432
+ maximum: readOptionalNumber(range.maximum),
433
+ minimum: readOptionalNumber(range.minimum),
434
+ }
435
+ }
436
+
437
+ function readDefaultNumber(value: string | null | undefined) {
438
+ return readOptionalNumber(value) ?? null
439
+ }
440
+
441
+ function readOptionalNumber(value: unknown) {
442
+ if (typeof value === 'string' && value.trim() === '') {
443
+ return null
444
+ }
445
+
446
+ const numberValue = typeof value === 'number'
447
+ ? value
448
+ : Number(value)
449
+ return Number.isFinite(numberValue) ? numberValue : null
450
+ }
451
+
452
+ function readOptionalString(value: unknown) {
453
+ return typeof value === 'string' ? value : ''
454
+ }
455
+
456
+ function normalizeStringSet(value: unknown) {
457
+ const values = Array.isArray(value) ? value : typeof value === 'string' ? [value] : []
458
+ return Array.from(
459
+ new Set(
460
+ values
461
+ .map((entry) => typeof entry === 'string' ? entry.trim() : '')
462
+ .filter((entry) => entry.length > 0),
463
+ ),
464
+ )
465
+ }
466
+
467
+ function applyLiteralInputFormDefaultValues(
468
+ inputForm: InputFormDefinition,
469
+ value: ProjectedQueryFormValue,
470
+ ) {
471
+ const defaultValues = inputForm.DefaultValues ?? []
472
+ if (defaultValues.length === 0) {
473
+ return value
474
+ }
475
+
476
+ const target = clonePlainObject(value)
477
+ for (const binding of defaultValues) {
478
+ const resolvedValue = resolveLiteralDefaultValue(binding.Source)
479
+ if ((resolvedValue === null || resolvedValue === undefined) && binding.OmitWhenNull) {
480
+ continue
481
+ }
482
+
483
+ if (resolvedValue !== undefined) {
484
+ writeObjectPath(target, binding.TargetPath, resolvedValue)
485
+ }
486
+ }
487
+
488
+ return target
489
+ }
490
+
491
+ function resolveLiteralDefaultValue(value: PresentationValueDefinition) {
492
+ if (isPresentationValueKind(value.Kind, presentationValueKinds.literal, 'literal')) {
493
+ return parseDefaultValue(value.Literal)
494
+ }
495
+
496
+ return undefined
497
+ }
498
+
499
+ function parseDefaultValue(value: string | null | undefined) {
500
+ if (value === null || value === undefined) {
501
+ return undefined
502
+ }
503
+
504
+ if (value === 'null') {
505
+ return null
506
+ }
507
+
508
+ if (value === 'true') {
509
+ return true
510
+ }
511
+
512
+ if (value === 'false') {
513
+ return false
514
+ }
515
+
516
+ const numberValue = Number(value)
517
+ return Number.isFinite(numberValue) && value.trim() !== ''
518
+ ? numberValue
519
+ : value
520
+ }
521
+
522
+ function isInputFormFieldKind(
523
+ field: InputFormFieldDefinition,
524
+ kind: keyof typeof inputFormFieldKinds,
525
+ label: string,
526
+ ) {
527
+ return matchesPresentationEnum(
528
+ field.Kind,
529
+ createPresentationEnumDiscriminator(inputFormFieldKinds, kind, label),
530
+ )
531
+ }
532
+
533
+ function isInputFormChoiceDefaultSelection(
534
+ field: InputFormFieldDefinition,
535
+ selection: keyof typeof inputFormChoiceDefaultSelections,
536
+ label: string,
537
+ ) {
538
+ const defaultSelection = field.ChoiceSource?.DefaultSelection
539
+ return defaultSelection !== null &&
540
+ defaultSelection !== undefined &&
541
+ matchesPresentationEnum(
542
+ defaultSelection,
543
+ createPresentationEnumDiscriminator(inputFormChoiceDefaultSelections, selection, label),
544
+ )
545
+ }
546
+
547
+ function isDateTimeFilterControl(field: InputFormFieldDefinition) {
548
+ const control = field.Display?.Control
549
+ return control === inputFormFieldControlKinds.dateTimeFilter ||
550
+ control?.toString().toLocaleLowerCase() === 'datetimefilter'
551
+ }
552
+
553
+ function isPresentationValueKind(
554
+ value: unknown,
555
+ numericKind: number,
556
+ stringKind: string,
557
+ ) {
558
+ return value === numericKind ||
559
+ (typeof value === 'string' && value.toLowerCase() === stringKind.toLowerCase())
560
+ }
561
+
562
+ function clonePlainObject(value: ProjectedQueryFormValue) {
563
+ return clonePlainValue(value) as ProjectedQueryFormValue
564
+ }
565
+
566
+ function clonePlainValue(value: unknown): unknown {
567
+ if (Array.isArray(value)) {
568
+ return value.map(clonePlainValue)
569
+ }
570
+
571
+ if (value && typeof value === 'object') {
572
+ return Object.fromEntries(
573
+ Object.entries(value).map(([key, entry]) => [key, clonePlainValue(entry)]),
574
+ )
575
+ }
576
+
577
+ return value
578
+ }