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