@getodk/xforms-engine 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/.vite/manifest.json +1 -0
  2. package/dist/XFormDOM.d.ts +1 -0
  3. package/dist/XFormDataType.d.ts +2 -1
  4. package/dist/XFormDefinition.d.ts +1 -0
  5. package/dist/body/BodyDefinition.d.ts +27 -9
  6. package/dist/body/BodyElementDefinition.d.ts +5 -4
  7. package/dist/body/RepeatElementDefinition.d.ts +19 -0
  8. package/dist/body/UnsupportedBodyElementDefinition.d.ts +3 -2
  9. package/dist/body/appearance/inputAppearanceParser.d.ts +4 -0
  10. package/dist/body/appearance/selectAppearanceParser.d.ts +4 -0
  11. package/dist/body/appearance/structureElementAppearanceParser.d.ts +4 -0
  12. package/dist/body/control/ControlDefinition.d.ts +5 -2
  13. package/dist/body/control/InputDefinition.d.ts +6 -0
  14. package/dist/body/control/select/ItemDefinition.d.ts +4 -3
  15. package/dist/body/control/select/ItemsetDefinition.d.ts +4 -3
  16. package/dist/body/control/select/ItemsetNodesetContext.d.ts +3 -2
  17. package/dist/body/control/select/ItemsetNodesetExpression.d.ts +2 -1
  18. package/dist/body/control/select/ItemsetValueExpression.d.ts +2 -1
  19. package/dist/body/control/select/SelectDefinition.d.ts +16 -5
  20. package/dist/body/group/BaseGroupDefinition.d.ts +6 -10
  21. package/dist/body/group/LogicalGroupDefinition.d.ts +1 -0
  22. package/dist/body/group/PresentationGroupDefinition.d.ts +3 -2
  23. package/dist/body/group/StructuralGroupDefinition.d.ts +1 -0
  24. package/dist/body/text/HintDefinition.d.ts +4 -4
  25. package/dist/body/text/LabelDefinition.d.ts +9 -7
  26. package/dist/body/text/TextElementDefinition.d.ts +9 -8
  27. package/dist/body/text/TextElementOutputPart.d.ts +2 -1
  28. package/dist/body/text/TextElementPart.d.ts +5 -4
  29. package/dist/body/text/TextElementReferencePart.d.ts +2 -1
  30. package/dist/body/text/TextElementStaticPart.d.ts +2 -1
  31. package/dist/client/BaseNode.d.ts +9 -3
  32. package/dist/client/EngineConfig.d.ts +2 -1
  33. package/dist/client/GroupNode.d.ts +10 -6
  34. package/dist/client/NodeAppearances.d.ts +15 -0
  35. package/dist/client/RepeatInstanceNode.d.ts +10 -6
  36. package/dist/client/RepeatRangeNode.d.ts +11 -7
  37. package/dist/client/RootNode.d.ts +24 -4
  38. package/dist/client/SelectNode.d.ts +10 -6
  39. package/dist/client/StringNode.d.ts +9 -5
  40. package/dist/client/SubtreeNode.d.ts +6 -4
  41. package/dist/client/TextRange.d.ts +2 -1
  42. package/dist/client/hierarchy.d.ts +9 -8
  43. package/dist/client/index.d.ts +3 -2
  44. package/dist/expression/DependencyContext.d.ts +2 -1
  45. package/dist/expression/DependentExpression.d.ts +3 -2
  46. package/dist/index.d.ts +2 -1
  47. package/dist/index.js +1882 -1757
  48. package/dist/index.js.map +1 -1
  49. package/dist/instance/Group.d.ts +15 -15
  50. package/dist/instance/RepeatInstance.d.ts +39 -15
  51. package/dist/instance/RepeatRange.d.ts +98 -20
  52. package/dist/instance/Root.d.ts +25 -39
  53. package/dist/instance/SelectField.d.ts +17 -17
  54. package/dist/instance/StringField.d.ts +17 -17
  55. package/dist/instance/Subtree.d.ts +13 -13
  56. package/dist/instance/abstract/DescendantNode.d.ts +26 -18
  57. package/dist/instance/abstract/InstanceNode.d.ts +44 -46
  58. package/dist/instance/children.d.ts +2 -1
  59. package/dist/instance/hierarchy.d.ts +8 -7
  60. package/dist/instance/index.d.ts +4 -3
  61. package/dist/instance/internal-api/EvaluationContext.d.ts +10 -8
  62. package/dist/instance/internal-api/InstanceConfig.d.ts +3 -2
  63. package/dist/instance/internal-api/SubscribableDependency.d.ts +2 -1
  64. package/dist/instance/internal-api/TranslationContext.d.ts +2 -1
  65. package/dist/instance/internal-api/ValueContext.d.ts +6 -5
  66. package/dist/instance/resource.d.ts +3 -2
  67. package/dist/instance/text/TextChunk.d.ts +4 -3
  68. package/dist/instance/text/TextRange.d.ts +2 -1
  69. package/dist/lib/TokenListParser.d.ts +84 -0
  70. package/dist/lib/dom/query.d.ts +8 -2
  71. package/dist/lib/reactivity/createChildrenState.d.ts +4 -3
  72. package/dist/lib/reactivity/createComputedExpression.d.ts +4 -3
  73. package/dist/lib/reactivity/createSelectItems.d.ts +4 -3
  74. package/dist/lib/reactivity/createValueState.d.ts +3 -2
  75. package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +6 -4
  76. package/dist/lib/reactivity/node-state/createClientState.d.ts +7 -6
  77. package/dist/lib/reactivity/node-state/createCurrentState.d.ts +5 -4
  78. package/dist/lib/reactivity/node-state/createEngineState.d.ts +4 -3
  79. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +7 -6
  80. package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +2 -1
  81. package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +3 -2
  82. package/dist/lib/reactivity/node-state/representations.d.ts +2 -1
  83. package/dist/lib/reactivity/scope.d.ts +2 -1
  84. package/dist/lib/reactivity/text/createFieldHint.d.ts +4 -3
  85. package/dist/lib/reactivity/text/createNodeLabel.d.ts +4 -3
  86. package/dist/lib/reactivity/text/createTextRange.d.ts +6 -5
  87. package/dist/lib/reactivity/types.d.ts +2 -1
  88. package/dist/lib/xpath/analysis.d.ts +2 -1
  89. package/dist/model/BindComputation.d.ts +2 -1
  90. package/dist/model/BindDefinition.d.ts +6 -5
  91. package/dist/model/DescendentNodeDefinition.d.ts +6 -6
  92. package/dist/model/ModelBindMap.d.ts +4 -3
  93. package/dist/model/ModelDefinition.d.ts +2 -1
  94. package/dist/model/NodeDefinition.d.ts +20 -19
  95. package/dist/model/RepeatInstanceDefinition.d.ts +7 -7
  96. package/dist/model/{RepeatSequenceDefinition.d.ts → RepeatRangeDefinition.d.ts} +7 -6
  97. package/dist/model/RepeatTemplateDefinition.d.ts +8 -8
  98. package/dist/model/RootDefinition.d.ts +8 -5
  99. package/dist/model/SubtreeDefinition.d.ts +5 -4
  100. package/dist/model/ValueNodeDefinition.d.ts +5 -5
  101. package/dist/solid.js +1873 -1751
  102. package/dist/solid.js.map +1 -1
  103. package/package.json +14 -18
  104. package/src/XFormDOM.ts +81 -8
  105. package/src/body/BodyDefinition.ts +38 -23
  106. package/src/body/RepeatElementDefinition.ts +70 -0
  107. package/src/body/appearance/inputAppearanceParser.ts +39 -0
  108. package/src/body/appearance/selectAppearanceParser.ts +38 -0
  109. package/src/body/appearance/structureElementAppearanceParser.ts +7 -0
  110. package/src/body/control/ControlDefinition.ts +4 -0
  111. package/src/body/control/InputDefinition.ts +13 -0
  112. package/src/body/control/select/SelectDefinition.ts +14 -5
  113. package/src/body/group/BaseGroupDefinition.ts +11 -49
  114. package/src/body/text/LabelDefinition.ts +15 -1
  115. package/src/body/text/TextElementDefinition.ts +5 -5
  116. package/src/client/BaseNode.ts +9 -1
  117. package/src/client/GroupNode.ts +6 -2
  118. package/src/client/NodeAppearances.ts +22 -0
  119. package/src/client/RepeatInstanceNode.ts +4 -0
  120. package/src/client/RepeatRangeNode.ts +6 -2
  121. package/src/client/RootNode.ts +22 -0
  122. package/src/client/SelectNode.ts +4 -0
  123. package/src/client/StringNode.ts +4 -0
  124. package/src/client/SubtreeNode.ts +1 -0
  125. package/src/instance/Group.ts +14 -9
  126. package/src/instance/RepeatInstance.ts +59 -15
  127. package/src/instance/RepeatRange.ts +133 -15
  128. package/src/instance/Root.ts +20 -64
  129. package/src/instance/SelectField.ts +7 -7
  130. package/src/instance/StringField.ts +8 -7
  131. package/src/instance/Subtree.ts +10 -7
  132. package/src/instance/abstract/DescendantNode.ts +45 -43
  133. package/src/instance/abstract/InstanceNode.ts +69 -86
  134. package/src/instance/children.ts +17 -7
  135. package/src/instance/index.ts +1 -1
  136. package/src/instance/internal-api/EvaluationContext.ts +5 -6
  137. package/src/instance/internal-api/ValueContext.ts +2 -2
  138. package/src/lib/TokenListParser.ts +156 -0
  139. package/src/lib/dom/query.ts +13 -0
  140. package/src/lib/reactivity/createChildrenState.ts +51 -6
  141. package/src/lib/reactivity/createComputedExpression.ts +1 -1
  142. package/src/lib/reactivity/createSelectItems.ts +4 -6
  143. package/src/lib/reactivity/createValueState.ts +6 -6
  144. package/src/lib/reactivity/materializeCurrentStateChildren.ts +3 -1
  145. package/src/model/DescendentNodeDefinition.ts +1 -2
  146. package/src/model/ModelDefinition.ts +1 -1
  147. package/src/model/NodeDefinition.ts +12 -12
  148. package/src/model/RepeatInstanceDefinition.ts +8 -13
  149. package/src/model/{RepeatSequenceDefinition.ts → RepeatRangeDefinition.ts} +6 -6
  150. package/src/model/RepeatTemplateDefinition.ts +10 -15
  151. package/src/model/RootDefinition.ts +6 -12
  152. package/src/model/SubtreeDefinition.ts +3 -3
  153. package/src/model/ValueNodeDefinition.ts +2 -3
  154. package/dist/body/RepeatDefinition.d.ts +0 -15
  155. package/dist/body/group/RepeatGroupDefinition.d.ts +0 -12
  156. package/src/body/RepeatDefinition.ts +0 -54
  157. package/src/body/group/RepeatGroupDefinition.ts +0 -91
@@ -1,11 +1,12 @@
1
1
  import { UnreachableError } from '@getodk/common/lib/error/UnreachableError.ts';
2
- import { SelectDefinition } from '../body/control/select/SelectDefinition.ts';
3
2
  import type { GroupDefinition } from '../client/GroupNode.ts';
4
3
  import type { SubtreeDefinition } from '../client/SubtreeNode.ts';
5
4
  import type { SubtreeDefinition as ModelSubtreeDefinition } from '../model/SubtreeDefinition.ts';
6
5
  import { Group } from './Group.ts';
7
6
  import { RepeatRange } from './RepeatRange.ts';
8
- import { SelectField, type SelectFieldDefinition } from './SelectField.ts';
7
+ import type { SelectFieldDefinition } from './SelectField.ts';
8
+ import { SelectField } from './SelectField.ts';
9
+ import type { StringFieldDefinition } from './StringField.ts';
9
10
  import { StringField } from './StringField.ts';
10
11
  import { Subtree } from './Subtree.ts';
11
12
  import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
@@ -32,16 +33,25 @@ export const buildChildren = (parent: GeneralParentNode): GeneralChildNode[] =>
32
33
  return new Group(parent, child as GroupDefinition);
33
34
  }
34
35
 
35
- case 'repeat-sequence': {
36
+ case 'repeat-range': {
36
37
  return new RepeatRange(parent, child);
37
38
  }
38
39
 
39
40
  case 'value-node': {
40
- if (child.bodyElement instanceof SelectDefinition) {
41
- return new SelectField(parent, child as SelectFieldDefinition);
41
+ // TODO: this sort of awkwardness might go away if we embrace a
42
+ // proliferation of node types throughout.
43
+ switch (child.bodyElement?.type) {
44
+ case 'select':
45
+ case 'select1':
46
+ return new SelectField(parent, child as SelectFieldDefinition);
47
+
48
+ case 'input':
49
+ case undefined:
50
+ return new StringField(parent, child as StringFieldDefinition);
51
+
52
+ default:
53
+ throw new UnreachableError(child.bodyElement);
42
54
  }
43
-
44
- return new StringField(parent, child);
45
55
  }
46
56
 
47
57
  default: {
@@ -31,7 +31,7 @@ export const initializeForm = async (
31
31
  const sourceXML = await retrieveSourceXMLResource(input, engineConfig);
32
32
  const form = new XFormDefinition(sourceXML);
33
33
 
34
- return Root.initialize(form.xformDOM, form.model.root, engineConfig);
34
+ return new Root(form.xformDOM, form.model.root, engineConfig);
35
35
  };
36
36
 
37
37
  initializeForm satisfies InitializeForm;
@@ -1,4 +1,5 @@
1
1
  import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
+ import type { Accessor } from 'solid-js';
2
3
  import type { DependentExpression } from '../../expression/DependentExpression.ts';
3
4
  import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
4
5
  import type { SubscribableDependency } from './SubscribableDependency.ts';
@@ -27,15 +28,13 @@ export interface EvaluationContext {
27
28
  * Produces the current absolute reference to the {@link contextNode}, where
28
29
  * the absolute `/` resolves to the active form state's primary instance root.
29
30
  */
30
- get contextReference(): string;
31
+ readonly contextReference: Accessor<string>;
31
32
 
32
33
  readonly contextNode: Node;
33
34
 
34
35
  /**
35
- * Resolves a nodeset reference, possibly relative to the
36
- * {@link EvaluationContext.contextNode}.
36
+ * Resolves nodes corresponding to the provided node-set reference, possibly
37
+ * relative to the {@link EvaluationContext.contextNode}.
37
38
  */
38
- readonly getSubscribableDependencyByReference: (
39
- reference: string
40
- ) => SubscribableDependency | null;
39
+ getSubscribableDependenciesByReference(reference: string): readonly SubscribableDependency[];
41
40
  }
@@ -19,8 +19,8 @@ export interface ValueContext<RuntimeValue> extends EvaluationContext {
19
19
  readonly definition: ValueContextDefinition;
20
20
  readonly contextNode: Element;
21
21
 
22
- get isReadonly(): boolean;
23
- get isRelevant(): boolean;
22
+ isReadonly(): boolean;
23
+ isRelevant(): boolean;
24
24
 
25
25
  readonly encodeValue: (this: unknown, runtimeValue: RuntimeValue) => InstanceValue;
26
26
  readonly decodeValue: (this: unknown, instanceValue: InstanceValue) => RuntimeValue;
@@ -0,0 +1,156 @@
1
+ import { xmlXPathWhitespaceSeparatedList } from '@getodk/common/lib/string/whitespace.ts';
2
+ import type { PartiallyKnownString } from '@getodk/common/types/string/PartiallyKnownString.ts';
3
+
4
+ type SymbolIterator = typeof Symbol.iterator;
5
+
6
+ type TokenListKey<CanonicalToken extends string> =
7
+ | PartiallyKnownString<CanonicalToken>
8
+ | SymbolIterator;
9
+
10
+ type TokenListIterator<CanonicalToken extends string> = IterableIterator<
11
+ PartiallyKnownString<CanonicalToken>
12
+ >;
13
+
14
+ /**
15
+ * @see {@link TokenListParser}
16
+ */
17
+ // prettier-ignore
18
+ export type TokenList<CanonicalToken extends string = string> = {
19
+ readonly [Key in TokenListKey<CanonicalToken>]:
20
+ Key extends SymbolIterator
21
+ ? () => TokenListIterator<CanonicalToken>
22
+ : boolean;
23
+ };
24
+
25
+ interface TokenListAlias<CanonicalToken extends string> {
26
+ readonly fromAlias: string;
27
+ readonly toCanonical: CanonicalToken;
28
+ }
29
+
30
+ type TokenAliases<CanonicalToken extends string> = ReadonlyArray<TokenListAlias<CanonicalToken>>;
31
+
32
+ interface TokenListParserOptions<CanonicalToken extends string> {
33
+ readonly aliases?: TokenAliases<CanonicalToken>;
34
+ }
35
+
36
+ type TokenListAttributeName = PartiallyKnownString<'appearance' | 'class'>;
37
+
38
+ /**
39
+ * Intended primarily for use in parsing these features:
40
+ *
41
+ * - {@link https://getodk.github.io/xforms-spec/#appearances | appearances} ({@link https://xlsform.org/en/#appearance | additional documentation})
42
+ *
43
+ * - {@link https://getodk.github.io/xforms-spec/#body-attributes | body `class` attribute}
44
+ *
45
+ * This class is named as a reference to {@link DOMTokenList}
46
+ * ({@link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList | MDN}),
47
+ * with these similarities:
48
+ *
49
+ * - Represents a value whose serialization is a space-separated list.
50
+ * - Each member ("token") is a string (without whitespace).
51
+ * - Provides set-like semantics to determine presence of members.
52
+ * - Provides ordering semantics as determined by the serialization.
53
+ *
54
+ * This class differs from that prior art in that:
55
+ *
56
+ * - It provides a notion of "canonical" members. This is _mostly_ (but not
57
+ * entirely) intended as a convenience to client developers, automatically
58
+ * populating canonical/known tokens for a given parser type in
59
+ * editor-provided autocomplete, etc. **Importantly**, non-canonical tokens
60
+ * _are not ignored_ in either the {@link TokenList}'s types or runtime
61
+ * values.
62
+ *
63
+ * - Provided "canonical" members may also be used to specify
64
+ * {@link TokenListParserOptions.aliases | optional aliases}. (Example: when
65
+ * parsing "appearances", an alias might map an older deprecated appearance to
66
+ * a newer canonical equivalent.) **Importantly**, when a token matches an
67
+ * alias, both that alias _and the token as-specified_ will be present in the
68
+ * produced {@link TokenList}.
69
+ *
70
+ * - As a parser, it is intended to be read-only. The serialized format which it
71
+ * parses is _generally_ the source of truth (excepting e.g. aliases).
72
+ * Notably, and as mentioned above, ordering is determined by:
73
+ *
74
+ * - Iterating each member, as provided by the serialized representation
75
+ *
76
+ * - If that member corresponds to an alias, that alias is yielded first
77
+ *
78
+ * - Regardless of whether the member corresponds to an alias, the member is
79
+ * yielded as-specified
80
+ *
81
+ * - A parsed {@link TokenList} is intended to maximize convenience of read-only
82
+ * access. Despite many _conceptual similarities_, most of the
83
+ * {@link DOMTokenList} **interface** is eschewed in favor of two (mutually
84
+ * equivalent) access mechanisms:
85
+ *
86
+ * - `Iterable<Token>`, with the ordering semantics described above
87
+ * - `Record<Token, boolean>`
88
+ *
89
+ * \* This may change, as we refine requirements. In the future, we may
90
+ * introduce a notion of mutually exclusive tokens (e.g. "appearances" which
91
+ * cannot be used together), which may in turn utilize instance-defined ordering
92
+ * as part of that mechanism.
93
+ */
94
+ export class TokenListParser<
95
+ CanonicalToken extends string,
96
+ // Note: this is a separate type parameter so that specifying an alias does
97
+ // not cause it to be mistakenly inferred as a `CanonicalToken` which was
98
+ // not otherwise specified.
99
+ TokenAlias extends CanonicalToken = CanonicalToken,
100
+ > {
101
+ private readonly aliases: ReadonlyMap<string, CanonicalToken>;
102
+
103
+ constructor(
104
+ readonly canonicalTokens: readonly CanonicalToken[],
105
+ options?: TokenListParserOptions<TokenAlias>
106
+ ) {
107
+ this.aliases = new Map(
108
+ (options?.aliases ?? []).map(({ fromAlias, toCanonical }) => {
109
+ return [fromAlias, toCanonical];
110
+ })
111
+ );
112
+ }
113
+
114
+ parseFrom(element: Element, attributeName: TokenListAttributeName): TokenList<CanonicalToken> {
115
+ const serialized = element.getAttribute(attributeName) ?? '';
116
+ const specified = xmlXPathWhitespaceSeparatedList(serialized, {
117
+ ignoreEmpty: true,
118
+ });
119
+ const { aliases } = this;
120
+ const resolved = specified.flatMap((token) => {
121
+ const alias = aliases.get(token);
122
+
123
+ if (alias == null) {
124
+ return token;
125
+ }
126
+
127
+ return [alias, token];
128
+ });
129
+ const tokens = new Set(resolved);
130
+
131
+ return new Proxy(
132
+ {
133
+ [Symbol.iterator]() {
134
+ return resolved.values();
135
+ },
136
+ } satisfies TokenList<string> as TokenList<CanonicalToken>,
137
+ {
138
+ get(target, property, receiver) {
139
+ if (typeof property === 'symbol') {
140
+ return Reflect.get(target, property, receiver);
141
+ }
142
+
143
+ return tokens.has(property);
144
+ },
145
+
146
+ set() {
147
+ return false;
148
+ },
149
+ }
150
+ );
151
+ }
152
+ }
153
+
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ export type ParsedTokenList<Parser extends TokenListParser<any>> =
156
+ Parser extends TokenListParser<infer CanonicalToken> ? TokenList<CanonicalToken> : never;
@@ -9,6 +9,10 @@ const hintLookup = new ScopedElementLookup(':scope > hint', 'hint');
9
9
  const itemLookup = new ScopedElementLookup(':scope > item', 'item');
10
10
  const itemsetLookup = new ScopedElementLookup(':scope > itemset[nodeset]', 'itemset[nodeset]');
11
11
  const labelLookup = new ScopedElementLookup(':scope > label', 'label');
12
+ const repeatGroupLabelLookup = new ScopedElementLookup(
13
+ ':scope > label[form-definition-source="repeat-group"]',
14
+ 'label[form-definition-source="repeat-group"]'
15
+ );
12
16
  const repeatLookup = new ScopedElementLookup(':scope > repeat[nodeset]', 'repeat[nodeset]');
13
17
  const valueLookup = new ScopedElementLookup(':scope > value', 'value');
14
18
 
@@ -20,6 +24,11 @@ export interface ItemsetElement extends KnownAttributeLocalNamedElement<'itemset
20
24
 
21
25
  export interface LabelElement extends LocalNamedElement<'label'> {}
22
26
 
27
+ export interface RepeatGroupLabelElement extends LabelElement {
28
+ getAttribute(name: 'form-definition-source'): 'repeat-group';
29
+ getAttribute(name: string): string;
30
+ }
31
+
23
32
  export interface RepeatElement extends KnownAttributeLocalNamedElement<'repeat', 'nodeset'> {}
24
33
 
25
34
  export interface ValueElement extends LocalNamedElement<'value'> {}
@@ -40,6 +49,10 @@ export const getLabelElement = (parent: Element): LabelElement | null => {
40
49
  return labelLookup.getElement<LabelElement>(parent);
41
50
  };
42
51
 
52
+ export const getRepeatGroupLabelElement = (parent: Element): RepeatGroupLabelElement | null => {
53
+ return repeatGroupLabelLookup.getElement<RepeatGroupLabelElement>(parent);
54
+ };
55
+
43
56
  export const getRepeatElement = (parent: Element): RepeatElement | null => {
44
57
  return repeatLookup.getElement<RepeatElement>(parent);
45
58
  };
@@ -1,4 +1,5 @@
1
- import { createMemo, createSignal, type Accessor, type Setter, type Signal } from 'solid-js';
1
+ import type { Accessor, Setter, Signal } from 'solid-js';
2
+ import { createSignal } from 'solid-js';
2
3
  import type { OpaqueReactiveObjectFactory } from '../../index.ts';
3
4
  import type { AnyChildNode, AnyParentNode } from '../../instance/hierarchy.ts';
4
5
  import type { NodeID } from '../../instance/identity.ts';
@@ -44,11 +45,55 @@ export const createChildrenState = <Parent extends AnyParentNode, Child extends
44
45
  parent: Parent
45
46
  ): ChildrenState<Child> => {
46
47
  return parent.scope.runTask(() => {
47
- const children = createSignal<readonly Child[]>([]);
48
- const [getChildren, setChildren] = children;
49
- const childIds = createMemo((): readonly NodeID[] => {
50
- return getChildren().map((child) => child.nodeId);
51
- });
48
+ const baseState = createSignal<readonly Child[]>([]);
49
+ const [getChildren, baseSetChildren] = baseState;
50
+
51
+ /**
52
+ * Note: this is clearly derived state. It would be obvious to use
53
+ * `createMemo`, which is exactly what a previous iteration did. This caused
54
+ * mysterious issues when clients:
55
+ *
56
+ * - Also used Solid-based reactivity
57
+ * - Accessed node children state within their own `createMemo` calls
58
+ *
59
+ * It's quite likely that there's a more robust and general solution, which
60
+ * may be applicable if we also generalize this approach to
61
+ * encode/materialize shared structured state (e.g. it may be applicable for
62
+ * select values, form language, probably much more in coming features).
63
+ *
64
+ * In the meantime, while this approach is marginally more complex, it is
65
+ * likely also slightly more efficient. We can revisit the tradeoff if/when
66
+ * those hypothetical generalizations become a priority.
67
+ */
68
+ const ids = createSignal<readonly NodeID[]>([]);
69
+ const [childIds, setChildIds] = ids;
70
+
71
+ type ChildrenSetterCallback = (prev: readonly Child[]) => readonly Child[];
72
+ type ChildrenOrSetterCallback = ChildrenSetterCallback | readonly Child[];
73
+
74
+ const setChildren: Setter<readonly Child[]> = (
75
+ valueOrSetterCallback: ChildrenOrSetterCallback
76
+ ) => {
77
+ let setterCallback: ChildrenSetterCallback;
78
+
79
+ if (typeof valueOrSetterCallback === 'function') {
80
+ setterCallback = valueOrSetterCallback;
81
+ } else {
82
+ setterCallback = (_) => valueOrSetterCallback;
83
+ }
84
+
85
+ return baseSetChildren((prev) => {
86
+ const result = setterCallback(prev);
87
+
88
+ setChildIds(() => {
89
+ return result.map((child) => child.nodeId);
90
+ });
91
+
92
+ return result;
93
+ });
94
+ };
95
+
96
+ const children: Signal<readonly Child[]> = [getChildren, setChildren];
52
97
 
53
98
  return {
54
99
  children,
@@ -87,7 +87,7 @@ export const createComputedExpression = <Type extends DependentExpressionResultT
87
87
 
88
88
  const getReferencedDependencies = createMemo(() => {
89
89
  return dependencyReferences.flatMap((reference) => {
90
- return context.getSubscribableDependencyByReference(reference) ?? [];
90
+ return context.getSubscribableDependenciesByReference(reference) ?? [];
91
91
  });
92
92
  });
93
93
 
@@ -52,10 +52,7 @@ class ItemsetItemEvaluationContext implements EvaluationContext {
52
52
  readonly scope: ReactiveScope;
53
53
  readonly evaluator: XFormsXPathEvaluator;
54
54
  readonly root: EvaluationContextRoot;
55
-
56
- get contextReference(): string {
57
- return this.selectField.contextReference;
58
- }
55
+ readonly contextReference: Accessor<string>;
59
56
 
60
57
  constructor(
61
58
  private readonly selectField: SelectField,
@@ -64,10 +61,11 @@ class ItemsetItemEvaluationContext implements EvaluationContext {
64
61
  this.scope = selectField.scope;
65
62
  this.evaluator = selectField.evaluator;
66
63
  this.root = selectField.root;
64
+ this.contextReference = selectField.contextReference;
67
65
  }
68
66
 
69
- getSubscribableDependencyByReference(reference: string): SubscribableDependency | null {
70
- return this.selectField.getSubscribableDependencyByReference(reference);
67
+ getSubscribableDependenciesByReference(reference: string): readonly SubscribableDependency[] {
68
+ return this.selectField.getSubscribableDependenciesByReference(reference);
71
69
  }
72
70
  }
73
71
 
@@ -72,7 +72,7 @@ const createPrimaryInstanceValueState = <RuntimeValue>(
72
72
 
73
73
  const persistedValueState = createSignal<PersistedValueState>(
74
74
  {
75
- isRelevant: context.isRelevant,
75
+ isRelevant: context.isRelevant(),
76
76
  value: initialValue,
77
77
  },
78
78
  {
@@ -91,7 +91,7 @@ const createPrimaryInstanceValueState = <RuntimeValue>(
91
91
  const [persistedValue, setValueForPersistence] = persistedValueState;
92
92
 
93
93
  createComputed(() => {
94
- const isRelevant = context.isRelevant;
94
+ const isRelevant = context.isRelevant();
95
95
 
96
96
  setValueForPersistence((persisted) => {
97
97
  return {
@@ -122,7 +122,7 @@ const createPrimaryInstanceValueState = <RuntimeValue>(
122
122
  const setPrimaryInstanceValue: SimpleAtomicStateSetter<string> = (value) => {
123
123
  // TODO: Check (error?) for non-relevant value change?
124
124
  const persisted = setValueForPersistence({
125
- isRelevant: context.isRelevant,
125
+ isRelevant: context.isRelevant(),
126
126
  value,
127
127
  });
128
128
 
@@ -189,8 +189,8 @@ const guardDownstreamReadonlyWrites = <RuntimeValue>(
189
189
  const [getValue, baseSetValue] = baseState;
190
190
 
191
191
  const setValue: SimpleAtomicStateSetter<RuntimeValue> = (value) => {
192
- if (context.isReadonly) {
193
- const reference = untrack(() => context.contextReference);
192
+ if (context.isReadonly()) {
193
+ const reference = untrack(() => context.contextReference());
194
194
 
195
195
  throw new Error(`Cannot write to readonly field: ${reference}`);
196
196
  }
@@ -215,7 +215,7 @@ const createCalculation = <RuntimeValue>(
215
215
  const calculate = createComputedExpression(context, calculateDefinition);
216
216
 
217
217
  createComputed(() => {
218
- if (context.isRelevant) {
218
+ if (context.isRelevant()) {
219
219
  const calculated = calculate();
220
220
  const value = context.decodeValue(calculated);
221
221
 
@@ -3,6 +3,7 @@ import type { NodeID } from '../../instance/identity.ts';
3
3
  import type { ChildrenState, createChildrenState } from './createChildrenState.ts';
4
4
  import type { ClientState } from './node-state/createClientState.ts';
5
5
  import type { CurrentState } from './node-state/createCurrentState.ts';
6
+ import type { ReactiveScope } from './scope.ts';
6
7
 
7
8
  interface InconsistentChildrenStateDetails {
8
9
  readonly missingIds: readonly NodeID[];
@@ -96,6 +97,7 @@ export const materializeCurrentStateChildren = <
96
97
  Child extends AnyChildNode,
97
98
  ParentState extends EncodedParentState,
98
99
  >(
100
+ scope: ReactiveScope,
99
101
  currentState: ParentState,
100
102
  childrenState: ChildrenState<Child>
101
103
  ): MaterializedChildren<ParentState, Child> => {
@@ -105,7 +107,7 @@ export const materializeCurrentStateChildren = <
105
107
  return new Proxy(proxyTarget, {
106
108
  get(_, key) {
107
109
  if (key === 'children') {
108
- const expectedChildIDs = currentState.children;
110
+ const expectedChildIDs = scope.runTask(() => currentState.children);
109
111
  const children = childrenState.getChildren();
110
112
 
111
113
  if (import.meta.env.DEV) {
@@ -1,5 +1,4 @@
1
1
  import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
2
- import type { RepeatDefinition } from '../body/RepeatDefinition.ts';
3
2
  import type { BindDefinition } from './BindDefinition.ts';
4
3
  import type {
5
4
  ModelNode,
@@ -14,7 +13,7 @@ import type { RootDefinition } from './RootDefinition.ts';
14
13
 
15
14
  export type DescendentNodeType = Exclude<NodeDefinitionType, 'root'>;
16
15
 
17
- type DescendentNodeBodyElement = AnyBodyElementDefinition | RepeatDefinition;
16
+ type DescendentNodeBodyElement = AnyBodyElementDefinition;
18
17
 
19
18
  export abstract class DescendentNodeDefinition<
20
19
  Type extends DescendentNodeType,
@@ -8,7 +8,7 @@ export class ModelDefinition {
8
8
 
9
9
  constructor(readonly form: XFormDefinition) {
10
10
  this.binds = ModelBindMap.fromModel(this);
11
- this.root = new RootDefinition(form, this);
11
+ this.root = new RootDefinition(form, this, form.body.classes);
12
12
  }
13
13
 
14
14
  toJSON() {
@@ -1,8 +1,8 @@
1
1
  import type { AnyBodyElementDefinition } from '../body/BodyDefinition.ts';
2
- import type { RepeatDefinition } from '../body/RepeatDefinition.ts';
2
+ import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
3
3
  import type { BindDefinition } from './BindDefinition.ts';
4
4
  import type { RepeatInstanceDefinition } from './RepeatInstanceDefinition.ts';
5
- import type { RepeatSequenceDefinition } from './RepeatSequenceDefinition.ts';
5
+ import type { RepeatRangeDefinition } from './RepeatRangeDefinition.ts';
6
6
  import type { RepeatTemplateDefinition } from './RepeatTemplateDefinition.ts';
7
7
  import type { RootDefinition } from './RootDefinition.ts';
8
8
  import type { SubtreeDefinition } from './SubtreeDefinition.ts';
@@ -17,13 +17,13 @@ import type { ValueNodeDefinition } from './ValueNodeDefinition.ts';
17
17
  export type RootNodeType = 'root';
18
18
 
19
19
  /**
20
- * Corresponds to a sequence of model/entry DOM subtrees which in turn
20
+ * Corresponds to a range/sequence of model/entry DOM subtrees which in turn
21
21
  * corresponds to a <repeat> in the form body definition.
22
22
  */
23
- export type RepeatSequenceType = 'repeat-sequence';
23
+ export type RepeatRangeType = 'repeat-range';
24
24
 
25
25
  /**
26
- * Corresponds to a template definition for a repeat sequence, which either has
26
+ * Corresponds to a template definition for a repeat range, which either has
27
27
  * an explicit `jr:template=""` attribute in the form definition or is inferred
28
28
  * as a template from the form's first element matched by a <repeat nodeset>.
29
29
  */
@@ -32,7 +32,7 @@ export type RepeatTemplateType = 'repeat-template';
32
32
  /**
33
33
  * Corresponds to a single instance of a model/entry DOM subtree which
34
34
  * in turn corresponds to a <repeat> in the form body definition, and a
35
- * 'repeat-sequence' definition.
35
+ * 'repeat-range' definition.
36
36
  */
37
37
  export type RepeatInstanceType = 'repeat-instance';
38
38
 
@@ -55,7 +55,7 @@ export type ValueNodeType = 'value-node';
55
55
  export type NodeDefinitionType =
56
56
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
57
57
  | RootNodeType
58
- | RepeatSequenceType
58
+ | RepeatRangeType
59
59
  | RepeatTemplateType
60
60
  | RepeatInstanceType
61
61
  | SubtreeNodeType
@@ -71,7 +71,7 @@ export type ParentNodeDefinition =
71
71
 
72
72
  // prettier-ignore
73
73
  export type ChildNodeDefinition =
74
- | RepeatSequenceDefinition
74
+ | RepeatRangeDefinition
75
75
  | SubtreeDefinition
76
76
  | ValueNodeDefinition;
77
77
 
@@ -91,7 +91,7 @@ export type NodeChildren<Type extends NodeDefinitionType> =
91
91
 
92
92
  // prettier-ignore
93
93
  export type NodeInstances<Type extends NodeDefinitionType> =
94
- Type extends 'repeat-sequence'
94
+ Type extends 'repeat-range'
95
95
  ? readonly RepeatInstanceDefinition[]
96
96
  : null;
97
97
 
@@ -104,7 +104,7 @@ export type NodeParent<Type extends NodeDefinitionType> =
104
104
  // TODO: value-node may be Attr
105
105
  // prettier-ignore
106
106
  export type ModelNode<Type extends NodeDefinitionType> =
107
- Type extends 'repeat-sequence'
107
+ Type extends 'repeat-range'
108
108
  ? null
109
109
  : Element;
110
110
 
@@ -120,7 +120,7 @@ export interface NodeDefinition<Type extends NodeDefinitionType> {
120
120
  readonly bind: BindDefinition;
121
121
  readonly nodeset: string;
122
122
  readonly nodeName: string;
123
- readonly bodyElement: AnyBodyElementDefinition | RepeatDefinition | null;
123
+ readonly bodyElement: AnyBodyElementDefinition | RepeatElementDefinition | null;
124
124
 
125
125
  readonly isTranslated: boolean;
126
126
  readonly dependencyExpressions: ReadonlySet<string>;
@@ -137,7 +137,7 @@ export interface NodeDefinition<Type extends NodeDefinitionType> {
137
137
  export type AnyNodeDefinition =
138
138
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
139
139
  | RootDefinition
140
- | RepeatSequenceDefinition
140
+ | RepeatRangeDefinition
141
141
  | RepeatTemplateDefinition
142
142
  | RepeatInstanceDefinition
143
143
  | SubtreeDefinition
@@ -1,10 +1,10 @@
1
- import { RepeatDefinition } from '../body/RepeatDefinition.ts';
1
+ import { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
2
2
  import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
3
3
  import type { ChildNodeDefinition, NodeDefinition } from './NodeDefinition.ts';
4
- import type { RepeatSequenceDefinition } from './RepeatSequenceDefinition.ts';
4
+ import type { RepeatRangeDefinition } from './RepeatRangeDefinition.ts';
5
5
 
6
6
  export class RepeatInstanceDefinition
7
- extends DescendentNodeDefinition<'repeat-instance', RepeatDefinition>
7
+ extends DescendentNodeDefinition<'repeat-instance', RepeatElementDefinition>
8
8
  implements NodeDefinition<'repeat-instance'>
9
9
  {
10
10
  readonly type = 'repeat-instance';
@@ -15,24 +15,19 @@ export class RepeatInstanceDefinition
15
15
  readonly defaultValue = null;
16
16
 
17
17
  constructor(
18
- protected readonly sequence: RepeatSequenceDefinition,
18
+ range: RepeatRangeDefinition,
19
19
  readonly node: Element
20
20
  ) {
21
- const {
22
- bind,
23
- bodyElement: repeatGroupBodyElement,
24
- parent: repeatSequenceParent,
25
- root,
26
- } = sequence;
21
+ const { bind, bodyElement, parent, root } = range;
27
22
 
28
- super(repeatSequenceParent, bind, repeatGroupBodyElement.repeat);
23
+ super(parent, bind, bodyElement);
29
24
 
30
- this.nodeName = sequence.nodeName;
25
+ this.nodeName = range.nodeName;
31
26
  this.children = root.buildSubtree(this);
32
27
  }
33
28
 
34
29
  toJSON() {
35
- const { bind, bodyElement, parent, root, sequence, ...rest } = this;
30
+ const { bind, bodyElement, parent, root, ...rest } = this;
36
31
 
37
32
  return rest;
38
33
  }
@@ -1,13 +1,13 @@
1
- import { RepeatGroupDefinition } from '../body/group/RepeatGroupDefinition.ts';
1
+ import type { RepeatElementDefinition } from '../body/RepeatElementDefinition.ts';
2
2
  import type { BindDefinition } from './BindDefinition.ts';
3
3
  import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
4
4
  import type { NodeDefinition, ParentNodeDefinition } from './NodeDefinition.ts';
5
5
  import { RepeatInstanceDefinition } from './RepeatInstanceDefinition.ts';
6
6
  import { RepeatTemplateDefinition } from './RepeatTemplateDefinition.ts';
7
7
 
8
- export class RepeatSequenceDefinition
9
- extends DescendentNodeDefinition<'repeat-sequence', RepeatGroupDefinition>
10
- implements NodeDefinition<'repeat-sequence'>
8
+ export class RepeatRangeDefinition
9
+ extends DescendentNodeDefinition<'repeat-range', RepeatElementDefinition>
10
+ implements NodeDefinition<'repeat-range'>
11
11
  {
12
12
  // TODO: if an implicit template is derived from an instance in a form
13
13
  // definition, should its default values (if any) be cleared? Probably!
@@ -19,7 +19,7 @@ export class RepeatSequenceDefinition
19
19
  return templateElement.cloneNode(true) as Element;
20
20
  }
21
21
 
22
- readonly type = 'repeat-sequence';
22
+ readonly type = 'repeat-range';
23
23
 
24
24
  readonly template: RepeatTemplateDefinition;
25
25
  readonly children = null;
@@ -32,7 +32,7 @@ export class RepeatSequenceDefinition
32
32
  constructor(
33
33
  parent: ParentNodeDefinition,
34
34
  bind: BindDefinition,
35
- bodyElement: RepeatGroupDefinition,
35
+ bodyElement: RepeatElementDefinition,
36
36
  modelNodes: readonly [Element, ...Element[]]
37
37
  ) {
38
38
  super(parent, bind, bodyElement);