@getodk/xforms-engine 0.1.0 → 0.2.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.
- package/dist/.vite/manifest.json +1 -0
- package/dist/XFormDOM.d.ts +1 -0
- package/dist/XFormDataType.d.ts +2 -1
- package/dist/XFormDefinition.d.ts +1 -0
- package/dist/body/BodyDefinition.d.ts +27 -9
- package/dist/body/BodyElementDefinition.d.ts +5 -4
- package/dist/body/RepeatElementDefinition.d.ts +19 -0
- package/dist/body/UnsupportedBodyElementDefinition.d.ts +3 -2
- package/dist/body/appearance/inputAppearanceParser.d.ts +4 -0
- package/dist/body/appearance/selectAppearanceParser.d.ts +4 -0
- package/dist/body/appearance/structureElementAppearanceParser.d.ts +4 -0
- package/dist/body/control/ControlDefinition.d.ts +5 -2
- package/dist/body/control/InputDefinition.d.ts +6 -0
- package/dist/body/control/select/ItemDefinition.d.ts +4 -3
- package/dist/body/control/select/ItemsetDefinition.d.ts +4 -3
- package/dist/body/control/select/ItemsetNodesetContext.d.ts +3 -2
- package/dist/body/control/select/ItemsetNodesetExpression.d.ts +2 -1
- package/dist/body/control/select/ItemsetValueExpression.d.ts +2 -1
- package/dist/body/control/select/SelectDefinition.d.ts +16 -5
- package/dist/body/group/BaseGroupDefinition.d.ts +6 -10
- package/dist/body/group/LogicalGroupDefinition.d.ts +1 -0
- package/dist/body/group/PresentationGroupDefinition.d.ts +3 -2
- package/dist/body/group/StructuralGroupDefinition.d.ts +1 -0
- package/dist/body/text/HintDefinition.d.ts +4 -4
- package/dist/body/text/LabelDefinition.d.ts +9 -7
- package/dist/body/text/TextElementDefinition.d.ts +9 -8
- package/dist/body/text/TextElementOutputPart.d.ts +2 -1
- package/dist/body/text/TextElementPart.d.ts +5 -4
- package/dist/body/text/TextElementReferencePart.d.ts +2 -1
- package/dist/body/text/TextElementStaticPart.d.ts +2 -1
- package/dist/client/BaseNode.d.ts +9 -3
- package/dist/client/EngineConfig.d.ts +2 -1
- package/dist/client/GroupNode.d.ts +10 -6
- package/dist/client/NodeAppearances.d.ts +15 -0
- package/dist/client/RepeatInstanceNode.d.ts +10 -6
- package/dist/client/RepeatRangeNode.d.ts +11 -7
- package/dist/client/RootNode.d.ts +24 -4
- package/dist/client/SelectNode.d.ts +10 -6
- package/dist/client/StringNode.d.ts +9 -5
- package/dist/client/SubtreeNode.d.ts +6 -4
- package/dist/client/TextRange.d.ts +2 -1
- package/dist/client/hierarchy.d.ts +9 -8
- package/dist/client/index.d.ts +3 -2
- package/dist/expression/DependencyContext.d.ts +2 -1
- package/dist/expression/DependentExpression.d.ts +3 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1882 -1757
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +15 -15
- package/dist/instance/RepeatInstance.d.ts +39 -15
- package/dist/instance/RepeatRange.d.ts +98 -20
- package/dist/instance/Root.d.ts +25 -39
- package/dist/instance/SelectField.d.ts +17 -17
- package/dist/instance/StringField.d.ts +17 -17
- package/dist/instance/Subtree.d.ts +13 -13
- package/dist/instance/abstract/DescendantNode.d.ts +26 -18
- package/dist/instance/abstract/InstanceNode.d.ts +44 -46
- package/dist/instance/children.d.ts +2 -1
- package/dist/instance/hierarchy.d.ts +8 -7
- package/dist/instance/index.d.ts +4 -3
- package/dist/instance/internal-api/EvaluationContext.d.ts +10 -8
- package/dist/instance/internal-api/InstanceConfig.d.ts +3 -2
- package/dist/instance/internal-api/SubscribableDependency.d.ts +2 -1
- package/dist/instance/internal-api/TranslationContext.d.ts +2 -1
- package/dist/instance/internal-api/ValueContext.d.ts +6 -5
- package/dist/instance/resource.d.ts +3 -2
- package/dist/instance/text/TextChunk.d.ts +4 -3
- package/dist/instance/text/TextRange.d.ts +2 -1
- package/dist/lib/TokenListParser.d.ts +84 -0
- package/dist/lib/dom/query.d.ts +8 -2
- package/dist/lib/reactivity/createChildrenState.d.ts +4 -3
- package/dist/lib/reactivity/createComputedExpression.d.ts +4 -3
- package/dist/lib/reactivity/createSelectItems.d.ts +4 -3
- package/dist/lib/reactivity/createValueState.d.ts +3 -2
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +6 -4
- package/dist/lib/reactivity/node-state/createClientState.d.ts +7 -6
- package/dist/lib/reactivity/node-state/createCurrentState.d.ts +5 -4
- package/dist/lib/reactivity/node-state/createEngineState.d.ts +4 -3
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +7 -6
- package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +2 -1
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +3 -2
- package/dist/lib/reactivity/node-state/representations.d.ts +2 -1
- package/dist/lib/reactivity/scope.d.ts +2 -1
- package/dist/lib/reactivity/text/createFieldHint.d.ts +4 -3
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +4 -3
- package/dist/lib/reactivity/text/createTextRange.d.ts +6 -5
- package/dist/lib/reactivity/types.d.ts +2 -1
- package/dist/lib/xpath/analysis.d.ts +2 -1
- package/dist/model/BindComputation.d.ts +2 -1
- package/dist/model/BindDefinition.d.ts +6 -5
- package/dist/model/DescendentNodeDefinition.d.ts +6 -6
- package/dist/model/ModelBindMap.d.ts +4 -3
- package/dist/model/ModelDefinition.d.ts +2 -1
- package/dist/model/NodeDefinition.d.ts +20 -19
- package/dist/model/RepeatInstanceDefinition.d.ts +7 -7
- package/dist/model/{RepeatSequenceDefinition.d.ts → RepeatRangeDefinition.d.ts} +7 -6
- package/dist/model/RepeatTemplateDefinition.d.ts +8 -8
- package/dist/model/RootDefinition.d.ts +8 -5
- package/dist/model/SubtreeDefinition.d.ts +5 -4
- package/dist/model/ValueNodeDefinition.d.ts +5 -5
- package/dist/solid.js +1873 -1751
- package/dist/solid.js.map +1 -1
- package/package.json +14 -18
- package/src/XFormDOM.ts +81 -8
- package/src/body/BodyDefinition.ts +38 -23
- package/src/body/RepeatElementDefinition.ts +70 -0
- package/src/body/appearance/inputAppearanceParser.ts +39 -0
- package/src/body/appearance/selectAppearanceParser.ts +38 -0
- package/src/body/appearance/structureElementAppearanceParser.ts +7 -0
- package/src/body/control/ControlDefinition.ts +4 -0
- package/src/body/control/InputDefinition.ts +13 -0
- package/src/body/control/select/SelectDefinition.ts +14 -5
- package/src/body/group/BaseGroupDefinition.ts +11 -49
- package/src/body/text/LabelDefinition.ts +15 -1
- package/src/body/text/TextElementDefinition.ts +5 -5
- package/src/client/BaseNode.ts +9 -1
- package/src/client/GroupNode.ts +6 -2
- package/src/client/NodeAppearances.ts +22 -0
- package/src/client/RepeatInstanceNode.ts +4 -0
- package/src/client/RepeatRangeNode.ts +6 -2
- package/src/client/RootNode.ts +22 -0
- package/src/client/SelectNode.ts +4 -0
- package/src/client/StringNode.ts +4 -0
- package/src/client/SubtreeNode.ts +1 -0
- package/src/instance/Group.ts +14 -9
- package/src/instance/RepeatInstance.ts +59 -15
- package/src/instance/RepeatRange.ts +133 -15
- package/src/instance/Root.ts +20 -64
- package/src/instance/SelectField.ts +7 -7
- package/src/instance/StringField.ts +8 -7
- package/src/instance/Subtree.ts +10 -7
- package/src/instance/abstract/DescendantNode.ts +45 -43
- package/src/instance/abstract/InstanceNode.ts +69 -86
- package/src/instance/children.ts +17 -7
- package/src/instance/index.ts +1 -1
- package/src/instance/internal-api/EvaluationContext.ts +5 -6
- package/src/instance/internal-api/ValueContext.ts +2 -2
- package/src/lib/TokenListParser.ts +156 -0
- package/src/lib/dom/query.ts +13 -0
- package/src/lib/reactivity/createChildrenState.ts +51 -6
- package/src/lib/reactivity/createComputedExpression.ts +1 -1
- package/src/lib/reactivity/createSelectItems.ts +4 -6
- package/src/lib/reactivity/createValueState.ts +6 -6
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +3 -1
- package/src/model/DescendentNodeDefinition.ts +1 -2
- package/src/model/ModelDefinition.ts +1 -1
- package/src/model/NodeDefinition.ts +12 -12
- package/src/model/RepeatInstanceDefinition.ts +8 -13
- package/src/model/{RepeatSequenceDefinition.ts → RepeatRangeDefinition.ts} +6 -6
- package/src/model/RepeatTemplateDefinition.ts +10 -15
- package/src/model/RootDefinition.ts +6 -12
- package/src/model/SubtreeDefinition.ts +3 -3
- package/src/model/ValueNodeDefinition.ts +2 -3
- package/dist/body/RepeatDefinition.d.ts +0 -15
- package/dist/body/group/RepeatGroupDefinition.d.ts +0 -12
- package/src/body/RepeatDefinition.ts +0 -54
- package/src/body/group/RepeatGroupDefinition.ts +0 -91
package/src/instance/Root.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { XFormsXPathEvaluator } from '@getodk/xpath';
|
|
|
2
2
|
import type { Accessor, Signal } from 'solid-js';
|
|
3
3
|
import { createSignal } from 'solid-js';
|
|
4
4
|
import type { XFormDOM } from '../XFormDOM.ts';
|
|
5
|
+
import type { BodyClassList } from '../body/BodyDefinition.ts';
|
|
5
6
|
import type { ActiveLanguage, FormLanguage, FormLanguages } from '../client/FormLanguage.ts';
|
|
6
7
|
import type { RootNode } from '../client/RootNode.ts';
|
|
7
8
|
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
@@ -98,27 +99,20 @@ export class Root
|
|
|
98
99
|
SubscribableDependency,
|
|
99
100
|
TranslationContext
|
|
100
101
|
{
|
|
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
102
|
private readonly childrenState: ChildrenState<GeneralChildNode>;
|
|
114
103
|
|
|
115
104
|
// InstanceNode
|
|
105
|
+
readonly hasReadonlyAncestor = () => false;
|
|
106
|
+
readonly isReadonly = () => false;
|
|
107
|
+
readonly hasNonRelevantAncestor = () => false;
|
|
108
|
+
readonly isRelevant = () => true;
|
|
116
109
|
protected readonly state: SharedNodeState<RootStateSpec>;
|
|
117
110
|
protected readonly engineState: EngineState<RootStateSpec>;
|
|
118
111
|
|
|
119
112
|
// RootNode
|
|
120
113
|
readonly nodeType = 'root';
|
|
121
|
-
|
|
114
|
+
readonly appearances = null;
|
|
115
|
+
readonly classes: BodyClassList;
|
|
122
116
|
readonly currentState: MaterializedChildren<CurrentState<RootStateSpec>, GeneralChildNode>;
|
|
123
117
|
|
|
124
118
|
protected readonly instanceDOM: XFormDOM;
|
|
@@ -129,12 +123,6 @@ export class Root
|
|
|
129
123
|
// EvaluationContext
|
|
130
124
|
readonly evaluator: XFormsXPathEvaluator;
|
|
131
125
|
|
|
132
|
-
private readonly rootReference: string;
|
|
133
|
-
|
|
134
|
-
override get contextReference(): string {
|
|
135
|
-
return this.rootReference;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
126
|
readonly contextNode: Element;
|
|
139
127
|
|
|
140
128
|
// RootNode
|
|
@@ -147,20 +135,18 @@ export class Root
|
|
|
147
135
|
return this.engineState.activeLanguage;
|
|
148
136
|
}
|
|
149
137
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
definition: RootDefinition,
|
|
153
|
-
engineConfig: InstanceConfig
|
|
154
|
-
) {
|
|
155
|
-
super(engineConfig, null, definition);
|
|
138
|
+
constructor(xformDOM: XFormDOM, definition: RootDefinition, engineConfig: InstanceConfig) {
|
|
139
|
+
const reference = definition.nodeset;
|
|
156
140
|
|
|
157
|
-
|
|
141
|
+
super(engineConfig, null, definition, {
|
|
142
|
+
computeReference: () => reference,
|
|
143
|
+
});
|
|
158
144
|
|
|
159
|
-
this.
|
|
145
|
+
this.classes = definition.classes;
|
|
160
146
|
|
|
161
|
-
const
|
|
147
|
+
const childrenState = createChildrenState<Root, GeneralChildNode>(this);
|
|
162
148
|
|
|
163
|
-
this.
|
|
149
|
+
this.childrenState = childrenState;
|
|
164
150
|
|
|
165
151
|
const instanceDOM = xformDOM.createInstance();
|
|
166
152
|
const evaluator = instanceDOM.primaryInstanceEvaluator;
|
|
@@ -187,7 +173,11 @@ export class Root
|
|
|
187
173
|
|
|
188
174
|
this.state = state;
|
|
189
175
|
this.engineState = state.engineState;
|
|
190
|
-
this.currentState = materializeCurrentStateChildren(
|
|
176
|
+
this.currentState = materializeCurrentStateChildren(
|
|
177
|
+
this.scope,
|
|
178
|
+
state.currentState,
|
|
179
|
+
childrenState
|
|
180
|
+
);
|
|
191
181
|
|
|
192
182
|
const contextNode = instanceDOM.xformDocument.createElement(definition.nodeName);
|
|
193
183
|
|
|
@@ -201,40 +191,6 @@ export class Root
|
|
|
201
191
|
childrenState.setChildren(buildChildren(this));
|
|
202
192
|
}
|
|
203
193
|
|
|
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
194
|
getChildren(): readonly GeneralChildNode[] {
|
|
239
195
|
return this.childrenState.getChildren();
|
|
240
196
|
}
|
|
@@ -2,7 +2,7 @@ import { xmlXPathWhitespaceSeparatedList } from '@getodk/common/lib/string/white
|
|
|
2
2
|
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
|
-
import type { SelectItem, SelectNode } from '../client/SelectNode.ts';
|
|
5
|
+
import type { SelectItem, SelectNode, SelectNodeAppearances } from '../client/SelectNode.ts';
|
|
6
6
|
import type { TextRange } from '../index.ts';
|
|
7
7
|
import { createSelectItems } from '../lib/reactivity/createSelectItems.ts';
|
|
8
8
|
import { createValueState } from '../lib/reactivity/createValueState.ts';
|
|
@@ -50,7 +50,7 @@ export class SelectField
|
|
|
50
50
|
|
|
51
51
|
// SelectNode
|
|
52
52
|
readonly nodeType = 'select';
|
|
53
|
-
|
|
53
|
+
readonly appearances: SelectNodeAppearances;
|
|
54
54
|
readonly currentState: CurrentState<SelectFieldStateSpec>;
|
|
55
55
|
|
|
56
56
|
// ValueContext
|
|
@@ -83,6 +83,7 @@ export class SelectField
|
|
|
83
83
|
constructor(parent: GeneralParentNode, definition: SelectFieldDefinition) {
|
|
84
84
|
super(parent, definition);
|
|
85
85
|
|
|
86
|
+
this.appearances = definition.bodyElement.appearances;
|
|
86
87
|
this.selectExclusive = definition.bodyElement.type === 'select1';
|
|
87
88
|
|
|
88
89
|
const valueOptions = createSelectItems(this);
|
|
@@ -92,7 +93,10 @@ export class SelectField
|
|
|
92
93
|
const state = createSharedNodeState(
|
|
93
94
|
this.scope,
|
|
94
95
|
{
|
|
95
|
-
|
|
96
|
+
reference: this.contextReference,
|
|
97
|
+
readonly: this.isReadonly,
|
|
98
|
+
relevant: this.isRelevant,
|
|
99
|
+
required: this.isRequired,
|
|
96
100
|
|
|
97
101
|
label: createNodeLabel(this, definition),
|
|
98
102
|
hint: createFieldHint(this, definition),
|
|
@@ -120,10 +124,6 @@ export class SelectField
|
|
|
120
124
|
);
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
protected computeReference(parent: GeneralParentNode): string {
|
|
124
|
-
return this.computeChildStepReference(parent);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
127
|
protected updateSelectedItemValues(values: readonly string[]) {
|
|
128
128
|
const itemsByValue = untrack(() => this.getSelectItemsByValue());
|
|
129
129
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { identity } from '@getodk/common/lib/identity.ts';
|
|
2
2
|
import type { Accessor } from 'solid-js';
|
|
3
3
|
import type { InputDefinition } from '../body/control/InputDefinition.ts';
|
|
4
|
-
import type { StringNode } from '../client/StringNode.ts';
|
|
4
|
+
import type { StringNode, StringNodeAppearances } from '../client/StringNode.ts';
|
|
5
5
|
import type { TextRange } from '../index.ts';
|
|
6
6
|
import { createValueState } from '../lib/reactivity/createValueState.ts';
|
|
7
7
|
import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
|
|
@@ -43,7 +43,7 @@ export class StringField
|
|
|
43
43
|
|
|
44
44
|
// StringNode
|
|
45
45
|
readonly nodeType = 'string';
|
|
46
|
-
|
|
46
|
+
readonly appearances: StringNodeAppearances;
|
|
47
47
|
readonly currentState: CurrentState<StringFieldStateSpec>;
|
|
48
48
|
|
|
49
49
|
// ValueContext
|
|
@@ -54,10 +54,15 @@ export class StringField
|
|
|
54
54
|
constructor(parent: GeneralParentNode, definition: StringFieldDefinition) {
|
|
55
55
|
super(parent, definition);
|
|
56
56
|
|
|
57
|
+
this.appearances = (definition.bodyElement?.appearances ?? null) as StringNodeAppearances;
|
|
58
|
+
|
|
57
59
|
const state = createSharedNodeState(
|
|
58
60
|
this.scope,
|
|
59
61
|
{
|
|
60
|
-
|
|
62
|
+
reference: this.contextReference,
|
|
63
|
+
readonly: this.isReadonly,
|
|
64
|
+
relevant: this.isRelevant,
|
|
65
|
+
required: this.isRequired,
|
|
61
66
|
|
|
62
67
|
label: createNodeLabel(this, definition),
|
|
63
68
|
hint: createFieldHint(this, definition),
|
|
@@ -75,10 +80,6 @@ export class StringField
|
|
|
75
80
|
this.currentState = state.currentState;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
protected computeReference(parent: GeneralParentNode): string {
|
|
79
|
-
return this.computeChildStepReference(parent);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
83
|
// InstanceNode
|
|
83
84
|
getChildren(): readonly [] {
|
|
84
85
|
return [];
|
package/src/instance/Subtree.ts
CHANGED
|
@@ -36,7 +36,7 @@ export class Subtree
|
|
|
36
36
|
|
|
37
37
|
// SubtreeNode
|
|
38
38
|
readonly nodeType = 'subtree';
|
|
39
|
-
|
|
39
|
+
readonly appearances = null;
|
|
40
40
|
readonly currentState: MaterializedChildren<CurrentState<SubtreeStateSpec>, GeneralChildNode>;
|
|
41
41
|
|
|
42
42
|
constructor(parent: GeneralParentNode, definition: SubtreeDefinition) {
|
|
@@ -49,7 +49,10 @@ export class Subtree
|
|
|
49
49
|
const state = createSharedNodeState(
|
|
50
50
|
this.scope,
|
|
51
51
|
{
|
|
52
|
-
|
|
52
|
+
reference: this.contextReference,
|
|
53
|
+
readonly: this.isReadonly,
|
|
54
|
+
relevant: this.isRelevant,
|
|
55
|
+
required: this.isRequired,
|
|
53
56
|
|
|
54
57
|
label: null,
|
|
55
58
|
hint: null,
|
|
@@ -64,15 +67,15 @@ export class Subtree
|
|
|
64
67
|
|
|
65
68
|
this.state = state;
|
|
66
69
|
this.engineState = state.engineState;
|
|
67
|
-
this.currentState = materializeCurrentStateChildren(
|
|
70
|
+
this.currentState = materializeCurrentStateChildren(
|
|
71
|
+
this.scope,
|
|
72
|
+
state.currentState,
|
|
73
|
+
childrenState
|
|
74
|
+
);
|
|
68
75
|
|
|
69
76
|
childrenState.setChildren(buildChildren(this));
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
protected computeReference(parent: GeneralParentNode): string {
|
|
73
|
-
return this.computeChildStepReference(parent);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
79
|
getChildren(): readonly GeneralChildNode[] {
|
|
77
80
|
return this.childrenState.getChildren();
|
|
78
81
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { XFormsXPathEvaluator } from '@getodk/xpath';
|
|
2
2
|
import type { Accessor } from 'solid-js';
|
|
3
|
-
import { createMemo } from 'solid-js';
|
|
4
3
|
import type { BaseNode } from '../../client/BaseNode.ts';
|
|
5
4
|
import { createComputedExpression } from '../../lib/reactivity/createComputedExpression.ts';
|
|
6
5
|
import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
|
|
@@ -52,6 +51,10 @@ export type AnyDescendantNode = DescendantNode<
|
|
|
52
51
|
any
|
|
53
52
|
>;
|
|
54
53
|
|
|
54
|
+
interface DescendantNodeOptions {
|
|
55
|
+
readonly computeReference?: Accessor<string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
55
58
|
export abstract class DescendantNode<
|
|
56
59
|
Definition extends DescendantNodeDefinition,
|
|
57
60
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -61,63 +64,62 @@ export abstract class DescendantNode<
|
|
|
61
64
|
extends InstanceNode<Definition, Spec, Child>
|
|
62
65
|
implements BaseNode, EvaluationContext, SubscribableDependency
|
|
63
66
|
{
|
|
67
|
+
readonly hasReadonlyAncestor: Accessor<boolean> = () => {
|
|
68
|
+
const { parent } = this;
|
|
69
|
+
|
|
70
|
+
return parent.hasReadonlyAncestor() || parent.isReadonly();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
readonly isSelfReadonly: Accessor<boolean>;
|
|
74
|
+
|
|
75
|
+
readonly isReadonly: Accessor<boolean> = () => {
|
|
76
|
+
if (this.hasReadonlyAncestor()) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return this.isSelfReadonly();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
readonly hasNonRelevantAncestor: Accessor<boolean> = () => {
|
|
84
|
+
const { parent } = this;
|
|
85
|
+
|
|
86
|
+
return parent.hasNonRelevantAncestor() || !parent.isRelevant();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
readonly isSelfRelevant: Accessor<boolean>;
|
|
90
|
+
|
|
91
|
+
readonly isRelevant: Accessor<boolean> = () => {
|
|
92
|
+
if (this.hasNonRelevantAncestor()) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this.isSelfRelevant();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
protected readonly isRequired: Accessor<boolean>;
|
|
100
|
+
|
|
64
101
|
readonly root: Root;
|
|
65
102
|
readonly evaluator: XFormsXPathEvaluator;
|
|
66
103
|
readonly contextNode: Element;
|
|
67
104
|
|
|
68
105
|
constructor(
|
|
69
106
|
override readonly parent: DescendantNodeParent<Definition>,
|
|
70
|
-
override readonly definition: Definition
|
|
107
|
+
override readonly definition: Definition,
|
|
108
|
+
options?: DescendantNodeOptions
|
|
71
109
|
) {
|
|
72
|
-
super(parent.engineConfig, parent, definition);
|
|
110
|
+
super(parent.engineConfig, parent, definition, options);
|
|
73
111
|
|
|
74
112
|
const { evaluator, root } = parent;
|
|
75
113
|
|
|
76
114
|
this.root = root;
|
|
77
115
|
this.evaluator = evaluator;
|
|
78
116
|
this.contextNode = this.initializeContextNode(parent.contextNode, definition.nodeName);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
protected computeChildStepReference(parent: DescendantNodeParent<Definition>): string {
|
|
82
|
-
return `${parent.contextReference}/${this.definition.nodeName}`;
|
|
83
|
-
}
|
|
84
117
|
|
|
85
|
-
|
|
86
|
-
parent: DescendantNodeParent<Definition>,
|
|
87
|
-
definition: Definition
|
|
88
|
-
): string;
|
|
89
|
-
|
|
90
|
-
protected buildSharedStateSpec(
|
|
91
|
-
parent: DescendantNodeParent<Definition>,
|
|
92
|
-
definition: Definition
|
|
93
|
-
): DescendantNodeSharedStateSpec {
|
|
94
|
-
return this.scope.runTask(() => {
|
|
95
|
-
const reference = createMemo(() => this.contextReference);
|
|
96
|
-
const { bind } = definition;
|
|
97
|
-
|
|
98
|
-
// TODO: we can likely short-circuit `readonly` computation when a node
|
|
99
|
-
// is non-relevant.
|
|
100
|
-
const selfReadonly = createComputedExpression(this, bind.readonly);
|
|
101
|
-
const readonly = createMemo(() => {
|
|
102
|
-
return parent.isReadonly || selfReadonly();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const selfRelevant = createComputedExpression(this, bind.relevant);
|
|
106
|
-
const relevant = createMemo(() => {
|
|
107
|
-
return parent.isRelevant && selfRelevant();
|
|
108
|
-
});
|
|
118
|
+
const { readonly, relevant, required } = definition.bind;
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
reference,
|
|
116
|
-
readonly,
|
|
117
|
-
relevant,
|
|
118
|
-
required,
|
|
119
|
-
};
|
|
120
|
-
});
|
|
120
|
+
this.isSelfReadonly = createComputedExpression(this, readonly);
|
|
121
|
+
this.isSelfRelevant = createComputedExpression(this, relevant);
|
|
122
|
+
this.isRequired = createComputedExpression(this, required);
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
protected createContextNode(parentContextNode: Element, nodeName: string): Element {
|
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
import type { TextRange } from '../../index.ts';
|
|
7
7
|
import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
@@ -40,10 +40,6 @@ type AnyInstanceNode = InstanceNode<
|
|
|
40
40
|
any
|
|
41
41
|
>;
|
|
42
42
|
|
|
43
|
-
interface InitializedStateOptions<T, K extends keyof T> {
|
|
44
|
-
readonly uninitializedFallback: T[K];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
43
|
/**
|
|
48
44
|
* This type has the same effect as {@link MaterializedChildren}, but abstractly
|
|
49
45
|
* handles leaf node types as well.
|
|
@@ -61,6 +57,21 @@ export type InstanceNodeCurrentState<
|
|
|
61
57
|
: null;
|
|
62
58
|
};
|
|
63
59
|
|
|
60
|
+
interface ComputableReferenceNode {
|
|
61
|
+
readonly parent: AnyParentNode | null;
|
|
62
|
+
readonly definition: AnyNodeDefinition;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type ComputeInstanceNodeReference = <This extends ComputableReferenceNode>(
|
|
66
|
+
this: This,
|
|
67
|
+
parent: This['parent'],
|
|
68
|
+
definition: This['definition']
|
|
69
|
+
) => string;
|
|
70
|
+
|
|
71
|
+
export interface InstanceNodeOptions {
|
|
72
|
+
readonly computeReference?: () => string;
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
export abstract class InstanceNode<
|
|
65
76
|
Definition extends AnyNodeDefinition,
|
|
66
77
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -69,60 +80,32 @@ export abstract class InstanceNode<
|
|
|
69
80
|
>
|
|
70
81
|
implements BaseNode, EvaluationContext, SubscribableDependency
|
|
71
82
|
{
|
|
72
|
-
protected readonly isStateInitialized: Accessor<boolean>;
|
|
73
|
-
|
|
74
83
|
protected abstract readonly state: SharedNodeState<Spec>;
|
|
75
84
|
protected abstract readonly engineState: EngineState<Spec>;
|
|
76
85
|
|
|
77
86
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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.
|
|
87
|
+
* @package Exposed on every node type to facilitate inheritance, as well as
|
|
88
|
+
* conditional behavior for value nodes.
|
|
95
89
|
*/
|
|
96
|
-
|
|
97
|
-
key: K,
|
|
98
|
-
options: InitializedStateOptions<EngineState<Spec>, K>
|
|
99
|
-
): EngineState<Spec>[K] {
|
|
100
|
-
if (this.isStateInitialized()) {
|
|
101
|
-
return this.engineState[key];
|
|
102
|
-
}
|
|
90
|
+
abstract readonly hasReadonlyAncestor: Accessor<boolean>;
|
|
103
91
|
|
|
104
|
-
|
|
105
|
-
|
|
92
|
+
/**
|
|
93
|
+
* @package Exposed on every node type to facilitate inheritance, as well as
|
|
94
|
+
* conditional behavior for value nodes.
|
|
95
|
+
*/
|
|
96
|
+
abstract readonly isReadonly: Accessor<boolean>;
|
|
106
97
|
|
|
107
98
|
/**
|
|
108
99
|
* @package Exposed on every node type to facilitate inheritance, as well as
|
|
109
100
|
* conditional behavior for value nodes.
|
|
110
101
|
*/
|
|
111
|
-
|
|
112
|
-
return (this as AnyInstanceNode).getInitializedState('readonly', {
|
|
113
|
-
uninitializedFallback: false,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
102
|
+
abstract readonly hasNonRelevantAncestor: Accessor<boolean>;
|
|
116
103
|
|
|
117
104
|
/**
|
|
118
105
|
* @package Exposed on every node type to facilitate inheritance, as well as
|
|
119
106
|
* conditional behavior for value nodes.
|
|
120
107
|
*/
|
|
121
|
-
|
|
122
|
-
return (this as AnyInstanceNode).getInitializedState('relevant', {
|
|
123
|
-
uninitializedFallback: true,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
108
|
+
abstract readonly isRelevant: Accessor<boolean>;
|
|
126
109
|
|
|
127
110
|
// BaseNode: identity
|
|
128
111
|
readonly nodeId: NodeID;
|
|
@@ -130,6 +113,8 @@ export abstract class InstanceNode<
|
|
|
130
113
|
// BaseNode: node types and variants (e.g. for narrowing)
|
|
131
114
|
abstract readonly nodeType: InstanceNodeType;
|
|
132
115
|
|
|
116
|
+
abstract readonly appearances: NodeAppearances<Definition>;
|
|
117
|
+
|
|
133
118
|
abstract readonly currentState: InstanceNodeCurrentState<Spec, Child>;
|
|
134
119
|
|
|
135
120
|
// BaseNode: structural
|
|
@@ -141,35 +126,40 @@ export abstract class InstanceNode<
|
|
|
141
126
|
// EvaluationContext *and* Subscribable: node-specific
|
|
142
127
|
readonly scope: ReactiveScope;
|
|
143
128
|
|
|
129
|
+
readonly computeReference: ComputeInstanceNodeReference;
|
|
130
|
+
|
|
131
|
+
protected readonly computeChildStepReference: ComputeInstanceNodeReference = (
|
|
132
|
+
parent,
|
|
133
|
+
definition
|
|
134
|
+
): string => {
|
|
135
|
+
if (parent == null) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'Cannot compute child step reference of node without parent (was this called from `Root`?)'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return `${parent.contextReference()}/${definition.nodeName}`;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
144
|
// EvaluationContext: node-specific
|
|
145
|
-
|
|
145
|
+
readonly contextReference = (): string => {
|
|
146
146
|
return this.computeReference(this.parent, this.definition);
|
|
147
|
-
}
|
|
147
|
+
};
|
|
148
148
|
|
|
149
149
|
abstract readonly contextNode: Element;
|
|
150
150
|
|
|
151
151
|
constructor(
|
|
152
152
|
readonly engineConfig: InstanceConfig,
|
|
153
153
|
readonly parent: AnyParentNode | null,
|
|
154
|
-
readonly definition: Definition
|
|
154
|
+
readonly definition: Definition,
|
|
155
|
+
options?: InstanceNodeOptions
|
|
155
156
|
) {
|
|
157
|
+
this.computeReference = options?.computeReference ?? this.computeChildStepReference;
|
|
158
|
+
|
|
156
159
|
this.scope = createReactiveScope();
|
|
157
160
|
this.engineConfig = engineConfig;
|
|
158
161
|
this.nodeId = declareNodeID(engineConfig.createUniqueId());
|
|
159
162
|
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
163
|
}
|
|
174
164
|
|
|
175
165
|
/**
|
|
@@ -184,18 +174,13 @@ export abstract class InstanceNode<
|
|
|
184
174
|
*/
|
|
185
175
|
abstract getChildren(this: AnyInstanceNode): readonly AnyChildNode[];
|
|
186
176
|
|
|
187
|
-
|
|
188
|
-
parent: AnyInstanceNode | null,
|
|
189
|
-
definition: Definition
|
|
190
|
-
): string;
|
|
191
|
-
|
|
192
|
-
getNodeByReference(
|
|
177
|
+
getNodesByReference(
|
|
193
178
|
this: AnyNode,
|
|
194
179
|
visited: WeakSet<AnyNode>,
|
|
195
180
|
dependencyReference: string
|
|
196
|
-
): SubscribableDependency
|
|
181
|
+
): readonly SubscribableDependency[] {
|
|
197
182
|
if (visited.has(this)) {
|
|
198
|
-
return
|
|
183
|
+
return [];
|
|
199
184
|
}
|
|
200
185
|
|
|
201
186
|
visited.add(this);
|
|
@@ -203,39 +188,37 @@ export abstract class InstanceNode<
|
|
|
203
188
|
const { nodeset } = this.definition;
|
|
204
189
|
|
|
205
190
|
if (dependencyReference === nodeset) {
|
|
206
|
-
|
|
191
|
+
if (this.nodeType === 'repeat-instance') {
|
|
192
|
+
return [this.parent];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return [this];
|
|
207
196
|
}
|
|
208
197
|
|
|
209
198
|
if (
|
|
210
199
|
dependencyReference.startsWith(`${nodeset}/`) ||
|
|
211
200
|
dependencyReference.startsWith(`${nodeset}[`)
|
|
212
201
|
) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
}
|
|
202
|
+
return this.getChildren().flatMap((child) => {
|
|
203
|
+
return child.getNodesByReference(visited, dependencyReference);
|
|
204
|
+
});
|
|
226
205
|
}
|
|
227
206
|
|
|
228
|
-
return this.parent?.
|
|
207
|
+
return this.parent?.getNodesByReference(visited, dependencyReference) ?? [];
|
|
229
208
|
}
|
|
230
209
|
|
|
231
210
|
// EvaluationContext: node-relative
|
|
232
|
-
|
|
211
|
+
getSubscribableDependenciesByReference(
|
|
233
212
|
this: AnyNode,
|
|
234
213
|
reference: string
|
|
235
|
-
): SubscribableDependency
|
|
236
|
-
|
|
214
|
+
): readonly SubscribableDependency[] {
|
|
215
|
+
if (this.nodeType === 'root') {
|
|
216
|
+
const visited = new WeakSet<AnyNode>();
|
|
217
|
+
|
|
218
|
+
return this.getNodesByReference(visited, reference);
|
|
219
|
+
}
|
|
237
220
|
|
|
238
|
-
return this.
|
|
221
|
+
return this.root.getSubscribableDependenciesByReference(reference);
|
|
239
222
|
}
|
|
240
223
|
|
|
241
224
|
// SubscribableDependency
|