@getodk/xforms-engine 0.1.1 → 0.3.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 (228) hide show
  1. package/dist/body/BodyDefinition.d.ts +24 -7
  2. package/dist/body/BodyElementDefinition.d.ts +4 -3
  3. package/dist/body/RepeatElementDefinition.d.ts +19 -0
  4. package/dist/body/appearance/inputAppearanceParser.d.ts +4 -0
  5. package/dist/body/appearance/selectAppearanceParser.d.ts +4 -0
  6. package/dist/body/appearance/structureElementAppearanceParser.d.ts +4 -0
  7. package/dist/body/control/ControlDefinition.d.ts +4 -2
  8. package/dist/body/control/InputDefinition.d.ts +5 -0
  9. package/dist/body/control/select/ItemDefinition.d.ts +2 -2
  10. package/dist/body/control/select/ItemsetDefinition.d.ts +5 -4
  11. package/dist/body/control/select/SelectDefinition.d.ts +11 -1
  12. package/dist/body/group/BaseGroupDefinition.d.ts +4 -9
  13. package/dist/body/group/PresentationGroupDefinition.d.ts +1 -1
  14. package/dist/client/BaseNode.d.ts +74 -3
  15. package/dist/client/GroupNode.d.ts +7 -2
  16. package/dist/client/ModelValueNode.d.ts +37 -0
  17. package/dist/client/NodeAppearances.d.ts +15 -0
  18. package/dist/client/NoteNode.d.ts +53 -0
  19. package/dist/client/RootNode.d.ts +21 -0
  20. package/dist/client/SelectNode.d.ts +8 -3
  21. package/dist/client/StringNode.d.ts +8 -3
  22. package/dist/client/SubtreeNode.d.ts +3 -0
  23. package/dist/client/TextRange.d.ts +85 -2
  24. package/dist/client/constants.d.ts +9 -0
  25. package/dist/client/hierarchy.d.ts +14 -9
  26. package/dist/client/node-types.d.ts +2 -1
  27. package/dist/client/{RepeatRangeNode.d.ts → repeat/BaseRepeatRangeNode.d.ts} +19 -15
  28. package/dist/client/{RepeatInstanceNode.d.ts → repeat/RepeatInstanceNode.d.ts} +11 -7
  29. package/dist/client/repeat/RepeatRangeControlledNode.d.ts +19 -0
  30. package/dist/client/repeat/RepeatRangeUncontrolledNode.d.ts +20 -0
  31. package/dist/client/validation.d.ts +163 -0
  32. package/dist/expression/DependentExpression.d.ts +12 -8
  33. package/dist/index.d.ts +9 -4
  34. package/dist/index.js +3173 -960
  35. package/dist/index.js.map +1 -1
  36. package/dist/instance/Group.d.ts +6 -4
  37. package/dist/instance/ModelValue.d.ts +40 -0
  38. package/dist/instance/Note.d.ts +42 -0
  39. package/dist/instance/Root.d.ts +10 -23
  40. package/dist/instance/SelectField.d.ts +12 -6
  41. package/dist/instance/StringField.d.ts +13 -7
  42. package/dist/instance/Subtree.d.ts +3 -1
  43. package/dist/instance/abstract/DescendantNode.d.ts +16 -9
  44. package/dist/instance/abstract/InstanceNode.d.ts +28 -29
  45. package/dist/instance/hierarchy.d.ts +10 -5
  46. package/dist/instance/internal-api/EvaluationContext.d.ts +5 -4
  47. package/dist/instance/internal-api/ValidationContext.d.ts +21 -0
  48. package/dist/instance/internal-api/ValueContext.d.ts +2 -2
  49. package/dist/instance/repeat/BaseRepeatRange.d.ts +160 -0
  50. package/dist/instance/{RepeatInstance.d.ts → repeat/RepeatInstance.d.ts} +38 -13
  51. package/dist/instance/repeat/RepeatRangeControlled.d.ts +16 -0
  52. package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +35 -0
  53. package/dist/instance/text/TextRange.d.ts +4 -4
  54. package/dist/lib/TokenListParser.d.ts +84 -0
  55. package/dist/lib/dom/query.d.ts +5 -0
  56. package/dist/lib/reactivity/createComputedExpression.d.ts +6 -1
  57. package/dist/lib/reactivity/createNoteReadonlyThunk.d.ts +5 -0
  58. package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +2 -1
  59. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +1 -1
  60. package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +1 -1
  61. package/dist/lib/reactivity/text/createFieldHint.d.ts +3 -3
  62. package/dist/lib/reactivity/text/createNodeLabel.d.ts +2 -2
  63. package/dist/lib/reactivity/text/createNoteText.d.ts +25 -0
  64. package/dist/lib/reactivity/text/createTextRange.d.ts +5 -7
  65. package/dist/lib/reactivity/validation/createAggregatedViolations.d.ts +9 -0
  66. package/dist/lib/reactivity/validation/createValidation.d.ts +18 -0
  67. package/dist/model/BindDefinition.d.ts +4 -2
  68. package/dist/model/BindElement.d.ts +1 -0
  69. package/dist/model/DescendentNodeDefinition.d.ts +1 -2
  70. package/dist/model/{ValueNodeDefinition.d.ts → LeafNodeDefinition.d.ts} +3 -4
  71. package/dist/model/NodeDefinition.d.ts +16 -16
  72. package/dist/model/RepeatInstanceDefinition.d.ts +5 -6
  73. package/dist/model/RepeatRangeDefinition.d.ts +30 -0
  74. package/dist/model/RepeatTemplateDefinition.d.ts +6 -7
  75. package/dist/model/RootDefinition.d.ts +3 -1
  76. package/dist/model/SubtreeDefinition.d.ts +2 -2
  77. package/dist/parse/NoteNodeDefinition.d.ts +31 -0
  78. package/dist/parse/expression/RepeatCountControlExpression.d.ts +19 -0
  79. package/dist/parse/text/HintDefinition.d.ts +9 -0
  80. package/dist/parse/text/ItemLabelDefinition.d.ts +9 -0
  81. package/dist/parse/text/ItemsetLabelDefinition.d.ts +13 -0
  82. package/dist/parse/text/LabelDefinition.d.ts +15 -0
  83. package/dist/parse/text/MessageDefinition.d.ts +15 -0
  84. package/dist/parse/text/OutputChunkDefinition.d.ts +8 -0
  85. package/dist/parse/text/ReferenceChunkDefinition.d.ts +8 -0
  86. package/dist/parse/text/StaticTextChunkDefinition.d.ts +10 -0
  87. package/dist/parse/text/TranslationChunkDefinition.d.ts +9 -0
  88. package/dist/parse/text/abstract/TextChunkDefinition.d.ts +18 -0
  89. package/dist/parse/text/abstract/TextElementDefinition.d.ts +23 -0
  90. package/dist/parse/text/abstract/TextRangeDefinition.d.ts +35 -0
  91. package/dist/parse/xpath/dependency-analysis.d.ts +40 -0
  92. package/dist/parse/xpath/path-resolution.d.ts +70 -0
  93. package/dist/parse/xpath/predicate-analysis.d.ts +30 -0
  94. package/dist/parse/xpath/reference-parsing.d.ts +18 -0
  95. package/dist/parse/xpath/semantic-analysis.d.ts +98 -0
  96. package/dist/parse/xpath/syntax-traversal.d.ts +69 -0
  97. package/dist/solid.js +3174 -961
  98. package/dist/solid.js.map +1 -1
  99. package/package.json +14 -15
  100. package/src/XFormDOM.ts +81 -8
  101. package/src/body/BodyDefinition.ts +38 -23
  102. package/src/body/BodyElementDefinition.ts +4 -3
  103. package/src/body/RepeatElementDefinition.ts +58 -0
  104. package/src/body/appearance/inputAppearanceParser.ts +39 -0
  105. package/src/body/appearance/selectAppearanceParser.ts +38 -0
  106. package/src/body/appearance/structureElementAppearanceParser.ts +7 -0
  107. package/src/body/control/ControlDefinition.ts +8 -3
  108. package/src/body/control/InputDefinition.ts +13 -0
  109. package/src/body/control/select/ItemDefinition.ts +3 -3
  110. package/src/body/control/select/ItemsetDefinition.ts +29 -12
  111. package/src/body/control/select/ItemsetNodesetExpression.ts +1 -1
  112. package/src/body/control/select/SelectDefinition.ts +14 -5
  113. package/src/body/group/BaseGroupDefinition.ts +14 -51
  114. package/src/body/group/PresentationGroupDefinition.ts +1 -1
  115. package/src/client/BaseNode.ts +82 -8
  116. package/src/client/GroupNode.ts +8 -2
  117. package/src/client/ModelValueNode.ts +40 -0
  118. package/src/client/NodeAppearances.ts +22 -0
  119. package/src/client/NoteNode.ts +74 -0
  120. package/src/client/README.md +1 -0
  121. package/src/client/RootNode.ts +24 -0
  122. package/src/client/SelectNode.ts +9 -3
  123. package/src/client/StringNode.ts +9 -3
  124. package/src/client/SubtreeNode.ts +3 -0
  125. package/src/client/TextRange.ts +99 -2
  126. package/src/client/constants.ts +10 -0
  127. package/src/client/hierarchy.ts +30 -14
  128. package/src/client/node-types.ts +8 -1
  129. package/src/client/{RepeatRangeNode.ts → repeat/BaseRepeatRangeNode.ts} +20 -17
  130. package/src/client/{RepeatInstanceNode.ts → repeat/RepeatInstanceNode.ts} +13 -7
  131. package/src/client/repeat/RepeatRangeControlledNode.ts +20 -0
  132. package/src/client/repeat/RepeatRangeUncontrolledNode.ts +21 -0
  133. package/src/client/validation.ts +199 -0
  134. package/src/expression/DependentExpression.ts +45 -27
  135. package/src/index.ts +15 -8
  136. package/src/instance/Group.ts +24 -13
  137. package/src/instance/ModelValue.ts +104 -0
  138. package/src/instance/Note.ts +142 -0
  139. package/src/instance/Root.ts +29 -67
  140. package/src/instance/SelectField.ts +35 -13
  141. package/src/instance/StringField.ts +40 -13
  142. package/src/instance/Subtree.ts +19 -10
  143. package/src/instance/abstract/DescendantNode.ts +50 -49
  144. package/src/instance/abstract/InstanceNode.ts +89 -92
  145. package/src/instance/children.ts +47 -10
  146. package/src/instance/hierarchy.ts +21 -2
  147. package/src/instance/index.ts +1 -1
  148. package/src/instance/internal-api/EvaluationContext.ts +5 -6
  149. package/src/instance/internal-api/ValidationContext.ts +23 -0
  150. package/src/instance/internal-api/ValueContext.ts +2 -2
  151. package/src/instance/repeat/BaseRepeatRange.ts +347 -0
  152. package/src/instance/{RepeatInstance.ts → repeat/RepeatInstance.ts} +85 -36
  153. package/src/instance/repeat/RepeatRangeControlled.ts +82 -0
  154. package/src/instance/repeat/RepeatRangeUncontrolled.ts +67 -0
  155. package/src/instance/text/TextRange.ts +10 -4
  156. package/src/lib/TokenListParser.ts +156 -0
  157. package/src/lib/dom/query.ts +13 -0
  158. package/src/lib/reactivity/createChildrenState.ts +51 -6
  159. package/src/lib/reactivity/createComputedExpression.ts +23 -25
  160. package/src/lib/reactivity/createNoteReadonlyThunk.ts +33 -0
  161. package/src/lib/reactivity/createSelectItems.ts +25 -20
  162. package/src/lib/reactivity/createValueState.ts +6 -6
  163. package/src/lib/reactivity/materializeCurrentStateChildren.ts +3 -1
  164. package/src/lib/reactivity/node-state/createSharedNodeState.ts +1 -1
  165. package/src/lib/reactivity/text/createFieldHint.ts +9 -7
  166. package/src/lib/reactivity/text/createNodeLabel.ts +7 -5
  167. package/src/lib/reactivity/text/createNoteText.ts +72 -0
  168. package/src/lib/reactivity/text/createTextRange.ts +17 -90
  169. package/src/lib/reactivity/validation/createAggregatedViolations.ts +70 -0
  170. package/src/lib/reactivity/validation/createValidation.ts +196 -0
  171. package/src/model/BindComputation.ts +0 -4
  172. package/src/model/BindDefinition.ts +8 -6
  173. package/src/model/BindElement.ts +1 -0
  174. package/src/model/DescendentNodeDefinition.ts +1 -2
  175. package/src/model/{ValueNodeDefinition.ts → LeafNodeDefinition.ts} +5 -6
  176. package/src/model/ModelBindMap.ts +4 -0
  177. package/src/model/ModelDefinition.ts +1 -1
  178. package/src/model/NodeDefinition.ts +21 -21
  179. package/src/model/RepeatInstanceDefinition.ts +8 -13
  180. package/src/model/RepeatRangeDefinition.ts +94 -0
  181. package/src/model/RepeatTemplateDefinition.ts +10 -15
  182. package/src/model/RootDefinition.ts +12 -14
  183. package/src/model/SubtreeDefinition.ts +3 -3
  184. package/src/parse/NoteNodeDefinition.ts +70 -0
  185. package/src/parse/TODO.md +3 -0
  186. package/src/parse/expression/RepeatCountControlExpression.ts +44 -0
  187. package/src/parse/text/HintDefinition.ts +25 -0
  188. package/src/parse/text/ItemLabelDefinition.ts +25 -0
  189. package/src/parse/text/ItemsetLabelDefinition.ts +44 -0
  190. package/src/parse/text/LabelDefinition.ts +61 -0
  191. package/src/parse/text/MessageDefinition.ts +49 -0
  192. package/src/parse/text/OutputChunkDefinition.ts +25 -0
  193. package/src/parse/text/ReferenceChunkDefinition.ts +14 -0
  194. package/src/parse/text/StaticTextChunkDefinition.ts +19 -0
  195. package/src/parse/text/TranslationChunkDefinition.ts +38 -0
  196. package/src/parse/text/abstract/TextChunkDefinition.ts +38 -0
  197. package/src/parse/text/abstract/TextElementDefinition.ts +71 -0
  198. package/src/parse/text/abstract/TextRangeDefinition.ts +70 -0
  199. package/src/parse/xpath/dependency-analysis.ts +105 -0
  200. package/src/parse/xpath/path-resolution.ts +475 -0
  201. package/src/parse/xpath/predicate-analysis.ts +61 -0
  202. package/src/parse/xpath/reference-parsing.ts +90 -0
  203. package/src/parse/xpath/semantic-analysis.ts +466 -0
  204. package/src/parse/xpath/syntax-traversal.ts +129 -0
  205. package/dist/body/RepeatDefinition.d.ts +0 -16
  206. package/dist/body/group/RepeatGroupDefinition.d.ts +0 -13
  207. package/dist/body/text/HintDefinition.d.ts +0 -11
  208. package/dist/body/text/LabelDefinition.d.ts +0 -20
  209. package/dist/body/text/TextElementDefinition.d.ts +0 -33
  210. package/dist/body/text/TextElementOutputPart.d.ts +0 -13
  211. package/dist/body/text/TextElementPart.d.ts +0 -13
  212. package/dist/body/text/TextElementReferencePart.d.ts +0 -7
  213. package/dist/body/text/TextElementStaticPart.d.ts +0 -7
  214. package/dist/instance/RepeatRange.d.ts +0 -80
  215. package/dist/lib/xpath/analysis.d.ts +0 -23
  216. package/dist/model/RepeatSequenceDefinition.d.ts +0 -20
  217. package/src/body/RepeatDefinition.ts +0 -54
  218. package/src/body/group/RepeatGroupDefinition.ts +0 -91
  219. package/src/body/text/HintDefinition.ts +0 -26
  220. package/src/body/text/LabelDefinition.ts +0 -54
  221. package/src/body/text/TextElementDefinition.ts +0 -97
  222. package/src/body/text/TextElementOutputPart.ts +0 -27
  223. package/src/body/text/TextElementPart.ts +0 -31
  224. package/src/body/text/TextElementReferencePart.ts +0 -21
  225. package/src/body/text/TextElementStaticPart.ts +0 -26
  226. package/src/instance/RepeatRange.ts +0 -214
  227. package/src/lib/xpath/analysis.ts +0 -241
  228. package/src/model/RepeatSequenceDefinition.ts +0 -53
@@ -1,8 +1,9 @@
1
1
  import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
2
  import type { Accessor, Signal } from 'solid-js';
3
- import { createSignal } from 'solid-js';
4
3
  import type { BaseNode } from '../../client/BaseNode.ts';
4
+ import type { NodeAppearances } from '../../client/NodeAppearances.ts';
5
5
  import type { InstanceNodeType } from '../../client/node-types.ts';
6
+ import type { NodeValidationState } from '../../client/validation.ts';
6
7
  import type { TextRange } from '../../index.ts';
7
8
  import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
8
9
  import type { CurrentState } from '../../lib/reactivity/node-state/createCurrentState.ts';
@@ -40,10 +41,6 @@ type AnyInstanceNode = InstanceNode<
40
41
  any
41
42
  >;
42
43
 
43
- interface InitializedStateOptions<T, K extends keyof T> {
44
- readonly uninitializedFallback: T[K];
45
- }
46
-
47
44
  /**
48
45
  * This type has the same effect as {@link MaterializedChildren}, but abstractly
49
46
  * handles leaf node types as well.
@@ -61,6 +58,21 @@ export type InstanceNodeCurrentState<
61
58
  : null;
62
59
  };
63
60
 
61
+ interface ComputableReferenceNode {
62
+ readonly parent: AnyParentNode | null;
63
+ readonly definition: AnyNodeDefinition;
64
+ }
65
+
66
+ type ComputeInstanceNodeReference = <This extends ComputableReferenceNode>(
67
+ this: This,
68
+ parent: This['parent'],
69
+ definition: This['definition']
70
+ ) => string;
71
+
72
+ export interface InstanceNodeOptions {
73
+ readonly computeReference?: () => string;
74
+ }
75
+
64
76
  export abstract class InstanceNode<
65
77
  Definition extends AnyNodeDefinition,
66
78
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -69,60 +81,32 @@ export abstract class InstanceNode<
69
81
  >
70
82
  implements BaseNode, EvaluationContext, SubscribableDependency
71
83
  {
72
- protected readonly isStateInitialized: Accessor<boolean>;
73
-
74
84
  protected abstract readonly state: SharedNodeState<Spec>;
75
85
  protected abstract readonly engineState: EngineState<Spec>;
76
86
 
77
87
  /**
78
- * Provides a generalized mechanism for accessing a reactive state value
79
- * during a node's construction, while {@link engineState} is still being
80
- * defined and thus isn't assigned.
81
- *
82
- * The fallback value specified in {@link options} will be returned on access
83
- * until {@link isStateInitialized} returns true. This ensures:
84
- *
85
- * - a value of the expected type will be available
86
- * - any read access will become reactive to the actual state, once it has
87
- * been initialized and {@link engineState} is assigned
88
- *
89
- * @todo This is one among several chicken/egg problems encountered trying to
90
- * support state initialization in which some aspects of the state derive from
91
- * other aspects of it. It would be nice to dispense with this entirely. But
92
- * if it must persist, we should also consider replacing the method with a
93
- * direct accessor once state initialization completes, so the initialized
94
- * check is only called until it becomes impertinent.
88
+ * @package Exposed on every node type to facilitate inheritance, as well as
89
+ * conditional behavior for value nodes.
95
90
  */
96
- protected getInitializedState<K extends keyof EngineState<Spec>>(
97
- key: K,
98
- options: InitializedStateOptions<EngineState<Spec>, K>
99
- ): EngineState<Spec>[K] {
100
- if (this.isStateInitialized()) {
101
- return this.engineState[key];
102
- }
91
+ abstract readonly hasReadonlyAncestor: Accessor<boolean>;
103
92
 
104
- return options.uninitializedFallback;
105
- }
93
+ /**
94
+ * @package Exposed on every node type to facilitate inheritance, as well as
95
+ * conditional behavior for value nodes.
96
+ */
97
+ abstract readonly isReadonly: Accessor<boolean>;
106
98
 
107
99
  /**
108
100
  * @package Exposed on every node type to facilitate inheritance, as well as
109
101
  * conditional behavior for value nodes.
110
102
  */
111
- get isReadonly(): boolean {
112
- return (this as AnyInstanceNode).getInitializedState('readonly', {
113
- uninitializedFallback: false,
114
- });
115
- }
103
+ abstract readonly hasNonRelevantAncestor: Accessor<boolean>;
116
104
 
117
105
  /**
118
106
  * @package Exposed on every node type to facilitate inheritance, as well as
119
107
  * conditional behavior for value nodes.
120
108
  */
121
- get isRelevant(): boolean {
122
- return (this as AnyInstanceNode).getInitializedState('relevant', {
123
- uninitializedFallback: true,
124
- });
125
- }
109
+ abstract readonly isRelevant: Accessor<boolean>;
126
110
 
127
111
  // BaseNode: identity
128
112
  readonly nodeId: NodeID;
@@ -130,8 +114,12 @@ export abstract class InstanceNode<
130
114
  // BaseNode: node types and variants (e.g. for narrowing)
131
115
  abstract readonly nodeType: InstanceNodeType;
132
116
 
117
+ abstract readonly appearances: NodeAppearances<Definition>;
118
+
133
119
  abstract readonly currentState: InstanceNodeCurrentState<Spec, Child>;
134
120
 
121
+ abstract readonly validationState: NodeValidationState;
122
+
135
123
  // BaseNode: structural
136
124
  abstract readonly root: Root;
137
125
 
@@ -141,35 +129,40 @@ export abstract class InstanceNode<
141
129
  // EvaluationContext *and* Subscribable: node-specific
142
130
  readonly scope: ReactiveScope;
143
131
 
132
+ readonly computeReference: ComputeInstanceNodeReference;
133
+
134
+ protected readonly computeChildStepReference: ComputeInstanceNodeReference = (
135
+ parent,
136
+ definition
137
+ ): string => {
138
+ if (parent == null) {
139
+ throw new Error(
140
+ 'Cannot compute child step reference of node without parent (was this called from `Root`?)'
141
+ );
142
+ }
143
+
144
+ return `${parent.contextReference()}/${definition.nodeName}`;
145
+ };
146
+
144
147
  // EvaluationContext: node-specific
145
- get contextReference(): string {
148
+ readonly contextReference = (): string => {
146
149
  return this.computeReference(this.parent, this.definition);
147
- }
150
+ };
148
151
 
149
152
  abstract readonly contextNode: Element;
150
153
 
151
154
  constructor(
152
155
  readonly engineConfig: InstanceConfig,
153
156
  readonly parent: AnyParentNode | null,
154
- readonly definition: Definition
157
+ readonly definition: Definition,
158
+ options?: InstanceNodeOptions
155
159
  ) {
160
+ this.computeReference = options?.computeReference ?? this.computeChildStepReference;
161
+
156
162
  this.scope = createReactiveScope();
157
163
  this.engineConfig = engineConfig;
158
164
  this.nodeId = declareNodeID(engineConfig.createUniqueId());
159
165
  this.definition = definition;
160
-
161
- const checkStateInitialized = () => this.engineState != null;
162
- const [isStateInitialized, setStateInitialized] = createSignal(checkStateInitialized());
163
-
164
- this.isStateInitialized = isStateInitialized;
165
-
166
- queueMicrotask(() => {
167
- if (checkStateInitialized()) {
168
- setStateInitialized(true);
169
- } else {
170
- throw new Error('Node state was never initialized');
171
- }
172
- });
173
166
  }
174
167
 
175
168
  /**
@@ -184,18 +177,13 @@ export abstract class InstanceNode<
184
177
  */
185
178
  abstract getChildren(this: AnyInstanceNode): readonly AnyChildNode[];
186
179
 
187
- protected abstract computeReference(
188
- parent: AnyInstanceNode | null,
189
- definition: Definition
190
- ): string;
191
-
192
- getNodeByReference(
180
+ getNodesByReference(
193
181
  this: AnyNode,
194
182
  visited: WeakSet<AnyNode>,
195
183
  dependencyReference: string
196
- ): SubscribableDependency | null {
184
+ ): readonly SubscribableDependency[] {
197
185
  if (visited.has(this)) {
198
- return null;
186
+ return [];
199
187
  }
200
188
 
201
189
  visited.add(this);
@@ -203,39 +191,37 @@ export abstract class InstanceNode<
203
191
  const { nodeset } = this.definition;
204
192
 
205
193
  if (dependencyReference === nodeset) {
206
- return this;
194
+ if (this.nodeType === 'repeat-instance') {
195
+ return [this.parent];
196
+ }
197
+
198
+ return [this];
207
199
  }
208
200
 
209
201
  if (
210
202
  dependencyReference.startsWith(`${nodeset}/`) ||
211
203
  dependencyReference.startsWith(`${nodeset}[`)
212
204
  ) {
213
- const children = this.getChildren();
214
-
215
- if (children == null) {
216
- return null;
217
- }
218
-
219
- for (const child of children) {
220
- const dependency = child.getNodeByReference(visited, dependencyReference);
221
-
222
- if (dependency != null) {
223
- return dependency;
224
- }
225
- }
205
+ return this.getChildren().flatMap((child) => {
206
+ return child.getNodesByReference(visited, dependencyReference);
207
+ });
226
208
  }
227
209
 
228
- return this.parent?.getNodeByReference(visited, dependencyReference) ?? null;
210
+ return this.parent?.getNodesByReference(visited, dependencyReference) ?? [];
229
211
  }
230
212
 
231
213
  // EvaluationContext: node-relative
232
- getSubscribableDependencyByReference(
214
+ getSubscribableDependenciesByReference(
233
215
  this: AnyNode,
234
216
  reference: string
235
- ): SubscribableDependency | null {
236
- const visited = new WeakSet<SubscribableDependency>();
217
+ ): readonly SubscribableDependency[] {
218
+ if (this.nodeType === 'root') {
219
+ const visited = new WeakSet<AnyNode>();
237
220
 
238
- return this.getNodeByReference(visited, reference);
221
+ return this.getNodesByReference(visited, reference);
222
+ }
223
+
224
+ return this.root.getSubscribableDependenciesByReference(reference);
239
225
  }
240
226
 
241
227
  // SubscribableDependency
@@ -247,11 +233,22 @@ export abstract class InstanceNode<
247
233
  subscribe(): void {
248
234
  const { engineState } = this;
249
235
 
250
- if (engineState.relevant) {
251
- engineState.reference;
252
- engineState.relevant;
253
- engineState.children;
254
- engineState.value;
255
- }
236
+ // Note: a previous iteration of this default implementation guarded these
237
+ // reactive reads behind a relevance check. This caused timing issues for
238
+ // downstream computations referencing a node whose relevance changes.
239
+ //
240
+ // That original guard was intended to reduce excessive redundant
241
+ // computations, and so removing it is intended as a naive compromise of
242
+ // performance for obvious correctness improvements.
243
+ //
244
+ // This compromise, like many others, will be moot if/when we decide to
245
+ // decouple XPath evaluation from the browser/XML DOM: reactive
246
+ // subscriptions would be established by evaluation of the expressions
247
+ // themselves (as they traverse instance state and access values), rather
248
+ // than this safer/less focused approach.
249
+ engineState.reference;
250
+ engineState.relevant;
251
+ engineState.children;
252
+ engineState.value;
256
253
  }
257
254
  }
@@ -1,14 +1,20 @@
1
1
  import { UnreachableError } from '@getodk/common/lib/error/UnreachableError.ts';
2
- import { SelectDefinition } from '../body/control/select/SelectDefinition.ts';
3
2
  import type { GroupDefinition } from '../client/GroupNode.ts';
4
3
  import type { SubtreeDefinition } from '../client/SubtreeNode.ts';
4
+ import type { LeafNodeDefinition } from '../model/LeafNodeDefinition.ts';
5
5
  import type { SubtreeDefinition as ModelSubtreeDefinition } from '../model/SubtreeDefinition.ts';
6
+ import { NoteNodeDefinition } from '../parse/NoteNodeDefinition.ts';
6
7
  import { Group } from './Group.ts';
7
- import { RepeatRange } from './RepeatRange.ts';
8
- import { SelectField, type SelectFieldDefinition } from './SelectField.ts';
8
+ import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
9
+ import { ModelValue, type ModelValueDefinition } from './ModelValue.ts';
10
+ import { Note } from './Note.ts';
11
+ import { RepeatRangeControlled } from './repeat/RepeatRangeControlled.ts';
12
+ import { RepeatRangeUncontrolled } from './repeat/RepeatRangeUncontrolled.ts';
13
+ import type { SelectFieldDefinition } from './SelectField.ts';
14
+ import { SelectField } from './SelectField.ts';
15
+ import type { StringFieldDefinition } from './StringField.ts';
9
16
  import { StringField } from './StringField.ts';
10
17
  import { Subtree } from './Subtree.ts';
11
- import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
12
18
 
13
19
  const isSubtreeDefinition = (
14
20
  definition: ModelSubtreeDefinition
@@ -16,6 +22,22 @@ const isSubtreeDefinition = (
16
22
  return definition.bodyElement == null;
17
23
  };
18
24
 
25
+ type ControlNodeDefinition = SelectFieldDefinition | StringFieldDefinition;
26
+
27
+ type AnyLeafNodeDefinition = ControlNodeDefinition | ModelValueDefinition;
28
+
29
+ const isModelValueDefinition = (
30
+ definition: LeafNodeDefinition
31
+ ): definition is ModelValueDefinition => {
32
+ return definition.bodyElement == null;
33
+ };
34
+
35
+ const isStringFieldDefinition = (
36
+ definition: ControlNodeDefinition
37
+ ): definition is StringFieldDefinition => {
38
+ return definition.bodyElement.type === 'input';
39
+ };
40
+
19
41
  export const buildChildren = (parent: GeneralParentNode): GeneralChildNode[] => {
20
42
  const { children } = parent.definition;
21
43
 
@@ -32,16 +54,31 @@ export const buildChildren = (parent: GeneralParentNode): GeneralChildNode[] =>
32
54
  return new Group(parent, child as GroupDefinition);
33
55
  }
34
56
 
35
- case 'repeat-sequence': {
36
- return new RepeatRange(parent, child);
57
+ case 'repeat-range': {
58
+ if (child.isControlled()) {
59
+ return new RepeatRangeControlled(parent, child);
60
+ }
61
+
62
+ return new RepeatRangeUncontrolled(parent, child);
37
63
  }
38
64
 
39
- case 'value-node': {
40
- if (child.bodyElement instanceof SelectDefinition) {
41
- return new SelectField(parent, child as SelectFieldDefinition);
65
+ case 'leaf-node': {
66
+ if (child instanceof NoteNodeDefinition) {
67
+ return new Note(parent, child);
68
+ }
69
+
70
+ // More specific type helps with narrowing below
71
+ const leafChild: AnyLeafNodeDefinition = child;
72
+
73
+ if (isModelValueDefinition(leafChild)) {
74
+ return new ModelValue(parent, leafChild);
75
+ }
76
+
77
+ if (isStringFieldDefinition(leafChild)) {
78
+ return new StringField(parent, leafChild);
42
79
  }
43
80
 
44
- return new StringField(parent, child);
81
+ return new SelectField(parent, leafChild);
45
82
  }
46
83
 
47
84
  default: {
@@ -1,11 +1,16 @@
1
1
  import type { Group } from './Group.ts';
2
- import type { RepeatInstance } from './RepeatInstance.ts';
3
- import type { RepeatRange } from './RepeatRange.ts';
2
+ import type { ModelValue } from './ModelValue.ts';
3
+ import type { Note } from './Note.ts';
4
+ import type { RepeatInstance } from './repeat/RepeatInstance.ts';
5
+ import type { RepeatRangeControlled } from './repeat/RepeatRangeControlled.ts';
6
+ import type { RepeatRangeUncontrolled } from './repeat/RepeatRangeUncontrolled.ts';
4
7
  import type { Root } from './Root.ts';
5
8
  import type { SelectField } from './SelectField.ts';
6
9
  import type { StringField } from './StringField.ts';
7
10
  import type { Subtree } from './Subtree.ts';
8
11
 
12
+ export type RepeatRange = RepeatRangeControlled | RepeatRangeUncontrolled;
13
+
9
14
  // prettier-ignore
10
15
  export type AnyNode =
11
16
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
@@ -14,6 +19,8 @@ export type AnyNode =
14
19
  | Subtree
15
20
  | RepeatRange
16
21
  | RepeatInstance
22
+ | Note
23
+ | ModelValue
17
24
  | StringField
18
25
  | SelectField;
19
26
 
@@ -41,6 +48,8 @@ export type AnyChildNode =
41
48
  | Subtree
42
49
  | RepeatRange
43
50
  | RepeatInstance
51
+ | ModelValue
52
+ | Note
44
53
  | StringField
45
54
  | SelectField;
46
55
 
@@ -50,5 +59,15 @@ export type GeneralChildNode =
50
59
  | Group
51
60
  | Subtree
52
61
  | RepeatRange
62
+ | ModelValue
63
+ | Note
64
+ | StringField
65
+ | SelectField;
66
+
67
+ // prettier-ignore
68
+ export type AnyValueNode =
69
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
70
+ | ModelValue
71
+ | Note
53
72
  | StringField
54
73
  | SelectField;
@@ -31,7 +31,7 @@ export const initializeForm = async (
31
31
  const sourceXML = await retrieveSourceXMLResource(input, engineConfig);
32
32
  const form = new XFormDefinition(sourceXML);
33
33
 
34
- return Root.initialize(form.xformDOM, form.model.root, engineConfig);
34
+ return new Root(form.xformDOM, form.model.root, engineConfig);
35
35
  };
36
36
 
37
37
  initializeForm satisfies InitializeForm;
@@ -1,4 +1,5 @@
1
1
  import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
+ import type { Accessor } from 'solid-js';
2
3
  import type { DependentExpression } from '../../expression/DependentExpression.ts';
3
4
  import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
4
5
  import type { SubscribableDependency } from './SubscribableDependency.ts';
@@ -27,15 +28,13 @@ export interface EvaluationContext {
27
28
  * Produces the current absolute reference to the {@link contextNode}, where
28
29
  * the absolute `/` resolves to the active form state's primary instance root.
29
30
  */
30
- get contextReference(): string;
31
+ readonly contextReference: Accessor<string>;
31
32
 
32
33
  readonly contextNode: Node;
33
34
 
34
35
  /**
35
- * Resolves a nodeset reference, possibly relative to the
36
- * {@link EvaluationContext.contextNode}.
36
+ * Resolves nodes corresponding to the provided node-set reference, possibly
37
+ * relative to the {@link EvaluationContext.contextNode}.
37
38
  */
38
- readonly getSubscribableDependencyByReference: (
39
- reference: string
40
- ) => SubscribableDependency | null;
39
+ getSubscribableDependenciesByReference(reference: string): readonly SubscribableDependency[];
41
40
  }
@@ -0,0 +1,23 @@
1
+ import type { BindComputation } from '../../model/BindComputation.ts';
2
+ import type { MessageDefinition } from '../../parse/text/MessageDefinition.ts';
3
+ import type { EvaluationContext } from './EvaluationContext.ts';
4
+ import type { SubscribableDependency } from './SubscribableDependency.ts';
5
+
6
+ interface ValidationContextDefinitionBind {
7
+ readonly constraint: BindComputation<'constraint'>;
8
+ readonly constraintMsg: MessageDefinition<'constraintMsg'> | null;
9
+ readonly required: BindComputation<'required'>;
10
+ readonly requiredMsg: MessageDefinition<'requiredMsg'> | null;
11
+ }
12
+
13
+ interface ValidationContextDefinition {
14
+ readonly bind: ValidationContextDefinitionBind;
15
+ }
16
+
17
+ export interface ValidationContext extends EvaluationContext, SubscribableDependency {
18
+ readonly definition: ValidationContextDefinition;
19
+
20
+ isRelevant(): boolean;
21
+ isRequired(): boolean;
22
+ isBlank(): boolean;
23
+ }
@@ -19,8 +19,8 @@ export interface ValueContext<RuntimeValue> extends EvaluationContext {
19
19
  readonly definition: ValueContextDefinition;
20
20
  readonly contextNode: Element;
21
21
 
22
- get isReadonly(): boolean;
23
- get isRelevant(): boolean;
22
+ isReadonly(): boolean;
23
+ isRelevant(): boolean;
24
24
 
25
25
  readonly encodeValue: (this: unknown, runtimeValue: RuntimeValue) => InstanceValue;
26
26
  readonly decodeValue: (this: unknown, instanceValue: InstanceValue) => RuntimeValue;