@getodk/xforms-engine 0.2.0 → 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 (184) hide show
  1. package/dist/body/BodyElementDefinition.d.ts +4 -3
  2. package/dist/body/RepeatElementDefinition.d.ts +2 -2
  3. package/dist/body/control/ControlDefinition.d.ts +2 -2
  4. package/dist/body/control/select/ItemDefinition.d.ts +2 -2
  5. package/dist/body/control/select/ItemsetDefinition.d.ts +5 -4
  6. package/dist/body/group/BaseGroupDefinition.d.ts +1 -1
  7. package/dist/body/group/PresentationGroupDefinition.d.ts +1 -1
  8. package/dist/client/BaseNode.d.ts +68 -2
  9. package/dist/client/GroupNode.d.ts +2 -0
  10. package/dist/client/ModelValueNode.d.ts +37 -0
  11. package/dist/client/NoteNode.d.ts +53 -0
  12. package/dist/client/RootNode.d.ts +2 -0
  13. package/dist/client/SelectNode.d.ts +5 -3
  14. package/dist/client/StringNode.d.ts +5 -3
  15. package/dist/client/SubtreeNode.d.ts +2 -0
  16. package/dist/client/TextRange.d.ts +85 -2
  17. package/dist/client/constants.d.ts +9 -0
  18. package/dist/client/hierarchy.d.ts +14 -9
  19. package/dist/client/node-types.d.ts +2 -1
  20. package/dist/client/{RepeatRangeNode.d.ts → repeat/BaseRepeatRangeNode.d.ts} +18 -17
  21. package/dist/client/{RepeatInstanceNode.d.ts → repeat/RepeatInstanceNode.d.ts} +9 -8
  22. package/dist/client/repeat/RepeatRangeControlledNode.d.ts +19 -0
  23. package/dist/client/repeat/RepeatRangeUncontrolledNode.d.ts +20 -0
  24. package/dist/client/validation.d.ts +163 -0
  25. package/dist/expression/DependentExpression.d.ts +12 -8
  26. package/dist/index.d.ts +9 -4
  27. package/dist/index.js +2635 -678
  28. package/dist/index.js.map +1 -1
  29. package/dist/instance/Group.d.ts +3 -1
  30. package/dist/instance/ModelValue.d.ts +40 -0
  31. package/dist/instance/Note.d.ts +42 -0
  32. package/dist/instance/Root.d.ts +2 -0
  33. package/dist/instance/SelectField.d.ts +10 -4
  34. package/dist/instance/StringField.d.ts +11 -5
  35. package/dist/instance/Subtree.d.ts +2 -0
  36. package/dist/instance/abstract/DescendantNode.d.ts +5 -6
  37. package/dist/instance/abstract/InstanceNode.d.ts +2 -0
  38. package/dist/instance/hierarchy.d.ts +10 -5
  39. package/dist/instance/internal-api/ValidationContext.d.ts +21 -0
  40. package/dist/instance/{RepeatRange.d.ts → repeat/BaseRepeatRange.d.ts} +46 -45
  41. package/dist/instance/{RepeatInstance.d.ts → repeat/RepeatInstance.d.ts} +13 -12
  42. package/dist/instance/repeat/RepeatRangeControlled.d.ts +16 -0
  43. package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +35 -0
  44. package/dist/instance/text/TextRange.d.ts +4 -4
  45. package/dist/lib/reactivity/createComputedExpression.d.ts +6 -1
  46. package/dist/lib/reactivity/createNoteReadonlyThunk.d.ts +5 -0
  47. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +1 -1
  48. package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +1 -1
  49. package/dist/lib/reactivity/text/createFieldHint.d.ts +3 -3
  50. package/dist/lib/reactivity/text/createNodeLabel.d.ts +2 -2
  51. package/dist/lib/reactivity/text/createNoteText.d.ts +25 -0
  52. package/dist/lib/reactivity/text/createTextRange.d.ts +5 -7
  53. package/dist/lib/reactivity/validation/createAggregatedViolations.d.ts +9 -0
  54. package/dist/lib/reactivity/validation/createValidation.d.ts +18 -0
  55. package/dist/model/BindDefinition.d.ts +4 -2
  56. package/dist/model/BindElement.d.ts +1 -0
  57. package/dist/model/{ValueNodeDefinition.d.ts → LeafNodeDefinition.d.ts} +2 -2
  58. package/dist/model/NodeDefinition.d.ts +8 -8
  59. package/dist/model/RepeatInstanceDefinition.d.ts +2 -2
  60. package/dist/model/RepeatRangeDefinition.d.ts +14 -4
  61. package/dist/parse/NoteNodeDefinition.d.ts +31 -0
  62. package/dist/parse/expression/RepeatCountControlExpression.d.ts +19 -0
  63. package/dist/parse/text/HintDefinition.d.ts +9 -0
  64. package/dist/parse/text/ItemLabelDefinition.d.ts +9 -0
  65. package/dist/parse/text/ItemsetLabelDefinition.d.ts +13 -0
  66. package/dist/parse/text/LabelDefinition.d.ts +15 -0
  67. package/dist/parse/text/MessageDefinition.d.ts +15 -0
  68. package/dist/parse/text/OutputChunkDefinition.d.ts +8 -0
  69. package/dist/parse/text/ReferenceChunkDefinition.d.ts +8 -0
  70. package/dist/parse/text/StaticTextChunkDefinition.d.ts +10 -0
  71. package/dist/parse/text/TranslationChunkDefinition.d.ts +9 -0
  72. package/dist/parse/text/abstract/TextChunkDefinition.d.ts +18 -0
  73. package/dist/parse/text/abstract/TextElementDefinition.d.ts +23 -0
  74. package/dist/parse/text/abstract/TextRangeDefinition.d.ts +35 -0
  75. package/dist/parse/xpath/dependency-analysis.d.ts +40 -0
  76. package/dist/parse/xpath/path-resolution.d.ts +70 -0
  77. package/dist/parse/xpath/predicate-analysis.d.ts +30 -0
  78. package/dist/parse/xpath/reference-parsing.d.ts +18 -0
  79. package/dist/parse/xpath/semantic-analysis.d.ts +98 -0
  80. package/dist/parse/xpath/syntax-traversal.d.ts +69 -0
  81. package/dist/solid.js +2636 -679
  82. package/dist/solid.js.map +1 -1
  83. package/package.json +14 -15
  84. package/src/body/BodyElementDefinition.ts +4 -3
  85. package/src/body/RepeatElementDefinition.ts +5 -17
  86. package/src/body/control/ControlDefinition.ts +4 -3
  87. package/src/body/control/select/ItemDefinition.ts +3 -3
  88. package/src/body/control/select/ItemsetDefinition.ts +29 -12
  89. package/src/body/control/select/ItemsetNodesetExpression.ts +1 -1
  90. package/src/body/group/BaseGroupDefinition.ts +3 -2
  91. package/src/body/group/PresentationGroupDefinition.ts +1 -1
  92. package/src/client/BaseNode.ts +73 -7
  93. package/src/client/GroupNode.ts +2 -0
  94. package/src/client/ModelValueNode.ts +40 -0
  95. package/src/client/NoteNode.ts +74 -0
  96. package/src/client/README.md +1 -0
  97. package/src/client/RootNode.ts +2 -0
  98. package/src/client/SelectNode.ts +5 -3
  99. package/src/client/StringNode.ts +5 -3
  100. package/src/client/SubtreeNode.ts +2 -0
  101. package/src/client/TextRange.ts +99 -2
  102. package/src/client/constants.ts +10 -0
  103. package/src/client/hierarchy.ts +30 -14
  104. package/src/client/node-types.ts +8 -1
  105. package/src/client/{RepeatRangeNode.ts → repeat/BaseRepeatRangeNode.ts} +18 -19
  106. package/src/client/{RepeatInstanceNode.ts → repeat/RepeatInstanceNode.ts} +10 -8
  107. package/src/client/repeat/RepeatRangeControlledNode.ts +20 -0
  108. package/src/client/repeat/RepeatRangeUncontrolledNode.ts +21 -0
  109. package/src/client/validation.ts +199 -0
  110. package/src/expression/DependentExpression.ts +45 -27
  111. package/src/index.ts +15 -8
  112. package/src/instance/Group.ts +10 -4
  113. package/src/instance/ModelValue.ts +104 -0
  114. package/src/instance/Note.ts +142 -0
  115. package/src/instance/Root.ts +9 -3
  116. package/src/instance/SelectField.ts +28 -6
  117. package/src/instance/StringField.ts +35 -9
  118. package/src/instance/Subtree.ts +9 -3
  119. package/src/instance/abstract/DescendantNode.ts +6 -7
  120. package/src/instance/abstract/InstanceNode.ts +20 -6
  121. package/src/instance/children.ts +42 -15
  122. package/src/instance/hierarchy.ts +21 -2
  123. package/src/instance/internal-api/ValidationContext.ts +23 -0
  124. package/src/instance/{RepeatRange.ts → repeat/BaseRepeatRange.ts} +114 -99
  125. package/src/instance/{RepeatInstance.ts → repeat/RepeatInstance.ts} +27 -22
  126. package/src/instance/repeat/RepeatRangeControlled.ts +82 -0
  127. package/src/instance/repeat/RepeatRangeUncontrolled.ts +67 -0
  128. package/src/instance/text/TextRange.ts +10 -4
  129. package/src/lib/reactivity/createComputedExpression.ts +22 -24
  130. package/src/lib/reactivity/createNoteReadonlyThunk.ts +33 -0
  131. package/src/lib/reactivity/createSelectItems.ts +21 -14
  132. package/src/lib/reactivity/node-state/createSharedNodeState.ts +1 -1
  133. package/src/lib/reactivity/text/createFieldHint.ts +9 -7
  134. package/src/lib/reactivity/text/createNodeLabel.ts +7 -5
  135. package/src/lib/reactivity/text/createNoteText.ts +72 -0
  136. package/src/lib/reactivity/text/createTextRange.ts +17 -90
  137. package/src/lib/reactivity/validation/createAggregatedViolations.ts +70 -0
  138. package/src/lib/reactivity/validation/createValidation.ts +196 -0
  139. package/src/model/BindComputation.ts +0 -4
  140. package/src/model/BindDefinition.ts +8 -6
  141. package/src/model/BindElement.ts +1 -0
  142. package/src/model/{ValueNodeDefinition.ts → LeafNodeDefinition.ts} +4 -4
  143. package/src/model/ModelBindMap.ts +4 -0
  144. package/src/model/NodeDefinition.ts +12 -12
  145. package/src/model/RepeatInstanceDefinition.ts +2 -2
  146. package/src/model/RepeatRangeDefinition.ts +49 -8
  147. package/src/model/RootDefinition.ts +7 -3
  148. package/src/parse/NoteNodeDefinition.ts +70 -0
  149. package/src/parse/TODO.md +3 -0
  150. package/src/parse/expression/RepeatCountControlExpression.ts +44 -0
  151. package/src/parse/text/HintDefinition.ts +25 -0
  152. package/src/parse/text/ItemLabelDefinition.ts +25 -0
  153. package/src/parse/text/ItemsetLabelDefinition.ts +44 -0
  154. package/src/parse/text/LabelDefinition.ts +61 -0
  155. package/src/parse/text/MessageDefinition.ts +49 -0
  156. package/src/parse/text/OutputChunkDefinition.ts +25 -0
  157. package/src/parse/text/ReferenceChunkDefinition.ts +14 -0
  158. package/src/parse/text/StaticTextChunkDefinition.ts +19 -0
  159. package/src/parse/text/TranslationChunkDefinition.ts +38 -0
  160. package/src/parse/text/abstract/TextChunkDefinition.ts +38 -0
  161. package/src/parse/text/abstract/TextElementDefinition.ts +71 -0
  162. package/src/parse/text/abstract/TextRangeDefinition.ts +70 -0
  163. package/src/parse/xpath/dependency-analysis.ts +105 -0
  164. package/src/parse/xpath/path-resolution.ts +475 -0
  165. package/src/parse/xpath/predicate-analysis.ts +61 -0
  166. package/src/parse/xpath/reference-parsing.ts +90 -0
  167. package/src/parse/xpath/semantic-analysis.ts +466 -0
  168. package/src/parse/xpath/syntax-traversal.ts +129 -0
  169. package/dist/body/text/HintDefinition.d.ts +0 -11
  170. package/dist/body/text/LabelDefinition.d.ts +0 -22
  171. package/dist/body/text/TextElementDefinition.d.ts +0 -33
  172. package/dist/body/text/TextElementOutputPart.d.ts +0 -13
  173. package/dist/body/text/TextElementPart.d.ts +0 -13
  174. package/dist/body/text/TextElementReferencePart.d.ts +0 -7
  175. package/dist/body/text/TextElementStaticPart.d.ts +0 -7
  176. package/dist/lib/xpath/analysis.d.ts +0 -23
  177. package/src/body/text/HintDefinition.ts +0 -26
  178. package/src/body/text/LabelDefinition.ts +0 -68
  179. package/src/body/text/TextElementDefinition.ts +0 -97
  180. package/src/body/text/TextElementOutputPart.ts +0 -27
  181. package/src/body/text/TextElementPart.ts +0 -31
  182. package/src/body/text/TextElementReferencePart.ts +0 -21
  183. package/src/body/text/TextElementStaticPart.ts +0 -26
  184. package/src/lib/xpath/analysis.ts +0 -241
@@ -1,10 +1,20 @@
1
1
  import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
- import { getNodesetDependencies, isItextFunctionCalled } from '../lib/xpath/analysis.ts';
2
+ import { resolveDependencyNodesets } from '../parse/xpath/dependency-analysis.ts';
3
+ import type {
4
+ ConstantExpression,
5
+ ConstantTruthyExpression,
6
+ } from '../parse/xpath/semantic-analysis.ts';
7
+ import {
8
+ isConstantExpression,
9
+ isConstantTruthyExpression,
10
+ isTranslationExpression,
11
+ } from '../parse/xpath/semantic-analysis.ts';
3
12
  import type { DependencyContext } from './DependencyContext.ts';
4
13
 
5
14
  const evaluatorMethodsByResultType = {
6
15
  boolean: 'evaluateBoolean',
7
16
  nodes: 'evaluateNodes',
17
+ number: 'evaluateNumber',
8
18
  string: 'evaluateString',
9
19
  } as const;
10
20
 
@@ -20,11 +30,6 @@ export type DependentExpressionResult<Type extends DependentExpressionResultType
20
30
  >;
21
31
 
22
32
  interface SemanticDependencyOptions {
23
- /**
24
- * @default false
25
- */
26
- readonly parentContext?: boolean | undefined;
27
-
28
33
  /**
29
34
  * @default false
30
35
  */
@@ -37,18 +42,24 @@ interface DependentExpressionOptions {
37
42
  */
38
43
  readonly ignoreContextReference?: boolean;
39
44
 
40
- /**
41
- * @default true
42
- */
43
- readonly ignoreNullExpressions?: boolean;
44
-
45
45
  readonly semanticDependencies?: SemanticDependencyOptions;
46
46
  }
47
47
 
48
+ export interface ConstantDependentExpression<Type extends DependentExpressionResultType>
49
+ extends DependentExpression<Type> {
50
+ readonly expression: ConstantExpression;
51
+ }
52
+
53
+ export interface ConstantTruthyDependentExpression extends ConstantDependentExpression<'boolean'> {
54
+ readonly expression: ConstantTruthyExpression;
55
+ }
56
+
48
57
  export class DependentExpression<Type extends DependentExpressionResultType> {
49
58
  readonly dependencyReferences: ReadonlySet<string> = new Set();
50
59
  readonly isTranslated: boolean = false;
51
60
  readonly evaluatorMethod: DependentExpressionEvaluatorMethod<Type>;
61
+ readonly constantExpression: ConstantExpression | null;
62
+ readonly constantTruthyExpression: ConstantTruthyExpression | null;
52
63
 
53
64
  constructor(
54
65
  context: DependencyContext,
@@ -56,34 +67,33 @@ export class DependentExpression<Type extends DependentExpressionResultType> {
56
67
  readonly expression: string,
57
68
  options: DependentExpressionOptions = {}
58
69
  ) {
70
+ if (resultType === 'boolean' && isConstantTruthyExpression(expression)) {
71
+ this.constantTruthyExpression = expression;
72
+ this.constantExpression = expression;
73
+ } else if (isConstantExpression(expression)) {
74
+ this.constantTruthyExpression = null;
75
+ this.constantExpression = expression;
76
+ } else {
77
+ this.constantTruthyExpression = null;
78
+ this.constantExpression = null;
79
+ }
80
+
59
81
  this.evaluatorMethod = evaluatorMethodsByResultType[resultType];
60
82
 
61
83
  const {
62
84
  ignoreContextReference = false,
63
- ignoreNullExpressions = true,
64
85
  semanticDependencies = {
65
- parentContext: false,
66
86
  translations: false,
67
87
  },
68
88
  } = options;
69
89
 
70
- const dependencyReferences = new Set<string>(
71
- getNodesetDependencies(expression, {
72
- contextReference: context.reference,
73
- ignoreContextReference,
74
- ignoreNullExpressions,
90
+ this.dependencyReferences = new Set(
91
+ resolveDependencyNodesets(context.reference, expression, {
92
+ ignoreReferenceToContextPath: ignoreContextReference,
75
93
  })
76
94
  );
77
95
 
78
- const parentDependency = semanticDependencies.parentContext ? context.parentReference : null;
79
-
80
- if (parentDependency != null) {
81
- dependencyReferences.add(parentDependency);
82
- }
83
-
84
- this.dependencyReferences = dependencyReferences;
85
-
86
- const isTranslated = semanticDependencies.translations && isItextFunctionCalled(expression);
96
+ const isTranslated = semanticDependencies.translations && isTranslationExpression(expression);
87
97
 
88
98
  if (isTranslated) {
89
99
  this.isTranslated = true;
@@ -93,6 +103,14 @@ export class DependentExpression<Type extends DependentExpressionResultType> {
93
103
  context.registerDependentExpression(this);
94
104
  }
95
105
 
106
+ isConstantExpression(): this is ConstantDependentExpression<Type> {
107
+ return this.constantExpression != null;
108
+ }
109
+
110
+ isConstantTruthyExpression(): this is ConstantTruthyDependentExpression {
111
+ return this.resultType === 'boolean' && this.constantTruthyExpression != null;
112
+ }
113
+
96
114
  toString(): string | null {
97
115
  return this.expression;
98
116
  }
package/src/index.ts CHANGED
@@ -3,26 +3,33 @@ import { initializeForm as engine__initializeForm } from './instance/index.ts';
3
3
 
4
4
  export const initializeForm: InitializeForm = engine__initializeForm;
5
5
 
6
+ export * as constants from './client/constants.ts';
6
7
  export type * from './client/EngineConfig.ts';
7
8
  export type * from './client/FormLanguage.ts';
8
9
  export type * from './client/GroupNode.ts';
9
- export type * from './client/OpaqueReactiveObjectFactory.ts';
10
- export type * from './client/RepeatInstanceNode.ts';
11
- export type * from './client/RepeatRangeNode.ts';
12
- export type * from './client/RootNode.ts';
13
- export type * from './client/SelectNode.ts';
14
- export type * from './client/StringNode.ts';
15
- export type * from './client/SubtreeNode.ts';
16
- export type * from './client/TextRange.ts';
17
10
  export type {
18
11
  AnyChildNode,
12
+ AnyControlNode,
19
13
  AnyLeafNode,
20
14
  AnyNode,
21
15
  AnyParentNode,
22
16
  GeneralChildNode,
23
17
  GeneralParentNode,
18
+ RepeatRangeNode,
24
19
  } from './client/hierarchy.ts';
25
20
  export type * from './client/index.ts';
21
+ export type * from './client/ModelValueNode.ts';
22
+ export type * from './client/NoteNode.ts';
23
+ export type * from './client/OpaqueReactiveObjectFactory.ts';
24
+ export type * from './client/repeat/RepeatInstanceNode.ts';
25
+ export type * from './client/repeat/RepeatRangeControlledNode.ts';
26
+ export type * from './client/repeat/RepeatRangeUncontrolledNode.ts';
27
+ export type * from './client/RootNode.ts';
28
+ export type * from './client/SelectNode.ts';
29
+ export type * from './client/StringNode.ts';
30
+ export type * from './client/SubtreeNode.ts';
31
+ export type * from './client/TextRange.ts';
32
+ export type * from './client/validation.ts';
26
33
 
27
34
  // TODO: notwithstanding potential conflicts with parallel work on `web-forms`
28
35
  // (former `ui-vue`), these are the last remaining references **outside of
@@ -1,6 +1,7 @@
1
1
  import type { Accessor } from 'solid-js';
2
2
  import type { GroupDefinition, GroupNode, GroupNodeAppearances } from '../client/GroupNode.ts';
3
- import type { TextRange } from '../index.ts';
3
+ import type { TextRange } from '../client/TextRange.ts';
4
+ import type { AncestorNodeValidationState } from '../client/validation.ts';
4
5
  import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
5
6
  import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
6
7
  import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
@@ -10,6 +11,7 @@ import type { EngineState } from '../lib/reactivity/node-state/createEngineState
10
11
  import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
11
12
  import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
12
13
  import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
14
+ import { createAggregatedViolations } from '../lib/reactivity/validation/createAggregatedViolations.ts';
13
15
  import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
14
16
  import { DescendantNode } from './abstract/DescendantNode.ts';
15
17
  import { buildChildren } from './children.ts';
@@ -41,6 +43,7 @@ export class Group
41
43
  readonly nodeType = 'group';
42
44
  readonly appearances: GroupNodeAppearances;
43
45
  readonly currentState: MaterializedChildren<CurrentState<GroupStateSpec>, GeneralChildNode>;
46
+ readonly validationState: AncestorNodeValidationState;
44
47
 
45
48
  constructor(parent: GeneralParentNode, definition: GroupDefinition) {
46
49
  super(parent, definition);
@@ -51,6 +54,10 @@ export class Group
51
54
 
52
55
  this.childrenState = childrenState;
53
56
 
57
+ const sharedStateOptions = {
58
+ clientStateFactory: this.engineConfig.stateFactory,
59
+ };
60
+
54
61
  const state = createSharedNodeState(
55
62
  this.scope,
56
63
  {
@@ -65,9 +72,7 @@ export class Group
65
72
  valueOptions: null,
66
73
  value: null,
67
74
  },
68
- {
69
- clientStateFactory: this.engineConfig.stateFactory,
70
- }
75
+ sharedStateOptions
71
76
  );
72
77
 
73
78
  this.state = state;
@@ -79,6 +84,7 @@ export class Group
79
84
  );
80
85
 
81
86
  childrenState.setChildren(buildChildren(this));
87
+ this.validationState = createAggregatedViolations(this, sharedStateOptions);
82
88
  }
83
89
 
84
90
  getChildren(): readonly GeneralChildNode[] {
@@ -0,0 +1,104 @@
1
+ import { identity } from '@getodk/common/lib/identity.ts';
2
+ import type { ModelValueNode } from '../client/ModelValueNode.ts';
3
+ import type { AnyViolation, LeafNodeValidationState } from '../client/validation.ts';
4
+ import { createValueState } from '../lib/reactivity/createValueState.ts';
5
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
6
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
7
+ import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
8
+ import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
9
+ import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
10
+ import type { SharedValidationState } from '../lib/reactivity/validation/createValidation.ts';
11
+ import { createValidationState } from '../lib/reactivity/validation/createValidation.ts';
12
+ import type { LeafNodeDefinition } from '../model/LeafNodeDefinition.ts';
13
+ import type { DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
14
+ import { DescendantNode } from './abstract/DescendantNode.ts';
15
+ import type { GeneralParentNode } from './hierarchy.ts';
16
+ import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
17
+ import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
18
+ import type { ValidationContext } from './internal-api/ValidationContext.ts';
19
+ import type { ValueContext } from './internal-api/ValueContext.ts';
20
+
21
+ export interface ModelValueDefinition extends LeafNodeDefinition {
22
+ readonly bodyElement: null;
23
+ }
24
+
25
+ interface ModelValueStateSpec extends DescendantNodeStateSpec<string> {
26
+ readonly label: null;
27
+ readonly hint: null;
28
+ readonly children: null;
29
+ readonly value: SimpleAtomicState<string>;
30
+ readonly valueOptions: null;
31
+ }
32
+
33
+ export class ModelValue
34
+ extends DescendantNode<ModelValueDefinition, ModelValueStateSpec, null>
35
+ implements
36
+ ModelValueNode,
37
+ EvaluationContext,
38
+ SubscribableDependency,
39
+ ValidationContext,
40
+ ValueContext<string>
41
+ {
42
+ private readonly validation: SharedValidationState;
43
+ protected readonly state: SharedNodeState<ModelValueStateSpec>;
44
+
45
+ // InstanceNode
46
+ protected engineState: EngineState<ModelValueStateSpec>;
47
+
48
+ // ModelValueNode
49
+ readonly nodeType = 'model-value';
50
+ readonly appearances = null;
51
+ readonly currentState: CurrentState<ModelValueStateSpec>;
52
+
53
+ get validationState(): LeafNodeValidationState {
54
+ return this.validation.currentState;
55
+ }
56
+
57
+ // ValueContext
58
+ readonly encodeValue = identity<string>;
59
+ readonly decodeValue = identity<string>;
60
+
61
+ constructor(parent: GeneralParentNode, definition: ModelValueDefinition) {
62
+ super(parent, definition);
63
+
64
+ const sharedStateOptions = {
65
+ clientStateFactory: this.engineConfig.stateFactory,
66
+ };
67
+
68
+ const state = createSharedNodeState(
69
+ this.scope,
70
+ {
71
+ reference: this.contextReference,
72
+ readonly: this.isReadonly,
73
+ relevant: this.isRelevant,
74
+ required: this.isRequired,
75
+
76
+ label: null,
77
+ hint: null,
78
+ children: null,
79
+ valueOptions: null,
80
+ value: createValueState(this),
81
+ },
82
+ sharedStateOptions
83
+ );
84
+
85
+ this.state = state;
86
+ this.engineState = state.engineState;
87
+ this.currentState = state.currentState;
88
+ this.validation = createValidationState(this, sharedStateOptions);
89
+ }
90
+
91
+ getViolation(): AnyViolation | null {
92
+ return this.validation.engineState.violation;
93
+ }
94
+
95
+ // ValidationContext
96
+ isBlank(): boolean {
97
+ return this.engineState.value === '';
98
+ }
99
+
100
+ // InstanceNode
101
+ getChildren(): readonly [] {
102
+ return [];
103
+ }
104
+ }
@@ -0,0 +1,142 @@
1
+ import { UnreachableError } from '@getodk/common/lib/error/UnreachableError.ts';
2
+ import { identity } from '@getodk/common/lib/identity.ts';
3
+ import type { Accessor } from 'solid-js';
4
+ import type { NoteNode, NoteNodeAppearances } from '../client/NoteNode.ts';
5
+ import type { TextRange } from '../client/TextRange.ts';
6
+ import type { AnyViolation, LeafNodeValidationState } from '../client/validation.ts';
7
+ import { createNoteReadonlyThunk } from '../lib/reactivity/createNoteReadonlyThunk.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 { createNoteText, type ComputedNoteText } from '../lib/reactivity/text/createNoteText.ts';
16
+ import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
17
+ import type { SharedValidationState } from '../lib/reactivity/validation/createValidation.ts';
18
+ import { createValidationState } from '../lib/reactivity/validation/createValidation.ts';
19
+ import type { NoteNodeDefinition } from '../parse/NoteNodeDefinition.ts';
20
+ import type { DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
21
+ import { DescendantNode } from './abstract/DescendantNode.ts';
22
+ import type { GeneralParentNode } from './hierarchy.ts';
23
+ import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
24
+ import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
25
+ import type { ValidationContext } from './internal-api/ValidationContext.ts';
26
+ import type { ValueContext } from './internal-api/ValueContext.ts';
27
+
28
+ interface NoteStateSpec extends DescendantNodeStateSpec<string> {
29
+ readonly readonly: Accessor<true>;
30
+ readonly noteText: ComputedNoteText;
31
+ readonly label: Accessor<TextRange<'label', 'form'> | null>;
32
+ readonly hint: Accessor<TextRange<'hint', 'form'> | null>;
33
+ readonly children: null;
34
+ readonly value: SimpleAtomicState<string>;
35
+ readonly valueOptions: null;
36
+ }
37
+
38
+ export class Note
39
+ extends DescendantNode<NoteNodeDefinition, NoteStateSpec, null>
40
+ implements
41
+ NoteNode,
42
+ EvaluationContext,
43
+ SubscribableDependency,
44
+ ValidationContext,
45
+ ValueContext<string>
46
+ {
47
+ private readonly validation: SharedValidationState;
48
+ protected readonly state: SharedNodeState<NoteStateSpec>;
49
+
50
+ // InstanceNode
51
+ protected engineState: EngineState<NoteStateSpec>;
52
+
53
+ // NoteNode
54
+ readonly nodeType = 'note';
55
+ readonly appearances: NoteNodeAppearances;
56
+ readonly currentState: CurrentState<NoteStateSpec>;
57
+
58
+ get validationState(): LeafNodeValidationState {
59
+ return this.validation.currentState;
60
+ }
61
+
62
+ // ValueContext
63
+ readonly encodeValue = identity<string>;
64
+
65
+ readonly decodeValue = identity<string>;
66
+
67
+ constructor(parent: GeneralParentNode, definition: NoteNodeDefinition) {
68
+ super(parent, definition);
69
+
70
+ this.appearances = definition.bodyElement.appearances;
71
+
72
+ const sharedStateOptions = {
73
+ clientStateFactory: this.engineConfig.stateFactory,
74
+ };
75
+
76
+ const isReadonly = createNoteReadonlyThunk(this, definition.bind.readonly);
77
+ const noteTextComputation = createNoteText(this, definition.noteTextDefinition);
78
+
79
+ let noteText: ComputedNoteText;
80
+ let label: Accessor<TextRange<'label', 'form'> | null>;
81
+ let hint: Accessor<TextRange<'hint', 'form'> | null>;
82
+
83
+ switch (noteTextComputation.role) {
84
+ case 'label': {
85
+ noteText = noteTextComputation.label;
86
+ label = noteTextComputation.label;
87
+ hint = createFieldHint(this, definition);
88
+
89
+ break;
90
+ }
91
+
92
+ case 'hint': {
93
+ noteText = noteTextComputation.hint;
94
+ label = createNodeLabel(this, definition);
95
+ hint = noteTextComputation.hint;
96
+
97
+ break;
98
+ }
99
+
100
+ default:
101
+ throw new UnreachableError(noteTextComputation);
102
+ }
103
+
104
+ const state = createSharedNodeState(
105
+ this.scope,
106
+ {
107
+ reference: this.contextReference,
108
+ readonly: isReadonly,
109
+ relevant: this.isRelevant,
110
+ required: this.isRequired,
111
+
112
+ label,
113
+ hint,
114
+ noteText,
115
+
116
+ children: null,
117
+ valueOptions: null,
118
+ value: createValueState(this),
119
+ },
120
+ sharedStateOptions
121
+ );
122
+
123
+ this.state = state;
124
+ this.engineState = state.engineState;
125
+ this.currentState = state.currentState;
126
+ this.validation = createValidationState(this, sharedStateOptions);
127
+ }
128
+
129
+ getViolation(): AnyViolation | null {
130
+ return this.validation.engineState.violation;
131
+ }
132
+
133
+ // ValidationContext
134
+ isBlank(): boolean {
135
+ return this.engineState.value === '';
136
+ }
137
+
138
+ // InstanceNode
139
+ getChildren(): readonly [] {
140
+ return [];
141
+ }
142
+ }
@@ -5,6 +5,7 @@ import type { XFormDOM } from '../XFormDOM.ts';
5
5
  import type { BodyClassList } from '../body/BodyDefinition.ts';
6
6
  import type { ActiveLanguage, FormLanguage, FormLanguages } from '../client/FormLanguage.ts';
7
7
  import type { RootNode } from '../client/RootNode.ts';
8
+ import type { AncestorNodeValidationState } from '../client/validation.ts';
8
9
  import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
9
10
  import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
10
11
  import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
@@ -13,6 +14,7 @@ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentSta
13
14
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
14
15
  import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
15
16
  import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
17
+ import { createAggregatedViolations } from '../lib/reactivity/validation/createAggregatedViolations.ts';
16
18
  import type { RootDefinition } from '../model/RootDefinition.ts';
17
19
  import { InstanceNode } from './abstract/InstanceNode.ts';
18
20
  import { buildChildren } from './children.ts';
@@ -114,6 +116,7 @@ export class Root
114
116
  readonly appearances = null;
115
117
  readonly classes: BodyClassList;
116
118
  readonly currentState: MaterializedChildren<CurrentState<RootStateSpec>, GeneralChildNode>;
119
+ readonly validationState: AncestorNodeValidationState;
117
120
 
118
121
  protected readonly instanceDOM: XFormDOM;
119
122
 
@@ -152,6 +155,10 @@ export class Root
152
155
  const evaluator = instanceDOM.primaryInstanceEvaluator;
153
156
  const { translations } = evaluator;
154
157
  const { defaultLanguage, languages } = getInitialLanguageState(translations);
158
+ const sharedStateOptions = {
159
+ clientStateFactory: this.engineConfig.stateFactory,
160
+ };
161
+
155
162
  const state = createSharedNodeState(
156
163
  this.scope,
157
164
  {
@@ -166,9 +173,7 @@ export class Root
166
173
  value: null,
167
174
  children: childrenState.childIds,
168
175
  },
169
- {
170
- clientStateFactory: engineConfig.stateFactory,
171
- }
176
+ sharedStateOptions
172
177
  );
173
178
 
174
179
  this.state = state;
@@ -189,6 +194,7 @@ export class Root
189
194
  this.languages = languages;
190
195
 
191
196
  childrenState.setChildren(buildChildren(this));
197
+ this.validationState = createAggregatedViolations(this, sharedStateOptions);
192
198
  }
193
199
 
194
200
  getChildren(): readonly GeneralChildNode[] {
@@ -3,7 +3,8 @@ import type { Accessor } from 'solid-js';
3
3
  import { untrack } from 'solid-js';
4
4
  import type { AnySelectDefinition } from '../body/control/select/SelectDefinition.ts';
5
5
  import type { SelectItem, SelectNode, SelectNodeAppearances } from '../client/SelectNode.ts';
6
- import type { TextRange } from '../index.ts';
6
+ import type { TextRange } from '../client/TextRange.ts';
7
+ import type { AnyViolation, LeafNodeValidationState } from '../client/validation.ts';
7
8
  import { createSelectItems } from '../lib/reactivity/createSelectItems.ts';
8
9
  import { createValueState } from '../lib/reactivity/createValueState.ts';
9
10
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
@@ -13,16 +14,19 @@ import { createSharedNodeState } from '../lib/reactivity/node-state/createShared
13
14
  import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
14
15
  import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
15
16
  import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
16
- import type { ValueNodeDefinition } from '../model/ValueNodeDefinition.ts';
17
+ import type { SharedValidationState } from '../lib/reactivity/validation/createValidation.ts';
18
+ import { createValidationState } from '../lib/reactivity/validation/createValidation.ts';
19
+ import type { LeafNodeDefinition } from '../model/LeafNodeDefinition.ts';
17
20
  import type { Root } from './Root.ts';
18
21
  import type { DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
19
22
  import { DescendantNode } from './abstract/DescendantNode.ts';
20
23
  import type { GeneralParentNode } from './hierarchy.ts';
21
24
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
22
25
  import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
26
+ import type { ValidationContext } from './internal-api/ValidationContext.ts';
23
27
  import type { ValueContext } from './internal-api/ValueContext.ts';
24
28
 
25
- export interface SelectFieldDefinition extends ValueNodeDefinition {
29
+ export interface SelectFieldDefinition extends LeafNodeDefinition {
26
30
  readonly bodyElement: AnySelectDefinition;
27
31
  }
28
32
 
@@ -40,9 +44,11 @@ export class SelectField
40
44
  SelectNode,
41
45
  EvaluationContext,
42
46
  SubscribableDependency,
47
+ ValidationContext,
43
48
  ValueContext<readonly SelectItem[]>
44
49
  {
45
50
  private readonly selectExclusive: boolean;
51
+ private readonly validation: SharedValidationState;
46
52
 
47
53
  // InstanceNode
48
54
  protected readonly state: SharedNodeState<SelectFieldStateSpec>;
@@ -53,6 +59,10 @@ export class SelectField
53
59
  readonly appearances: SelectNodeAppearances;
54
60
  readonly currentState: CurrentState<SelectFieldStateSpec>;
55
61
 
62
+ get validationState(): LeafNodeValidationState {
63
+ return this.validation.currentState;
64
+ }
65
+
56
66
  // ValueContext
57
67
  readonly encodeValue = (runtimeValue: readonly SelectItem[]): string => {
58
68
  const itemValues = new Set(runtimeValue.map(({ value }) => value));
@@ -90,6 +100,10 @@ export class SelectField
90
100
 
91
101
  this.getValueOptions = valueOptions;
92
102
 
103
+ const sharedStateOptions = {
104
+ clientStateFactory: this.engineConfig.stateFactory,
105
+ };
106
+
93
107
  const state = createSharedNodeState(
94
108
  this.scope,
95
109
  {
@@ -104,14 +118,17 @@ export class SelectField
104
118
  value: createValueState(this),
105
119
  valueOptions,
106
120
  },
107
- {
108
- clientStateFactory: this.engineConfig.stateFactory,
109
- }
121
+ sharedStateOptions
110
122
  );
111
123
 
112
124
  this.state = state;
113
125
  this.engineState = state.engineState;
114
126
  this.currentState = state.currentState;
127
+ this.validation = createValidationState(this, sharedStateOptions);
128
+ }
129
+
130
+ getViolation(): AnyViolation | null {
131
+ return this.validation.engineState.violation;
115
132
  }
116
133
 
117
134
  protected getSelectItemsByValue(
@@ -201,4 +218,9 @@ export class SelectField
201
218
  getChildren(): readonly [] {
202
219
  return [];
203
220
  }
221
+
222
+ // ValidationContext
223
+ isBlank(): boolean {
224
+ return this.engineState.value.length === 0;
225
+ }
204
226
  }