@getodk/xforms-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +44 -0
  2. package/dist/.vite/manifest.json +7 -0
  3. package/dist/XFormDOM.d.ts +31 -0
  4. package/dist/XFormDataType.d.ts +26 -0
  5. package/dist/XFormDefinition.d.ts +14 -0
  6. package/dist/body/BodyDefinition.d.ts +52 -0
  7. package/dist/body/BodyElementDefinition.d.ts +32 -0
  8. package/dist/body/RepeatDefinition.d.ts +15 -0
  9. package/dist/body/UnsupportedBodyElementDefinition.d.ts +10 -0
  10. package/dist/body/control/ControlDefinition.d.ts +16 -0
  11. package/dist/body/control/InputDefinition.d.ts +5 -0
  12. package/dist/body/control/select/ItemDefinition.d.ts +13 -0
  13. package/dist/body/control/select/ItemsetDefinition.d.ts +16 -0
  14. package/dist/body/control/select/ItemsetNodesetContext.d.ts +11 -0
  15. package/dist/body/control/select/ItemsetNodesetExpression.d.ts +5 -0
  16. package/dist/body/control/select/ItemsetValueExpression.d.ts +6 -0
  17. package/dist/body/control/select/SelectDefinition.d.ts +23 -0
  18. package/dist/body/group/BaseGroupDefinition.d.ts +46 -0
  19. package/dist/body/group/LogicalGroupDefinition.d.ts +6 -0
  20. package/dist/body/group/PresentationGroupDefinition.d.ts +11 -0
  21. package/dist/body/group/RepeatGroupDefinition.d.ts +12 -0
  22. package/dist/body/group/StructuralGroupDefinition.d.ts +6 -0
  23. package/dist/body/text/HintDefinition.d.ts +11 -0
  24. package/dist/body/text/LabelDefinition.d.ts +20 -0
  25. package/dist/body/text/TextElementDefinition.d.ts +32 -0
  26. package/dist/body/text/TextElementOutputPart.d.ts +12 -0
  27. package/dist/body/text/TextElementPart.d.ts +12 -0
  28. package/dist/body/text/TextElementReferencePart.d.ts +6 -0
  29. package/dist/body/text/TextElementStaticPart.d.ts +6 -0
  30. package/dist/client/BaseNode.d.ts +138 -0
  31. package/dist/client/EngineConfig.d.ts +78 -0
  32. package/dist/client/FormLanguage.d.ts +63 -0
  33. package/dist/client/GroupNode.d.ts +24 -0
  34. package/dist/client/OpaqueReactiveObjectFactory.d.ts +70 -0
  35. package/dist/client/RepeatInstanceNode.d.ts +28 -0
  36. package/dist/client/RepeatRangeNode.d.ts +94 -0
  37. package/dist/client/RootNode.d.ts +31 -0
  38. package/dist/client/SelectNode.d.ts +60 -0
  39. package/dist/client/StringNode.d.ts +41 -0
  40. package/dist/client/SubtreeNode.d.ts +52 -0
  41. package/dist/client/TextRange.d.ts +55 -0
  42. package/dist/client/hierarchy.d.ts +48 -0
  43. package/dist/client/index.d.ts +11 -0
  44. package/dist/client/node-types.d.ts +1 -0
  45. package/dist/expression/DependencyContext.d.ts +12 -0
  46. package/dist/expression/DependentExpression.d.ts +43 -0
  47. package/dist/index.d.ts +16 -0
  48. package/dist/index.js +37622 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/instance/Group.d.ts +31 -0
  51. package/dist/instance/RepeatInstance.d.ts +60 -0
  52. package/dist/instance/RepeatRange.d.ts +81 -0
  53. package/dist/instance/Root.d.ts +70 -0
  54. package/dist/instance/SelectField.d.ts +45 -0
  55. package/dist/instance/StringField.d.ts +39 -0
  56. package/dist/instance/Subtree.d.ts +30 -0
  57. package/dist/instance/abstract/DescendantNode.d.ts +76 -0
  58. package/dist/instance/abstract/InstanceNode.d.ts +107 -0
  59. package/dist/instance/children.d.ts +2 -0
  60. package/dist/instance/hierarchy.d.ts +12 -0
  61. package/dist/instance/identity.d.ts +7 -0
  62. package/dist/instance/index.d.ts +8 -0
  63. package/dist/instance/internal-api/EvaluationContext.d.ts +34 -0
  64. package/dist/instance/internal-api/InstanceConfig.d.ts +8 -0
  65. package/dist/instance/internal-api/SubscribableDependency.d.ts +59 -0
  66. package/dist/instance/internal-api/TranslationContext.d.ts +4 -0
  67. package/dist/instance/internal-api/ValueContext.d.ts +22 -0
  68. package/dist/instance/resource.d.ts +10 -0
  69. package/dist/instance/text/FormattedTextStub.d.ts +1 -0
  70. package/dist/instance/text/TextChunk.d.ts +11 -0
  71. package/dist/instance/text/TextRange.d.ts +10 -0
  72. package/dist/lib/dom/query.d.ts +20 -0
  73. package/dist/lib/reactivity/createChildrenState.d.ts +36 -0
  74. package/dist/lib/reactivity/createComputedExpression.d.ts +12 -0
  75. package/dist/lib/reactivity/createSelectItems.d.ts +16 -0
  76. package/dist/lib/reactivity/createValueState.d.ts +44 -0
  77. package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +18 -0
  78. package/dist/lib/reactivity/node-state/createClientState.d.ts +9 -0
  79. package/dist/lib/reactivity/node-state/createCurrentState.d.ts +6 -0
  80. package/dist/lib/reactivity/node-state/createEngineState.d.ts +5 -0
  81. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +22 -0
  82. package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +6 -0
  83. package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +139 -0
  84. package/dist/lib/reactivity/node-state/representations.d.ts +25 -0
  85. package/dist/lib/reactivity/scope.d.ts +23 -0
  86. package/dist/lib/reactivity/text/createFieldHint.d.ts +5 -0
  87. package/dist/lib/reactivity/text/createNodeLabel.d.ts +5 -0
  88. package/dist/lib/reactivity/text/createTextRange.d.ts +19 -0
  89. package/dist/lib/reactivity/types.d.ts +21 -0
  90. package/dist/lib/unique-id.d.ts +27 -0
  91. package/dist/lib/xpath/analysis.d.ts +22 -0
  92. package/dist/model/BindComputation.d.ts +30 -0
  93. package/dist/model/BindDefinition.d.ts +31 -0
  94. package/dist/model/BindElement.d.ts +6 -0
  95. package/dist/model/DescendentNodeDefinition.d.ts +25 -0
  96. package/dist/model/ModelBindMap.d.ts +15 -0
  97. package/dist/model/ModelDefinition.d.ts +10 -0
  98. package/dist/model/NodeDefinition.d.ts +74 -0
  99. package/dist/model/RepeatInstanceDefinition.d.ts +15 -0
  100. package/dist/model/RepeatSequenceDefinition.d.ts +19 -0
  101. package/dist/model/RepeatTemplateDefinition.d.ts +29 -0
  102. package/dist/model/RootDefinition.d.ts +24 -0
  103. package/dist/model/SubtreeDefinition.d.ts +14 -0
  104. package/dist/model/ValueNodeDefinition.d.ts +15 -0
  105. package/dist/solid.js +37273 -0
  106. package/dist/solid.js.map +1 -0
  107. package/package.json +87 -0
  108. package/src/XFormDOM.ts +224 -0
  109. package/src/XFormDataType.ts +64 -0
  110. package/src/XFormDefinition.ts +40 -0
  111. package/src/body/BodyDefinition.ts +202 -0
  112. package/src/body/BodyElementDefinition.ts +62 -0
  113. package/src/body/RepeatDefinition.ts +54 -0
  114. package/src/body/UnsupportedBodyElementDefinition.ts +17 -0
  115. package/src/body/control/ControlDefinition.ts +42 -0
  116. package/src/body/control/InputDefinition.ts +9 -0
  117. package/src/body/control/select/ItemDefinition.ts +31 -0
  118. package/src/body/control/select/ItemsetDefinition.ts +36 -0
  119. package/src/body/control/select/ItemsetNodesetContext.ts +26 -0
  120. package/src/body/control/select/ItemsetNodesetExpression.ts +8 -0
  121. package/src/body/control/select/ItemsetValueExpression.ts +11 -0
  122. package/src/body/control/select/SelectDefinition.ts +74 -0
  123. package/src/body/group/BaseGroupDefinition.ts +137 -0
  124. package/src/body/group/LogicalGroupDefinition.ts +11 -0
  125. package/src/body/group/PresentationGroupDefinition.ts +28 -0
  126. package/src/body/group/RepeatGroupDefinition.ts +91 -0
  127. package/src/body/group/StructuralGroupDefinition.ts +11 -0
  128. package/src/body/text/HintDefinition.ts +26 -0
  129. package/src/body/text/LabelDefinition.ts +54 -0
  130. package/src/body/text/TextElementDefinition.ts +97 -0
  131. package/src/body/text/TextElementOutputPart.ts +27 -0
  132. package/src/body/text/TextElementPart.ts +31 -0
  133. package/src/body/text/TextElementReferencePart.ts +21 -0
  134. package/src/body/text/TextElementStaticPart.ts +26 -0
  135. package/src/client/BaseNode.ts +180 -0
  136. package/src/client/EngineConfig.ts +83 -0
  137. package/src/client/FormLanguage.ts +77 -0
  138. package/src/client/GroupNode.ts +33 -0
  139. package/src/client/OpaqueReactiveObjectFactory.ts +100 -0
  140. package/src/client/README.md +39 -0
  141. package/src/client/RepeatInstanceNode.ts +41 -0
  142. package/src/client/RepeatRangeNode.ts +100 -0
  143. package/src/client/RootNode.ts +36 -0
  144. package/src/client/SelectNode.ts +69 -0
  145. package/src/client/StringNode.ts +46 -0
  146. package/src/client/SubtreeNode.ts +57 -0
  147. package/src/client/TextRange.ts +63 -0
  148. package/src/client/hierarchy.ts +63 -0
  149. package/src/client/index.ts +29 -0
  150. package/src/client/node-types.ts +10 -0
  151. package/src/expression/DependencyContext.ts +53 -0
  152. package/src/expression/DependentExpression.ts +102 -0
  153. package/src/index.ts +35 -0
  154. package/src/instance/Group.ts +82 -0
  155. package/src/instance/RepeatInstance.ts +164 -0
  156. package/src/instance/RepeatRange.ts +214 -0
  157. package/src/instance/Root.ts +264 -0
  158. package/src/instance/SelectField.ts +204 -0
  159. package/src/instance/StringField.ts +93 -0
  160. package/src/instance/Subtree.ts +79 -0
  161. package/src/instance/abstract/DescendantNode.ts +182 -0
  162. package/src/instance/abstract/InstanceNode.ts +257 -0
  163. package/src/instance/children.ts +52 -0
  164. package/src/instance/hierarchy.ts +54 -0
  165. package/src/instance/identity.ts +11 -0
  166. package/src/instance/index.ts +37 -0
  167. package/src/instance/internal-api/EvaluationContext.ts +41 -0
  168. package/src/instance/internal-api/InstanceConfig.ts +9 -0
  169. package/src/instance/internal-api/SubscribableDependency.ts +61 -0
  170. package/src/instance/internal-api/TranslationContext.ts +5 -0
  171. package/src/instance/internal-api/ValueContext.ts +27 -0
  172. package/src/instance/resource.ts +75 -0
  173. package/src/instance/text/FormattedTextStub.ts +8 -0
  174. package/src/instance/text/TextChunk.ts +20 -0
  175. package/src/instance/text/TextRange.ts +23 -0
  176. package/src/lib/dom/query.ts +49 -0
  177. package/src/lib/reactivity/createChildrenState.ts +60 -0
  178. package/src/lib/reactivity/createComputedExpression.ts +114 -0
  179. package/src/lib/reactivity/createSelectItems.ts +163 -0
  180. package/src/lib/reactivity/createValueState.ts +258 -0
  181. package/src/lib/reactivity/materializeCurrentStateChildren.ts +121 -0
  182. package/src/lib/reactivity/node-state/createClientState.ts +51 -0
  183. package/src/lib/reactivity/node-state/createCurrentState.ts +27 -0
  184. package/src/lib/reactivity/node-state/createEngineState.ts +18 -0
  185. package/src/lib/reactivity/node-state/createSharedNodeState.ts +79 -0
  186. package/src/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.ts +85 -0
  187. package/src/lib/reactivity/node-state/createSpecifiedState.ts +229 -0
  188. package/src/lib/reactivity/node-state/representations.ts +64 -0
  189. package/src/lib/reactivity/scope.ts +106 -0
  190. package/src/lib/reactivity/text/createFieldHint.ts +16 -0
  191. package/src/lib/reactivity/text/createNodeLabel.ts +16 -0
  192. package/src/lib/reactivity/text/createTextRange.ts +155 -0
  193. package/src/lib/reactivity/types.ts +27 -0
  194. package/src/lib/unique-id.ts +34 -0
  195. package/src/lib/xpath/analysis.ts +241 -0
  196. package/src/model/BindComputation.ts +88 -0
  197. package/src/model/BindDefinition.ts +104 -0
  198. package/src/model/BindElement.ts +8 -0
  199. package/src/model/DescendentNodeDefinition.ts +56 -0
  200. package/src/model/ModelBindMap.ts +71 -0
  201. package/src/model/ModelDefinition.ts +19 -0
  202. package/src/model/NodeDefinition.ts +146 -0
  203. package/src/model/RepeatInstanceDefinition.ts +39 -0
  204. package/src/model/RepeatSequenceDefinition.ts +53 -0
  205. package/src/model/RepeatTemplateDefinition.ts +150 -0
  206. package/src/model/RootDefinition.ts +121 -0
  207. package/src/model/SubtreeDefinition.ts +50 -0
  208. package/src/model/ValueNodeDefinition.ts +39 -0
@@ -0,0 +1,54 @@
1
+ import { getLabelElement } from '../../lib/dom/query.ts';
2
+ import type { XFormDefinition } from '../../XFormDefinition.ts';
3
+ import type { AnyControlDefinition } from '../control/ControlDefinition.ts';
4
+ import type { ItemDefinition } from '../control/select/ItemDefinition.ts';
5
+ import type { ItemsetDefinition } from '../control/select/ItemsetDefinition.ts';
6
+ import type { BaseGroupDefinition } from '../group/BaseGroupDefinition.ts';
7
+ import type { TextElement, TextElementOwner } from './TextElementDefinition.ts';
8
+ import { TextElementDefinition } from './TextElementDefinition.ts';
9
+
10
+ export interface LabelElement extends TextElement {
11
+ readonly localName: 'label';
12
+ }
13
+
14
+ type StaticLabelContext = Exclude<TextElementOwner, ItemsetDefinition>;
15
+
16
+ export class LabelDefinition extends TextElementDefinition<'label'> {
17
+ protected static staticDefinition(
18
+ form: XFormDefinition,
19
+ definition: StaticLabelContext
20
+ ): LabelDefinition | null {
21
+ const labelElement = getLabelElement(definition.element);
22
+
23
+ if (labelElement == null) {
24
+ return null;
25
+ }
26
+
27
+ return new this(form, definition, labelElement);
28
+ }
29
+
30
+ static forControl(form: XFormDefinition, control: AnyControlDefinition): LabelDefinition | null {
31
+ return this.staticDefinition(form, control);
32
+ }
33
+
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ static forGroup(form: XFormDefinition, group: BaseGroupDefinition<any>): LabelDefinition | null {
36
+ return this.staticDefinition(form, group);
37
+ }
38
+
39
+ static forItem(form: XFormDefinition, item: ItemDefinition): LabelDefinition | null {
40
+ return this.staticDefinition(form, item);
41
+ }
42
+
43
+ static forItemset(form: XFormDefinition, itemset: ItemsetDefinition): LabelDefinition | null {
44
+ const labelElement = getLabelElement(itemset.element);
45
+
46
+ if (labelElement == null) {
47
+ return null;
48
+ }
49
+
50
+ return new this(form, itemset, labelElement);
51
+ }
52
+
53
+ readonly type = 'label';
54
+ }
@@ -0,0 +1,97 @@
1
+ import { isCommentNode, isElementNode, isTextNode } from '@getodk/common/lib/dom/predicates.ts';
2
+ import type { XFormDefinition } from '../../XFormDefinition.ts';
3
+ import { type AnyDependentExpression } from '../../expression/DependentExpression.ts';
4
+ import type { AnyGroupElementDefinition } from '../BodyDefinition.ts';
5
+ import { BodyElementDefinition } from '../BodyElementDefinition.ts';
6
+ import type { InputDefinition } from '../control/InputDefinition.ts';
7
+ import type { ItemDefinition } from '../control/select/ItemDefinition.ts';
8
+ import type { ItemsetDefinition } from '../control/select/ItemsetDefinition.ts';
9
+ import type { AnySelectDefinition } from '../control/select/SelectDefinition.ts';
10
+ import { TextElementOutputPart } from './TextElementOutputPart.ts';
11
+ import { TextElementReferencePart } from './TextElementReferencePart.ts';
12
+ import { TextElementStaticPart } from './TextElementStaticPart.ts';
13
+
14
+ export type TextElementType = 'hint' | 'label';
15
+
16
+ export interface TextElement extends Element {
17
+ readonly localName: TextElementType;
18
+ }
19
+
20
+ export type TextElementOwner =
21
+ | AnyGroupElementDefinition
22
+ | AnySelectDefinition
23
+ | InputDefinition
24
+ | ItemDefinition
25
+ | ItemsetDefinition;
26
+
27
+ export type TextElementChild = TextElementOutputPart | TextElementStaticPart;
28
+
29
+ export abstract class TextElementDefinition<
30
+ Type extends TextElementType,
31
+ > extends BodyElementDefinition<Type> {
32
+ readonly category = 'support';
33
+ abstract override readonly type: Type;
34
+
35
+ override readonly reference: string | null;
36
+ override readonly parentReference: string | null;
37
+
38
+ readonly referenceExpression: TextElementReferencePart | null;
39
+ readonly children: readonly TextElementChild[];
40
+
41
+ override get isTranslated(): boolean {
42
+ return this.owner.isTranslated;
43
+ }
44
+
45
+ override set isTranslated(value: true) {
46
+ this.owner.isTranslated = value;
47
+ }
48
+
49
+ protected constructor(
50
+ form: XFormDefinition,
51
+ readonly owner: TextElementOwner,
52
+ element: TextElement
53
+ ) {
54
+ super(form, owner, element);
55
+
56
+ this.reference = owner.reference;
57
+ this.parentReference = owner.parentReference;
58
+ this.referenceExpression = TextElementReferencePart.from(this, element);
59
+
60
+ const children = Array.from(element.childNodes).flatMap((node) => {
61
+ if (isTextNode(node)) {
62
+ return new TextElementStaticPart(this, node);
63
+ }
64
+
65
+ if (isElementNode(node)) {
66
+ const output = TextElementOutputPart.from(this, node);
67
+
68
+ if (output != null) {
69
+ return output;
70
+ }
71
+ }
72
+
73
+ if (isCommentNode(node)) {
74
+ return [];
75
+ }
76
+
77
+ // eslint-disable-next-line no-console
78
+ console.error('Unexpected text element child', node);
79
+
80
+ throw new Error(`Unexpected <${element.nodeName}> child element`);
81
+ });
82
+
83
+ this.children = children;
84
+ }
85
+
86
+ override registerDependentExpression(expression: AnyDependentExpression): void {
87
+ this.owner.registerDependentExpression(expression);
88
+ }
89
+
90
+ override toJSON(): object {
91
+ const { form, owner, parent, ...rest } = this;
92
+
93
+ return rest;
94
+ }
95
+ }
96
+
97
+ export type AnyTextElementDefinition = TextElementDefinition<TextElementType>;
@@ -0,0 +1,27 @@
1
+ import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
2
+ import { TextElementPart } from './TextElementPart.ts';
3
+
4
+ interface OutputElement extends Element {
5
+ readonly localName: 'output';
6
+
7
+ getAttribute(name: 'value'): string;
8
+ getAttribute(name: string): string | null;
9
+ }
10
+
11
+ const isOutputElement = (element: Element): element is OutputElement => {
12
+ return element.localName === 'output' && element.hasAttribute('value');
13
+ };
14
+
15
+ export class TextElementOutputPart extends TextElementPart<'output'> {
16
+ static from(context: AnyTextElementDefinition, element: Element): TextElementOutputPart | null {
17
+ if (isOutputElement(element)) {
18
+ return new this(context, element);
19
+ }
20
+
21
+ return null;
22
+ }
23
+
24
+ protected constructor(context: AnyTextElementDefinition, element: OutputElement) {
25
+ super('output', context, element.getAttribute('value'));
26
+ }
27
+ }
@@ -0,0 +1,31 @@
1
+ import { DependentExpression } from '../../expression/DependentExpression.ts';
2
+ import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
3
+ import type { TextElementOutputPart } from './TextElementOutputPart.ts';
4
+ import type { TextElementReferencePart } from './TextElementReferencePart.ts';
5
+ import type { TextElementStaticPart } from './TextElementStaticPart.ts';
6
+
7
+ export type TextElementPartType = 'output' | 'reference' | 'static';
8
+
9
+ export abstract class TextElementPart<
10
+ Type extends TextElementPartType,
11
+ > extends DependentExpression<'string'> {
12
+ readonly stringValue?: string;
13
+
14
+ constructor(
15
+ readonly type: Type,
16
+ context: AnyTextElementDefinition,
17
+ expression: string
18
+ ) {
19
+ super(context, 'string', expression, {
20
+ semanticDependencies: {
21
+ translations: type !== 'static',
22
+ },
23
+ ignoreContextReference: true,
24
+ });
25
+ }
26
+ }
27
+
28
+ export type AnyTextElementPart =
29
+ | TextElementOutputPart
30
+ | TextElementReferencePart
31
+ | TextElementStaticPart;
@@ -0,0 +1,21 @@
1
+ import type { AnyTextElementDefinition, TextElement } from './TextElementDefinition.ts';
2
+ import { TextElementPart } from './TextElementPart.ts';
3
+
4
+ export class TextElementReferencePart extends TextElementPart<'reference'> {
5
+ static from(
6
+ context: AnyTextElementDefinition,
7
+ element: TextElement
8
+ ): TextElementReferencePart | null {
9
+ const expression = element.getAttribute('ref');
10
+
11
+ if (expression == null) {
12
+ return null;
13
+ }
14
+
15
+ return new this(context, expression);
16
+ }
17
+
18
+ protected constructor(context: AnyTextElementDefinition, expression: string) {
19
+ super('reference', context, expression);
20
+ }
21
+ }
@@ -0,0 +1,26 @@
1
+ import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
2
+ import { TextElementPart } from './TextElementPart.ts';
3
+
4
+ const toStaticXPathExpression = (staticTextValue: string): string => {
5
+ const quote = staticTextValue.includes('"') ? "'" : '"';
6
+
7
+ if (staticTextValue.includes(quote)) {
8
+ // throw new Error('todo concat()');
9
+ return 'todo(concat())';
10
+ }
11
+
12
+ return `${quote}${staticTextValue}${quote}`;
13
+ };
14
+
15
+ export class TextElementStaticPart extends TextElementPart<'static'> {
16
+ override readonly stringValue: string;
17
+
18
+ constructor(context: AnyTextElementDefinition, node: Text) {
19
+ const stringValue = node.data;
20
+ const expression = toStaticXPathExpression(stringValue);
21
+
22
+ super('static', context, expression);
23
+
24
+ this.stringValue = stringValue;
25
+ }
26
+ }
@@ -0,0 +1,180 @@
1
+ import type { AnyNodeDefinition } from '../model/NodeDefinition.ts';
2
+ import type { InstanceNodeType } from './node-types.js';
3
+ import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
4
+ import type { TextRange } from './TextRange.ts';
5
+
6
+ export interface BaseNodeState {
7
+ /**
8
+ * Location path reference to the node's primary instance state. This property
9
+ * may change if a node's position changes, e.g. when a repeat instance is
10
+ * removed. Its potential reactivity allows nodes to re-run computations which
11
+ * depend on the node's position itself, or when any other relative reference
12
+ * might target different nodes as a result of the positional change.
13
+ *
14
+ * @example
15
+ * /data/repeat[1]/foo
16
+ * /data/repeat[2]/foo
17
+ */
18
+ get reference(): string;
19
+
20
+ /**
21
+ * Note: a node's `readonly` state may become `true` by inheriting that state
22
+ * from one of its ancestors. Computing this inheritance is handled by the
23
+ * engine, but it may be of interest to clients.
24
+ *
25
+ * In the future, a more granular type might convey this detail more
26
+ * explicitly (at the expense of a more complex type). For now, a client can
27
+ * infer that inheritance by visiting the
28
+ * {@link BaseNode.parent | parent node}.
29
+ */
30
+ get readonly(): boolean;
31
+
32
+ /**
33
+ * Note: a node's `relevant` state may become `false` by inheriting that state
34
+ * from one of its ancestors. Computing this inheritance is handled by the
35
+ * engine, but it may be of interest to clients.
36
+ *
37
+ * In the future, a more granular type might convey this detail more
38
+ * explicitly (at the expense of a more complex type). For now, a client can
39
+ * infer that inheritance by visiting the
40
+ * {@link BaseNode.parent | parent node}.
41
+ */
42
+ get relevant(): boolean;
43
+
44
+ // Note: according to spec, `required` is NOT inherited from ancestor nodes.
45
+ // What this means for a `required` state on subtree nodes is an open
46
+ // question. It was also raised on the first engine-internals iteration, and I
47
+ // could have sworn it was discussed in that PR, but finding any record of
48
+ // this discussion has proven elusive.
49
+ get required(): boolean;
50
+
51
+ /**
52
+ * Interfaces for nodes which cannot provide a label should override this to
53
+ * specify that the property will always be `null`.
54
+ */
55
+ get label(): TextRange<'label'> | null;
56
+
57
+ /**
58
+ * Interfaces for nodes which cannot provide a hint should override this to
59
+ * specify that the property will always be `null`.
60
+ */
61
+ get hint(): TextRange<'hint'> | null;
62
+
63
+ /**
64
+ * Each node's children (if it is a parent node) will be accessed on that
65
+ * node's state. While some node types will technically have static children,
66
+ * other nodes' children will be stateful (i.e. repeats). For a client, both
67
+ * cases are accessed the same way for consistency.
68
+ *
69
+ * Certain kinds of nodes are considered parent nodes: they may have child
70
+ * nodes. In some cases (presently, repeat ranges), children may be added or
71
+ * removed while a user is filling a form. As such, those children must be
72
+ * accessed as part of the node's
73
+ * {@link BaseNode.currentState | current state}. (In contrast, child nodes
74
+ * are never moved between different parents, so their
75
+ * {@link BaseNode.parent | parent} is static rather than part of their
76
+ * current state).
77
+ *
78
+ * A node is either:
79
+ *
80
+ * - Always a parent, in which case its `children` state should always produce
81
+ * an array. When the parent node's children can be added or removed, an
82
+ * empty array should be used to represent the absence of any children in
83
+ * its current state.
84
+ * - Never a parent, in which case its `children` state should always produce
85
+ * `null`. Such a node will instead have a {@link value}.
86
+ */
87
+ get children(): readonly BaseNode[] | null;
88
+
89
+ /**
90
+ * Certain kinds of nodes restrict their {@link value} to a specific set of
91
+ * valid values. Where they do, they will provide a collection (typically an
92
+ * array) of those values. This collection may be updated depending on other
93
+ * aspects of form state, which is why it is treated as a part of the node's
94
+ * state.
95
+ *
96
+ * Nodes which do not have this restriction (including nodes which cannot have
97
+ * a value at all) will always produce `valueOptions: null`.
98
+ */
99
+ get valueOptions(): unknown;
100
+
101
+ /**
102
+ * Certain kinds of nodes store a value state. Where they do, they will
103
+ * specify the type of the value directly.
104
+ *
105
+ * Parent nodes, i.e. nodes which can contain {@link children}, do not store a
106
+ * value state. For those nodes, their value state should always be `null`.
107
+ */
108
+ get value(): unknown;
109
+ }
110
+
111
+ type FormNodeID = string;
112
+
113
+ /**
114
+ * Base interface for common/shared aspects of any node type.
115
+ */
116
+ export interface BaseNode {
117
+ /**
118
+ * Specifies the node's general type. This can be useful for narrowing types,
119
+ * e.g. those of children.
120
+ */
121
+ readonly nodeType: InstanceNodeType;
122
+
123
+ /**
124
+ * Each node has a unique identifier. This identifier is stable throughout
125
+ * the lifetime of an active session filling a form.
126
+ */
127
+ readonly nodeId: FormNodeID;
128
+
129
+ /**
130
+ * Each node has a definition which specifies aspects of the node defined in
131
+ * the form. These aspects include (but are not limited to) the node's data
132
+ * type, its body presentation constraints (if any), its bound nodeset, and
133
+ * so on...
134
+ *
135
+ * @todo Interfaces for specific (non-base) node types should override this
136
+ * to specify the actual (concrete or union) type of their `definition`.
137
+ */
138
+ readonly definition: AnyNodeDefinition;
139
+
140
+ /**
141
+ * Each node links back to the node representing the root of the form.
142
+ *
143
+ * @todo Interfaces for all concrete node types should override this to
144
+ * specify the actual root node type.
145
+ */
146
+ readonly root: BaseNode;
147
+
148
+ /**
149
+ * Each node links back to its parent, if any. All nodes have a parent except
150
+ * the form's {@link root}.
151
+ */
152
+ // TODO: the `children` state property discusses the fact that a child node
153
+ // cannot be reassigned to another parent. As such, it is currently treated as
154
+ // static. This fails to address the removal of nodes, i.e. when removing
155
+ // repeat instances. This suggests that perhaps `parent` should also be part
156
+ // of the node's state. However that would be insufficient to communicate the
157
+ // same information about a removed node's descendants. Some considerations:
158
+ //
159
+ // 1. If `parent` becomes part of state, how do we communicate that removal is
160
+ // the only possible state change (as in, a child node will never be
161
+ // reassigned to another parent)?
162
+ // 2. If `parent` does become nullable state, how best to convey the same
163
+ // information for removed descendants. Some ideas:
164
+ //
165
+ // - Apply null-as-removed recursively. This wouldn't technically be true
166
+ // for the engine's current use of a DOM backing store (but that's an
167
+ // implementation detail clients don't/shouldn't care about).
168
+ //
169
+ // - Borrow the browser DOM's notion of node "connected"-ness. When a node
170
+ // is removed, its `isConnected` property is `false`. The same is true
171
+ // for any of its descendants, even though they retain their own direct
172
+ // parent reference.
173
+ readonly parent: BaseNode | null;
174
+
175
+ /**
176
+ * Each node provides a discrete object representing the stateful aspects of
177
+ * that node which will change over time. When a client provides a {@link OpaqueReactiveObjectFactory}
178
+ */
179
+ readonly currentState: BaseNodeState;
180
+ }
@@ -0,0 +1,83 @@
1
+ import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
2
+
3
+ /**
4
+ * @todo this is currently a strict subset of the web standard `Response`. Is it
5
+ * sufficient? Ways it might not be:
6
+ *
7
+ * - No way to convey metadata about the resource
8
+ * - Ambiguous if a client supplies an alternative implementation which doesn't
9
+ * exhaust the body on access
10
+ */
11
+ export interface FetchResourceResponse {
12
+ readonly ok?: boolean;
13
+ readonly body?: ReadableStream<Uint8Array> | null;
14
+ readonly bodyUsed?: boolean;
15
+
16
+ readonly blob: () => Promise<Blob>;
17
+ readonly text: () => Promise<string>;
18
+ }
19
+
20
+ /**
21
+ * @todo this is a strict subset of the web standard `fetch` interface. It
22
+ * implicitly assumes that the engine itself will only ever issue `GET`-like
23
+ * requests. It also provides no further request-like semantics to the engine.
24
+ * This is presumed sufficient for now, but notably doesn't expose any notion of
25
+ * content negotiation (e.g. the ability to supply `Accept` headers).
26
+ *
27
+ * This also completely ignores any notion of mapping
28
+ * {@link https://getodk.github.io/xforms-spec/#uris | `jr:` URLs} to their
29
+ * actual resources (likely, but not necessarily, accessed at a corresponding
30
+ * HTTP URL).
31
+ */
32
+ export type FetchResource = (resource: URL) => Promise<FetchResourceResponse>;
33
+
34
+ /**
35
+ * Options provided by a client to specify certain aspects of engine runtime
36
+ * behavior. These options will generally be intended to facilitate cooperation
37
+ * where there is mixed responsibility between a client and the engine, or where
38
+ * the engine may provide sensible defaults which a client could be expected to
39
+ * override or augment.
40
+ */
41
+ export interface EngineConfig {
42
+ /**
43
+ * A client may specify a generic function for constructing stateful objects.
44
+ * The only hard requirement of this function is that it accepts an **object**
45
+ * and returns an object of the same shape. The engine will use this function
46
+ * to initialize client-facing state, and will mutate properties of the object
47
+ * when their corresponding state is changed.
48
+ *
49
+ * A client may use this function to provide its own implementation of
50
+ * reactivity with semantics like those described above. The mechanism of
51
+ * reactivity, if any, is at the discretion of the client. It is expected
52
+ * that clients providing this function will use a reactive subscribe-on-read
53
+ * mechanism to handle state updates conveyed by the engine.
54
+ */
55
+ readonly stateFactory?: OpaqueReactiveObjectFactory;
56
+
57
+ /**
58
+ * A client may specify a generic function for retrieving resources referenced
59
+ * by a form, such as:
60
+ *
61
+ * - Form definitions themselves (if not provided directly to the engine by
62
+ * the client)
63
+ * - External secondary instances
64
+ * - Media (images, audio, video, etc.)
65
+ *
66
+ * The function is expected to be a subset of the
67
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API},
68
+ * performing `GET` requests for:
69
+ *
70
+ * - Text resources (e.g. XML, CSV, JSON/GeoJSON)
71
+ * - Binary resources (e.g. media)
72
+ * - Optionally streamed binary data of either (e.g. for optimized
73
+ * presentation of audio/video)
74
+ *
75
+ * If provided by a client, this function will be used by the engine to
76
+ * retrieve any such resource, as required for engine functionality. If
77
+ * absent, the engine will use the native `fetch` function (if available, a
78
+ * polyfill otherwise). Clients may use this function to provide resources
79
+ * from sources other than the network, (or even in a test client to provide
80
+ * e.g. resources from test fixtures).
81
+ */
82
+ readonly fetchResource?: FetchResource;
83
+ }
@@ -0,0 +1,77 @@
1
+ interface BaseFormLanguage {
2
+ /**
3
+ * @see {@link ActiveLanguage} for details.
4
+ */
5
+ readonly isSyntheticDefault?: true;
6
+
7
+ /**
8
+ * As derived directly from the form's
9
+ * {@link https://getodk.github.io/xforms-spec/#languages | `itext` translations}.
10
+ */
11
+ readonly language: string;
12
+
13
+ /**
14
+ * Where possible, a form's languages may detect the standardized locale
15
+ * corresponding to the language as specified in the form.
16
+ */
17
+ // NOTE: this is proposed, hypothetical future functionality. Enketo has
18
+ // similar functionality, but we'll want to seriously consider how best to
19
+ // accomplish this if/as it becomes a priority.
20
+ readonly locale?: Intl.Locale;
21
+ }
22
+
23
+ /**
24
+ * @see {@link ActiveLanguage} for details.
25
+ */
26
+ export interface SyntheticDefaultLanguage extends BaseFormLanguage {
27
+ readonly isSyntheticDefault: true;
28
+ readonly language: '';
29
+ }
30
+
31
+ /**
32
+ * A language available for a given form, as defined by that form's {@link https://getodk.github.io/xforms-spec/#languages | `itext` translations}.
33
+ *
34
+ * @see {@link ActiveLanguage} for additional details.
35
+ */
36
+ export interface FormLanguage extends BaseFormLanguage {
37
+ readonly isSyntheticDefault?: never;
38
+ }
39
+
40
+ /**
41
+ * A form with translations will always have one or more {@link FormLanguage}s,
42
+ * with the default chosen as described in the ODK XForms specification.
43
+ *
44
+ * A form that specifies no translations will always have a single
45
+ * {@link SyntheticDefaultLanguage}.
46
+ *
47
+ * No form will ever combine both types of language.
48
+ *
49
+ * This distinction is intended to avoid confusion about the **potential** state
50
+ * of a form's active language. A naive type to express the possibility, without
51
+ * a synthetic default, would be something like `FormLanguage | null`, which
52
+ * would seem to suggest to a client that a form may **only sometimes** have an
53
+ * active language—for instance, that there might be a way for a client to turn
54
+ * translation off and on.
55
+ *
56
+ * By ensuring there is always _some active language value_, and by expressing
57
+ * the {@link FormLanguages} type to correspond to each of the possibilities
58
+ * discussed above, it's hoped that the set of potential translation states a
59
+ * client might encounter/establish are more clear.
60
+ */
61
+ // prettier-ignore
62
+ export type ActiveLanguage =
63
+ | FormLanguage
64
+ | SyntheticDefaultLanguage;
65
+
66
+ /**
67
+ * A form may either have:
68
+ *
69
+ * - one or more available {@link FormLanguage}s, as defined by the form
70
+ * - exactly one {@link SyntheticDefaultLanguage}
71
+ *
72
+ * @see {@link ActiveLanguage} for additional details.
73
+ */
74
+ // prettier-ignore
75
+ export type FormLanguages =
76
+ | readonly [FormLanguage, ...FormLanguage[]]
77
+ | readonly [SyntheticDefaultLanguage];
@@ -0,0 +1,33 @@
1
+ import type { NonRepeatGroupElementDefinition } from '../body/BodyDefinition.ts';
2
+ import type { SubtreeDefinition } from '../model/SubtreeDefinition.ts';
3
+ import type { BaseNode, BaseNodeState } from './BaseNode.ts';
4
+ import type { RootNode } from './RootNode.ts';
5
+ import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
6
+
7
+ export interface GroupNodeState extends BaseNodeState {
8
+ get hint(): null;
9
+ get children(): readonly GeneralChildNode[];
10
+ get valueOptions(): null;
11
+ get value(): null;
12
+ }
13
+
14
+ // TODO: as with `SubtreeNode`'s `SubtreeDefinition`, there is a naming
15
+ // inconsistency emerging here.
16
+ export interface GroupDefinition extends SubtreeDefinition {
17
+ readonly bodyElement: NonRepeatGroupElementDefinition;
18
+ }
19
+
20
+ /**
21
+ * A node corresponding to an XForms `<group>`.
22
+ */
23
+ // TODO: test (fix?) case where a `<group>` is implicitly connected to a
24
+ // subtree, but doesn't reference it directly. See
25
+ // https://github.com/getodk/web-forms/blob/6cfff8b4c5a2cf6a23a71ef6d4308343bccd2436/packages/odk-web-forms/src/lib/xform/model/ModelDefinition.test.ts#L480-L540
26
+ // for context.
27
+ export interface GroupNode extends BaseNode {
28
+ readonly nodeType: 'group';
29
+ readonly definition: GroupDefinition;
30
+ readonly root: RootNode;
31
+ readonly parent: GeneralParentNode;
32
+ readonly currentState: GroupNodeState;
33
+ }