@getodk/xforms-engine 0.14.0 → 0.15.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/client/AttributeNode.d.ts +53 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/node-types.d.ts +1 -1
- package/dist/client/validation.d.ts +7 -1
- package/dist/index.js +436 -164
- package/dist/index.js.map +1 -1
- package/dist/instance/Attribute.d.ts +58 -0
- package/dist/instance/Group.d.ts +4 -0
- package/dist/instance/PrimaryInstance.d.ts +4 -0
- package/dist/instance/Root.d.ts +4 -0
- package/dist/instance/UploadControl.d.ts +4 -0
- package/dist/instance/abstract/InstanceNode.d.ts +7 -4
- package/dist/instance/abstract/ValueNode.d.ts +1 -0
- package/dist/instance/attachments/buildAttributes.d.ts +3 -0
- package/dist/instance/hierarchy.d.ts +2 -1
- package/dist/instance/internal-api/AttributeContext.d.ts +29 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +16 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableParentNode.d.ts +2 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.d.ts +2 -2
- package/dist/instance/repeat/BaseRepeatRange.d.ts +5 -0
- package/dist/instance/repeat/RepeatInstance.d.ts +4 -2
- package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +2 -2
- package/dist/lib/client-reactivity/instance-state/createAttributeNodeInstanceState.d.ts +3 -0
- package/dist/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.d.ts +0 -3
- package/dist/lib/names/NamespaceDeclarationMap.d.ts +1 -1
- package/dist/lib/reactivity/createAttributeState.d.ts +16 -0
- package/dist/lib/reactivity/createAttributeValueState.d.ts +15 -0
- package/dist/lib/xml-serialization.d.ts +5 -9
- package/dist/parse/XFormDOM.d.ts +1 -1
- package/dist/parse/model/AttributeDefinition.d.ts +22 -0
- package/dist/parse/model/{RootAttributeMap.d.ts → AttributeDefinitionMap.d.ts} +4 -10
- package/dist/parse/model/GroupDefinition.d.ts +4 -1
- package/dist/parse/model/LeafNodeDefinition.d.ts +1 -0
- package/dist/parse/model/NodeDefinition.d.ts +8 -3
- package/dist/parse/model/RepeatDefinition.d.ts +4 -1
- package/dist/parse/model/RootDefinition.d.ts +2 -2
- package/dist/solid.js +436 -164
- package/dist/solid.js.map +1 -1
- package/package.json +5 -5
- package/src/client/AttributeNode.ts +62 -0
- package/src/client/index.ts +1 -0
- package/src/client/node-types.ts +1 -0
- package/src/client/validation.ts +9 -1
- package/src/instance/Attribute.ts +164 -0
- package/src/instance/Group.ts +16 -0
- package/src/instance/InputControl.ts +1 -0
- package/src/instance/ModelValue.ts +1 -0
- package/src/instance/Note.ts +1 -0
- package/src/instance/PrimaryInstance.ts +17 -0
- package/src/instance/RangeControl.ts +1 -0
- package/src/instance/RankControl.ts +1 -0
- package/src/instance/Root.ts +16 -0
- package/src/instance/SelectControl.ts +1 -0
- package/src/instance/TriggerControl.ts +1 -0
- package/src/instance/UploadControl.ts +14 -0
- package/src/instance/abstract/DescendantNode.ts +5 -1
- package/src/instance/abstract/InstanceNode.ts +6 -3
- package/src/instance/abstract/ValueNode.ts +1 -0
- package/src/instance/attachments/buildAttributes.ts +8 -0
- package/src/instance/hierarchy.ts +3 -1
- package/src/instance/internal-api/AttributeContext.ts +34 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +19 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableParentNode.ts +2 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.ts +2 -3
- package/src/instance/repeat/BaseRepeatRange.ts +14 -0
- package/src/instance/repeat/RepeatInstance.ts +14 -5
- package/src/integration/xpath/adapter/XFormsXPathNode.ts +3 -1
- package/src/lib/client-reactivity/instance-state/createAttributeNodeInstanceState.ts +16 -0
- package/src/lib/client-reactivity/instance-state/createParentNodeInstanceState.ts +5 -5
- package/src/lib/client-reactivity/instance-state/createRootInstanceState.ts +6 -9
- package/src/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.ts +5 -15
- package/src/lib/names/NamespaceDeclarationMap.ts +1 -1
- package/src/lib/reactivity/createAttributeState.ts +51 -0
- package/src/lib/reactivity/createAttributeValueState.ts +189 -0
- package/src/lib/xml-serialization.ts +30 -34
- package/src/parse/model/AttributeDefinition.ts +58 -0
- package/src/parse/model/{RootAttributeMap.ts → AttributeDefinitionMap.ts} +7 -13
- package/src/parse/model/GroupDefinition.ts +6 -0
- package/src/parse/model/LeafNodeDefinition.ts +1 -0
- package/src/parse/model/NodeDefinition.ts +11 -3
- package/src/parse/model/RepeatDefinition.ts +8 -1
- package/src/parse/model/RootDefinition.ts +5 -5
- package/src/parse/model/nodeDefinitionMap.ts +1 -1
- package/dist/error/TemplatedNodeAttributeSerializationError.d.ts +0 -22
- package/dist/parse/model/RootAttributeDefinition.d.ts +0 -21
- package/src/error/TemplatedNodeAttributeSerializationError.ts +0 -24
- package/src/parse/model/RootAttributeDefinition.ts +0 -44
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
XFormsXPathPrimaryInstanceNodeKind,
|
|
14
14
|
} from '../../integration/xpath/adapter/XFormsXPathNode.ts';
|
|
15
15
|
import type { PrimaryInstanceXPathNode } from '../../integration/xpath/adapter/kind.ts';
|
|
16
|
+
import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
|
|
16
17
|
import type { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
|
|
17
18
|
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
18
19
|
import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
@@ -24,6 +25,7 @@ import { createReactiveScope } from '../../lib/reactivity/scope.ts';
|
|
|
24
25
|
import type { SimpleAtomicState } from '../../lib/reactivity/types.ts';
|
|
25
26
|
import { createUniqueId } from '../../lib/unique-id.ts';
|
|
26
27
|
import type { AnyNodeDefinition } from '../../parse/model/NodeDefinition.ts';
|
|
28
|
+
import type { Attribute } from '../Attribute.ts';
|
|
27
29
|
import type { PrimaryInstance } from '../PrimaryInstance.ts';
|
|
28
30
|
import type { Root } from '../Root.ts';
|
|
29
31
|
import type { AnyChildNode, AnyNode, AnyParentNode } from '../hierarchy.ts';
|
|
@@ -51,6 +53,7 @@ export interface InstanceNodeStateSpec<Value = never> {
|
|
|
51
53
|
readonly label: Accessor<TextRange<'label'> | null> | null;
|
|
52
54
|
readonly hint: Accessor<TextRange<'hint'> | null> | null;
|
|
53
55
|
readonly children: Accessor<readonly FormNodeID[]> | null;
|
|
56
|
+
readonly attributes: Accessor<readonly Attribute[]> | null;
|
|
54
57
|
readonly valueOptions: InstanceNodeValueOptionsStateSpec;
|
|
55
58
|
readonly value: Signal<Value> | SimpleAtomicState<Value> | null;
|
|
56
59
|
}
|
|
@@ -191,7 +194,7 @@ export abstract class InstanceNode<
|
|
|
191
194
|
constructor(
|
|
192
195
|
readonly instanceConfig: InstanceConfig,
|
|
193
196
|
readonly parent: Parent,
|
|
194
|
-
readonly instanceNode: StaticDocument | StaticElement | null,
|
|
197
|
+
readonly instanceNode: StaticAttribute | StaticDocument | StaticElement | null,
|
|
195
198
|
readonly definition: Definition,
|
|
196
199
|
options?: InstanceNodeOptions
|
|
197
200
|
) {
|
|
@@ -237,13 +240,13 @@ export abstract class InstanceNode<
|
|
|
237
240
|
* (though for those nodes it will always be empty). This affords consistency
|
|
238
241
|
* and efficiency of interface for those internal uses.
|
|
239
242
|
*/
|
|
240
|
-
abstract getChildren(this: AnyInstanceNode):
|
|
243
|
+
abstract getChildren(this: AnyInstanceNode): ReadonlyArray<Exclude<AnyChildNode, Attribute>>;
|
|
241
244
|
|
|
242
245
|
// XFormsXPathNode
|
|
243
246
|
/**
|
|
244
247
|
* @todo Values as text nodes(?)
|
|
245
248
|
*/
|
|
246
|
-
getXPathChildNodes():
|
|
249
|
+
getXPathChildNodes(): ReadonlyArray<Exclude<AnyChildNode, Attribute>> {
|
|
247
250
|
return (this as AnyInstanceNode).getChildren().flatMap((child) => {
|
|
248
251
|
switch (child.nodeType) {
|
|
249
252
|
case 'repeat-range:controlled':
|
|
@@ -36,6 +36,7 @@ export type ValueNodeDefinition<V extends ValueType> = LeafNodeDefinition<V>;
|
|
|
36
36
|
|
|
37
37
|
export interface ValueNodeStateSpec<RuntimeValue> extends DescendantNodeStateSpec<RuntimeValue> {
|
|
38
38
|
readonly children: null;
|
|
39
|
+
readonly attributes: null;
|
|
39
40
|
readonly value: SimpleAtomicState<RuntimeValue>;
|
|
40
41
|
readonly instanceValue: Accessor<string>;
|
|
41
42
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Attribute } from '../Attribute';
|
|
2
|
+
import type { AnyParentNode } from '../hierarchy';
|
|
3
|
+
|
|
4
|
+
export function buildAttributes(parent: AnyParentNode): Attribute[] {
|
|
5
|
+
return Array.from(parent.definition.attributes.values()).map((attributeDefinition) => {
|
|
6
|
+
return new Attribute(parent, attributeDefinition, attributeDefinition.template);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Attribute } from './Attribute.ts';
|
|
1
2
|
import type { Group } from './Group.ts';
|
|
2
3
|
import type { AnyInputControl } from './InputControl.ts';
|
|
3
4
|
import type { AnyModelValue } from './ModelValue.ts';
|
|
@@ -62,7 +63,8 @@ export type AnyChildNode =
|
|
|
62
63
|
| RankControl
|
|
63
64
|
| SelectControl
|
|
64
65
|
| TriggerControl
|
|
65
|
-
| UploadControl
|
|
66
|
+
| UploadControl
|
|
67
|
+
| Attribute;
|
|
66
68
|
|
|
67
69
|
// prettier-ignore
|
|
68
70
|
export type GeneralChildNode =
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FormInstanceInitializationMode } from '../../client/index.ts';
|
|
2
|
+
import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
|
|
3
|
+
import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
|
|
4
|
+
import type { BindComputationExpression } from '../../parse/expression/BindComputationExpression.ts';
|
|
5
|
+
import type { AnyBindPreloadDefinition } from '../../parse/model/BindPreloadDefinition.ts';
|
|
6
|
+
import type { EvaluationContext } from './EvaluationContext.ts';
|
|
7
|
+
|
|
8
|
+
export interface InstanceAttributeContextDocument {
|
|
9
|
+
readonly initializationMode: FormInstanceInitializationMode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type DecodeInstanceValue = (value: string) => string;
|
|
13
|
+
|
|
14
|
+
interface InstanceAttributeContextDefinitionBind {
|
|
15
|
+
readonly preload: AnyBindPreloadDefinition | null;
|
|
16
|
+
readonly calculate: BindComputationExpression<'calculate'> | null;
|
|
17
|
+
readonly readonly: BindComputationExpression<'readonly'>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface InstanceAttributeContextDefinition {
|
|
21
|
+
readonly bind: InstanceAttributeContextDefinitionBind;
|
|
22
|
+
readonly template: StaticAttribute;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AttributeContext extends EvaluationContext {
|
|
26
|
+
readonly scope: ReactiveScope;
|
|
27
|
+
readonly rootDocument: InstanceAttributeContextDocument;
|
|
28
|
+
readonly definition: InstanceAttributeContextDefinition;
|
|
29
|
+
readonly instanceNode: StaticAttribute;
|
|
30
|
+
readonly decodeInstanceValue: DecodeInstanceValue;
|
|
31
|
+
|
|
32
|
+
isReadonly(): boolean;
|
|
33
|
+
isRelevant(): boolean;
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { InstanceState } from '../../../client/serialization/InstanceState.ts';
|
|
2
|
+
import type { QualifiedName } from '../../../lib/names/QualifiedName.ts';
|
|
3
|
+
|
|
4
|
+
export type SerializedInstanceValue = string;
|
|
5
|
+
|
|
6
|
+
interface ClientReactiveSerializableAttributeNodeCurrentState {
|
|
7
|
+
get relevant(): boolean;
|
|
8
|
+
get instanceValue(): SerializedInstanceValue;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ClientReactiveSerializableAttributeNodeDefinition {
|
|
12
|
+
readonly qualifiedName: QualifiedName;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClientReactiveSerializableAttributeNode {
|
|
16
|
+
readonly definition: ClientReactiveSerializableAttributeNodeDefinition;
|
|
17
|
+
readonly currentState: ClientReactiveSerializableAttributeNodeCurrentState;
|
|
18
|
+
readonly instanceState: InstanceState;
|
|
19
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { InstanceState } from '../../../client/serialization/InstanceState.ts';
|
|
2
2
|
import type { QualifiedName } from '../../../lib/names/QualifiedName.ts';
|
|
3
|
+
import type { Attribute } from '../../Attribute.ts';
|
|
3
4
|
|
|
4
5
|
export interface ClientReactiveSerializableChildNode {
|
|
5
6
|
readonly instanceState: InstanceState;
|
|
@@ -10,6 +11,7 @@ export interface ClientReactiveSerializableParentNodeCurrentState<
|
|
|
10
11
|
> {
|
|
11
12
|
get relevant(): boolean;
|
|
12
13
|
get children(): readonly Child[];
|
|
14
|
+
get attributes(): readonly Attribute[];
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface ClientReactiveSerializableParentNodeDefinition {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TemplatedNodeAttributeSerializationError } from '../../../error/TemplatedNodeAttributeSerializationError.ts';
|
|
2
1
|
import type { StaticElement } from '../../../integration/xpath/static-dom/StaticElement.ts';
|
|
2
|
+
import type { Attribute } from '../../Attribute.ts';
|
|
3
3
|
import type { GeneralChildNode } from '../../hierarchy.ts';
|
|
4
4
|
import type {
|
|
5
5
|
ClientReactiveSerializableParentNode,
|
|
@@ -9,8 +9,7 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
export interface ClientReactiveSerializableTemplatedNodeCurrentState
|
|
11
11
|
extends ClientReactiveSerializableParentNodeCurrentState<GeneralChildNode> {
|
|
12
|
-
|
|
13
|
-
get attributes(): unknown;
|
|
12
|
+
get attributes(): readonly Attribute[];
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export interface ClientReactiveSerializableTemplatedNodeDefinition
|
|
@@ -17,6 +17,10 @@ import type {
|
|
|
17
17
|
import { XFORMS_XPATH_NODE_RANGE_KIND } from '../../integration/xpath/adapter/XFormsXPathNode.ts';
|
|
18
18
|
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
19
19
|
import { createNodeRangeInstanceState } from '../../lib/client-reactivity/instance-state/createNodeRangeInstanceState.ts';
|
|
20
|
+
import {
|
|
21
|
+
createAttributeState,
|
|
22
|
+
type AttributeState,
|
|
23
|
+
} from '../../lib/reactivity/createAttributeState.ts';
|
|
20
24
|
import type { ChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
21
25
|
import { createChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
22
26
|
import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
@@ -32,6 +36,7 @@ import type {
|
|
|
32
36
|
DescendantNodeSharedStateSpec,
|
|
33
37
|
} from '../abstract/DescendantNode.ts';
|
|
34
38
|
import { DescendantNode } from '../abstract/DescendantNode.ts';
|
|
39
|
+
import type { Attribute } from '../Attribute.ts';
|
|
35
40
|
import type { GeneralParentNode, RepeatRange } from '../hierarchy.ts';
|
|
36
41
|
import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
|
|
37
42
|
import type { ClientReactiveSerializableParentNode } from '../internal-api/serialization/ClientReactiveSerializableParentNode.ts';
|
|
@@ -41,6 +46,7 @@ interface RepeatRangeStateSpec extends DescendantNodeSharedStateSpec {
|
|
|
41
46
|
readonly hint: null;
|
|
42
47
|
readonly label: Accessor<TextRange<'label'> | null>;
|
|
43
48
|
readonly children: Accessor<readonly FormNodeID[]>;
|
|
49
|
+
readonly attributes: Accessor<readonly Attribute[]>;
|
|
44
50
|
readonly valueOptions: null;
|
|
45
51
|
readonly value: null;
|
|
46
52
|
}
|
|
@@ -60,6 +66,7 @@ export abstract class BaseRepeatRange<Definition extends AnyRepeatDefinition>
|
|
|
60
66
|
ClientReactiveSerializableParentNode<RepeatInstance>
|
|
61
67
|
{
|
|
62
68
|
protected readonly childrenState: ChildrenState<RepeatInstance>;
|
|
69
|
+
protected readonly attributeState: AttributeState;
|
|
63
70
|
|
|
64
71
|
/**
|
|
65
72
|
* A repeat range doesn't have a corresponding primary instance element of its
|
|
@@ -156,8 +163,10 @@ export abstract class BaseRepeatRange<Definition extends AnyRepeatDefinition>
|
|
|
156
163
|
const repeatRange = this as AnyDescendantNode as RepeatRange;
|
|
157
164
|
|
|
158
165
|
const childrenState = createChildrenState<RepeatRange, RepeatInstance>(repeatRange);
|
|
166
|
+
const attributeState = createAttributeState(this.scope);
|
|
159
167
|
|
|
160
168
|
this.childrenState = childrenState;
|
|
169
|
+
this.attributeState = attributeState;
|
|
161
170
|
|
|
162
171
|
const state = createSharedNodeState(
|
|
163
172
|
this.scope,
|
|
@@ -170,6 +179,7 @@ export abstract class BaseRepeatRange<Definition extends AnyRepeatDefinition>
|
|
|
170
179
|
label: createNodeLabel(this, definition),
|
|
171
180
|
hint: null,
|
|
172
181
|
children: childrenState.childIds,
|
|
182
|
+
attributes: attributeState.getAttributes,
|
|
173
183
|
valueOptions: null,
|
|
174
184
|
value: null,
|
|
175
185
|
},
|
|
@@ -260,4 +270,8 @@ export abstract class BaseRepeatRange<Definition extends AnyRepeatDefinition>
|
|
|
260
270
|
getChildren(): readonly RepeatInstance[] {
|
|
261
271
|
return this.childrenState.getChildren();
|
|
262
272
|
}
|
|
273
|
+
|
|
274
|
+
getAttributes(): readonly Attribute[] {
|
|
275
|
+
return this.attributeState.getAttributes();
|
|
276
|
+
}
|
|
263
277
|
}
|
|
@@ -10,10 +10,13 @@ import type {
|
|
|
10
10
|
import type { InstanceState } from '../../client/serialization/InstanceState.ts';
|
|
11
11
|
import type { TextRange } from '../../client/TextRange.ts';
|
|
12
12
|
import type { AncestorNodeValidationState } from '../../client/validation.ts';
|
|
13
|
-
import type { TemplatedNodeAttributeSerializationError } from '../../error/TemplatedNodeAttributeSerializationError.ts';
|
|
14
13
|
import type { XFormsXPathElement } from '../../integration/xpath/adapter/XFormsXPathNode.ts';
|
|
15
14
|
import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
16
15
|
import { createTemplatedNodeInstanceState } from '../../lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.ts';
|
|
16
|
+
import {
|
|
17
|
+
createAttributeState,
|
|
18
|
+
type AttributeState,
|
|
19
|
+
} from '../../lib/reactivity/createAttributeState.ts';
|
|
17
20
|
import type { ChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
18
21
|
import { createChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
19
22
|
import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
@@ -26,6 +29,7 @@ import { createNodeLabel } from '../../lib/reactivity/text/createNodeLabel.ts';
|
|
|
26
29
|
import { createAggregatedViolations } from '../../lib/reactivity/validation/createAggregatedViolations.ts';
|
|
27
30
|
import type { DescendantNodeSharedStateSpec } from '../abstract/DescendantNode.ts';
|
|
28
31
|
import { DescendantNode } from '../abstract/DescendantNode.ts';
|
|
32
|
+
import type { Attribute } from '../Attribute.ts';
|
|
29
33
|
import { buildChildren } from '../children/buildChildren.ts';
|
|
30
34
|
import type { GeneralChildNode, RepeatRange } from '../hierarchy.ts';
|
|
31
35
|
import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
|
|
@@ -35,9 +39,7 @@ interface RepeatInstanceStateSpec extends DescendantNodeSharedStateSpec {
|
|
|
35
39
|
readonly label: Accessor<TextRange<'label'> | null>;
|
|
36
40
|
readonly hint: null;
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
readonly attributes: null;
|
|
40
|
-
|
|
42
|
+
readonly attributes: Accessor<readonly Attribute[]>;
|
|
41
43
|
readonly children: Accessor<readonly FormNodeID[]>;
|
|
42
44
|
readonly valueOptions: null;
|
|
43
45
|
readonly value: null;
|
|
@@ -61,6 +63,7 @@ export class RepeatInstance
|
|
|
61
63
|
ClientReactiveSerializableTemplatedNode
|
|
62
64
|
{
|
|
63
65
|
private readonly childrenState: ChildrenState<GeneralChildNode>;
|
|
66
|
+
private readonly attributeState: AttributeState;
|
|
64
67
|
private readonly currentIndex: Accessor<number>;
|
|
65
68
|
|
|
66
69
|
override readonly [XPathNodeKindKey] = 'element';
|
|
@@ -132,8 +135,10 @@ export class RepeatInstance
|
|
|
132
135
|
this.appearances = definition.bodyElement.appearances;
|
|
133
136
|
|
|
134
137
|
const childrenState = createChildrenState<RepeatInstance, GeneralChildNode>(this);
|
|
138
|
+
const attributeState = createAttributeState(this.scope);
|
|
135
139
|
|
|
136
140
|
this.childrenState = childrenState;
|
|
141
|
+
this.attributeState = attributeState;
|
|
137
142
|
this.currentIndex = currentIndex;
|
|
138
143
|
|
|
139
144
|
const state = createSharedNodeState(
|
|
@@ -147,7 +152,7 @@ export class RepeatInstance
|
|
|
147
152
|
// TODO: only-child <group><label>
|
|
148
153
|
label: createNodeLabel(this, definition),
|
|
149
154
|
hint: null,
|
|
150
|
-
attributes:
|
|
155
|
+
attributes: attributeState.getAttributes,
|
|
151
156
|
children: childrenState.childIds,
|
|
152
157
|
valueOptions: null,
|
|
153
158
|
value: null,
|
|
@@ -192,4 +197,8 @@ export class RepeatInstance
|
|
|
192
197
|
getChildren(): readonly GeneralChildNode[] {
|
|
193
198
|
return this.childrenState.getChildren();
|
|
194
199
|
}
|
|
200
|
+
|
|
201
|
+
getAttributes(): readonly Attribute[] {
|
|
202
|
+
return this.attributeState.getAttributes();
|
|
203
|
+
}
|
|
195
204
|
}
|
|
@@ -97,7 +97,8 @@ export type XFormsXPathDescendantNodeKind =
|
|
|
97
97
|
| XFormsXPathNodeRangeKind
|
|
98
98
|
| XPathElementKind
|
|
99
99
|
| XPathTextKind
|
|
100
|
-
| XPathCommentKind
|
|
100
|
+
| XPathCommentKind
|
|
101
|
+
| XPathAttributeKind;
|
|
101
102
|
|
|
102
103
|
export interface XFormsXPathDescendantNode extends XFormsXPathNode {
|
|
103
104
|
readonly [XPathNodeKindKey]: XFormsXPathDescendantNodeKind;
|
|
@@ -109,6 +110,7 @@ export type XFormsXPathPrimaryInstanceNodeKind =
|
|
|
109
110
|
| XPathDocumentKind
|
|
110
111
|
| XFormsXPathNodeRangeKind
|
|
111
112
|
| XPathElementKind
|
|
113
|
+
| XPathAttributeKind
|
|
112
114
|
| XPathTextKind;
|
|
113
115
|
|
|
114
116
|
export interface XFormsXPathPrimaryInstanceNode extends XFormsXPathNode {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InstanceState } from '../../../client/serialization/InstanceState.ts';
|
|
2
|
+
import type { ClientReactiveSerializableAttributeNode } from '../../../instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts';
|
|
3
|
+
import { escapeXMLText, serializeAttributeXML } from '../../xml-serialization.ts';
|
|
4
|
+
|
|
5
|
+
export const createAttributeNodeInstanceState = (
|
|
6
|
+
node: ClientReactiveSerializableAttributeNode
|
|
7
|
+
): InstanceState => {
|
|
8
|
+
const { qualifiedName } = node.definition;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
get instanceXML() {
|
|
12
|
+
const xmlValue = escapeXMLText(node.currentState.instanceValue, true);
|
|
13
|
+
return serializeAttributeXML(qualifiedName, xmlValue);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -12,11 +12,11 @@ export const createParentNodeInstanceState = (
|
|
|
12
12
|
return '';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
return serializeParentElementXML(
|
|
16
|
+
node.definition.qualifiedName,
|
|
17
|
+
node.currentState.children,
|
|
18
|
+
node.currentState.attributes
|
|
19
|
+
);
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
};
|
|
@@ -5,15 +5,12 @@ import { serializeParentElementXML } from '../../xml-serialization.ts';
|
|
|
5
5
|
export const createRootInstanceState = (node: Root): InstanceState => {
|
|
6
6
|
return {
|
|
7
7
|
get instanceXML() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
namespaceDeclarations,
|
|
15
|
-
attributes: Array.from(attributes.values()),
|
|
16
|
-
});
|
|
8
|
+
return serializeParentElementXML(
|
|
9
|
+
node.definition.qualifiedName,
|
|
10
|
+
node.currentState.children,
|
|
11
|
+
node.currentState.attributes,
|
|
12
|
+
node.definition.namespaceDeclarations
|
|
13
|
+
);
|
|
17
14
|
},
|
|
18
15
|
};
|
|
19
16
|
};
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { InstanceState } from '../../../client/serialization/InstanceState.ts';
|
|
2
|
-
import { TemplatedNodeAttributeSerializationError } from '../../../error/TemplatedNodeAttributeSerializationError.ts';
|
|
3
2
|
import type { ClientReactiveSerializableTemplatedNode } from '../../../instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.ts';
|
|
4
3
|
import { serializeParentElementXML } from '../../xml-serialization.ts';
|
|
5
4
|
|
|
6
|
-
/**
|
|
7
|
-
* @see {@link TemplatedNodeAttributeSerializationError}
|
|
8
|
-
*/
|
|
9
5
|
export const createTemplatedNodeInstanceState = (
|
|
10
6
|
node: ClientReactiveSerializableTemplatedNode
|
|
11
7
|
): InstanceState => {
|
|
@@ -15,17 +11,11 @@ export const createTemplatedNodeInstanceState = (
|
|
|
15
11
|
return '';
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (attributes != null) {
|
|
25
|
-
throw new TemplatedNodeAttributeSerializationError();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return serializeParentElementXML(node.definition.qualifiedName, serializedChildren);
|
|
14
|
+
return serializeParentElementXML(
|
|
15
|
+
node.definition.qualifiedName,
|
|
16
|
+
node.currentState.children,
|
|
17
|
+
node.currentState.attributes
|
|
18
|
+
);
|
|
29
19
|
},
|
|
30
20
|
};
|
|
31
21
|
};
|
|
@@ -11,7 +11,7 @@ type NamedNodeDefinitionMap = ReadonlyMap<QualifiedName, NamedNodeDefinition>;
|
|
|
11
11
|
export interface NamedSubtreeDefinition extends NamedNodeDefinition {
|
|
12
12
|
readonly namespaceDeclarations: NamespaceDeclarationMap;
|
|
13
13
|
readonly parent: NamedSubtreeDefinition | null;
|
|
14
|
-
readonly attributes
|
|
14
|
+
readonly attributes: NamedNodeDefinitionMap | null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Accessor, Setter, Signal } from 'solid-js';
|
|
2
|
+
import { createSignal } from 'solid-js';
|
|
3
|
+
import type { Attribute } from '../../instance/Attribute.ts';
|
|
4
|
+
import type { ReactiveScope } from './scope.ts';
|
|
5
|
+
|
|
6
|
+
export interface AttributeState {
|
|
7
|
+
readonly attributes: Signal<readonly Attribute[]>;
|
|
8
|
+
readonly getAttributes: Accessor<readonly Attribute[]>;
|
|
9
|
+
readonly setAttributes: Setter<readonly Attribute[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates attributes state suitable for all node types
|
|
14
|
+
*
|
|
15
|
+
* The produced {@link AttributeState.attributes} (and its get/set convenience
|
|
16
|
+
* methods) signal is intended to be used to store the engine's attribute state,
|
|
17
|
+
* and update that state when appropriate.
|
|
18
|
+
*/
|
|
19
|
+
export const createAttributeState = (scope: ReactiveScope): AttributeState => {
|
|
20
|
+
return scope.runTask(() => {
|
|
21
|
+
const baseState = createSignal<readonly Attribute[]>([]);
|
|
22
|
+
const [getAttributes, baseSetAttributes] = baseState;
|
|
23
|
+
|
|
24
|
+
type AttributeSetterCallback = (prev: readonly Attribute[]) => readonly Attribute[];
|
|
25
|
+
type AttributeOrSetterCallback = AttributeSetterCallback | readonly Attribute[];
|
|
26
|
+
|
|
27
|
+
const setAttributes: Setter<readonly Attribute[]> = (
|
|
28
|
+
valueOrSetterCallback: AttributeOrSetterCallback
|
|
29
|
+
) => {
|
|
30
|
+
let setterCallback: AttributeSetterCallback;
|
|
31
|
+
|
|
32
|
+
if (typeof valueOrSetterCallback === 'function') {
|
|
33
|
+
setterCallback = valueOrSetterCallback;
|
|
34
|
+
} else {
|
|
35
|
+
setterCallback = (_) => valueOrSetterCallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return baseSetAttributes((prev) => {
|
|
39
|
+
return setterCallback(prev);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const attributes: Signal<readonly Attribute[]> = [getAttributes, setAttributes];
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
attributes,
|
|
47
|
+
getAttributes,
|
|
48
|
+
setAttributes,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { Signal } from 'solid-js';
|
|
2
|
+
import { createComputed, createMemo, createSignal, untrack } from 'solid-js';
|
|
3
|
+
import type { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
|
|
4
|
+
import type { BindComputationExpression } from '../../parse/expression/BindComputationExpression.ts';
|
|
5
|
+
import { createComputedExpression } from './createComputedExpression.ts';
|
|
6
|
+
import type { SimpleAtomicState, SimpleAtomicStateSetter } from './types.ts';
|
|
7
|
+
|
|
8
|
+
const isInstanceFirstLoad = (context: AttributeContext) => {
|
|
9
|
+
return context.rootDocument.initializationMode === 'create';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Special case, does not correspond to any event.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link shouldPreloadUID}
|
|
16
|
+
*/
|
|
17
|
+
const isEditInitialLoad = (context: AttributeContext) => {
|
|
18
|
+
return context.rootDocument.initializationMode === 'edit';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getInitialValue = (context: AttributeContext): string => {
|
|
22
|
+
const sourceNode = context.instanceNode ?? context.definition.template;
|
|
23
|
+
|
|
24
|
+
return context.decodeInstanceValue(sourceNode.value);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type BaseValueState = Signal<string>;
|
|
28
|
+
|
|
29
|
+
type RelevantValueState = SimpleAtomicState<string>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Wraps {@link baseValueState} in a signal-like interface which:
|
|
33
|
+
*
|
|
34
|
+
* - produces a blank value for nodes ({@link context}) in a non-relevant state
|
|
35
|
+
* - persists, and restores, the most recent non-blank value state when a
|
|
36
|
+
* node/context's relevance is restored
|
|
37
|
+
*/
|
|
38
|
+
const createRelevantValueState = (
|
|
39
|
+
context: AttributeContext,
|
|
40
|
+
baseValueState: BaseValueState
|
|
41
|
+
): RelevantValueState => {
|
|
42
|
+
return context.scope.runTask(() => {
|
|
43
|
+
const [getRelevantValue, setValue] = baseValueState;
|
|
44
|
+
|
|
45
|
+
const getValue = createMemo(() => {
|
|
46
|
+
if (context.isRelevant()) {
|
|
47
|
+
return getRelevantValue();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return '';
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return [getValue, setValue];
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* For fields with a `readonly` bind expression, prevent downstream
|
|
59
|
+
* (client/user) writes when the field is in a `readonly` state.
|
|
60
|
+
*/
|
|
61
|
+
const guardDownstreamReadonlyWrites = (
|
|
62
|
+
context: AttributeContext,
|
|
63
|
+
baseState: SimpleAtomicState<string>
|
|
64
|
+
): SimpleAtomicState<string> => {
|
|
65
|
+
const { readonly } = context.definition.bind;
|
|
66
|
+
|
|
67
|
+
if (readonly.isDefaultExpression) {
|
|
68
|
+
return baseState;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const [getValue, baseSetValue] = baseState;
|
|
72
|
+
|
|
73
|
+
const setValue: SimpleAtomicStateSetter<string> = (value) => {
|
|
74
|
+
if (context.isReadonly()) {
|
|
75
|
+
const reference = untrack(() => context.contextReference());
|
|
76
|
+
|
|
77
|
+
throw new Error(`Cannot write to readonly field: ${reference}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return baseSetValue(value);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return [getValue, setValue];
|
|
84
|
+
};
|
|
85
|
+
|
|
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
|
+
/**
|
|
92
|
+
* @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
|
+
*
|
|
94
|
+
* - When an instance is first loaded ({@link isInstanceFirstLoad})
|
|
95
|
+
* - When an instance is initially loaded for editing ({@link isEditInitialLoad})
|
|
96
|
+
*/
|
|
97
|
+
const shouldPreloadUID = (context: AttributeContext) => {
|
|
98
|
+
return isInstanceFirstLoad(context) || isEditInitialLoad(context);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @todo This is a temporary one-off, until we support the full range of
|
|
103
|
+
* {@link https://getodk.github.io/xforms-spec/#preload-attributes | preloads}.
|
|
104
|
+
*
|
|
105
|
+
* @todo ALSO, IMPORTANTLY(!): the **call site** for this function is
|
|
106
|
+
* semantically where we would expect to trigger a
|
|
107
|
+
* {@link https://getodk.github.io/xforms-spec/#event:odk-instance-first-load | odk-instance-first-load event},
|
|
108
|
+
* _and compute_ preloads semantically associated with that event.
|
|
109
|
+
*/
|
|
110
|
+
const setPreloadUIDValue = (context: AttributeContext, valueState: RelevantValueState): void => {
|
|
111
|
+
const { preload } = context.definition.bind;
|
|
112
|
+
|
|
113
|
+
if (preload?.type !== 'uid' || !shouldPreloadUID(context)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const preloadUIDValue = context.evaluator.evaluateString(PRELOAD_UID_EXPRESSION, {
|
|
118
|
+
contextNode: context.contextNode,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const [, setValue] = valueState;
|
|
122
|
+
|
|
123
|
+
setValue(preloadUIDValue);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Defines a reactive effect which writes the result of `calculate` bind
|
|
128
|
+
* computations to the provided value setter, on initialization and any
|
|
129
|
+
* subsequent reactive update.
|
|
130
|
+
*
|
|
131
|
+
* @see {@link setPreloadUIDValue} for important details about spec ordering of
|
|
132
|
+
* events and computations.
|
|
133
|
+
*/
|
|
134
|
+
const createCalculation = (
|
|
135
|
+
context: AttributeContext,
|
|
136
|
+
setRelevantValue: SimpleAtomicStateSetter<string>,
|
|
137
|
+
calculateDefinition: BindComputationExpression<'calculate'>
|
|
138
|
+
): void => {
|
|
139
|
+
context.scope.runTask(() => {
|
|
140
|
+
const calculate = createComputedExpression(context, calculateDefinition, {
|
|
141
|
+
defaultValue: '',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
createComputed(() => {
|
|
145
|
+
if (context.isAttached() && context.isRelevant()) {
|
|
146
|
+
const calculated = calculate();
|
|
147
|
+
const value = context.decodeInstanceValue(calculated);
|
|
148
|
+
|
|
149
|
+
setRelevantValue(value);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export type InstanceValueState = SimpleAtomicState<string>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Provides a consistent interface for value nodes of any type which:
|
|
159
|
+
*
|
|
160
|
+
* - derives initial state from either an existing instance (e.g. for edits) or
|
|
161
|
+
* the node's definition (e.g. initializing a new instance)
|
|
162
|
+
* - decodes current primary instance state into the value node's runtime type
|
|
163
|
+
* - encodes updated runtime values to store updated instance state
|
|
164
|
+
* - initializes reactive computation of `calculate` bind expressions for those
|
|
165
|
+
* nodes defined with one
|
|
166
|
+
* - prevents downstream writes to nodes in a readonly state
|
|
167
|
+
*/
|
|
168
|
+
export const createAttributeValueState = (context: AttributeContext): InstanceValueState => {
|
|
169
|
+
return context.scope.runTask(() => {
|
|
170
|
+
const initialValue = getInitialValue(context);
|
|
171
|
+
const baseValueState = createSignal(initialValue);
|
|
172
|
+
const relevantValueState = createRelevantValueState(context, baseValueState);
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @see {@link setPreloadUIDValue} for important details about spec ordering of events and computations.
|
|
176
|
+
*/
|
|
177
|
+
setPreloadUIDValue(context, relevantValueState);
|
|
178
|
+
|
|
179
|
+
const { calculate } = context.definition.bind;
|
|
180
|
+
|
|
181
|
+
if (calculate != null) {
|
|
182
|
+
const [, setValue] = relevantValueState;
|
|
183
|
+
|
|
184
|
+
createCalculation(context, setValue, calculate);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return guardDownstreamReadonlyWrites(context, relevantValueState);
|
|
188
|
+
});
|
|
189
|
+
};
|