@getodk/xforms-engine 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/body/BodyDefinition.d.ts +24 -7
- package/dist/body/RepeatElementDefinition.d.ts +19 -0
- package/dist/body/appearance/inputAppearanceParser.d.ts +4 -0
- package/dist/body/appearance/selectAppearanceParser.d.ts +4 -0
- package/dist/body/appearance/structureElementAppearanceParser.d.ts +4 -0
- package/dist/body/control/ControlDefinition.d.ts +2 -0
- package/dist/body/control/InputDefinition.d.ts +5 -0
- package/dist/body/control/select/SelectDefinition.d.ts +11 -1
- package/dist/body/group/BaseGroupDefinition.d.ts +3 -8
- package/dist/body/text/LabelDefinition.d.ts +2 -0
- package/dist/body/text/TextElementDefinition.d.ts +3 -3
- package/dist/client/BaseNode.d.ts +6 -1
- package/dist/client/GroupNode.d.ts +5 -2
- package/dist/client/NodeAppearances.d.ts +15 -0
- package/dist/client/RepeatInstanceNode.d.ts +3 -0
- package/dist/client/RepeatRangeNode.d.ts +5 -2
- package/dist/client/RootNode.d.ts +19 -0
- package/dist/client/SelectNode.d.ts +3 -0
- package/dist/client/StringNode.d.ts +3 -0
- package/dist/client/SubtreeNode.d.ts +1 -0
- package/dist/index.js +624 -368
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +3 -3
- package/dist/instance/RepeatInstance.d.ts +26 -2
- package/dist/instance/RepeatRange.d.ts +84 -5
- package/dist/instance/Root.d.ts +8 -23
- package/dist/instance/SelectField.d.ts +2 -2
- package/dist/instance/StringField.d.ts +2 -2
- package/dist/instance/Subtree.d.ts +1 -1
- package/dist/instance/abstract/DescendantNode.d.ts +12 -4
- package/dist/instance/abstract/InstanceNode.d.ts +26 -29
- package/dist/instance/internal-api/EvaluationContext.d.ts +5 -4
- package/dist/instance/internal-api/ValueContext.d.ts +2 -2
- package/dist/lib/TokenListParser.d.ts +84 -0
- package/dist/lib/dom/query.d.ts +5 -0
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +2 -1
- package/dist/model/DescendentNodeDefinition.d.ts +1 -2
- package/dist/model/NodeDefinition.d.ts +12 -12
- package/dist/model/RepeatInstanceDefinition.d.ts +5 -6
- package/dist/model/{RepeatSequenceDefinition.d.ts → RepeatRangeDefinition.d.ts} +4 -4
- package/dist/model/RepeatTemplateDefinition.d.ts +6 -7
- package/dist/model/RootDefinition.d.ts +3 -1
- package/dist/model/SubtreeDefinition.d.ts +2 -2
- package/dist/model/ValueNodeDefinition.d.ts +2 -3
- package/dist/solid.js +625 -369
- package/dist/solid.js.map +1 -1
- package/package.json +2 -2
- package/src/XFormDOM.ts +81 -8
- package/src/body/BodyDefinition.ts +38 -23
- package/src/body/RepeatElementDefinition.ts +70 -0
- package/src/body/appearance/inputAppearanceParser.ts +39 -0
- package/src/body/appearance/selectAppearanceParser.ts +38 -0
- package/src/body/appearance/structureElementAppearanceParser.ts +7 -0
- package/src/body/control/ControlDefinition.ts +4 -0
- package/src/body/control/InputDefinition.ts +13 -0
- package/src/body/control/select/SelectDefinition.ts +14 -5
- package/src/body/group/BaseGroupDefinition.ts +11 -49
- package/src/body/text/LabelDefinition.ts +15 -1
- package/src/body/text/TextElementDefinition.ts +5 -5
- package/src/client/BaseNode.ts +9 -1
- package/src/client/GroupNode.ts +6 -2
- package/src/client/NodeAppearances.ts +22 -0
- package/src/client/RepeatInstanceNode.ts +4 -0
- package/src/client/RepeatRangeNode.ts +6 -2
- package/src/client/RootNode.ts +22 -0
- package/src/client/SelectNode.ts +4 -0
- package/src/client/StringNode.ts +4 -0
- package/src/client/SubtreeNode.ts +1 -0
- package/src/instance/Group.ts +14 -9
- package/src/instance/RepeatInstance.ts +59 -15
- package/src/instance/RepeatRange.ts +133 -15
- package/src/instance/Root.ts +20 -64
- package/src/instance/SelectField.ts +7 -7
- package/src/instance/StringField.ts +8 -7
- package/src/instance/Subtree.ts +10 -7
- package/src/instance/abstract/DescendantNode.ts +45 -43
- package/src/instance/abstract/InstanceNode.ts +69 -86
- package/src/instance/children.ts +17 -7
- package/src/instance/index.ts +1 -1
- package/src/instance/internal-api/EvaluationContext.ts +5 -6
- package/src/instance/internal-api/ValueContext.ts +2 -2
- package/src/lib/TokenListParser.ts +156 -0
- package/src/lib/dom/query.ts +13 -0
- package/src/lib/reactivity/createChildrenState.ts +51 -6
- package/src/lib/reactivity/createComputedExpression.ts +1 -1
- package/src/lib/reactivity/createSelectItems.ts +4 -6
- package/src/lib/reactivity/createValueState.ts +6 -6
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +3 -1
- package/src/model/DescendentNodeDefinition.ts +1 -2
- package/src/model/ModelDefinition.ts +1 -1
- package/src/model/NodeDefinition.ts +12 -12
- package/src/model/RepeatInstanceDefinition.ts +8 -13
- package/src/model/{RepeatSequenceDefinition.ts → RepeatRangeDefinition.ts} +6 -6
- package/src/model/RepeatTemplateDefinition.ts +10 -15
- package/src/model/RootDefinition.ts +6 -12
- package/src/model/SubtreeDefinition.ts +3 -3
- package/src/model/ValueNodeDefinition.ts +2 -3
- package/dist/body/RepeatDefinition.d.ts +0 -16
- package/dist/body/group/RepeatGroupDefinition.d.ts +0 -13
- package/src/body/RepeatDefinition.ts +0 -54
- package/src/body/group/RepeatGroupDefinition.ts +0 -91
|
@@ -3,10 +3,10 @@ import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
|
3
3
|
import { type AnyDependentExpression } from '../../expression/DependentExpression.ts';
|
|
4
4
|
import type { AnyGroupElementDefinition } from '../BodyDefinition.ts';
|
|
5
5
|
import { BodyElementDefinition } from '../BodyElementDefinition.ts';
|
|
6
|
-
import type {
|
|
6
|
+
import type { RepeatElementDefinition } from '../RepeatElementDefinition.ts';
|
|
7
|
+
import type { AnyControlDefinition } from '../control/ControlDefinition.ts';
|
|
7
8
|
import type { ItemDefinition } from '../control/select/ItemDefinition.ts';
|
|
8
9
|
import type { ItemsetDefinition } from '../control/select/ItemsetDefinition.ts';
|
|
9
|
-
import type { AnySelectDefinition } from '../control/select/SelectDefinition.ts';
|
|
10
10
|
import { TextElementOutputPart } from './TextElementOutputPart.ts';
|
|
11
11
|
import { TextElementReferencePart } from './TextElementReferencePart.ts';
|
|
12
12
|
import { TextElementStaticPart } from './TextElementStaticPart.ts';
|
|
@@ -18,11 +18,11 @@ export interface TextElement extends Element {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export type TextElementOwner =
|
|
21
|
+
| AnyControlDefinition
|
|
21
22
|
| AnyGroupElementDefinition
|
|
22
|
-
| AnySelectDefinition
|
|
23
|
-
| InputDefinition
|
|
24
23
|
| ItemDefinition
|
|
25
|
-
| ItemsetDefinition
|
|
24
|
+
| ItemsetDefinition
|
|
25
|
+
| RepeatElementDefinition;
|
|
26
26
|
|
|
27
27
|
export type TextElementChild = TextElementOutputPart | TextElementStaticPart;
|
|
28
28
|
|
package/src/client/BaseNode.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { TokenListParser } from '../lib/TokenListParser.ts';
|
|
1
2
|
import type { AnyNodeDefinition } from '../model/NodeDefinition.ts';
|
|
2
|
-
import type {
|
|
3
|
+
import type { NodeAppearances } from './NodeAppearances.ts';
|
|
3
4
|
import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
|
|
4
5
|
import type { TextRange } from './TextRange.ts';
|
|
6
|
+
import type { InstanceNodeType } from './node-types.ts';
|
|
5
7
|
|
|
6
8
|
export interface BaseNodeState {
|
|
7
9
|
/**
|
|
@@ -126,6 +128,12 @@ export interface BaseNode {
|
|
|
126
128
|
*/
|
|
127
129
|
readonly nodeId: FormNodeID;
|
|
128
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @see {@link TokenListParser} for details.
|
|
133
|
+
*/
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
|
+
readonly appearances: NodeAppearances<any> | null;
|
|
136
|
+
|
|
129
137
|
/**
|
|
130
138
|
* Each node has a definition which specifies aspects of the node defined in
|
|
131
139
|
* the form. These aspects include (but are not limited to) the node's data
|
package/src/client/GroupNode.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyGroupElementDefinition } from '../body/BodyDefinition.ts';
|
|
2
2
|
import type { SubtreeDefinition } from '../model/SubtreeDefinition.ts';
|
|
3
3
|
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
4
|
+
import type { NodeAppearances } from './NodeAppearances.ts';
|
|
4
5
|
import type { RootNode } from './RootNode.ts';
|
|
5
6
|
import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
|
|
6
7
|
|
|
@@ -14,9 +15,11 @@ export interface GroupNodeState extends BaseNodeState {
|
|
|
14
15
|
// TODO: as with `SubtreeNode`'s `SubtreeDefinition`, there is a naming
|
|
15
16
|
// inconsistency emerging here.
|
|
16
17
|
export interface GroupDefinition extends SubtreeDefinition {
|
|
17
|
-
readonly bodyElement:
|
|
18
|
+
readonly bodyElement: AnyGroupElementDefinition;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
export type GroupNodeAppearances = NodeAppearances<GroupDefinition>;
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* A node corresponding to an XForms `<group>`.
|
|
22
25
|
*/
|
|
@@ -26,6 +29,7 @@ export interface GroupDefinition extends SubtreeDefinition {
|
|
|
26
29
|
// for context.
|
|
27
30
|
export interface GroupNode extends BaseNode {
|
|
28
31
|
readonly nodeType: 'group';
|
|
32
|
+
readonly appearances: GroupNodeAppearances;
|
|
29
33
|
readonly definition: GroupDefinition;
|
|
30
34
|
readonly root: RootNode;
|
|
31
35
|
readonly parent: GeneralParentNode;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ParsedTokenList } from '../lib/TokenListParser.ts';
|
|
2
|
+
import type { NodeDefinition } from '../model/NodeDefinition.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* - Provides a means to distinguish between internal and client-facing names
|
|
6
|
+
* for the same {@link ParsedTokenList} types.
|
|
7
|
+
*
|
|
8
|
+
* - Anticipates some iteration on both parsed ("definition") types and
|
|
9
|
+
* client-facing node types, which may not happen in tandem.
|
|
10
|
+
*/
|
|
11
|
+
// prettier-ignore
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export type NodeAppearances<Definition extends NodeDefinition<any>> =
|
|
14
|
+
Definition extends {
|
|
15
|
+
readonly bodyElement: {
|
|
16
|
+
readonly appearances:
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
infer Appearances extends ParsedTokenList<any>
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
? Appearances
|
|
22
|
+
: null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RepeatInstanceDefinition } from '../model/RepeatInstanceDefinition.ts';
|
|
2
2
|
import type { RepeatTemplateDefinition } from '../model/RepeatTemplateDefinition.ts';
|
|
3
3
|
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
4
|
+
import type { NodeAppearances } from './NodeAppearances.ts';
|
|
4
5
|
import type { RepeatRangeNode } from './RepeatRangeNode.ts';
|
|
5
6
|
import type { RootNode } from './RootNode.ts';
|
|
6
7
|
import type { GeneralChildNode } from './hierarchy.ts';
|
|
@@ -22,8 +23,11 @@ export type RepeatDefinition =
|
|
|
22
23
|
| RepeatInstanceDefinition
|
|
23
24
|
| RepeatTemplateDefinition;
|
|
24
25
|
|
|
26
|
+
export type RepeatInstanceNodeAppearances = NodeAppearances<RepeatDefinition>;
|
|
27
|
+
|
|
25
28
|
export interface RepeatInstanceNode extends BaseNode {
|
|
26
29
|
readonly nodeType: 'repeat-instance';
|
|
30
|
+
readonly appearances: RepeatInstanceNodeAppearances;
|
|
27
31
|
readonly definition: RepeatDefinition;
|
|
28
32
|
readonly root: RootNode;
|
|
29
33
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RepeatRangeDefinition } from '../model/RepeatRangeDefinition.ts';
|
|
2
2
|
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
3
|
+
import type { NodeAppearances } from './NodeAppearances.ts';
|
|
3
4
|
import type { RepeatInstanceNode } from './RepeatInstanceNode.ts';
|
|
4
5
|
import type { RootNode } from './RootNode.ts';
|
|
5
6
|
import type { TextRange } from './TextRange.ts';
|
|
@@ -23,6 +24,8 @@ export interface RepeatRangeNodeState extends BaseNodeState {
|
|
|
23
24
|
get value(): null;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
export type RepeatRangeNodeAppearances = NodeAppearances<RepeatRangeDefinition>;
|
|
28
|
+
|
|
26
29
|
/**
|
|
27
30
|
* Represents a contiguous set of zero or more {@link RepeatInstanceNode}s
|
|
28
31
|
* (accessed by its
|
|
@@ -89,7 +92,8 @@ export interface RepeatRangeNodeState extends BaseNodeState {
|
|
|
89
92
|
*/
|
|
90
93
|
export interface RepeatRangeNode extends BaseNode {
|
|
91
94
|
readonly nodeType: 'repeat-range';
|
|
92
|
-
readonly
|
|
95
|
+
readonly appearances: RepeatRangeNodeAppearances;
|
|
96
|
+
readonly definition: RepeatRangeDefinition;
|
|
93
97
|
readonly root: RootNode;
|
|
94
98
|
readonly parent: GeneralParentNode;
|
|
95
99
|
readonly currentState: RepeatRangeNodeState;
|
package/src/client/RootNode.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BodyClassList } from '../body/BodyDefinition.ts';
|
|
1
2
|
import type { RootDefinition } from '../model/RootDefinition.ts';
|
|
2
3
|
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
3
4
|
import type { ActiveLanguage, FormLanguage, FormLanguages } from './FormLanguage.ts';
|
|
@@ -21,6 +22,27 @@ export interface RootNodeState extends BaseNodeState {
|
|
|
21
22
|
|
|
22
23
|
export interface RootNode extends BaseNode {
|
|
23
24
|
readonly nodeType: 'root';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @todo this along with {@link classes} is... awkward.
|
|
28
|
+
*/
|
|
29
|
+
readonly appearances: null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @todo This is another odd deviation in {@link RootNode}. Unlike
|
|
33
|
+
* {@link languages}, it doesn't feel particularly **essential**. While it
|
|
34
|
+
* would deviate from XForms spec terminology, it seems like it _might be
|
|
35
|
+
* reasonable_ to instead convey `<h:body class="...">` as
|
|
36
|
+
* {@link RootNode.appearances} in the client interface. They do have slightly
|
|
37
|
+
* different spec semantics (i.e. a body class can be anything, to trigger
|
|
38
|
+
* styling in a form UI). But the **most likely anticipated** use case in Web
|
|
39
|
+
* Forms would be the "pages" class, and perhaps "theme-grid". The former is
|
|
40
|
+
* definitely conceptually similar to a XForms `appearance` (albeit
|
|
41
|
+
* form-global, which is not a spec concept). The latter does as well, and we
|
|
42
|
+
* already anticipate applying that concept in non-form-global ways.
|
|
43
|
+
*/
|
|
44
|
+
readonly classes: BodyClassList;
|
|
45
|
+
|
|
24
46
|
readonly definition: RootDefinition;
|
|
25
47
|
readonly root: RootNode;
|
|
26
48
|
readonly parent: null;
|
package/src/client/SelectNode.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnySelectDefinition } from '../body/control/select/SelectDefinition.ts';
|
|
2
2
|
import type { ValueNodeDefinition } from '../model/ValueNodeDefinition.ts';
|
|
3
3
|
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
4
|
+
import type { NodeAppearances } from './NodeAppearances.ts';
|
|
4
5
|
import type { RootNode } from './RootNode.ts';
|
|
5
6
|
import type { StringNode } from './StringNode.ts';
|
|
6
7
|
import type { TextRange } from './TextRange.ts';
|
|
@@ -38,8 +39,11 @@ export interface SelectDefinition extends ValueNodeDefinition {
|
|
|
38
39
|
readonly bodyElement: AnySelectDefinition;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
export type SelectNodeAppearances = NodeAppearances<SelectDefinition>;
|
|
43
|
+
|
|
41
44
|
export interface SelectNode extends BaseNode {
|
|
42
45
|
readonly nodeType: 'select';
|
|
46
|
+
readonly appearances: SelectNodeAppearances;
|
|
43
47
|
readonly definition: SelectDefinition;
|
|
44
48
|
readonly root: RootNode;
|
|
45
49
|
readonly parent: GeneralParentNode;
|
package/src/client/StringNode.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InputDefinition } from '../body/control/InputDefinition.ts';
|
|
2
2
|
import type { ValueNodeDefinition } from '../model/ValueNodeDefinition.ts';
|
|
3
3
|
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
4
|
+
import type { NodeAppearances } from './NodeAppearances.ts';
|
|
4
5
|
import type { RootNode } from './RootNode.ts';
|
|
5
6
|
import type { GeneralParentNode } from './hierarchy.ts';
|
|
6
7
|
|
|
@@ -21,6 +22,8 @@ export interface StringDefinition extends ValueNodeDefinition {
|
|
|
21
22
|
readonly bodyElement: InputDefinition | null;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export type StringNodeAppearances = NodeAppearances<StringDefinition>;
|
|
26
|
+
|
|
24
27
|
/**
|
|
25
28
|
* A node which can be assigned a string/text value. A string node **MAY**
|
|
26
29
|
* correspond to form field defined as an XForms `<input>`, which a user-facing
|
|
@@ -30,6 +33,7 @@ export interface StringDefinition extends ValueNodeDefinition {
|
|
|
30
33
|
*/
|
|
31
34
|
export interface StringNode extends BaseNode {
|
|
32
35
|
readonly nodeType: 'string';
|
|
36
|
+
readonly appearances: StringNodeAppearances;
|
|
33
37
|
readonly definition: StringDefinition;
|
|
34
38
|
readonly root: RootNode;
|
|
35
39
|
readonly parent: GeneralParentNode;
|
|
@@ -50,6 +50,7 @@ export interface SubtreeDefinition extends BaseSubtreeDefinition {
|
|
|
50
50
|
// TODO: directly test presentation of non-group subtree children/descendants
|
|
51
51
|
export interface SubtreeNode extends BaseNode {
|
|
52
52
|
readonly nodeType: 'subtree';
|
|
53
|
+
readonly appearances: null;
|
|
53
54
|
readonly definition: SubtreeDefinition;
|
|
54
55
|
readonly root: RootNode;
|
|
55
56
|
readonly parent: GeneralParentNode;
|
package/src/instance/Group.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Accessor } from 'solid-js';
|
|
2
|
-
import type { GroupDefinition, GroupNode } from '../client/GroupNode.ts';
|
|
2
|
+
import type { GroupDefinition, GroupNode, GroupNodeAppearances } from '../client/GroupNode.ts';
|
|
3
3
|
import type { TextRange } from '../index.ts';
|
|
4
4
|
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
5
5
|
import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
@@ -38,13 +38,15 @@ export class Group
|
|
|
38
38
|
protected override engineState: EngineState<GroupStateSpec>;
|
|
39
39
|
|
|
40
40
|
// GroupNode
|
|
41
|
-
readonly currentState: MaterializedChildren<CurrentState<GroupStateSpec>, GeneralChildNode>;
|
|
42
|
-
|
|
43
41
|
readonly nodeType = 'group';
|
|
42
|
+
readonly appearances: GroupNodeAppearances;
|
|
43
|
+
readonly currentState: MaterializedChildren<CurrentState<GroupStateSpec>, GeneralChildNode>;
|
|
44
44
|
|
|
45
45
|
constructor(parent: GeneralParentNode, definition: GroupDefinition) {
|
|
46
46
|
super(parent, definition);
|
|
47
47
|
|
|
48
|
+
this.appearances = definition.bodyElement.appearances;
|
|
49
|
+
|
|
48
50
|
const childrenState = createChildrenState<Group, GeneralChildNode>(this);
|
|
49
51
|
|
|
50
52
|
this.childrenState = childrenState;
|
|
@@ -52,7 +54,10 @@ export class Group
|
|
|
52
54
|
const state = createSharedNodeState(
|
|
53
55
|
this.scope,
|
|
54
56
|
{
|
|
55
|
-
|
|
57
|
+
reference: this.contextReference,
|
|
58
|
+
readonly: this.isReadonly,
|
|
59
|
+
relevant: this.isRelevant,
|
|
60
|
+
required: this.isRequired,
|
|
56
61
|
|
|
57
62
|
label: createNodeLabel(this, definition),
|
|
58
63
|
hint: null,
|
|
@@ -67,15 +72,15 @@ export class Group
|
|
|
67
72
|
|
|
68
73
|
this.state = state;
|
|
69
74
|
this.engineState = state.engineState;
|
|
70
|
-
this.currentState = materializeCurrentStateChildren(
|
|
75
|
+
this.currentState = materializeCurrentStateChildren(
|
|
76
|
+
this.scope,
|
|
77
|
+
state.currentState,
|
|
78
|
+
childrenState
|
|
79
|
+
);
|
|
71
80
|
|
|
72
81
|
childrenState.setChildren(buildChildren(this));
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
protected computeReference(parent: GeneralParentNode): string {
|
|
76
|
-
return this.computeChildStepReference(parent);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
84
|
getChildren(): readonly GeneralChildNode[] {
|
|
80
85
|
return this.childrenState.getChildren();
|
|
81
86
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Accessor } from 'solid-js';
|
|
2
2
|
import { createComputed, createSignal, on } from 'solid-js';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
RepeatDefinition,
|
|
5
|
+
RepeatInstanceNode,
|
|
6
|
+
RepeatInstanceNodeAppearances,
|
|
7
|
+
} from '../client/RepeatInstanceNode.ts';
|
|
4
8
|
import type { TextRange } from '../index.ts';
|
|
5
9
|
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
6
10
|
import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
@@ -46,9 +50,39 @@ export class RepeatInstance
|
|
|
46
50
|
protected readonly state: SharedNodeState<RepeatInstanceStateSpec>;
|
|
47
51
|
protected override engineState: EngineState<RepeatInstanceStateSpec>;
|
|
48
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @todo Should we special case repeat `readonly` inheritance the same way
|
|
55
|
+
* we do for `relevant`?
|
|
56
|
+
*
|
|
57
|
+
* @see {@link hasNonRelevantAncestor}
|
|
58
|
+
*/
|
|
59
|
+
declare readonly hasReadonlyAncestor: Accessor<boolean>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A repeat instance can inherit non-relevance, just like any other node. That
|
|
63
|
+
* inheritance is derived from the repeat instance's parent node in the
|
|
64
|
+
* primary instance XML/DOM tree (and would be semantically expected to do so
|
|
65
|
+
* even if we move away from that implementation detail).
|
|
66
|
+
*
|
|
67
|
+
* Since {@link RepeatInstance.parent} is a {@link RepeatRange}, which is a
|
|
68
|
+
* runtime data model fiction that does not exist in that hierarchy, we pass
|
|
69
|
+
* this call through, allowing the {@link RepeatRange} to check the actual
|
|
70
|
+
* primary instance parent node's relevance state.
|
|
71
|
+
*
|
|
72
|
+
* @todo Should we apply similar reasoning in {@link hasReadonlyAncestor}?
|
|
73
|
+
*/
|
|
74
|
+
override readonly hasNonRelevantAncestor: Accessor<boolean> = () => {
|
|
75
|
+
return this.parent.hasNonRelevantAncestor();
|
|
76
|
+
};
|
|
77
|
+
|
|
49
78
|
// RepeatInstanceNode
|
|
50
79
|
readonly nodeType = 'repeat-instance';
|
|
51
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @see {@link RepeatRange.appearances}
|
|
83
|
+
*/
|
|
84
|
+
readonly appearances: RepeatInstanceNodeAppearances;
|
|
85
|
+
|
|
52
86
|
readonly currentState: MaterializedChildren<
|
|
53
87
|
CurrentState<RepeatInstanceStateSpec>,
|
|
54
88
|
GeneralChildNode
|
|
@@ -59,7 +93,20 @@ export class RepeatInstance
|
|
|
59
93
|
definition: RepeatDefinition,
|
|
60
94
|
options: RepeatInstanceOptions
|
|
61
95
|
) {
|
|
62
|
-
|
|
96
|
+
const { precedingInstance } = options;
|
|
97
|
+
const precedingIndex = precedingInstance?.currentIndex ?? (() => -1);
|
|
98
|
+
const initialIndex = precedingIndex() + 1;
|
|
99
|
+
const [currentIndex, setCurrentIndex] = createSignal(initialIndex);
|
|
100
|
+
|
|
101
|
+
super(parent, definition, {
|
|
102
|
+
computeReference: (): string => {
|
|
103
|
+
const currentPosition = currentIndex() + 1;
|
|
104
|
+
|
|
105
|
+
return `${parent.contextReference()}[${currentPosition}]`;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.appearances = definition.bodyElement.appearances;
|
|
63
110
|
|
|
64
111
|
const childrenState = createChildrenState<RepeatInstance, GeneralChildNode>(this);
|
|
65
112
|
|
|
@@ -67,18 +114,17 @@ export class RepeatInstance
|
|
|
67
114
|
|
|
68
115
|
options.precedingPrimaryInstanceNode.after(this.contextNode);
|
|
69
116
|
|
|
70
|
-
const { precedingInstance } = options;
|
|
71
|
-
const precedingIndex = precedingInstance?.currentIndex ?? (() => -1);
|
|
72
|
-
const initialIndex = precedingIndex() + 1;
|
|
73
|
-
const [currentIndex, setCurrentIndex] = createSignal(initialIndex);
|
|
74
|
-
|
|
75
117
|
this.currentIndex = currentIndex;
|
|
76
118
|
|
|
77
119
|
const state = createSharedNodeState(
|
|
78
120
|
this.scope,
|
|
79
121
|
{
|
|
80
|
-
|
|
122
|
+
reference: this.contextReference,
|
|
123
|
+
readonly: this.isReadonly,
|
|
124
|
+
relevant: this.isRelevant,
|
|
125
|
+
required: this.isRequired,
|
|
81
126
|
|
|
127
|
+
// TODO: only-child <group><label>
|
|
82
128
|
label: createNodeLabel(this, definition),
|
|
83
129
|
hint: null,
|
|
84
130
|
children: childrenState.childIds,
|
|
@@ -92,7 +138,11 @@ export class RepeatInstance
|
|
|
92
138
|
|
|
93
139
|
this.state = state;
|
|
94
140
|
this.engineState = state.engineState;
|
|
95
|
-
this.currentState = materializeCurrentStateChildren(
|
|
141
|
+
this.currentState = materializeCurrentStateChildren(
|
|
142
|
+
this.scope,
|
|
143
|
+
state.currentState,
|
|
144
|
+
childrenState
|
|
145
|
+
);
|
|
96
146
|
|
|
97
147
|
// Maintain current index state, updating as the parent range's children
|
|
98
148
|
// state is changed. Notable Solid reactivity nuances:
|
|
@@ -118,12 +168,6 @@ export class RepeatInstance
|
|
|
118
168
|
childrenState.setChildren(buildChildren(this));
|
|
119
169
|
}
|
|
120
170
|
|
|
121
|
-
protected computeReference(parent: RepeatRange): string {
|
|
122
|
-
const currentPosition = this.currentIndex() + 1;
|
|
123
|
-
|
|
124
|
-
return `${parent.contextReference}[${currentPosition}]`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
171
|
protected override initializeContextNode(parentContextNode: Element, nodeName: string): Element {
|
|
128
172
|
return this.createContextNode(parentContextNode, nodeName);
|
|
129
173
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { insertAtIndex } from '@getodk/common/lib/array/insert.ts';
|
|
2
2
|
import type { Accessor } from 'solid-js';
|
|
3
|
-
import type { RepeatRangeNode } from '../client/RepeatRangeNode.ts';
|
|
3
|
+
import type { RepeatRangeNode, RepeatRangeNodeAppearances } from '../client/RepeatRangeNode.ts';
|
|
4
4
|
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
5
5
|
import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
6
|
+
import { createComputedExpression } from '../lib/reactivity/createComputedExpression.ts';
|
|
6
7
|
import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
7
8
|
import { materializeCurrentStateChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
8
9
|
import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
|
|
@@ -10,7 +11,7 @@ import type { EngineState } from '../lib/reactivity/node-state/createEngineState
|
|
|
10
11
|
import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
11
12
|
import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
12
13
|
import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
|
|
13
|
-
import type {
|
|
14
|
+
import type { RepeatRangeDefinition } from '../model/RepeatRangeDefinition.ts';
|
|
14
15
|
import type { RepeatDefinition } from './RepeatInstance.ts';
|
|
15
16
|
import { RepeatInstance } from './RepeatInstance.ts';
|
|
16
17
|
import type { Root } from './Root.ts';
|
|
@@ -31,7 +32,7 @@ interface RepeatRangeStateSpec extends DescendantNodeSharedStateSpec {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export class RepeatRange
|
|
34
|
-
extends DescendantNode<
|
|
35
|
+
extends DescendantNode<RepeatRangeDefinition, RepeatRangeStateSpec, RepeatInstance>
|
|
35
36
|
implements RepeatRangeNode, EvaluationContext, SubscribableDependency
|
|
36
37
|
{
|
|
37
38
|
/**
|
|
@@ -64,22 +65,144 @@ export class RepeatRange
|
|
|
64
65
|
protected readonly state: SharedNodeState<RepeatRangeStateSpec>;
|
|
65
66
|
protected override engineState: EngineState<RepeatRangeStateSpec>;
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @todo Should we special case repeat `readonly` state the same way
|
|
70
|
+
* we do for `relevant`?
|
|
71
|
+
*
|
|
72
|
+
* @see {@link isSelfRelevant}
|
|
73
|
+
*/
|
|
74
|
+
declare isSelfReadonly: Accessor<boolean>;
|
|
75
|
+
|
|
76
|
+
private readonly emptyRangeEvaluationContext: EvaluationContext & {
|
|
77
|
+
readonly contextNode: Comment;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @see {@link isSelfRelevant}
|
|
82
|
+
*/
|
|
83
|
+
private readonly isEmptyRangeSelfRelevant: Accessor<boolean>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A repeat range does not exist in the primary instance tree. A `relevant`
|
|
87
|
+
* expression applies to each {@link RepeatInstance} child of the repeat
|
|
88
|
+
* range. Determining whether a repeat range itself "is relevant" isn't a
|
|
89
|
+
* concept the spec addresses, but it may be used by clients to determine
|
|
90
|
+
* whether to allow interaction with the range (e.g. by adding a repeat
|
|
91
|
+
* instance, or presenting the range's label when empty).
|
|
92
|
+
*
|
|
93
|
+
* As a naive first pass, it seems like the heuristic for this should be:
|
|
94
|
+
*
|
|
95
|
+
* 1. Does the repeat range have any repeat instance children?
|
|
96
|
+
*
|
|
97
|
+
* - If yes, go to 2.
|
|
98
|
+
* - If no, go to 3.
|
|
99
|
+
*
|
|
100
|
+
* 2. Does one or more of those children return `true` for the node's
|
|
101
|
+
* `relevant` expression (i.e. is the repeat instance "self relevant")?
|
|
102
|
+
*
|
|
103
|
+
* 3. Does the relevant expression return `true` for the repeat range itself
|
|
104
|
+
* (where, at least for now, the context of that evaluation would be the
|
|
105
|
+
* repeat range's {@link anchorNode} to ensure correct relative expressions
|
|
106
|
+
* resolve correctly)?
|
|
107
|
+
*
|
|
108
|
+
* @todo While (3) is proactively implemented, there isn't presently a test
|
|
109
|
+
* exercising it. It felt best for now to surface this for discussion in
|
|
110
|
+
* review to validate that it's going in the right direction.
|
|
111
|
+
*
|
|
112
|
+
* @todo While (2) **is actually tested**, the tests currently in place behave
|
|
113
|
+
* the same way with only the logic for (3), regardless of whether the repeat
|
|
114
|
+
* range actually has any repeat instance children. It's unclear (a) if that's
|
|
115
|
+
* a preferable simplification and (b) how that might affect performance (in
|
|
116
|
+
* theory it could vary depending on form structure and runtime state).
|
|
117
|
+
*/
|
|
118
|
+
override readonly isSelfRelevant: Accessor<boolean> = () => {
|
|
119
|
+
const instances = this.childrenState.getChildren();
|
|
120
|
+
|
|
121
|
+
if (instances.length > 0) {
|
|
122
|
+
return instances.some((instance) => instance.isSelfRelevant());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.isEmptyRangeSelfRelevant();
|
|
126
|
+
};
|
|
127
|
+
|
|
67
128
|
// RepeatRangeNode
|
|
68
129
|
readonly nodeType = 'repeat-range';
|
|
69
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @todo RepeatRange*, RepeatInstance* (and RepeatTemplate*) all share the
|
|
133
|
+
* same body element, and thus all share the same definition `bodyElement`. As
|
|
134
|
+
* such, they also all share the same `appearances`. At time of writing,
|
|
135
|
+
* `web-forms` (Vue UI package) treats a `RepeatRangeNode`...
|
|
136
|
+
*
|
|
137
|
+
* - ... as a group, if the node has a label (i.e.
|
|
138
|
+
* `<group><label/><repeat/></group>`)
|
|
139
|
+
* - ... effectively as a fragment containing only its instances, otherwise
|
|
140
|
+
*
|
|
141
|
+
* We now collapse `<group><repeat>` into `<repeat>`, and no longer treat
|
|
142
|
+
* "repeat group" as a concept (after parsing). According to the spec, these
|
|
143
|
+
* appearances **are supposed to** come from that "repeat group" in the form
|
|
144
|
+
* definition. In practice, many forms do define appearances directly on a
|
|
145
|
+
* repeat element. The engine currently produces an error if both are defined
|
|
146
|
+
* simultaneously, but otherwise makes no distinction between appearances in
|
|
147
|
+
* these form definition shapes:
|
|
148
|
+
*
|
|
149
|
+
* ```xml
|
|
150
|
+
* <group ref="/data/rep1" appearance="...">
|
|
151
|
+
* <repeat nodeset="/data/rep1"/>
|
|
152
|
+
* </group>
|
|
153
|
+
*
|
|
154
|
+
* <group ref="/data/rep1">
|
|
155
|
+
* <repeat nodeset="/data/rep1"/ appearance="...">
|
|
156
|
+
* </group>
|
|
157
|
+
*
|
|
158
|
+
* <repeat nodeset="/data/rep1"/ appearance="...">
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* All of the above creates considerable ambiguity about where "repeat
|
|
162
|
+
* appearances" should apply, under which circumstances.
|
|
163
|
+
*/
|
|
164
|
+
readonly appearances: RepeatRangeNodeAppearances;
|
|
165
|
+
|
|
70
166
|
readonly currentState: MaterializedChildren<CurrentState<RepeatRangeStateSpec>, RepeatInstance>;
|
|
71
167
|
|
|
72
|
-
constructor(parent: GeneralParentNode, definition:
|
|
168
|
+
constructor(parent: GeneralParentNode, definition: RepeatRangeDefinition) {
|
|
73
169
|
super(parent, definition);
|
|
74
170
|
|
|
171
|
+
this.appearances = definition.bodyElement.appearances;
|
|
172
|
+
|
|
75
173
|
const childrenState = createChildrenState<RepeatRange, RepeatInstance>(this);
|
|
76
174
|
|
|
77
175
|
this.childrenState = childrenState;
|
|
78
176
|
|
|
177
|
+
this.anchorNode = this.contextNode.ownerDocument.createComment(
|
|
178
|
+
`Begin repeat range: ${definition.nodeset}`
|
|
179
|
+
);
|
|
180
|
+
this.contextNode.append(this.anchorNode);
|
|
181
|
+
|
|
182
|
+
this.emptyRangeEvaluationContext = {
|
|
183
|
+
scope: this.scope,
|
|
184
|
+
evaluator: this.evaluator,
|
|
185
|
+
root: this.root,
|
|
186
|
+
contextReference: this.contextReference,
|
|
187
|
+
contextNode: this.anchorNode,
|
|
188
|
+
|
|
189
|
+
getSubscribableDependenciesByReference: (reference) => {
|
|
190
|
+
return this.getSubscribableDependenciesByReference(reference);
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
this.isEmptyRangeSelfRelevant = createComputedExpression(
|
|
195
|
+
this.emptyRangeEvaluationContext,
|
|
196
|
+
definition.bind.relevant
|
|
197
|
+
);
|
|
198
|
+
|
|
79
199
|
const state = createSharedNodeState(
|
|
80
200
|
this.scope,
|
|
81
201
|
{
|
|
82
|
-
|
|
202
|
+
reference: this.contextReference,
|
|
203
|
+
readonly: this.isReadonly,
|
|
204
|
+
relevant: this.isRelevant,
|
|
205
|
+
required: this.isRequired,
|
|
83
206
|
|
|
84
207
|
label: createNodeLabel(this, definition),
|
|
85
208
|
hint: null,
|
|
@@ -92,14 +215,13 @@ export class RepeatRange
|
|
|
92
215
|
}
|
|
93
216
|
);
|
|
94
217
|
|
|
95
|
-
this.anchorNode = this.contextNode.ownerDocument.createComment(
|
|
96
|
-
`Begin repeat range: ${definition.nodeset}`
|
|
97
|
-
);
|
|
98
|
-
this.contextNode.append(this.anchorNode);
|
|
99
|
-
|
|
100
218
|
this.state = state;
|
|
101
219
|
this.engineState = state.engineState;
|
|
102
|
-
this.currentState = materializeCurrentStateChildren(
|
|
220
|
+
this.currentState = materializeCurrentStateChildren(
|
|
221
|
+
this.scope,
|
|
222
|
+
state.currentState,
|
|
223
|
+
childrenState
|
|
224
|
+
);
|
|
103
225
|
|
|
104
226
|
definition.instances.forEach((instanceDefinition, index) => {
|
|
105
227
|
const afterIndex = index - 1;
|
|
@@ -116,10 +238,6 @@ export class RepeatRange
|
|
|
116
238
|
return parentContextNode;
|
|
117
239
|
}
|
|
118
240
|
|
|
119
|
-
protected computeReference(parent: GeneralParentNode): string {
|
|
120
|
-
return this.computeChildStepReference(parent);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
241
|
getInstanceIndex(instance: RepeatInstance): number {
|
|
124
242
|
return this.engineState.children.indexOf(instance.nodeId);
|
|
125
243
|
}
|