@getodk/xforms-engine 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/client/AttributeNode.d.ts +53 -0
  2. package/dist/client/BaseItem.d.ts +6 -0
  3. package/dist/client/GroupNode.d.ts +4 -4
  4. package/dist/client/MarkdownNode.d.ts +33 -0
  5. package/dist/client/RankNode.d.ts +2 -4
  6. package/dist/client/SelectNode.d.ts +2 -5
  7. package/dist/client/TextRange.d.ts +2 -2
  8. package/dist/client/hierarchy.d.ts +1 -2
  9. package/dist/client/index.d.ts +2 -1
  10. package/dist/client/node-types.d.ts +2 -2
  11. package/dist/client/validation.d.ts +7 -1
  12. package/dist/index.js +10758 -402
  13. package/dist/index.js.map +1 -1
  14. package/dist/instance/Attribute.d.ts +58 -0
  15. package/dist/instance/Group.d.ts +4 -0
  16. package/dist/instance/PrimaryInstance.d.ts +4 -0
  17. package/dist/instance/Root.d.ts +4 -0
  18. package/dist/instance/UploadControl.d.ts +4 -0
  19. package/dist/instance/abstract/InstanceNode.d.ts +7 -4
  20. package/dist/instance/abstract/ValueNode.d.ts +1 -0
  21. package/dist/instance/attachments/buildAttributes.d.ts +3 -0
  22. package/dist/instance/hierarchy.d.ts +6 -6
  23. package/dist/instance/internal-api/AttributeContext.d.ts +29 -0
  24. package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +16 -0
  25. package/dist/instance/internal-api/serialization/ClientReactiveSerializableParentNode.d.ts +2 -0
  26. package/dist/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.d.ts +2 -2
  27. package/dist/instance/markdown/MarkdownNode.d.ts +75 -0
  28. package/dist/instance/repeat/BaseRepeatRange.d.ts +5 -0
  29. package/dist/instance/repeat/RepeatInstance.d.ts +4 -2
  30. package/dist/instance/text/TextChunk.d.ts +0 -1
  31. package/dist/instance/text/TextRange.d.ts +2 -1
  32. package/dist/instance/text/markdownFormat.d.ts +3 -0
  33. package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +2 -2
  34. package/dist/lib/client-reactivity/instance-state/createAttributeNodeInstanceState.d.ts +3 -0
  35. package/dist/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.d.ts +0 -3
  36. package/dist/lib/names/NamespaceDeclarationMap.d.ts +1 -1
  37. package/dist/lib/reactivity/createAttributeState.d.ts +16 -0
  38. package/dist/lib/reactivity/createAttributeValueState.d.ts +15 -0
  39. package/dist/lib/reactivity/createItemCollection.d.ts +5 -7
  40. package/dist/lib/xml-serialization.d.ts +5 -9
  41. package/dist/parse/XFormDOM.d.ts +1 -1
  42. package/dist/parse/body/BodyDefinition.d.ts +2 -8
  43. package/dist/parse/body/GroupElementDefinition.d.ts +22 -0
  44. package/dist/parse/body/control/ItemsetDefinition.d.ts +3 -0
  45. package/dist/parse/expression/ItemPropertyExpression.d.ts +6 -0
  46. package/dist/parse/model/AttributeDefinition.d.ts +22 -0
  47. package/dist/parse/model/{RootAttributeMap.d.ts → AttributeDefinitionMap.d.ts} +4 -10
  48. package/dist/parse/model/{SubtreeDefinition.d.ts → GroupDefinition.d.ts} +8 -4
  49. package/dist/parse/model/LeafNodeDefinition.d.ts +1 -0
  50. package/dist/parse/model/NodeDefinition.d.ts +13 -9
  51. package/dist/parse/model/RepeatDefinition.d.ts +5 -2
  52. package/dist/parse/model/RootDefinition.d.ts +3 -3
  53. package/dist/parse/text/LabelDefinition.d.ts +4 -5
  54. package/dist/solid.js +10758 -402
  55. package/dist/solid.js.map +1 -1
  56. package/package.json +6 -5
  57. package/src/client/AttributeNode.ts +62 -0
  58. package/src/client/BaseItem.ts +7 -0
  59. package/src/client/GroupNode.ts +4 -10
  60. package/src/client/MarkdownNode.ts +53 -0
  61. package/src/client/RankNode.ts +2 -5
  62. package/src/client/SelectNode.ts +2 -6
  63. package/src/client/TextRange.ts +2 -2
  64. package/src/client/hierarchy.ts +0 -2
  65. package/src/client/index.ts +2 -1
  66. package/src/client/node-types.ts +1 -1
  67. package/src/client/validation.ts +9 -1
  68. package/src/instance/Attribute.ts +164 -0
  69. package/src/instance/Group.ts +17 -1
  70. package/src/instance/InputControl.ts +1 -0
  71. package/src/instance/ModelValue.ts +1 -0
  72. package/src/instance/Note.ts +1 -0
  73. package/src/instance/PrimaryInstance.ts +17 -0
  74. package/src/instance/RangeControl.ts +1 -0
  75. package/src/instance/RankControl.ts +1 -0
  76. package/src/instance/Root.ts +16 -0
  77. package/src/instance/SelectControl.ts +1 -0
  78. package/src/instance/TriggerControl.ts +1 -0
  79. package/src/instance/UploadControl.ts +14 -0
  80. package/src/instance/abstract/DescendantNode.ts +5 -1
  81. package/src/instance/abstract/InstanceNode.ts +6 -3
  82. package/src/instance/abstract/ValueNode.ts +1 -0
  83. package/src/instance/attachments/buildAttributes.ts +8 -0
  84. package/src/instance/children/buildChildren.ts +1 -17
  85. package/src/instance/children/normalizeChildInitOptions.ts +44 -59
  86. package/src/instance/hierarchy.ts +3 -7
  87. package/src/instance/internal-api/AttributeContext.ts +34 -0
  88. package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +19 -0
  89. package/src/instance/internal-api/serialization/ClientReactiveSerializableParentNode.ts +2 -0
  90. package/src/instance/internal-api/serialization/ClientReactiveSerializableTemplatedNode.ts +2 -3
  91. package/src/instance/markdown/MarkdownNode.ts +115 -0
  92. package/src/instance/repeat/BaseRepeatRange.ts +14 -0
  93. package/src/instance/repeat/RepeatInstance.ts +14 -5
  94. package/src/instance/text/TextChunk.ts +0 -5
  95. package/src/instance/text/TextRange.ts +5 -3
  96. package/src/instance/text/markdownFormat.ts +214 -0
  97. package/src/integration/xpath/adapter/XFormsXPathNode.ts +3 -1
  98. package/src/integration/xpath/adapter/names.ts +0 -1
  99. package/src/lib/client-reactivity/instance-state/createAttributeNodeInstanceState.ts +16 -0
  100. package/src/lib/client-reactivity/instance-state/createParentNodeInstanceState.ts +5 -5
  101. package/src/lib/client-reactivity/instance-state/createRootInstanceState.ts +6 -9
  102. package/src/lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.ts +5 -15
  103. package/src/lib/names/NamespaceDeclarationMap.ts +1 -1
  104. package/src/lib/reactivity/createAttributeState.ts +51 -0
  105. package/src/lib/reactivity/createAttributeValueState.ts +189 -0
  106. package/src/lib/reactivity/createItemCollection.ts +25 -9
  107. package/src/lib/xml-serialization.ts +30 -34
  108. package/src/parse/body/BodyDefinition.ts +7 -34
  109. package/src/parse/body/GroupElementDefinition.ts +47 -0
  110. package/src/parse/body/control/ItemsetDefinition.ts +7 -0
  111. package/src/parse/expression/ItemPropertyExpression.ts +12 -0
  112. package/src/parse/model/AttributeDefinition.ts +58 -0
  113. package/src/parse/model/{RootAttributeMap.ts → AttributeDefinitionMap.ts} +7 -13
  114. package/src/parse/model/{SubtreeDefinition.ts → GroupDefinition.ts} +12 -8
  115. package/src/parse/model/LeafNodeDefinition.ts +1 -0
  116. package/src/parse/model/NodeDefinition.ts +19 -12
  117. package/src/parse/model/RepeatDefinition.ts +10 -3
  118. package/src/parse/model/RootDefinition.ts +6 -6
  119. package/src/parse/model/nodeDefinitionMap.ts +1 -1
  120. package/src/parse/text/LabelDefinition.ts +4 -9
  121. package/dist/client/SubtreeNode.d.ts +0 -56
  122. package/dist/error/TemplatedNodeAttributeSerializationError.d.ts +0 -22
  123. package/dist/instance/Subtree.d.ts +0 -38
  124. package/dist/instance/text/FormattedTextStub.d.ts +0 -1
  125. package/dist/parse/body/group/BaseGroupDefinition.d.ts +0 -40
  126. package/dist/parse/body/group/LogicalGroupDefinition.d.ts +0 -6
  127. package/dist/parse/body/group/PresentationGroupDefinition.d.ts +0 -11
  128. package/dist/parse/body/group/StructuralGroupDefinition.d.ts +0 -6
  129. package/dist/parse/model/RootAttributeDefinition.d.ts +0 -21
  130. package/src/client/SubtreeNode.ts +0 -61
  131. package/src/error/TemplatedNodeAttributeSerializationError.ts +0 -24
  132. package/src/instance/Subtree.ts +0 -102
  133. package/src/instance/text/FormattedTextStub.ts +0 -8
  134. package/src/parse/body/group/BaseGroupDefinition.ts +0 -89
  135. package/src/parse/body/group/LogicalGroupDefinition.ts +0 -11
  136. package/src/parse/body/group/PresentationGroupDefinition.ts +0 -28
  137. package/src/parse/body/group/StructuralGroupDefinition.ts +0 -11
  138. package/src/parse/model/RootAttributeDefinition.ts +0 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getodk/xforms-engine",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "XForms engine for ODK Web Forms",
6
6
  "type": "module",
@@ -55,22 +55,23 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "bin-packer": "1.7.0",
58
+ "mdast-util-from-markdown": "^2.0.2",
58
59
  "papaparse": "^5.5.3",
59
60
  "solid-js": "^1.9.7",
60
61
  "temporal-polyfill": "^0.3.0"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@babel/core": "^7.28.0",
64
- "@getodk/tree-sitter-xpath": "0.2.0",
65
- "@getodk/xpath": "0.8.0",
66
- "@playwright/test": "^1.53.2",
65
+ "@getodk/tree-sitter-xpath": "0.2.1",
66
+ "@getodk/xpath": "0.9.0",
67
+ "@playwright/test": "^1.55.1",
67
68
  "@types/papaparse": "^5.3.16",
68
69
  "@vitest/browser": "^3.2.4",
69
70
  "babel-plugin-transform-jsbi-to-bigint": "^1.4.2",
70
71
  "http-server": "^14.1.1",
71
72
  "jsdom": "^26.1.0",
72
73
  "typedoc": "^0.28.7",
73
- "vite": "^7.0.3",
74
+ "vite": "^7.0.8",
74
75
  "vite-plugin-dts": "^4.5.4",
75
76
  "vite-plugin-no-bundle": "^4.0.0",
76
77
  "vitest": "^3.2.4"
@@ -0,0 +1,62 @@
1
+ import type { Root } from '../instance/Root.ts';
2
+ import type { AttributeDefinition } from '../parse/model/AttributeDefinition.ts';
3
+ import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
4
+ import type { InstanceState } from './serialization/InstanceState.ts';
5
+
6
+ export interface AttributeNodeState {
7
+ get value(): string;
8
+ }
9
+
10
+ /**
11
+ * Base interface for common/shared aspects of attributes.
12
+ */
13
+ export interface AttributeNode {
14
+ /**
15
+ * Specifies the node's general type. This can be useful for narrowing types,
16
+ * e.g. those of children.
17
+ */
18
+ readonly nodeType: 'attribute';
19
+
20
+ /**
21
+ * Each node has a definition which specifies aspects of the node defined in
22
+ * the form. These aspects include (but are not limited to) the node's data
23
+ * type, its body presentation constraints (if any), its bound nodeset, and
24
+ * so on...
25
+ */
26
+ readonly definition: AttributeDefinition;
27
+
28
+ /**
29
+ * Each node links back to the node representing the root of the form.
30
+ */
31
+ readonly root: Root;
32
+
33
+ /**
34
+ * Each node links back to its parent, if any. All nodes have a parent except
35
+ * the form's {@link root}.
36
+ */
37
+ readonly parent: unknown;
38
+
39
+ /**
40
+ * Each node provides a discrete object representing the stateful aspects of
41
+ * that node which will change over time. This includes state which is either
42
+ * client-/user-mutable, or state which is computed based on the core XForms
43
+ * computation model. Each node also exposes {@link validationState}, which
44
+ * reflects the validity of the node, or its descendants.
45
+ *
46
+ * When a client provides a {@link OpaqueReactiveObjectFactory}, the engine
47
+ * will update the properties of this object as their respective states
48
+ * change, so a client can implement reactive updates that respond to changes
49
+ * as they occur.
50
+ */
51
+ readonly currentState: AttributeNodeState;
52
+
53
+ /**
54
+ * Represents the current instance state of the node.
55
+ *
56
+ * @see {@link InstanceState.instanceXML} for additional detail.
57
+ */
58
+ readonly instanceState: InstanceState;
59
+
60
+ readonly appearances: null;
61
+ readonly nodeOptions: null;
62
+ }
@@ -0,0 +1,7 @@
1
+ import type { TextRange } from './TextRange.ts';
2
+
3
+ export interface BaseItem {
4
+ get label(): TextRange<'item-label'>;
5
+ get value(): string;
6
+ properties: Array<[string, string]>;
7
+ }
@@ -1,5 +1,5 @@
1
- import type { AnyGroupElementDefinition } from '../parse/body/BodyDefinition.ts';
2
- import type { SubtreeDefinition } from '../parse/model/SubtreeDefinition.ts';
1
+ import type { GroupElementDefinition } from '../parse/body/GroupElementDefinition.ts';
2
+ import type { GroupDefinition as GroupNodeDefinition } from '../parse/model/GroupDefinition.ts';
3
3
  import type { BaseNode, BaseNodeState } from './BaseNode.ts';
4
4
  import type { NodeAppearances } from './NodeAppearances.ts';
5
5
  import type { RootNode } from './RootNode.ts';
@@ -13,10 +13,8 @@ export interface GroupNodeState extends BaseNodeState {
13
13
  get value(): null;
14
14
  }
15
15
 
16
- // TODO: as with `SubtreeNode`'s `SubtreeDefinition`, there is a naming
17
- // inconsistency emerging here.
18
- export interface GroupDefinition extends SubtreeDefinition {
19
- readonly bodyElement: AnyGroupElementDefinition;
16
+ export interface GroupDefinition extends GroupNodeDefinition {
17
+ readonly bodyElement: GroupElementDefinition;
20
18
  }
21
19
 
22
20
  export type GroupNodeAppearances = NodeAppearances<GroupDefinition>;
@@ -24,10 +22,6 @@ export type GroupNodeAppearances = NodeAppearances<GroupDefinition>;
24
22
  /**
25
23
  * A node corresponding to an XForms `<group>`.
26
24
  */
27
- // TODO: test (fix?) case where a `<group>` is implicitly connected to a
28
- // subtree, but doesn't reference it directly. See
29
- // https://github.com/getodk/web-forms/blob/6cfff8b4c5a2cf6a23a71ef6d4308343bccd2436/packages/odk-web-forms/src/lib/xform/model/ModelDefinition.test.ts#L480-L540
30
- // for context.
31
25
  export interface GroupNode extends BaseNode {
32
26
  readonly nodeType: 'group';
33
27
  readonly appearances: GroupNodeAppearances;
@@ -0,0 +1,53 @@
1
+ export type Heading = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
2
+
3
+ export type ElementName =
4
+ | Heading
5
+ | 'a'
6
+ | 'div'
7
+ | 'em'
8
+ | 'li'
9
+ | 'ol'
10
+ | 'p'
11
+ | 'span'
12
+ | 'strong'
13
+ | 'u'
14
+ | 'ul';
15
+
16
+ export type MarkdownNode = ChildMarkdownNode | HtmlMarkdownNode | ParentMarkdownNode;
17
+
18
+ export interface ParentMarkdownNode {
19
+ readonly role: 'parent';
20
+ readonly elementName: string;
21
+ readonly children: MarkdownNode[];
22
+ }
23
+
24
+ export interface ChildMarkdownNode {
25
+ readonly role: 'child';
26
+ readonly value: string;
27
+ }
28
+
29
+ export interface HtmlMarkdownNode {
30
+ readonly role: 'html';
31
+ readonly unsafeHtml: string;
32
+ }
33
+
34
+ export interface AnchorMarkdownNode extends ParentMarkdownNode {
35
+ readonly elementName: 'a';
36
+ readonly url: string;
37
+ }
38
+
39
+ export interface StyledMarkdownNode extends ParentMarkdownNode {
40
+ readonly elementName: 'div' | 'p' | 'span';
41
+ readonly properties: MarkdownProperty | undefined;
42
+ }
43
+
44
+ export interface MarkdownProperty {
45
+ readonly style: StyleProperty;
46
+ }
47
+
48
+ export interface StyleProperty {
49
+ readonly color: string | undefined;
50
+ readonly 'font-family': string | undefined;
51
+ readonly 'text-align': 'center' | 'left' | 'right' | undefined;
52
+ readonly 'font-size': string | undefined;
53
+ }
@@ -1,6 +1,7 @@
1
1
  import type { RankControlDefinition } from '../parse/body/control/RankControlDefinition.ts';
2
2
  import type { LeafNodeDefinition } from '../parse/model/LeafNodeDefinition.ts';
3
3
  import type { BaseValueNode, BaseValueNodeState } from './BaseValueNode.ts';
4
+ import type { BaseItem } from './BaseItem.ts';
4
5
  import type { RootNode } from './RootNode.ts';
5
6
  import type { TextRange } from './TextRange.ts';
6
7
  import type { GeneralParentNode } from './hierarchy.ts';
@@ -8,11 +9,7 @@ import type { LeafNodeValidationState } from './validation.ts';
8
9
  import type { UnknownAppearanceDefinition } from '../parse/body/appearance/unknownAppearanceParser.ts';
9
10
  import type { ValueType } from './ValueType.ts';
10
11
 
11
- export interface RankItem {
12
- get label(): TextRange<'item-label'>;
13
- get value(): string;
14
- }
15
-
12
+ export type RankItem = BaseItem;
16
13
  export type RankValueOptions = readonly RankItem[];
17
14
 
18
15
  export interface RankNodeState extends BaseValueNodeState<readonly string[]> {
@@ -4,18 +4,14 @@ import type {
4
4
  } from '../parse/body/control/SelectControlDefinition.ts';
5
5
  import type { LeafNodeDefinition } from '../parse/model/LeafNodeDefinition.ts';
6
6
  import type { BaseValueNode, BaseValueNodeState } from './BaseValueNode.ts';
7
+ import type { BaseItem } from './BaseItem.ts';
7
8
  import type { NodeAppearances } from './NodeAppearances.ts';
8
9
  import type { RootNode } from './RootNode.ts';
9
- import type { TextRange } from './TextRange.ts';
10
10
  import type { ValueType } from './ValueType.ts';
11
11
  import type { GeneralParentNode } from './hierarchy.ts';
12
12
  import type { LeafNodeValidationState } from './validation.ts';
13
13
 
14
- export interface SelectItem {
15
- get label(): TextRange<'item-label'>;
16
- get value(): string;
17
- }
18
-
14
+ export type SelectItem = BaseItem;
19
15
  export type SelectValueOptions = readonly SelectItem[];
20
16
 
21
17
  export interface SelectNodeState extends BaseValueNodeState<readonly string[]> {
@@ -1,5 +1,6 @@
1
1
  import { JRResourceURL } from '@getodk/common/jr-resources/JRResourceURL.ts';
2
2
  import type { ActiveLanguage } from './FormLanguage.ts';
3
+ import type { MarkdownNode } from './MarkdownNode.ts';
3
4
 
4
5
  /**
5
6
  * **COMMENTARY**
@@ -73,7 +74,6 @@ export interface TextChunk {
73
74
  get language(): ActiveLanguage;
74
75
 
75
76
  get asString(): string;
76
- get formatted(): unknown;
77
77
  }
78
78
 
79
79
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
@@ -146,7 +146,7 @@ export interface TextRange<Role extends TextRole, Origin extends TextOrigin = Te
146
146
  [Symbol.iterator](): Iterable<TextChunk>;
147
147
 
148
148
  get asString(): string;
149
- get formatted(): unknown;
149
+ get formatted(): MarkdownNode[];
150
150
 
151
151
  get imageSource(): JRResourceURL | undefined;
152
152
  get audioSource(): JRResourceURL | undefined;
@@ -10,7 +10,6 @@ import type { RepeatRangeControlledNode } from './repeat/RepeatRangeControlledNo
10
10
  import type { RepeatRangeUncontrolledNode } from './repeat/RepeatRangeUncontrolledNode.ts';
11
11
  import type { RootNode } from './RootNode.ts';
12
12
  import type { SelectNode } from './SelectNode.ts';
13
- import type { SubtreeNode } from './SubtreeNode.ts';
14
13
  import type { TriggerNode } from './TriggerNode.ts';
15
14
  import type { UploadNode } from './UploadNode.ts';
16
15
 
@@ -40,7 +39,6 @@ export type RepeatRangeNode =
40
39
  */
41
40
  export type GeneralParentNode =
42
41
  | RootNode // eslint-disable-line @typescript-eslint/sort-type-constituents
43
- | SubtreeNode
44
42
  | GroupNode
45
43
  | RepeatInstanceNode;
46
44
 
@@ -1,5 +1,6 @@
1
1
  export type * from './attachments/InstanceAttachmentMeta.ts';
2
2
  export type * from './attachments/InstanceAttachmentsConfig.ts';
3
+ export type * from './AttributeNode.ts';
3
4
  export type * from './constants.ts';
4
5
  export * as constants from './constants.ts';
5
6
  export type * from './form/CreateFormInstance.ts';
@@ -24,6 +25,7 @@ export type {
24
25
  } from './hierarchy.ts';
25
26
  export type * from './identity.ts';
26
27
  export type * from './InputNode.ts';
28
+ export type * from './MarkdownNode.ts';
27
29
  export type * from './ModelValueNode.ts';
28
30
  export type * from './NoteNode.ts';
29
31
  export type * from './OpaqueReactiveObjectFactory.ts';
@@ -41,7 +43,6 @@ export type * from './serialization/InstancePayload.ts';
41
43
  export type * from './serialization/InstancePayloadOptions.ts';
42
44
  export type * from './serialization/InstanceState.ts';
43
45
  export type * from './submission/SubmissionMeta.ts';
44
- export type * from './SubtreeNode.ts';
45
46
  export type * from './TextRange.ts';
46
47
  export type * from './TriggerNode.ts';
47
48
  export type * from './UploadNode.ts';
@@ -13,6 +13,7 @@ export type LeafNodeType =
13
13
  | 'trigger'
14
14
  | 'range'
15
15
  | 'rank'
16
+ | 'attribute'
16
17
  | 'upload';
17
18
 
18
19
  // prettier-ignore
@@ -22,5 +23,4 @@ export type InstanceNodeType =
22
23
  | RepeatRangeNodeType
23
24
  | 'repeat-instance'
24
25
  | 'group'
25
- | 'subtree'
26
26
  | LeafNodeType;
@@ -193,7 +193,15 @@ export interface AncestorNodeValidationState {
193
193
  get violations(): readonly DescendantNodeViolationReference[];
194
194
  }
195
195
 
196
+ /**
197
+ * Convenience interface for nodes that cannot be invalid.
198
+ */
199
+ export interface NullValidationState {
200
+ get violations(): readonly [];
201
+ }
202
+
196
203
  // prettier-ignore
197
204
  export type NodeValidationState =
198
205
  | AncestorNodeValidationState
199
- | LeafNodeValidationState;
206
+ | LeafNodeValidationState
207
+ | NullValidationState;
@@ -0,0 +1,164 @@
1
+ import { XPathNodeKindKey } from '@getodk/xpath';
2
+ import type { Accessor } from 'solid-js';
3
+ import type { AttributeNode } from '../client/AttributeNode.ts';
4
+ import type { ActiveLanguage, InstanceState, NullValidationState } from '../client/index.ts';
5
+ import type { XFormsXPathAttribute } from '../integration/xpath/adapter/XFormsXPathNode.ts';
6
+ import type { EngineXPathEvaluator } from '../integration/xpath/EngineXPathEvaluator.ts';
7
+ import type { StaticAttribute } from '../integration/xpath/static-dom/StaticAttribute.ts';
8
+ import { createAttributeNodeInstanceState } from '../lib/client-reactivity/instance-state/createAttributeNodeInstanceState.ts';
9
+ import {
10
+ getSharedValueCodec,
11
+ type RuntimeInputValue,
12
+ type RuntimeValue,
13
+ } from '../lib/codecs/getSharedValueCodec.ts';
14
+ import type { RuntimeValueSetter, RuntimeValueState } from '../lib/codecs/ValueCodec.ts';
15
+ import { createAttributeValueState } from '../lib/reactivity/createAttributeValueState.ts';
16
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
17
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
18
+ import {
19
+ createSharedNodeState,
20
+ type SharedNodeState,
21
+ } from '../lib/reactivity/node-state/createSharedNodeState.ts';
22
+ import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
23
+ import type { AttributeDefinition } from '../parse/model/AttributeDefinition.ts';
24
+ import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
25
+ import { InstanceNode } from './abstract/InstanceNode.ts';
26
+ import type { AnyParentNode } from './hierarchy.ts';
27
+ import type { AttributeContext } from './internal-api/AttributeContext.ts';
28
+ import type { DecodeInstanceValue } from './internal-api/InstanceValueContext.ts';
29
+ import type { ClientReactiveSerializableAttributeNode } from './internal-api/serialization/ClientReactiveSerializableAttributeNode.ts';
30
+ import type { Root } from './Root.ts';
31
+
32
+ export interface AttributeStateSpec extends DescendantNodeSharedStateSpec {
33
+ readonly children: null;
34
+ readonly attributes: null;
35
+ readonly value: SimpleAtomicState<string>;
36
+ readonly instanceValue: Accessor<string>;
37
+ readonly label: null;
38
+ readonly hint: null;
39
+ readonly valueOptions: null;
40
+ }
41
+
42
+ export class Attribute
43
+ extends InstanceNode<AttributeDefinition, AttributeStateSpec, AnyParentNode, null>
44
+ implements
45
+ AttributeNode,
46
+ ClientReactiveSerializableAttributeNode,
47
+ AttributeContext,
48
+ XFormsXPathAttribute
49
+ {
50
+ override readonly [XPathNodeKindKey] = 'attribute';
51
+
52
+ protected readonly state: SharedNodeState<AttributeStateSpec>;
53
+ protected readonly engineState: EngineState<AttributeStateSpec>;
54
+ readonly validationState: NullValidationState;
55
+
56
+ readonly nodeType = 'attribute';
57
+ readonly currentState: CurrentState<AttributeStateSpec>;
58
+ override readonly instanceState: InstanceState;
59
+
60
+ readonly appearances = null;
61
+ readonly nodeOptions = null;
62
+
63
+ readonly valueType: string;
64
+ readonly decodeInstanceValue: DecodeInstanceValue;
65
+
66
+ protected readonly getInstanceValue: Accessor<string>;
67
+ protected readonly valueState: RuntimeValueState<RuntimeValue<'string'>>;
68
+ protected readonly setValueState: RuntimeValueSetter<RuntimeInputValue<'string'>>;
69
+ readonly evaluator: EngineXPathEvaluator;
70
+ readonly getActiveLanguage: Accessor<ActiveLanguage>;
71
+
72
+ override readonly root: Root;
73
+
74
+ readonly isRelevant: Accessor<boolean> = () => {
75
+ return this.parent.isRelevant();
76
+ };
77
+
78
+ readonly isAttached: Accessor<boolean> = () => {
79
+ return this.parent.isAttached();
80
+ };
81
+
82
+ readonly isReadonly: Accessor<boolean> = () => {
83
+ return true;
84
+ };
85
+
86
+ readonly hasReadonlyAncestor: Accessor<boolean> = () => {
87
+ const { parent } = this;
88
+ return parent.hasReadonlyAncestor() || parent.isReadonly();
89
+ };
90
+
91
+ readonly hasNonRelevantAncestor: Accessor<boolean> = () => {
92
+ const { parent } = this;
93
+ return parent.hasNonRelevantAncestor() || !parent.isRelevant();
94
+ };
95
+
96
+ constructor(
97
+ parent: AnyParentNode,
98
+ definition: AttributeDefinition,
99
+ override readonly instanceNode: StaticAttribute
100
+ ) {
101
+ const codec = getSharedValueCodec('string');
102
+
103
+ super(parent.instanceConfig, parent, instanceNode, definition, {
104
+ scope: parent.scope,
105
+ computeReference: (): string => '@' + this.definition.qualifiedName.getPrefixedName(),
106
+ });
107
+
108
+ this.root = parent.root;
109
+
110
+ this.getActiveLanguage = parent.getActiveLanguage;
111
+ this.validationState = { violations: [] };
112
+
113
+ this.valueType = 'string';
114
+ this.evaluator = parent.evaluator;
115
+ this.decodeInstanceValue = codec.decodeInstanceValue;
116
+
117
+ const instanceValueState = createAttributeValueState(this);
118
+ const valueState = codec.createRuntimeValueState(instanceValueState);
119
+
120
+ const [getInstanceValue] = instanceValueState;
121
+ const [, setValueState] = valueState;
122
+
123
+ this.getInstanceValue = getInstanceValue;
124
+ this.setValueState = setValueState;
125
+ this.getXPathValue = () => {
126
+ return this.getInstanceValue();
127
+ };
128
+ this.valueState = valueState;
129
+
130
+ const state = createSharedNodeState(
131
+ this.scope,
132
+ {
133
+ reference: this.contextReference,
134
+ readonly: this.isReadonly,
135
+ relevant: this.isRelevant,
136
+ required: () => false,
137
+
138
+ label: null,
139
+ hint: null,
140
+ children: null,
141
+ valueOptions: null,
142
+ value: this.valueState,
143
+ instanceValue: this.getInstanceValue,
144
+ attributes: null,
145
+ },
146
+ this.instanceConfig
147
+ );
148
+
149
+ this.state = state;
150
+ this.engineState = state.engineState;
151
+ this.currentState = state.currentState;
152
+ this.instanceState = createAttributeNodeInstanceState(this);
153
+ }
154
+
155
+ setValue(value: string): Root {
156
+ this.setValueState(value);
157
+
158
+ return this.root;
159
+ }
160
+
161
+ getChildren(): readonly [] {
162
+ return [];
163
+ }
164
+ }
@@ -8,6 +8,10 @@ import type { AncestorNodeValidationState } from '../client/validation.ts';
8
8
  import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPathNode.ts';
9
9
  import type { StaticElement } from '../integration/xpath/static-dom/StaticElement.ts';
10
10
  import { createParentNodeInstanceState } from '../lib/client-reactivity/instance-state/createParentNodeInstanceState.ts';
11
+ import {
12
+ createAttributeState,
13
+ type AttributeState,
14
+ } from '../lib/reactivity/createAttributeState.ts';
11
15
  import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
12
16
  import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
13
17
  import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
@@ -20,6 +24,8 @@ import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
20
24
  import { createAggregatedViolations } from '../lib/reactivity/validation/createAggregatedViolations.ts';
21
25
  import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
22
26
  import { DescendantNode } from './abstract/DescendantNode.ts';
27
+ import { buildAttributes } from './attachments/buildAttributes.ts';
28
+ import { Attribute } from './Attribute.ts';
23
29
  import { buildChildren } from './children/buildChildren.ts';
24
30
  import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
25
31
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
@@ -30,6 +36,7 @@ interface GroupStateSpec extends DescendantNodeSharedStateSpec {
30
36
  readonly label: Accessor<TextRange<'label'> | null>;
31
37
  readonly hint: null;
32
38
  readonly children: Accessor<readonly FormNodeID[]>;
39
+ readonly attributes: Accessor<readonly Attribute[]>;
33
40
  readonly valueOptions: null;
34
41
  readonly value: null;
35
42
  }
@@ -43,6 +50,7 @@ export class Group
43
50
  ClientReactiveSerializableParentNode<GeneralChildNode>
44
51
  {
45
52
  private readonly childrenState: ChildrenState<GeneralChildNode>;
53
+ private readonly attributeState: AttributeState;
46
54
 
47
55
  override readonly [XPathNodeKindKey] = 'element';
48
56
 
@@ -65,11 +73,13 @@ export class Group
65
73
  ) {
66
74
  super(parent, instanceNode, definition);
67
75
 
68
- this.appearances = definition.bodyElement.appearances;
76
+ this.appearances = definition.bodyElement?.appearances ?? null;
69
77
 
70
78
  const childrenState = createChildrenState<Group, GeneralChildNode>(this);
79
+ const attributeState = createAttributeState(this.scope);
71
80
 
72
81
  this.childrenState = childrenState;
82
+ this.attributeState = attributeState;
73
83
 
74
84
  const state = createSharedNodeState(
75
85
  this.scope,
@@ -82,6 +92,7 @@ export class Group
82
92
  label: createNodeLabel(this, definition),
83
93
  hint: null,
84
94
  children: childrenState.childIds,
95
+ attributes: attributeState.getAttributes,
85
96
  valueOptions: null,
86
97
  value: null,
87
98
  },
@@ -97,6 +108,7 @@ export class Group
97
108
  );
98
109
 
99
110
  childrenState.setChildren(buildChildren(this));
111
+ attributeState.setAttributes(buildAttributes(this));
100
112
  this.validationState = createAggregatedViolations(this, this.instanceConfig);
101
113
  this.instanceState = createParentNodeInstanceState(this);
102
114
  }
@@ -104,4 +116,8 @@ export class Group
104
116
  getChildren(): readonly GeneralChildNode[] {
105
117
  return this.childrenState.getChildren();
106
118
  }
119
+
120
+ getAttributes(): readonly Attribute[] {
121
+ return this.attributeState.getAttributes();
122
+ }
107
123
  }
@@ -130,6 +130,7 @@ export class InputControl<V extends ValueType = ValueType>
130
130
  label: createNodeLabel(this, definition),
131
131
  hint: createFieldHint(this, definition),
132
132
  children: null,
133
+ attributes: null,
133
134
  valueOptions: null,
134
135
  value: this.valueState,
135
136
  instanceValue: this.getInstanceValue,
@@ -76,6 +76,7 @@ export class ModelValue<V extends ValueType = ValueType>
76
76
  label: null,
77
77
  hint: null,
78
78
  children: null,
79
+ attributes: null,
79
80
  valueOptions: null,
80
81
  value: this.valueState,
81
82
  instanceValue: this.getInstanceValue,
@@ -105,6 +105,7 @@ export class Note<V extends ValueType = ValueType>
105
105
  noteText,
106
106
 
107
107
  children: null,
108
+ attributes: null,
108
109
  valueOptions: null,
109
110
  value: this.valueState,
110
111
  instanceValue: this.getInstanceValue,
@@ -17,6 +17,10 @@ import { EngineXPathEvaluator } from '../integration/xpath/EngineXPathEvaluator.
17
17
  import type { StaticDocument } from '../integration/xpath/static-dom/StaticDocument.ts';
18
18
  import { createPrimaryInstanceState } from '../lib/client-reactivity/instance-state/createPrimaryInstanceState.ts';
19
19
  import { prepareInstancePayload } from '../lib/client-reactivity/instance-state/prepareInstancePayload.ts';
20
+ import {
21
+ createAttributeState,
22
+ type AttributeState,
23
+ } from '../lib/reactivity/createAttributeState.ts';
20
24
  import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
21
25
  import { createTranslationState } from '../lib/reactivity/createTranslationState.ts';
22
26
  import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
@@ -32,7 +36,9 @@ import type { ModelDefinition } from '../parse/model/ModelDefinition.ts';
32
36
  import type { RootDefinition } from '../parse/model/RootDefinition.ts';
33
37
  import type { SecondaryInstancesDefinition } from '../parse/model/SecondaryInstance/SecondaryInstancesDefinition.ts';
34
38
  import { InstanceNode } from './abstract/InstanceNode.ts';
39
+ import { buildAttributes } from './attachments/buildAttributes.ts';
35
40
  import { InstanceAttachmentsState } from './attachments/InstanceAttachmentsState.ts';
41
+ import type { Attribute } from './Attribute.ts';
36
42
  import type { InitialInstanceState } from './input/InitialInstanceState.ts';
37
43
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
38
44
  import type { InstanceConfig } from './internal-api/InstanceConfig.ts';
@@ -71,6 +77,7 @@ interface PrimaryInstanceStateSpec {
71
77
  readonly label: null;
72
78
  readonly hint: null;
73
79
  readonly children: Accessor<readonly FormNodeID[]>;
80
+ readonly attributes: Accessor<readonly Attribute[]>;
74
81
  readonly valueOptions: null;
75
82
  readonly value: null;
76
83
 
@@ -154,6 +161,8 @@ export class PrimaryInstance<
154
161
  readonly evaluator: EngineXPathEvaluator;
155
162
  override readonly contextNode = this;
156
163
 
164
+ private readonly attributeState: AttributeState;
165
+
157
166
  constructor(options: PrimaryInstanceOptions<Mode>) {
158
167
  const { mode, initialState, scope, model, secondaryInstances, config } = options;
159
168
  const { instance: modelInstance } = model;
@@ -193,8 +202,10 @@ export class PrimaryInstance<
193
202
  this.classes = definition.classes;
194
203
 
195
204
  const childrenState = createChildrenState<this, Root>(this);
205
+ const attributeState = createAttributeState(this.scope);
196
206
 
197
207
  this.getChildren = childrenState.getChildren;
208
+ this.attributeState = attributeState;
198
209
 
199
210
  const stateSpec: PrimaryInstanceStateSpec = {
200
211
  activeLanguage: getActiveLanguage,
@@ -207,6 +218,7 @@ export class PrimaryInstance<
207
218
  valueOptions: null,
208
219
  value: null,
209
220
  children: childrenState.childIds,
221
+ attributes: attributeState.getAttributes,
210
222
  };
211
223
 
212
224
  const state = createSharedNodeState(scope, stateSpec, config);
@@ -227,6 +239,7 @@ export class PrimaryInstance<
227
239
  this.instanceState = createPrimaryInstanceState(this);
228
240
 
229
241
  childrenState.setChildren([root]);
242
+ attributeState.setAttributes(buildAttributes(this));
230
243
  setIsAttached(true);
231
244
  }
232
245
 
@@ -279,4 +292,8 @@ export class PrimaryInstance<
279
292
 
280
293
  return Promise.resolve(result);
281
294
  }
295
+
296
+ getAttributes(): readonly Attribute[] {
297
+ return this.attributeState.getAttributes();
298
+ }
282
299
  }