@getodk/xforms-engine 0.1.0

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 (208) hide show
  1. package/README.md +44 -0
  2. package/dist/.vite/manifest.json +7 -0
  3. package/dist/XFormDOM.d.ts +31 -0
  4. package/dist/XFormDataType.d.ts +26 -0
  5. package/dist/XFormDefinition.d.ts +14 -0
  6. package/dist/body/BodyDefinition.d.ts +52 -0
  7. package/dist/body/BodyElementDefinition.d.ts +32 -0
  8. package/dist/body/RepeatDefinition.d.ts +15 -0
  9. package/dist/body/UnsupportedBodyElementDefinition.d.ts +10 -0
  10. package/dist/body/control/ControlDefinition.d.ts +16 -0
  11. package/dist/body/control/InputDefinition.d.ts +5 -0
  12. package/dist/body/control/select/ItemDefinition.d.ts +13 -0
  13. package/dist/body/control/select/ItemsetDefinition.d.ts +16 -0
  14. package/dist/body/control/select/ItemsetNodesetContext.d.ts +11 -0
  15. package/dist/body/control/select/ItemsetNodesetExpression.d.ts +5 -0
  16. package/dist/body/control/select/ItemsetValueExpression.d.ts +6 -0
  17. package/dist/body/control/select/SelectDefinition.d.ts +23 -0
  18. package/dist/body/group/BaseGroupDefinition.d.ts +46 -0
  19. package/dist/body/group/LogicalGroupDefinition.d.ts +6 -0
  20. package/dist/body/group/PresentationGroupDefinition.d.ts +11 -0
  21. package/dist/body/group/RepeatGroupDefinition.d.ts +12 -0
  22. package/dist/body/group/StructuralGroupDefinition.d.ts +6 -0
  23. package/dist/body/text/HintDefinition.d.ts +11 -0
  24. package/dist/body/text/LabelDefinition.d.ts +20 -0
  25. package/dist/body/text/TextElementDefinition.d.ts +32 -0
  26. package/dist/body/text/TextElementOutputPart.d.ts +12 -0
  27. package/dist/body/text/TextElementPart.d.ts +12 -0
  28. package/dist/body/text/TextElementReferencePart.d.ts +6 -0
  29. package/dist/body/text/TextElementStaticPart.d.ts +6 -0
  30. package/dist/client/BaseNode.d.ts +138 -0
  31. package/dist/client/EngineConfig.d.ts +78 -0
  32. package/dist/client/FormLanguage.d.ts +63 -0
  33. package/dist/client/GroupNode.d.ts +24 -0
  34. package/dist/client/OpaqueReactiveObjectFactory.d.ts +70 -0
  35. package/dist/client/RepeatInstanceNode.d.ts +28 -0
  36. package/dist/client/RepeatRangeNode.d.ts +94 -0
  37. package/dist/client/RootNode.d.ts +31 -0
  38. package/dist/client/SelectNode.d.ts +60 -0
  39. package/dist/client/StringNode.d.ts +41 -0
  40. package/dist/client/SubtreeNode.d.ts +52 -0
  41. package/dist/client/TextRange.d.ts +55 -0
  42. package/dist/client/hierarchy.d.ts +48 -0
  43. package/dist/client/index.d.ts +11 -0
  44. package/dist/client/node-types.d.ts +1 -0
  45. package/dist/expression/DependencyContext.d.ts +12 -0
  46. package/dist/expression/DependentExpression.d.ts +43 -0
  47. package/dist/index.d.ts +16 -0
  48. package/dist/index.js +37622 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/instance/Group.d.ts +31 -0
  51. package/dist/instance/RepeatInstance.d.ts +60 -0
  52. package/dist/instance/RepeatRange.d.ts +81 -0
  53. package/dist/instance/Root.d.ts +70 -0
  54. package/dist/instance/SelectField.d.ts +45 -0
  55. package/dist/instance/StringField.d.ts +39 -0
  56. package/dist/instance/Subtree.d.ts +30 -0
  57. package/dist/instance/abstract/DescendantNode.d.ts +76 -0
  58. package/dist/instance/abstract/InstanceNode.d.ts +107 -0
  59. package/dist/instance/children.d.ts +2 -0
  60. package/dist/instance/hierarchy.d.ts +12 -0
  61. package/dist/instance/identity.d.ts +7 -0
  62. package/dist/instance/index.d.ts +8 -0
  63. package/dist/instance/internal-api/EvaluationContext.d.ts +34 -0
  64. package/dist/instance/internal-api/InstanceConfig.d.ts +8 -0
  65. package/dist/instance/internal-api/SubscribableDependency.d.ts +59 -0
  66. package/dist/instance/internal-api/TranslationContext.d.ts +4 -0
  67. package/dist/instance/internal-api/ValueContext.d.ts +22 -0
  68. package/dist/instance/resource.d.ts +10 -0
  69. package/dist/instance/text/FormattedTextStub.d.ts +1 -0
  70. package/dist/instance/text/TextChunk.d.ts +11 -0
  71. package/dist/instance/text/TextRange.d.ts +10 -0
  72. package/dist/lib/dom/query.d.ts +20 -0
  73. package/dist/lib/reactivity/createChildrenState.d.ts +36 -0
  74. package/dist/lib/reactivity/createComputedExpression.d.ts +12 -0
  75. package/dist/lib/reactivity/createSelectItems.d.ts +16 -0
  76. package/dist/lib/reactivity/createValueState.d.ts +44 -0
  77. package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +18 -0
  78. package/dist/lib/reactivity/node-state/createClientState.d.ts +9 -0
  79. package/dist/lib/reactivity/node-state/createCurrentState.d.ts +6 -0
  80. package/dist/lib/reactivity/node-state/createEngineState.d.ts +5 -0
  81. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +22 -0
  82. package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +6 -0
  83. package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +139 -0
  84. package/dist/lib/reactivity/node-state/representations.d.ts +25 -0
  85. package/dist/lib/reactivity/scope.d.ts +23 -0
  86. package/dist/lib/reactivity/text/createFieldHint.d.ts +5 -0
  87. package/dist/lib/reactivity/text/createNodeLabel.d.ts +5 -0
  88. package/dist/lib/reactivity/text/createTextRange.d.ts +19 -0
  89. package/dist/lib/reactivity/types.d.ts +21 -0
  90. package/dist/lib/unique-id.d.ts +27 -0
  91. package/dist/lib/xpath/analysis.d.ts +22 -0
  92. package/dist/model/BindComputation.d.ts +30 -0
  93. package/dist/model/BindDefinition.d.ts +31 -0
  94. package/dist/model/BindElement.d.ts +6 -0
  95. package/dist/model/DescendentNodeDefinition.d.ts +25 -0
  96. package/dist/model/ModelBindMap.d.ts +15 -0
  97. package/dist/model/ModelDefinition.d.ts +10 -0
  98. package/dist/model/NodeDefinition.d.ts +74 -0
  99. package/dist/model/RepeatInstanceDefinition.d.ts +15 -0
  100. package/dist/model/RepeatSequenceDefinition.d.ts +19 -0
  101. package/dist/model/RepeatTemplateDefinition.d.ts +29 -0
  102. package/dist/model/RootDefinition.d.ts +24 -0
  103. package/dist/model/SubtreeDefinition.d.ts +14 -0
  104. package/dist/model/ValueNodeDefinition.d.ts +15 -0
  105. package/dist/solid.js +37273 -0
  106. package/dist/solid.js.map +1 -0
  107. package/package.json +87 -0
  108. package/src/XFormDOM.ts +224 -0
  109. package/src/XFormDataType.ts +64 -0
  110. package/src/XFormDefinition.ts +40 -0
  111. package/src/body/BodyDefinition.ts +202 -0
  112. package/src/body/BodyElementDefinition.ts +62 -0
  113. package/src/body/RepeatDefinition.ts +54 -0
  114. package/src/body/UnsupportedBodyElementDefinition.ts +17 -0
  115. package/src/body/control/ControlDefinition.ts +42 -0
  116. package/src/body/control/InputDefinition.ts +9 -0
  117. package/src/body/control/select/ItemDefinition.ts +31 -0
  118. package/src/body/control/select/ItemsetDefinition.ts +36 -0
  119. package/src/body/control/select/ItemsetNodesetContext.ts +26 -0
  120. package/src/body/control/select/ItemsetNodesetExpression.ts +8 -0
  121. package/src/body/control/select/ItemsetValueExpression.ts +11 -0
  122. package/src/body/control/select/SelectDefinition.ts +74 -0
  123. package/src/body/group/BaseGroupDefinition.ts +137 -0
  124. package/src/body/group/LogicalGroupDefinition.ts +11 -0
  125. package/src/body/group/PresentationGroupDefinition.ts +28 -0
  126. package/src/body/group/RepeatGroupDefinition.ts +91 -0
  127. package/src/body/group/StructuralGroupDefinition.ts +11 -0
  128. package/src/body/text/HintDefinition.ts +26 -0
  129. package/src/body/text/LabelDefinition.ts +54 -0
  130. package/src/body/text/TextElementDefinition.ts +97 -0
  131. package/src/body/text/TextElementOutputPart.ts +27 -0
  132. package/src/body/text/TextElementPart.ts +31 -0
  133. package/src/body/text/TextElementReferencePart.ts +21 -0
  134. package/src/body/text/TextElementStaticPart.ts +26 -0
  135. package/src/client/BaseNode.ts +180 -0
  136. package/src/client/EngineConfig.ts +83 -0
  137. package/src/client/FormLanguage.ts +77 -0
  138. package/src/client/GroupNode.ts +33 -0
  139. package/src/client/OpaqueReactiveObjectFactory.ts +100 -0
  140. package/src/client/README.md +39 -0
  141. package/src/client/RepeatInstanceNode.ts +41 -0
  142. package/src/client/RepeatRangeNode.ts +100 -0
  143. package/src/client/RootNode.ts +36 -0
  144. package/src/client/SelectNode.ts +69 -0
  145. package/src/client/StringNode.ts +46 -0
  146. package/src/client/SubtreeNode.ts +57 -0
  147. package/src/client/TextRange.ts +63 -0
  148. package/src/client/hierarchy.ts +63 -0
  149. package/src/client/index.ts +29 -0
  150. package/src/client/node-types.ts +10 -0
  151. package/src/expression/DependencyContext.ts +53 -0
  152. package/src/expression/DependentExpression.ts +102 -0
  153. package/src/index.ts +35 -0
  154. package/src/instance/Group.ts +82 -0
  155. package/src/instance/RepeatInstance.ts +164 -0
  156. package/src/instance/RepeatRange.ts +214 -0
  157. package/src/instance/Root.ts +264 -0
  158. package/src/instance/SelectField.ts +204 -0
  159. package/src/instance/StringField.ts +93 -0
  160. package/src/instance/Subtree.ts +79 -0
  161. package/src/instance/abstract/DescendantNode.ts +182 -0
  162. package/src/instance/abstract/InstanceNode.ts +257 -0
  163. package/src/instance/children.ts +52 -0
  164. package/src/instance/hierarchy.ts +54 -0
  165. package/src/instance/identity.ts +11 -0
  166. package/src/instance/index.ts +37 -0
  167. package/src/instance/internal-api/EvaluationContext.ts +41 -0
  168. package/src/instance/internal-api/InstanceConfig.ts +9 -0
  169. package/src/instance/internal-api/SubscribableDependency.ts +61 -0
  170. package/src/instance/internal-api/TranslationContext.ts +5 -0
  171. package/src/instance/internal-api/ValueContext.ts +27 -0
  172. package/src/instance/resource.ts +75 -0
  173. package/src/instance/text/FormattedTextStub.ts +8 -0
  174. package/src/instance/text/TextChunk.ts +20 -0
  175. package/src/instance/text/TextRange.ts +23 -0
  176. package/src/lib/dom/query.ts +49 -0
  177. package/src/lib/reactivity/createChildrenState.ts +60 -0
  178. package/src/lib/reactivity/createComputedExpression.ts +114 -0
  179. package/src/lib/reactivity/createSelectItems.ts +163 -0
  180. package/src/lib/reactivity/createValueState.ts +258 -0
  181. package/src/lib/reactivity/materializeCurrentStateChildren.ts +121 -0
  182. package/src/lib/reactivity/node-state/createClientState.ts +51 -0
  183. package/src/lib/reactivity/node-state/createCurrentState.ts +27 -0
  184. package/src/lib/reactivity/node-state/createEngineState.ts +18 -0
  185. package/src/lib/reactivity/node-state/createSharedNodeState.ts +79 -0
  186. package/src/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.ts +85 -0
  187. package/src/lib/reactivity/node-state/createSpecifiedState.ts +229 -0
  188. package/src/lib/reactivity/node-state/representations.ts +64 -0
  189. package/src/lib/reactivity/scope.ts +106 -0
  190. package/src/lib/reactivity/text/createFieldHint.ts +16 -0
  191. package/src/lib/reactivity/text/createNodeLabel.ts +16 -0
  192. package/src/lib/reactivity/text/createTextRange.ts +155 -0
  193. package/src/lib/reactivity/types.ts +27 -0
  194. package/src/lib/unique-id.ts +34 -0
  195. package/src/lib/xpath/analysis.ts +241 -0
  196. package/src/model/BindComputation.ts +88 -0
  197. package/src/model/BindDefinition.ts +104 -0
  198. package/src/model/BindElement.ts +8 -0
  199. package/src/model/DescendentNodeDefinition.ts +56 -0
  200. package/src/model/ModelBindMap.ts +71 -0
  201. package/src/model/ModelDefinition.ts +19 -0
  202. package/src/model/NodeDefinition.ts +146 -0
  203. package/src/model/RepeatInstanceDefinition.ts +39 -0
  204. package/src/model/RepeatSequenceDefinition.ts +53 -0
  205. package/src/model/RepeatTemplateDefinition.ts +150 -0
  206. package/src/model/RootDefinition.ts +121 -0
  207. package/src/model/SubtreeDefinition.ts +50 -0
  208. package/src/model/ValueNodeDefinition.ts +39 -0
@@ -0,0 +1,264 @@
1
+ import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
+ import type { Accessor, Signal } from 'solid-js';
3
+ import { createSignal } from 'solid-js';
4
+ import type { XFormDOM } from '../XFormDOM.ts';
5
+ import type { ActiveLanguage, FormLanguage, FormLanguages } from '../client/FormLanguage.ts';
6
+ import type { RootNode } from '../client/RootNode.ts';
7
+ import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
8
+ import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
9
+ import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
10
+ import { materializeCurrentStateChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
11
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
12
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
13
+ import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
14
+ import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
15
+ import type { RootDefinition } from '../model/RootDefinition.ts';
16
+ import { InstanceNode } from './abstract/InstanceNode.ts';
17
+ import { buildChildren } from './children.ts';
18
+ import type { GeneralChildNode } from './hierarchy.ts';
19
+ import type { NodeID } from './identity.ts';
20
+ import type { EvaluationContext, EvaluationContextRoot } from './internal-api/EvaluationContext.ts';
21
+ import type { InstanceConfig } from './internal-api/InstanceConfig.ts';
22
+ import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
23
+ import type { TranslationContext } from './internal-api/TranslationContext.ts';
24
+
25
+ interface RootStateSpec {
26
+ readonly reference: string;
27
+ readonly readonly: boolean;
28
+ readonly relevant: boolean;
29
+ readonly required: boolean;
30
+ readonly label: null;
31
+ readonly hint: null;
32
+ readonly children: Accessor<readonly NodeID[]>;
33
+ readonly valueOptions: null;
34
+ readonly value: null;
35
+
36
+ // Root-specific
37
+ readonly activeLanguage: Signal<ActiveLanguage>;
38
+ }
39
+
40
+ // Subset of types expected from evaluator
41
+ interface ItextTranslations {
42
+ getActiveLanguage(): string | null;
43
+ getLanguages(): readonly string[];
44
+ }
45
+
46
+ interface InitialLanguageState {
47
+ readonly defaultLanguage: ActiveLanguage;
48
+ readonly languages: FormLanguages;
49
+ }
50
+
51
+ // TODO: it's really very silly that the XPath evaluator is the current
52
+ // definitional source of truth for translation stuff... even though it currently makes sense that that's where it's first derived.
53
+ const getInitialLanguageState = (translations: ItextTranslations): InitialLanguageState => {
54
+ const activeLanguageName = translations.getActiveLanguage();
55
+
56
+ if (activeLanguageName == null) {
57
+ const defaultLanguage: ActiveLanguage = {
58
+ isSyntheticDefault: true,
59
+ language: '',
60
+ };
61
+ const languages = [defaultLanguage] satisfies FormLanguages;
62
+
63
+ return {
64
+ defaultLanguage,
65
+ languages,
66
+ };
67
+ }
68
+
69
+ const languageNames = translations.getLanguages();
70
+
71
+ const inactiveLanguages = languageNames
72
+ .filter((languageName) => {
73
+ return languageName !== activeLanguageName;
74
+ })
75
+ .map((language): FormLanguage => {
76
+ return { language };
77
+ });
78
+
79
+ const languages = [
80
+ { language: activeLanguageName } satisfies FormLanguage,
81
+
82
+ ...inactiveLanguages,
83
+ ] satisfies FormLanguages;
84
+ const [defaultLanguage] = languages;
85
+
86
+ return {
87
+ defaultLanguage,
88
+ languages,
89
+ };
90
+ };
91
+
92
+ export class Root
93
+ extends InstanceNode<RootDefinition, RootStateSpec, GeneralChildNode>
94
+ implements
95
+ RootNode,
96
+ EvaluationContext,
97
+ EvaluationContextRoot,
98
+ SubscribableDependency,
99
+ TranslationContext
100
+ {
101
+ static async initialize(
102
+ xformDOM: XFormDOM,
103
+ definition: RootDefinition,
104
+ engineConfig: InstanceConfig
105
+ ): Promise<Root> {
106
+ const instance = new Root(xformDOM, definition, engineConfig);
107
+
108
+ await instance.formStateInitialized();
109
+
110
+ return instance;
111
+ }
112
+
113
+ private readonly childrenState: ChildrenState<GeneralChildNode>;
114
+
115
+ // InstanceNode
116
+ protected readonly state: SharedNodeState<RootStateSpec>;
117
+ protected readonly engineState: EngineState<RootStateSpec>;
118
+
119
+ // RootNode
120
+ readonly nodeType = 'root';
121
+
122
+ readonly currentState: MaterializedChildren<CurrentState<RootStateSpec>, GeneralChildNode>;
123
+
124
+ protected readonly instanceDOM: XFormDOM;
125
+
126
+ // BaseNode
127
+ readonly root = this;
128
+
129
+ // EvaluationContext
130
+ readonly evaluator: XFormsXPathEvaluator;
131
+
132
+ private readonly rootReference: string;
133
+
134
+ override get contextReference(): string {
135
+ return this.rootReference;
136
+ }
137
+
138
+ readonly contextNode: Element;
139
+
140
+ // RootNode
141
+ override readonly parent = null;
142
+
143
+ readonly languages: FormLanguages;
144
+
145
+ // TranslationContext
146
+ get activeLanguage(): ActiveLanguage {
147
+ return this.engineState.activeLanguage;
148
+ }
149
+
150
+ protected constructor(
151
+ xformDOM: XFormDOM,
152
+ definition: RootDefinition,
153
+ engineConfig: InstanceConfig
154
+ ) {
155
+ super(engineConfig, null, definition);
156
+
157
+ const childrenState = createChildrenState<Root, GeneralChildNode>(this);
158
+
159
+ this.childrenState = childrenState;
160
+
161
+ const reference = definition.nodeset;
162
+
163
+ this.rootReference = reference;
164
+
165
+ const instanceDOM = xformDOM.createInstance();
166
+ const evaluator = instanceDOM.primaryInstanceEvaluator;
167
+ const { translations } = evaluator;
168
+ const { defaultLanguage, languages } = getInitialLanguageState(translations);
169
+ const state = createSharedNodeState(
170
+ this.scope,
171
+ {
172
+ activeLanguage: createSignal<ActiveLanguage>(defaultLanguage),
173
+ reference,
174
+ label: null,
175
+ hint: null,
176
+ readonly: false,
177
+ relevant: true,
178
+ required: false,
179
+ valueOptions: null,
180
+ value: null,
181
+ children: childrenState.childIds,
182
+ },
183
+ {
184
+ clientStateFactory: engineConfig.stateFactory,
185
+ }
186
+ );
187
+
188
+ this.state = state;
189
+ this.engineState = state.engineState;
190
+ this.currentState = materializeCurrentStateChildren(state.currentState, childrenState);
191
+
192
+ const contextNode = instanceDOM.xformDocument.createElement(definition.nodeName);
193
+
194
+ instanceDOM.primaryInstanceRoot.replaceWith(contextNode);
195
+
196
+ this.evaluator = evaluator;
197
+ this.contextNode = contextNode;
198
+ this.instanceDOM = instanceDOM;
199
+ this.languages = languages;
200
+
201
+ childrenState.setChildren(buildChildren(this));
202
+ }
203
+
204
+ /**
205
+ * Waits until form state is fully initialized.
206
+ *
207
+ * As much as possible, all instance state computations are implemented so
208
+ * that they complete synchronously.
209
+ *
210
+ * There is currently one exception: because instance nodes may form
211
+ * computation dependencies into their descendants as well as their ancestors,
212
+ * there is an allowance **during form initialization only** to account for
213
+ * this chicken/egg scenario. Note that this allowance is intentionally,
214
+ * strictly limited: if form state initialization is not resolved within a
215
+ * single microtask tick we throw/reject.
216
+ *
217
+ * All subsequent computations are always performed synchronously (and we will
218
+ * use tests to validate this, by utilizing the synchronously returned `Root`
219
+ * state from client-facing write interfaces).
220
+ */
221
+ async formStateInitialized(): Promise<void> {
222
+ await new Promise<void>((resolve) => {
223
+ queueMicrotask(resolve);
224
+ });
225
+
226
+ if (!this.isStateInitialized()) {
227
+ throw new Error(
228
+ 'Form state initialization failed to complete in a single frame. Has some aspect of reactive computation been made asynchronous by mistake?'
229
+ );
230
+ }
231
+ }
232
+
233
+ // InstanceNode
234
+ protected computeReference(_parent: null, definition: RootDefinition): string {
235
+ return definition.nodeset;
236
+ }
237
+
238
+ getChildren(): readonly GeneralChildNode[] {
239
+ return this.childrenState.getChildren();
240
+ }
241
+
242
+ // RootNode
243
+ setLanguage(language: FormLanguage): Root {
244
+ const activeLanguage = this.languages.find((formLanguage) => {
245
+ return formLanguage.language === language.language;
246
+ });
247
+
248
+ if (activeLanguage == null) {
249
+ throw new Error(`Language "${language.language}" not available`);
250
+ }
251
+
252
+ this.evaluator.translations.setActiveLanguage(activeLanguage.language);
253
+ this.state.setProperty('activeLanguage', activeLanguage);
254
+
255
+ return this;
256
+ }
257
+
258
+ // SubscribableDependency
259
+ override subscribe(): void {
260
+ super.subscribe();
261
+
262
+ this.engineState.activeLanguage;
263
+ }
264
+ }
@@ -0,0 +1,204 @@
1
+ import { xmlXPathWhitespaceSeparatedList } from '@getodk/common/lib/string/whitespace.ts';
2
+ import type { Accessor } from 'solid-js';
3
+ import { untrack } from 'solid-js';
4
+ import type { AnySelectDefinition } from '../body/control/select/SelectDefinition.ts';
5
+ import type { SelectItem, SelectNode } from '../client/SelectNode.ts';
6
+ import type { TextRange } from '../index.ts';
7
+ import { createSelectItems } from '../lib/reactivity/createSelectItems.ts';
8
+ import { createValueState } from '../lib/reactivity/createValueState.ts';
9
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
10
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
11
+ import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
12
+ import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
13
+ import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
14
+ import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
15
+ import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
16
+ import type { ValueNodeDefinition } from '../model/ValueNodeDefinition.ts';
17
+ import type { Root } from './Root.ts';
18
+ import type { DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
19
+ import { DescendantNode } from './abstract/DescendantNode.ts';
20
+ import type { GeneralParentNode } from './hierarchy.ts';
21
+ import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
22
+ import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
23
+ import type { ValueContext } from './internal-api/ValueContext.ts';
24
+
25
+ export interface SelectFieldDefinition extends ValueNodeDefinition {
26
+ readonly bodyElement: AnySelectDefinition;
27
+ }
28
+
29
+ interface SelectFieldStateSpec extends DescendantNodeStateSpec<readonly SelectItem[]> {
30
+ readonly label: Accessor<TextRange<'label'> | null>;
31
+ readonly hint: Accessor<TextRange<'hint'> | null>;
32
+ readonly children: null;
33
+ readonly value: SimpleAtomicState<readonly SelectItem[]>;
34
+ readonly valueOptions: Accessor<readonly SelectItem[]>;
35
+ }
36
+
37
+ export class SelectField
38
+ extends DescendantNode<SelectFieldDefinition, SelectFieldStateSpec, null>
39
+ implements
40
+ SelectNode,
41
+ EvaluationContext,
42
+ SubscribableDependency,
43
+ ValueContext<readonly SelectItem[]>
44
+ {
45
+ private readonly selectExclusive: boolean;
46
+
47
+ // InstanceNode
48
+ protected readonly state: SharedNodeState<SelectFieldStateSpec>;
49
+ protected override engineState: EngineState<SelectFieldStateSpec>;
50
+
51
+ // SelectNode
52
+ readonly nodeType = 'select';
53
+
54
+ readonly currentState: CurrentState<SelectFieldStateSpec>;
55
+
56
+ // ValueContext
57
+ readonly encodeValue = (runtimeValue: readonly SelectItem[]): string => {
58
+ const itemValues = new Set(runtimeValue.map(({ value }) => value));
59
+
60
+ return Array.from(itemValues).join(' ');
61
+ };
62
+
63
+ readonly decodeValue = (instanceValue: string): readonly SelectItem[] => {
64
+ return this.scope.runTask(() => {
65
+ const values = xmlXPathWhitespaceSeparatedList(instanceValue, {
66
+ ignoreEmpty: true,
67
+ });
68
+
69
+ const items = this.getSelectItemsByValue();
70
+
71
+ return values
72
+ .map((value) => {
73
+ return items.get(value);
74
+ })
75
+ .filter((item): item is SelectItem => {
76
+ return item != null;
77
+ });
78
+ });
79
+ };
80
+
81
+ protected readonly getValueOptions: Accessor<readonly SelectItem[]>;
82
+
83
+ constructor(parent: GeneralParentNode, definition: SelectFieldDefinition) {
84
+ super(parent, definition);
85
+
86
+ this.selectExclusive = definition.bodyElement.type === 'select1';
87
+
88
+ const valueOptions = createSelectItems(this);
89
+
90
+ this.getValueOptions = valueOptions;
91
+
92
+ const state = createSharedNodeState(
93
+ this.scope,
94
+ {
95
+ ...this.buildSharedStateSpec(parent, definition),
96
+
97
+ label: createNodeLabel(this, definition),
98
+ hint: createFieldHint(this, definition),
99
+ children: null,
100
+ value: createValueState(this),
101
+ valueOptions,
102
+ },
103
+ {
104
+ clientStateFactory: this.engineConfig.stateFactory,
105
+ }
106
+ );
107
+
108
+ this.state = state;
109
+ this.engineState = state.engineState;
110
+ this.currentState = state.currentState;
111
+ }
112
+
113
+ protected getSelectItemsByValue(
114
+ valueOptions: readonly SelectItem[] = this.getValueOptions()
115
+ ): ReadonlyMap<string, SelectItem> {
116
+ return new Map(
117
+ valueOptions.map((item) => {
118
+ return [item.value, item];
119
+ })
120
+ );
121
+ }
122
+
123
+ protected computeReference(parent: GeneralParentNode): string {
124
+ return this.computeChildStepReference(parent);
125
+ }
126
+
127
+ protected updateSelectedItemValues(values: readonly string[]) {
128
+ const itemsByValue = untrack(() => this.getSelectItemsByValue());
129
+
130
+ const items = values.flatMap((value) => {
131
+ const item = itemsByValue.get(value);
132
+
133
+ if (item == null) {
134
+ return [];
135
+ }
136
+
137
+ return item ?? [];
138
+ });
139
+
140
+ this.state.setProperty('value', items);
141
+ }
142
+
143
+ protected setSelectedItemValue(value: string | null) {
144
+ if (value == null) {
145
+ this.state.setProperty('value', []);
146
+
147
+ return;
148
+ }
149
+
150
+ this.updateSelectedItemValues([value]);
151
+ }
152
+
153
+ // SelectNode
154
+ select(selectedItem: SelectItem): Root {
155
+ const { engineState, root } = this;
156
+
157
+ if (this.selectExclusive) {
158
+ this.setSelectedItemValue(selectedItem.value);
159
+
160
+ return root;
161
+ }
162
+
163
+ const currentValues = engineState.value.map(({ value }) => {
164
+ return value;
165
+ });
166
+
167
+ const selectedValue = selectedItem.value;
168
+
169
+ if (currentValues.includes(selectedValue)) {
170
+ return root;
171
+ }
172
+
173
+ this.updateSelectedItemValues(currentValues.concat(selectedValue));
174
+
175
+ return root;
176
+ }
177
+
178
+ deselect(deselectedItem: SelectItem): Root {
179
+ const { engineState, root } = this;
180
+
181
+ const currentValues = engineState.value.map(({ value }) => {
182
+ return value;
183
+ });
184
+
185
+ const selectedValue = deselectedItem.value;
186
+
187
+ if (!currentValues.includes(selectedValue)) {
188
+ return root;
189
+ }
190
+
191
+ const updatedValues = currentValues.filter((value) => {
192
+ return value !== selectedValue;
193
+ });
194
+
195
+ this.updateSelectedItemValues(updatedValues);
196
+
197
+ return root;
198
+ }
199
+
200
+ // InstanceNode
201
+ getChildren(): readonly [] {
202
+ return [];
203
+ }
204
+ }
@@ -0,0 +1,93 @@
1
+ import { identity } from '@getodk/common/lib/identity.ts';
2
+ import type { Accessor } from 'solid-js';
3
+ import type { InputDefinition } from '../body/control/InputDefinition.ts';
4
+ import type { StringNode } from '../client/StringNode.ts';
5
+ import type { TextRange } from '../index.ts';
6
+ import { createValueState } from '../lib/reactivity/createValueState.ts';
7
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
8
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
9
+ import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
10
+ import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
11
+ import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
12
+ import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
13
+ import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
14
+ import type { ValueNodeDefinition } from '../model/ValueNodeDefinition.ts';
15
+ import type { Root } from './Root.ts';
16
+ import type { DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
17
+ import { DescendantNode } from './abstract/DescendantNode.ts';
18
+ import type { GeneralParentNode } from './hierarchy.ts';
19
+ import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
20
+ import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
21
+ import type { ValueContext } from './internal-api/ValueContext.ts';
22
+
23
+ export interface StringFieldDefinition extends ValueNodeDefinition {
24
+ readonly bodyElement: InputDefinition | null;
25
+ }
26
+
27
+ interface StringFieldStateSpec extends DescendantNodeStateSpec<string> {
28
+ readonly label: Accessor<TextRange<'label'> | null>;
29
+ readonly hint: Accessor<TextRange<'hint'> | null>;
30
+ readonly children: null;
31
+ readonly value: SimpleAtomicState<string>;
32
+ readonly valueOptions: null;
33
+ }
34
+
35
+ export class StringField
36
+ extends DescendantNode<StringFieldDefinition, StringFieldStateSpec, null>
37
+ implements StringNode, EvaluationContext, SubscribableDependency, ValueContext<string>
38
+ {
39
+ protected readonly state: SharedNodeState<StringFieldStateSpec>;
40
+
41
+ // InstanceNode
42
+ protected engineState: EngineState<StringFieldStateSpec>;
43
+
44
+ // StringNode
45
+ readonly nodeType = 'string';
46
+
47
+ readonly currentState: CurrentState<StringFieldStateSpec>;
48
+
49
+ // ValueContext
50
+ readonly encodeValue = identity<string>;
51
+
52
+ readonly decodeValue = identity<string>;
53
+
54
+ constructor(parent: GeneralParentNode, definition: StringFieldDefinition) {
55
+ super(parent, definition);
56
+
57
+ const state = createSharedNodeState(
58
+ this.scope,
59
+ {
60
+ ...this.buildSharedStateSpec(parent, definition),
61
+
62
+ label: createNodeLabel(this, definition),
63
+ hint: createFieldHint(this, definition),
64
+ children: null,
65
+ valueOptions: null,
66
+ value: createValueState(this),
67
+ },
68
+ {
69
+ clientStateFactory: this.engineConfig.stateFactory,
70
+ }
71
+ );
72
+
73
+ this.state = state;
74
+ this.engineState = state.engineState;
75
+ this.currentState = state.currentState;
76
+ }
77
+
78
+ protected computeReference(parent: GeneralParentNode): string {
79
+ return this.computeChildStepReference(parent);
80
+ }
81
+
82
+ // InstanceNode
83
+ getChildren(): readonly [] {
84
+ return [];
85
+ }
86
+
87
+ // StringNode
88
+ setValue(value: string): Root {
89
+ this.state.setProperty('value', value);
90
+
91
+ return this.root;
92
+ }
93
+ }
@@ -0,0 +1,79 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import type { SubtreeDefinition, SubtreeNode } from '../client/SubtreeNode.ts';
3
+ import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
4
+ import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
5
+ import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
6
+ import { materializeCurrentStateChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
7
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
8
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
9
+ import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
10
+ import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
11
+ import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
12
+ import { DescendantNode } from './abstract/DescendantNode.ts';
13
+ import { buildChildren } from './children.ts';
14
+ import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
15
+ import type { NodeID } from './identity.ts';
16
+ import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
17
+ import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
18
+
19
+ interface SubtreeStateSpec extends DescendantNodeSharedStateSpec {
20
+ readonly label: null;
21
+ readonly hint: null;
22
+ readonly children: Accessor<readonly NodeID[]>;
23
+ readonly valueOptions: null;
24
+ readonly value: null;
25
+ }
26
+
27
+ export class Subtree
28
+ extends DescendantNode<SubtreeDefinition, SubtreeStateSpec, GeneralChildNode>
29
+ implements SubtreeNode, EvaluationContext, SubscribableDependency
30
+ {
31
+ private readonly childrenState: ChildrenState<GeneralChildNode>;
32
+
33
+ // InstanceNode
34
+ protected readonly state: SharedNodeState<SubtreeStateSpec>;
35
+ protected engineState: EngineState<SubtreeStateSpec>;
36
+
37
+ // SubtreeNode
38
+ readonly nodeType = 'subtree';
39
+
40
+ readonly currentState: MaterializedChildren<CurrentState<SubtreeStateSpec>, GeneralChildNode>;
41
+
42
+ constructor(parent: GeneralParentNode, definition: SubtreeDefinition) {
43
+ super(parent, definition);
44
+
45
+ const childrenState = createChildrenState<Subtree, GeneralChildNode>(this);
46
+
47
+ this.childrenState = childrenState;
48
+
49
+ const state = createSharedNodeState(
50
+ this.scope,
51
+ {
52
+ ...this.buildSharedStateSpec(parent, definition),
53
+
54
+ label: null,
55
+ hint: null,
56
+ children: childrenState.childIds,
57
+ valueOptions: null,
58
+ value: null,
59
+ },
60
+ {
61
+ clientStateFactory: this.engineConfig.stateFactory,
62
+ }
63
+ );
64
+
65
+ this.state = state;
66
+ this.engineState = state.engineState;
67
+ this.currentState = materializeCurrentStateChildren(state.currentState, childrenState);
68
+
69
+ childrenState.setChildren(buildChildren(this));
70
+ }
71
+
72
+ protected computeReference(parent: GeneralParentNode): string {
73
+ return this.computeChildStepReference(parent);
74
+ }
75
+
76
+ getChildren(): readonly GeneralChildNode[] {
77
+ return this.childrenState.getChildren();
78
+ }
79
+ }