@getodk/xforms-engine 0.15.0 → 0.16.1
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/client/AttributeNode.d.ts +4 -6
- package/dist/client/BaseNode.d.ts +5 -0
- package/dist/client/form/FormInstanceConfig.d.ts +15 -0
- package/dist/index.js +654 -429
- package/dist/index.js.map +1 -1
- package/dist/instance/Attribute.d.ts +13 -19
- package/dist/instance/Group.d.ts +2 -1
- package/dist/instance/InputControl.d.ts +5 -0
- package/dist/instance/ModelValue.d.ts +4 -0
- package/dist/instance/Note.d.ts +4 -0
- package/dist/instance/PrimaryInstance.d.ts +3 -2
- package/dist/instance/RangeControl.d.ts +4 -0
- package/dist/instance/RankControl.d.ts +5 -1
- package/dist/instance/Root.d.ts +2 -1
- package/dist/instance/SelectControl.d.ts +5 -1
- package/dist/instance/TriggerControl.d.ts +4 -0
- package/dist/instance/UploadControl.d.ts +3 -2
- package/dist/instance/abstract/DescendantNode.d.ts +5 -4
- package/dist/instance/abstract/InstanceNode.d.ts +6 -5
- package/dist/instance/abstract/ValueNode.d.ts +2 -1
- package/dist/instance/attachments/buildAttributes.d.ts +6 -2
- package/dist/instance/hierarchy.d.ts +2 -2
- package/dist/instance/internal-api/AttributeContext.d.ts +6 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
- package/dist/instance/internal-api/InstanceValueContext.d.ts +6 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +0 -1
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +4 -0
- package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +1 -1
- package/dist/integration/xpath/adapter/kind.d.ts +5 -3
- package/dist/integration/xpath/adapter/traversal.d.ts +3 -3
- package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +1 -0
- package/dist/lib/codecs/items/SingleValueItemCodec.d.ts +1 -1
- package/dist/lib/reactivity/createInstanceValueState.d.ts +4 -1
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +2 -2
- package/dist/lib/xml-serialization.d.ts +1 -1
- package/dist/parse/XFormDOM.d.ts +3 -0
- package/dist/parse/expression/ActionComputationExpression.d.ts +4 -0
- package/dist/parse/model/ActionDefinition.d.ts +15 -0
- package/dist/parse/model/AttributeDefinition.d.ts +5 -1
- package/dist/parse/model/BindPreloadDefinition.d.ts +6 -10
- package/dist/parse/model/Event.d.ts +8 -0
- package/dist/parse/model/LeafNodeDefinition.d.ts +5 -2
- package/dist/parse/model/ModelActionMap.d.ts +9 -0
- package/dist/parse/model/ModelDefinition.d.ts +8 -1
- package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
- package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
- package/dist/parse/model/RootDefinition.d.ts +1 -0
- package/dist/solid.js +654 -429
- package/dist/solid.js.map +1 -1
- package/package.json +21 -17
- package/src/client/AttributeNode.ts +4 -6
- package/src/client/BaseNode.ts +6 -0
- package/src/client/form/FormInstanceConfig.ts +17 -0
- package/src/client/validation.ts +1 -1
- package/src/entrypoints/FormInstance.ts +1 -0
- package/src/instance/Attribute.ts +43 -59
- package/src/instance/Group.ts +5 -6
- package/src/instance/InputControl.ts +16 -1
- package/src/instance/ModelValue.ts +16 -1
- package/src/instance/Note.ts +15 -1
- package/src/instance/PrimaryInstance.ts +8 -10
- package/src/instance/RangeControl.ts +15 -1
- package/src/instance/RankControl.ts +17 -2
- package/src/instance/Root.ts +5 -6
- package/src/instance/SelectControl.ts +16 -2
- package/src/instance/TriggerControl.ts +15 -1
- package/src/instance/UploadControl.ts +9 -8
- package/src/instance/abstract/DescendantNode.ts +4 -8
- package/src/instance/abstract/InstanceNode.ts +7 -5
- package/src/instance/abstract/ValueNode.ts +2 -1
- package/src/instance/attachments/buildAttributes.ts +15 -4
- package/src/instance/children/childrenInitOptions.ts +2 -1
- package/src/instance/children/normalizeChildInitOptions.ts +1 -1
- package/src/instance/hierarchy.ts +2 -2
- package/src/instance/internal-api/AttributeContext.ts +6 -0
- package/src/instance/internal-api/InstanceConfig.ts +6 -1
- package/src/instance/internal-api/InstanceValueContext.ts +6 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +0 -1
- package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +4 -0
- package/src/instance/repeat/RepeatInstance.ts +3 -4
- package/src/integration/xpath/adapter/XFormsXPathNode.ts +1 -0
- package/src/integration/xpath/adapter/engineDOMAdapter.ts +2 -2
- package/src/integration/xpath/adapter/kind.ts +6 -1
- package/src/integration/xpath/adapter/names.ts +1 -0
- package/src/integration/xpath/adapter/traversal.ts +5 -6
- package/src/integration/xpath/static-dom/StaticAttribute.ts +1 -0
- package/src/lib/client-reactivity/instance-state/createValueNodeInstanceState.ts +2 -1
- package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +1 -0
- package/src/lib/codecs/NoteCodec.ts +1 -1
- package/src/lib/codecs/items/SingleValueItemCodec.ts +1 -3
- package/src/lib/reactivity/createInstanceValueState.ts +177 -52
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +2 -2
- package/src/lib/xml-serialization.ts +9 -1
- package/src/parse/XFormDOM.ts +9 -0
- package/src/parse/body/GroupElementDefinition.ts +1 -1
- package/src/parse/body/control/InputControlDefinition.ts +1 -1
- package/src/parse/expression/ActionComputationExpression.ts +12 -0
- package/src/parse/model/ActionDefinition.ts +70 -0
- package/src/parse/model/AttributeDefinition.ts +10 -2
- package/src/parse/model/AttributeDefinitionMap.ts +1 -1
- package/src/parse/model/BindDefinition.ts +1 -6
- package/src/parse/model/BindPreloadDefinition.ts +44 -12
- package/src/parse/model/Event.ts +9 -0
- package/src/parse/model/LeafNodeDefinition.ts +5 -1
- package/src/parse/model/ModelActionMap.ts +37 -0
- package/src/parse/model/ModelDefinition.ts +18 -3
- package/src/parse/model/NoteNodeDefinition.ts +5 -2
- package/src/parse/model/RangeNodeDefinition.ts +5 -2
- package/src/parse/model/RootDefinition.ts +22 -4
- package/dist/lib/reactivity/createAttributeValueState.d.ts +0 -15
- package/src/lib/reactivity/createAttributeValueState.ts +0 -189
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { InstanceState } from '../../../client/serialization/InstanceState.ts';
|
|
2
2
|
import type { QualifiedName } from '../../../lib/names/QualifiedName.ts';
|
|
3
|
+
import type { BindDefinition } from '../../../parse/model/BindDefinition.ts';
|
|
4
|
+
import type { Attribute } from '../../Attribute.ts';
|
|
3
5
|
import type {
|
|
4
6
|
ClientReactiveSerializableChildNode,
|
|
5
7
|
ClientReactiveSerializableParentNode,
|
|
@@ -14,10 +16,12 @@ interface ClientReactiveSerializableValueNodeCurrentState {
|
|
|
14
16
|
* @todo Consider moving into {@link InstanceState}
|
|
15
17
|
*/
|
|
16
18
|
get instanceValue(): SerializedInstanceValue;
|
|
19
|
+
get attributes(): readonly Attribute[];
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
interface ClientReactiveSerializableValueNodeDefinition {
|
|
20
23
|
readonly qualifiedName: QualifiedName;
|
|
24
|
+
readonly bind: BindDefinition;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export interface ClientReactiveSerializableValueNode {
|
|
@@ -135,10 +135,9 @@ export class RepeatInstance
|
|
|
135
135
|
this.appearances = definition.bodyElement.appearances;
|
|
136
136
|
|
|
137
137
|
const childrenState = createChildrenState<RepeatInstance, GeneralChildNode>(this);
|
|
138
|
-
|
|
138
|
+
this.attributeState = createAttributeState(this.scope);
|
|
139
139
|
|
|
140
140
|
this.childrenState = childrenState;
|
|
141
|
-
this.attributeState = attributeState;
|
|
142
141
|
this.currentIndex = currentIndex;
|
|
143
142
|
|
|
144
143
|
const state = createSharedNodeState(
|
|
@@ -152,7 +151,7 @@ export class RepeatInstance
|
|
|
152
151
|
// TODO: only-child <group><label>
|
|
153
152
|
label: createNodeLabel(this, definition),
|
|
154
153
|
hint: null,
|
|
155
|
-
attributes: attributeState.getAttributes,
|
|
154
|
+
attributes: this.attributeState.getAttributes,
|
|
156
155
|
children: childrenState.childIds,
|
|
157
156
|
valueOptions: null,
|
|
158
157
|
value: null,
|
|
@@ -198,7 +197,7 @@ export class RepeatInstance
|
|
|
198
197
|
return this.childrenState.getChildren();
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
getAttributes(): readonly Attribute[] {
|
|
200
|
+
override getAttributes(): readonly Attribute[] {
|
|
202
201
|
return this.attributeState.getAttributes();
|
|
203
202
|
}
|
|
204
203
|
}
|
|
@@ -120,6 +120,7 @@ export interface XFormsXPathPrimaryInstanceNode extends XFormsXPathNode {
|
|
|
120
120
|
// prettier-ignore
|
|
121
121
|
export type XFormsXPathPrimaryInstanceDescendantNodeKind =
|
|
122
122
|
| XFormsXPathNodeRangeKind
|
|
123
|
+
| XPathAttributeKind
|
|
123
124
|
| XPathElementKind
|
|
124
125
|
| XPathTextKind;
|
|
125
126
|
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
} from './names.ts';
|
|
11
11
|
import {
|
|
12
12
|
compareDocumentOrder,
|
|
13
|
+
getAttributes,
|
|
13
14
|
getChildElements,
|
|
14
15
|
getChildNodes,
|
|
15
16
|
getContainingEngineXPathDocument,
|
|
16
|
-
getEngineXPathAttributes,
|
|
17
17
|
getNamespaceDeclarations,
|
|
18
18
|
getNextSiblingElement,
|
|
19
19
|
getNextSiblingNode,
|
|
@@ -43,7 +43,7 @@ export const engineDOMAdapter: EngineDOMAdapter = {
|
|
|
43
43
|
|
|
44
44
|
// XPathTraversalAdapter
|
|
45
45
|
compareDocumentOrder,
|
|
46
|
-
getAttributes:
|
|
46
|
+
getAttributes: getAttributes,
|
|
47
47
|
getChildElements: getChildElements,
|
|
48
48
|
getChildNodes: getChildNodes,
|
|
49
49
|
getContainingDocument: getContainingEngineXPathDocument,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { XPathNodeKind } from '@getodk/xpath';
|
|
2
2
|
import { XPathNodeKindKey } from '@getodk/xpath';
|
|
3
|
+
import type { Attribute } from '../../../instance/Attribute.ts';
|
|
3
4
|
import type { AnyChildNode, AnyNode, AnyParentNode } from '../../../instance/hierarchy.ts';
|
|
4
5
|
import type { PrimaryInstance } from '../../../instance/PrimaryInstance.ts';
|
|
5
6
|
import type { StaticAttribute } from '../static-dom/StaticAttribute.ts';
|
|
@@ -7,6 +8,7 @@ import type { StaticDocument } from '../static-dom/StaticDocument.ts';
|
|
|
7
8
|
import type { StaticElement } from '../static-dom/StaticElement.ts';
|
|
8
9
|
import type { StaticText } from '../static-dom/StaticText.ts';
|
|
9
10
|
import type {
|
|
11
|
+
XFormsXPathAttribute,
|
|
10
12
|
XFormsXPathComment,
|
|
11
13
|
XFormsXPathDocument,
|
|
12
14
|
XFormsXPathElement,
|
|
@@ -17,12 +19,15 @@ export type PrimaryInstanceXPathNode = Extract<AnyNode, XFormsXPathPrimaryInstan
|
|
|
17
19
|
|
|
18
20
|
export type PrimaryInstanceXPathElement = Extract<AnyChildNode, XFormsXPathElement>;
|
|
19
21
|
|
|
22
|
+
export type PrimaryInstanceXPathAttribute = Extract<Attribute, XFormsXPathAttribute>;
|
|
23
|
+
|
|
20
24
|
export type PrimaryInstanceXPathComment = Extract<AnyChildNode, XFormsXPathComment>;
|
|
21
25
|
|
|
22
26
|
// prettier-ignore
|
|
23
27
|
export type PrimaryInstanceXPathChildNode =
|
|
24
28
|
// eslint-disable-next-line @typescript-eslint/sort-type-constituents
|
|
25
29
|
| PrimaryInstanceXPathElement
|
|
30
|
+
| PrimaryInstanceXPathAttribute
|
|
26
31
|
| PrimaryInstanceXPathComment;
|
|
27
32
|
|
|
28
33
|
// prettier-ignore
|
|
@@ -41,7 +46,7 @@ export type EngineXPathComment =
|
|
|
41
46
|
|
|
42
47
|
// Giving this a type alias anticipates eventually implementing attributes
|
|
43
48
|
// in primary instance state as well
|
|
44
|
-
export type EngineXPathAttribute = StaticAttribute;
|
|
49
|
+
export type EngineXPathAttribute = PrimaryInstanceXPathAttribute | StaticAttribute;
|
|
45
50
|
|
|
46
51
|
export type EngineXPathText = StaticText;
|
|
47
52
|
|
|
@@ -8,7 +8,6 @@ import type {
|
|
|
8
8
|
EngineXPathDocument,
|
|
9
9
|
EngineXPathElement,
|
|
10
10
|
EngineXPathNode,
|
|
11
|
-
EngineXPathParentNode,
|
|
12
11
|
PrimaryInstanceXPathChildNode,
|
|
13
12
|
XFormsXPathChildNode,
|
|
14
13
|
} from './kind.ts';
|
|
@@ -18,13 +17,13 @@ export const getContainingEngineXPathDocument = (node: EngineXPathNode): EngineX
|
|
|
18
17
|
return node.rootDocument;
|
|
19
18
|
};
|
|
20
19
|
|
|
21
|
-
export const
|
|
22
|
-
node: EngineXPathNode
|
|
23
|
-
): readonly EngineXPathAttribute[] => {
|
|
20
|
+
export const getAttributes = (node: EngineXPathNode): readonly EngineXPathAttribute[] => {
|
|
24
21
|
if (node.nodeType === 'static-element') {
|
|
25
22
|
return node.attributes;
|
|
26
23
|
}
|
|
27
|
-
|
|
24
|
+
if (isEngineXPathElement(node)) {
|
|
25
|
+
return node.getAttributes();
|
|
26
|
+
}
|
|
28
27
|
return [];
|
|
29
28
|
};
|
|
30
29
|
|
|
@@ -43,7 +42,7 @@ export const getEngineXPathAttributes = (
|
|
|
43
42
|
*/
|
|
44
43
|
export const getNamespaceDeclarations = (): readonly [] => [];
|
|
45
44
|
|
|
46
|
-
export const getParentNode = (node: EngineXPathNode):
|
|
45
|
+
export const getParentNode = (node: EngineXPathNode): EngineXPathNode | null => {
|
|
47
46
|
if (node.nodeType === 'repeat-instance') {
|
|
48
47
|
return node.parent.parent;
|
|
49
48
|
}
|
|
@@ -14,8 +14,9 @@ export const createValueNodeInstanceState = (
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const xmlValue = escapeXMLText(node.currentState.instanceValue);
|
|
17
|
+
const attributes = node.currentState.attributes;
|
|
17
18
|
|
|
18
|
-
return serializeLeafElementXML(qualifiedName, xmlValue);
|
|
19
|
+
return serializeLeafElementXML(qualifiedName, xmlValue, attributes);
|
|
19
20
|
},
|
|
20
21
|
};
|
|
21
22
|
};
|
|
@@ -208,6 +208,7 @@ export const prepareInstancePayload = <PayloadType extends InstancePayloadType>(
|
|
|
208
208
|
instanceRoot: ClientReactiveSerializableInstance,
|
|
209
209
|
options: PrepareInstancePayloadOptions<PayloadType>
|
|
210
210
|
): InstancePayload<PayloadType> => {
|
|
211
|
+
instanceRoot.root.parent.model.triggerXformsRevalidateListeners();
|
|
211
212
|
const validation = validateInstance(instanceRoot);
|
|
212
213
|
const submissionMeta = instanceRoot.definition.submission;
|
|
213
214
|
const instanceFile = new InstanceFile(instanceRoot);
|
|
@@ -10,7 +10,7 @@ export type NoteRuntimeValue<V extends ValueType> =
|
|
|
10
10
|
// prettier-ignore
|
|
11
11
|
export type NoteInputValue<V extends ValueType> =
|
|
12
12
|
| RuntimeInputValue<V>
|
|
13
|
-
| RuntimeValue<V>
|
|
13
|
+
| RuntimeValue<V> // eslint-disable-line @typescript-eslint/no-redundant-type-constituents
|
|
14
14
|
| null;
|
|
15
15
|
|
|
16
16
|
export class NoteCodec<V extends ValueType> extends ValueCodec<
|
|
@@ -12,9 +12,7 @@ export type SingleValueSelectRuntimeValues =
|
|
|
12
12
|
* @see {@link encodeValueFactory}
|
|
13
13
|
*/
|
|
14
14
|
// prettier-ignore
|
|
15
|
-
type SingleValueSelectCodecValues =
|
|
16
|
-
| SingleValueSelectRuntimeValues
|
|
17
|
-
| readonly string[];
|
|
15
|
+
type SingleValueSelectCodecValues = readonly string[];
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* @todo This is more permissive than it should be, allowing an array of any
|
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
import type { Signal } from 'solid-js';
|
|
2
2
|
import { createComputed, createMemo, createSignal, untrack } from 'solid-js';
|
|
3
|
+
import type { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
|
|
3
4
|
import type { InstanceValueContext } from '../../instance/internal-api/InstanceValueContext.ts';
|
|
5
|
+
import { ActionComputationExpression } from '../../parse/expression/ActionComputationExpression.ts';
|
|
4
6
|
import type { BindComputationExpression } from '../../parse/expression/BindComputationExpression.ts';
|
|
7
|
+
import { ActionDefinition } from '../../parse/model/ActionDefinition.ts';
|
|
8
|
+
import type { AnyBindPreloadDefinition } from '../../parse/model/BindPreloadDefinition.ts';
|
|
9
|
+
import { XFORM_EVENT } from '../../parse/model/Event.ts';
|
|
5
10
|
import { createComputedExpression } from './createComputedExpression.ts';
|
|
6
11
|
import type { SimpleAtomicState, SimpleAtomicStateSetter } from './types.ts';
|
|
7
12
|
|
|
8
|
-
const
|
|
13
|
+
const REPEAT_INDEX_REGEX = /([^[]*)(\[[0-9]+\])/g;
|
|
14
|
+
|
|
15
|
+
type ValueContext = AttributeContext | InstanceValueContext;
|
|
16
|
+
|
|
17
|
+
const isInstanceFirstLoad = (context: ValueContext) => {
|
|
9
18
|
return context.rootDocument.initializationMode === 'create';
|
|
10
19
|
};
|
|
11
20
|
|
|
21
|
+
const isAddingRepeatChild = (context: ValueContext) => {
|
|
22
|
+
return context.rootDocument.isAttached();
|
|
23
|
+
};
|
|
24
|
+
|
|
12
25
|
/**
|
|
13
26
|
* Special case, does not correspond to any event.
|
|
14
|
-
*
|
|
15
|
-
* @see {@link shouldPreloadUID}
|
|
16
27
|
*/
|
|
17
|
-
const isEditInitialLoad = (context:
|
|
28
|
+
const isEditInitialLoad = (context: ValueContext) => {
|
|
18
29
|
return context.rootDocument.initializationMode === 'edit';
|
|
19
30
|
};
|
|
20
31
|
|
|
21
|
-
const getInitialValue = (context:
|
|
32
|
+
const getInitialValue = (context: ValueContext): string => {
|
|
22
33
|
const sourceNode = context.instanceNode ?? context.definition.template;
|
|
23
34
|
|
|
24
35
|
return context.decodeInstanceValue(sourceNode.value);
|
|
@@ -36,7 +47,7 @@ type RelevantValueState = SimpleAtomicState<string>;
|
|
|
36
47
|
* node/context's relevance is restored
|
|
37
48
|
*/
|
|
38
49
|
const createRelevantValueState = (
|
|
39
|
-
context:
|
|
50
|
+
context: ValueContext,
|
|
40
51
|
baseValueState: BaseValueState
|
|
41
52
|
): RelevantValueState => {
|
|
42
53
|
return context.scope.runTask(() => {
|
|
@@ -59,7 +70,7 @@ const createRelevantValueState = (
|
|
|
59
70
|
* (client/user) writes when the field is in a `readonly` state.
|
|
60
71
|
*/
|
|
61
72
|
const guardDownstreamReadonlyWrites = (
|
|
62
|
-
context:
|
|
73
|
+
context: ValueContext,
|
|
63
74
|
baseState: SimpleAtomicState<string>
|
|
64
75
|
): SimpleAtomicState<string> => {
|
|
65
76
|
const { readonly } = context.definition.bind;
|
|
@@ -83,47 +94,86 @@ const guardDownstreamReadonlyWrites = (
|
|
|
83
94
|
return [getValue, setValue];
|
|
84
95
|
};
|
|
85
96
|
|
|
86
|
-
/**
|
|
87
|
-
* Per {@link https://getodk.github.io/xforms-spec/#preload-attributes:~:text=concatenation%20of%20%E2%80%98uuid%3A%E2%80%99%20and%20uuid()}
|
|
88
|
-
*/
|
|
89
|
-
const PRELOAD_UID_EXPRESSION = 'concat("uuid:", uuid())';
|
|
90
|
-
|
|
91
97
|
/**
|
|
92
98
|
* @todo It feels increasingly awkward to keep piling up preload stuff here, but it won't stay that way for long. In the meantime, this seems like the best way to express the cases where `preload="uid"` should be effective, i.e.:
|
|
93
99
|
*
|
|
94
100
|
* - When an instance is first loaded ({@link isInstanceFirstLoad})
|
|
95
101
|
* - When an instance is initially loaded for editing ({@link isEditInitialLoad})
|
|
96
102
|
*/
|
|
97
|
-
const
|
|
103
|
+
const isLoading = (context: ValueContext) => {
|
|
98
104
|
return isInstanceFirstLoad(context) || isEditInitialLoad(context);
|
|
99
105
|
};
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
const setValueIfPreloadDefined = (
|
|
108
|
+
context: ValueContext,
|
|
109
|
+
setValue: SimpleAtomicStateSetter<string>,
|
|
110
|
+
preload: AnyBindPreloadDefinition
|
|
111
|
+
) => {
|
|
112
|
+
const value = preload.getValue(context);
|
|
113
|
+
if (value) {
|
|
114
|
+
setValue(value);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const postloadValue = (
|
|
119
|
+
context: ValueContext,
|
|
120
|
+
setValue: SimpleAtomicStateSetter<string>,
|
|
121
|
+
preload: AnyBindPreloadDefinition
|
|
122
|
+
) => {
|
|
123
|
+
const ref = context.contextReference();
|
|
124
|
+
context.definition.model.registerXformsRevalidateListener(ref, () => {
|
|
125
|
+
setValueIfPreloadDefined(context, setValue, preload);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const preloadValue = (context: ValueContext, setValue: SimpleAtomicStateSetter<string>): void => {
|
|
114
130
|
const { preload } = context.definition.bind;
|
|
131
|
+
if (!preload) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
115
134
|
|
|
116
|
-
if (preload
|
|
135
|
+
if (preload.event === XFORM_EVENT.xformsRevalidate) {
|
|
136
|
+
postloadValue(context, setValue, preload);
|
|
117
137
|
return;
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
if (isLoading(context)) {
|
|
141
|
+
setValueIfPreloadDefined(context, setValue, preload);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const referencesCurrentNode = (context: ValueContext, ref: string): boolean => {
|
|
146
|
+
const nodes = context.evaluator.evaluateNodes(ref, {
|
|
121
147
|
contextNode: context.contextNode,
|
|
122
148
|
});
|
|
149
|
+
if (nodes.length > 1) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
'You are trying to target a repeated field. Currently you may only target a field in a specific repeat instance. XPath nodeset has more than one node.'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return nodes.includes(context.contextNode);
|
|
155
|
+
};
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
157
|
+
// Replaces the unbound repeat references in source and ref, with references
|
|
158
|
+
// bound to the repeat instace of the context.
|
|
159
|
+
const bindToRepeatInstance = (
|
|
160
|
+
context: ValueContext,
|
|
161
|
+
action: ActionDefinition
|
|
162
|
+
): { source: string | undefined; ref: string } => {
|
|
163
|
+
let source = action.source;
|
|
164
|
+
let ref = action.ref;
|
|
165
|
+
if (source) {
|
|
166
|
+
const contextRef = context.contextReference();
|
|
167
|
+
for (const part of contextRef.matchAll(REPEAT_INDEX_REGEX)) {
|
|
168
|
+
const unbound = part[1] + '/';
|
|
169
|
+
if (source.includes(unbound)) {
|
|
170
|
+
const bound = part[0] + '/';
|
|
171
|
+
source = source.replace(unbound, bound);
|
|
172
|
+
ref = ref.replace(unbound, bound);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { source, ref };
|
|
127
177
|
};
|
|
128
178
|
|
|
129
179
|
/**
|
|
@@ -131,30 +181,104 @@ const setPreloadUIDValue = (
|
|
|
131
181
|
* computations to the provided value setter, on initialization and any
|
|
132
182
|
* subsequent reactive update.
|
|
133
183
|
*
|
|
134
|
-
* @see {@link
|
|
184
|
+
* @see {@link preloadValue} for important details about spec ordering of
|
|
135
185
|
* events and computations.
|
|
136
186
|
*/
|
|
137
187
|
const createCalculation = (
|
|
138
|
-
context:
|
|
188
|
+
context: ValueContext,
|
|
139
189
|
setRelevantValue: SimpleAtomicStateSetter<string>,
|
|
140
|
-
|
|
190
|
+
computation: ActionComputationExpression<'string'> | BindComputationExpression<'calculate'>
|
|
141
191
|
): void => {
|
|
142
|
-
context
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
192
|
+
const calculate = createComputedExpression(context, computation);
|
|
193
|
+
createComputed(() => {
|
|
194
|
+
if (context.isAttached() && context.isRelevant()) {
|
|
195
|
+
const calculated = calculate();
|
|
196
|
+
const value = context.decodeInstanceValue(calculated);
|
|
197
|
+
setRelevantValue(value);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
};
|
|
146
201
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Runs the computation without maintaining a reactive listener, so
|
|
204
|
+
* actions that should run only at a specific time are not triggered
|
|
205
|
+
* when referenced elements are updated.
|
|
206
|
+
*/
|
|
207
|
+
const createActionCalculation = (
|
|
208
|
+
context: ValueContext,
|
|
209
|
+
setRelevantValue: SimpleAtomicStateSetter<string>,
|
|
210
|
+
computation: ActionComputationExpression<'string'>
|
|
211
|
+
): void => {
|
|
212
|
+
createComputed(() => {
|
|
213
|
+
if (context.isAttached()) {
|
|
214
|
+
// use untrack so the expression evaluation isn't reactive
|
|
215
|
+
const relevant = untrack(() => context.isRelevant());
|
|
216
|
+
if (!relevant) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const calculated = untrack(() => {
|
|
220
|
+
return context.evaluator.evaluateString(computation.expression, context);
|
|
221
|
+
});
|
|
222
|
+
const value = context.decodeInstanceValue(calculated);
|
|
223
|
+
setRelevantValue(value);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
};
|
|
151
227
|
|
|
152
|
-
|
|
228
|
+
const createValueChangedCalculation = (
|
|
229
|
+
context: ValueContext,
|
|
230
|
+
setRelevantValue: SimpleAtomicStateSetter<string>,
|
|
231
|
+
action: ActionDefinition
|
|
232
|
+
): void => {
|
|
233
|
+
const { source, ref } = bindToRepeatInstance(context, action);
|
|
234
|
+
if (!source) {
|
|
235
|
+
// no element to listen to
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
let previous = '';
|
|
239
|
+
const sourceElementExpression = new ActionComputationExpression('string', source);
|
|
240
|
+
const calculateValueSource = createComputedExpression(context, sourceElementExpression); // registers listener
|
|
241
|
+
createComputed(() => {
|
|
242
|
+
if (context.isAttached() && context.isRelevant()) {
|
|
243
|
+
const valueSource = calculateValueSource();
|
|
244
|
+
if (previous !== valueSource) {
|
|
245
|
+
// only update if value has changed
|
|
246
|
+
if (referencesCurrentNode(context, ref)) {
|
|
247
|
+
const calc = context.evaluator.evaluateString(action.computation.expression, context);
|
|
248
|
+
const value = context.decodeInstanceValue(calc);
|
|
249
|
+
setRelevantValue(value);
|
|
250
|
+
}
|
|
153
251
|
}
|
|
154
|
-
|
|
252
|
+
previous = valueSource;
|
|
253
|
+
}
|
|
155
254
|
});
|
|
156
255
|
};
|
|
157
256
|
|
|
257
|
+
const registerAction = (
|
|
258
|
+
context: ValueContext,
|
|
259
|
+
setValue: SimpleAtomicStateSetter<string>,
|
|
260
|
+
action: ActionDefinition
|
|
261
|
+
) => {
|
|
262
|
+
if (action.events.includes(XFORM_EVENT.odkInstanceFirstLoad)) {
|
|
263
|
+
if (isInstanceFirstLoad(context)) {
|
|
264
|
+
createActionCalculation(context, setValue, action.computation);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (action.events.includes(XFORM_EVENT.odkInstanceLoad)) {
|
|
268
|
+
if (!isAddingRepeatChild(context)) {
|
|
269
|
+
createActionCalculation(context, setValue, action.computation);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (action.events.includes(XFORM_EVENT.odkNewRepeat)) {
|
|
273
|
+
if (isAddingRepeatChild(context)) {
|
|
274
|
+
createActionCalculation(context, setValue, action.computation);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (action.events.includes(XFORM_EVENT.xformsValueChanged)) {
|
|
278
|
+
createValueChangedCalculation(context, setValue, action);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
158
282
|
export type InstanceValueState = SimpleAtomicState<string>;
|
|
159
283
|
|
|
160
284
|
/**
|
|
@@ -168,25 +292,26 @@ export type InstanceValueState = SimpleAtomicState<string>;
|
|
|
168
292
|
* nodes defined with one
|
|
169
293
|
* - prevents downstream writes to nodes in a readonly state
|
|
170
294
|
*/
|
|
171
|
-
export const createInstanceValueState = (context:
|
|
295
|
+
export const createInstanceValueState = (context: ValueContext): InstanceValueState => {
|
|
172
296
|
return context.scope.runTask(() => {
|
|
173
297
|
const initialValue = getInitialValue(context);
|
|
174
298
|
const baseValueState = createSignal(initialValue);
|
|
175
299
|
const relevantValueState = createRelevantValueState(context, baseValueState);
|
|
176
300
|
|
|
177
|
-
|
|
178
|
-
* @see {@link setPreloadUIDValue} for important details about spec ordering of events and computations.
|
|
179
|
-
*/
|
|
180
|
-
setPreloadUIDValue(context, relevantValueState);
|
|
301
|
+
const [, setValue] = relevantValueState;
|
|
181
302
|
|
|
182
|
-
|
|
303
|
+
preloadValue(context, setValue);
|
|
183
304
|
|
|
305
|
+
const { calculate } = context.definition.bind;
|
|
184
306
|
if (calculate != null) {
|
|
185
|
-
const [, setValue] = relevantValueState;
|
|
186
|
-
|
|
187
307
|
createCalculation(context, setValue, calculate);
|
|
188
308
|
}
|
|
189
309
|
|
|
310
|
+
const action = context.definition.model.actions.get(context.contextReference());
|
|
311
|
+
if (action) {
|
|
312
|
+
registerAction(context, setValue, action);
|
|
313
|
+
}
|
|
314
|
+
|
|
190
315
|
return guardDownstreamReadonlyWrites(context, relevantValueState);
|
|
191
316
|
});
|
|
192
317
|
};
|
|
@@ -13,10 +13,10 @@ import { isComputedPropertySpec, isMutablePropertySpec } from './createSpecified
|
|
|
13
13
|
// prettier-ignore
|
|
14
14
|
type MutableKeyOf<Spec extends StateSpec> = {
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
-
[K in
|
|
16
|
+
[K in Extract<keyof Spec, string>]: Spec[K] extends MutablePropertySpec<any>
|
|
17
17
|
? K
|
|
18
18
|
: never;
|
|
19
|
-
}[
|
|
19
|
+
}[Extract<keyof Spec, string>];
|
|
20
20
|
|
|
21
21
|
type SetEnginePropertyState<Spec extends StateSpec> = <K extends MutableKeyOf<Spec>>(
|
|
22
22
|
key: K,
|
|
@@ -153,7 +153,15 @@ export const serializeParentElementXML = (
|
|
|
153
153
|
export const serializeLeafElementXML = (
|
|
154
154
|
qualifiedName: QualifiedName,
|
|
155
155
|
xmlValue: EscapedXMLText,
|
|
156
|
+
attributes: readonly Attribute[],
|
|
156
157
|
namespaceDeclarations?: NamespaceDeclarationMap
|
|
157
158
|
): string => {
|
|
158
|
-
|
|
159
|
+
const serializedAttributes =
|
|
160
|
+
attributes?.map((attribute) => attribute.instanceState.instanceXML).join('') ?? '';
|
|
161
|
+
return serializeElementXML(
|
|
162
|
+
qualifiedName,
|
|
163
|
+
xmlValue.normalize(),
|
|
164
|
+
serializedAttributes,
|
|
165
|
+
namespaceDeclarations
|
|
166
|
+
);
|
|
159
167
|
};
|
package/src/parse/XFormDOM.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { DefaultEvaluator } from '@getodk/xpath';
|
|
12
12
|
|
|
13
13
|
interface DOMBindElement extends KnownAttributeLocalNamedElement<'bind', 'nodeset'> {}
|
|
14
|
+
interface DOMSetValueElement extends KnownAttributeLocalNamedElement<'setvalue', 'event'> {}
|
|
14
15
|
|
|
15
16
|
const getMetaElement = (primaryInstanceRoot: Element): Element | null => {
|
|
16
17
|
for (const child of primaryInstanceRoot.children) {
|
|
@@ -336,6 +337,7 @@ export class XFormDOM {
|
|
|
336
337
|
|
|
337
338
|
readonly model: Element;
|
|
338
339
|
readonly binds: readonly DOMBindElement[];
|
|
340
|
+
readonly setValues: readonly DOMSetValueElement[];
|
|
339
341
|
readonly primaryInstance: Element;
|
|
340
342
|
readonly primaryInstanceRoot: Element;
|
|
341
343
|
|
|
@@ -368,6 +370,12 @@ export class XFormDOM {
|
|
|
368
370
|
contextNode: model,
|
|
369
371
|
}
|
|
370
372
|
);
|
|
373
|
+
const setValues: readonly DOMSetValueElement[] = evaluator.evaluateNodes<DOMSetValueElement>(
|
|
374
|
+
'./xf:setvalue[@event]',
|
|
375
|
+
{
|
|
376
|
+
contextNode: model,
|
|
377
|
+
}
|
|
378
|
+
);
|
|
371
379
|
|
|
372
380
|
const instances = evaluator.evaluateNodes<DOMInstanceElement>('./xf:instance', {
|
|
373
381
|
contextNode: model,
|
|
@@ -417,6 +425,7 @@ export class XFormDOM {
|
|
|
417
425
|
this.title = title;
|
|
418
426
|
this.model = model;
|
|
419
427
|
this.binds = binds;
|
|
428
|
+
this.setValues = setValues;
|
|
420
429
|
this.primaryInstance = primaryInstance;
|
|
421
430
|
this.primaryInstanceRoot = primaryInstanceRoot;
|
|
422
431
|
this.itextTranslationElements = itextTranslationElements;
|
|
@@ -39,8 +39,8 @@ export class GroupElementDefinition extends BodyElementDefinition<'group'> {
|
|
|
39
39
|
return childName !== 'label';
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
this.children = this.body.getChildElementDefinitions(form, this, element, childElements);
|
|
43
42
|
this.reference = parseNodesetReference(parent, element, 'ref');
|
|
43
|
+
this.children = this.body.getChildElementDefinitions(form, this, element, childElements);
|
|
44
44
|
this.appearances = structureElementAppearanceParser.parseFrom(element, 'appearance');
|
|
45
45
|
this.label = LabelDefinition.forGroup(form, this);
|
|
46
46
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { parseToFloat, parseToInteger } from '../../../lib/number-parsers.ts';
|
|
1
2
|
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
2
3
|
import type { InputAppearanceDefinition } from '../appearance/inputAppearanceParser.ts';
|
|
3
4
|
import { inputAppearanceParser } from '../appearance/inputAppearanceParser.ts';
|
|
4
5
|
import type { BodyElementParentContext } from '../BodyDefinition.ts';
|
|
5
|
-
import { parseToFloat, parseToInteger } from '../../../lib/number-parsers.ts';
|
|
6
6
|
import { ControlDefinition } from './ControlDefinition.ts';
|
|
7
7
|
|
|
8
8
|
export class InputControlDefinition extends ControlDefinition<'input'> {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DependentExpression,
|
|
3
|
+
type DependentExpressionResultType,
|
|
4
|
+
} from './abstract/DependentExpression.ts';
|
|
5
|
+
|
|
6
|
+
export class ActionComputationExpression<
|
|
7
|
+
Type extends DependentExpressionResultType,
|
|
8
|
+
> extends DependentExpression<Type> {
|
|
9
|
+
constructor(resultType: Type, expression: string) {
|
|
10
|
+
super(resultType, expression);
|
|
11
|
+
}
|
|
12
|
+
}
|