@getodk/xforms-engine 0.15.0 → 0.16.1

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 (111) hide show
  1. package/dist/client/AttributeNode.d.ts +4 -6
  2. package/dist/client/BaseNode.d.ts +5 -0
  3. package/dist/client/form/FormInstanceConfig.d.ts +15 -0
  4. package/dist/index.js +654 -429
  5. package/dist/index.js.map +1 -1
  6. package/dist/instance/Attribute.d.ts +13 -19
  7. package/dist/instance/Group.d.ts +2 -1
  8. package/dist/instance/InputControl.d.ts +5 -0
  9. package/dist/instance/ModelValue.d.ts +4 -0
  10. package/dist/instance/Note.d.ts +4 -0
  11. package/dist/instance/PrimaryInstance.d.ts +3 -2
  12. package/dist/instance/RangeControl.d.ts +4 -0
  13. package/dist/instance/RankControl.d.ts +5 -1
  14. package/dist/instance/Root.d.ts +2 -1
  15. package/dist/instance/SelectControl.d.ts +5 -1
  16. package/dist/instance/TriggerControl.d.ts +4 -0
  17. package/dist/instance/UploadControl.d.ts +3 -2
  18. package/dist/instance/abstract/DescendantNode.d.ts +5 -4
  19. package/dist/instance/abstract/InstanceNode.d.ts +6 -5
  20. package/dist/instance/abstract/ValueNode.d.ts +2 -1
  21. package/dist/instance/attachments/buildAttributes.d.ts +6 -2
  22. package/dist/instance/hierarchy.d.ts +2 -2
  23. package/dist/instance/internal-api/AttributeContext.d.ts +6 -0
  24. package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
  25. package/dist/instance/internal-api/InstanceValueContext.d.ts +6 -0
  26. package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +0 -1
  27. package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +4 -0
  28. package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +1 -1
  29. package/dist/integration/xpath/adapter/kind.d.ts +5 -3
  30. package/dist/integration/xpath/adapter/traversal.d.ts +3 -3
  31. package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +1 -0
  32. package/dist/lib/codecs/items/SingleValueItemCodec.d.ts +1 -1
  33. package/dist/lib/reactivity/createInstanceValueState.d.ts +4 -1
  34. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +2 -2
  35. package/dist/lib/xml-serialization.d.ts +1 -1
  36. package/dist/parse/XFormDOM.d.ts +3 -0
  37. package/dist/parse/expression/ActionComputationExpression.d.ts +4 -0
  38. package/dist/parse/model/ActionDefinition.d.ts +15 -0
  39. package/dist/parse/model/AttributeDefinition.d.ts +5 -1
  40. package/dist/parse/model/BindPreloadDefinition.d.ts +6 -10
  41. package/dist/parse/model/Event.d.ts +8 -0
  42. package/dist/parse/model/LeafNodeDefinition.d.ts +5 -2
  43. package/dist/parse/model/ModelActionMap.d.ts +9 -0
  44. package/dist/parse/model/ModelDefinition.d.ts +8 -1
  45. package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
  46. package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
  47. package/dist/parse/model/RootDefinition.d.ts +1 -0
  48. package/dist/solid.js +654 -429
  49. package/dist/solid.js.map +1 -1
  50. package/package.json +21 -17
  51. package/src/client/AttributeNode.ts +4 -6
  52. package/src/client/BaseNode.ts +6 -0
  53. package/src/client/form/FormInstanceConfig.ts +17 -0
  54. package/src/client/validation.ts +1 -1
  55. package/src/entrypoints/FormInstance.ts +1 -0
  56. package/src/instance/Attribute.ts +43 -59
  57. package/src/instance/Group.ts +5 -6
  58. package/src/instance/InputControl.ts +16 -1
  59. package/src/instance/ModelValue.ts +16 -1
  60. package/src/instance/Note.ts +15 -1
  61. package/src/instance/PrimaryInstance.ts +8 -10
  62. package/src/instance/RangeControl.ts +15 -1
  63. package/src/instance/RankControl.ts +17 -2
  64. package/src/instance/Root.ts +5 -6
  65. package/src/instance/SelectControl.ts +16 -2
  66. package/src/instance/TriggerControl.ts +15 -1
  67. package/src/instance/UploadControl.ts +9 -8
  68. package/src/instance/abstract/DescendantNode.ts +4 -8
  69. package/src/instance/abstract/InstanceNode.ts +7 -5
  70. package/src/instance/abstract/ValueNode.ts +2 -1
  71. package/src/instance/attachments/buildAttributes.ts +15 -4
  72. package/src/instance/children/childrenInitOptions.ts +2 -1
  73. package/src/instance/children/normalizeChildInitOptions.ts +1 -1
  74. package/src/instance/hierarchy.ts +2 -2
  75. package/src/instance/internal-api/AttributeContext.ts +6 -0
  76. package/src/instance/internal-api/InstanceConfig.ts +6 -1
  77. package/src/instance/internal-api/InstanceValueContext.ts +6 -0
  78. package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +0 -1
  79. package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +4 -0
  80. package/src/instance/repeat/RepeatInstance.ts +3 -4
  81. package/src/integration/xpath/adapter/XFormsXPathNode.ts +1 -0
  82. package/src/integration/xpath/adapter/engineDOMAdapter.ts +2 -2
  83. package/src/integration/xpath/adapter/kind.ts +6 -1
  84. package/src/integration/xpath/adapter/names.ts +1 -0
  85. package/src/integration/xpath/adapter/traversal.ts +5 -6
  86. package/src/integration/xpath/static-dom/StaticAttribute.ts +1 -0
  87. package/src/lib/client-reactivity/instance-state/createValueNodeInstanceState.ts +2 -1
  88. package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +1 -0
  89. package/src/lib/codecs/NoteCodec.ts +1 -1
  90. package/src/lib/codecs/items/SingleValueItemCodec.ts +1 -3
  91. package/src/lib/reactivity/createInstanceValueState.ts +177 -52
  92. package/src/lib/reactivity/node-state/createSharedNodeState.ts +2 -2
  93. package/src/lib/xml-serialization.ts +9 -1
  94. package/src/parse/XFormDOM.ts +9 -0
  95. package/src/parse/body/GroupElementDefinition.ts +1 -1
  96. package/src/parse/body/control/InputControlDefinition.ts +1 -1
  97. package/src/parse/expression/ActionComputationExpression.ts +12 -0
  98. package/src/parse/model/ActionDefinition.ts +70 -0
  99. package/src/parse/model/AttributeDefinition.ts +10 -2
  100. package/src/parse/model/AttributeDefinitionMap.ts +1 -1
  101. package/src/parse/model/BindDefinition.ts +1 -6
  102. package/src/parse/model/BindPreloadDefinition.ts +44 -12
  103. package/src/parse/model/Event.ts +9 -0
  104. package/src/parse/model/LeafNodeDefinition.ts +5 -1
  105. package/src/parse/model/ModelActionMap.ts +37 -0
  106. package/src/parse/model/ModelDefinition.ts +18 -3
  107. package/src/parse/model/NoteNodeDefinition.ts +5 -2
  108. package/src/parse/model/RangeNodeDefinition.ts +5 -2
  109. package/src/parse/model/RootDefinition.ts +22 -4
  110. package/dist/lib/reactivity/createAttributeValueState.d.ts +0 -15
  111. package/src/lib/reactivity/createAttributeValueState.ts +0 -189
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getodk/xforms-engine",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "XForms engine for ODK Web Forms",
6
6
  "type": "module",
@@ -29,20 +29,20 @@
29
29
  "README.md"
30
30
  ],
31
31
  "engines": {
32
- "node": "^20.19.3 || ^22.12.0 || ^24.3.0",
33
- "yarn": "1.22.22"
32
+ "node": "^20.19.3 || ^22.12.0 || ^24.11.0",
33
+ "yarn": "4.11.0"
34
34
  },
35
35
  "scripts": {
36
- "build": "npm-run-all -nl build:*",
36
+ "build": "npm-run-all -nl 'build:*'",
37
37
  "build:clean": "rimraf dist/",
38
38
  "build:js": "vite build",
39
- "build:solid": "export VITE_BUILD_TARGET=solid && vite build",
39
+ "build:solid": "VITE_BUILD_TARGET=solid vite build",
40
40
  "dev": "vite",
41
- "docs": "npm-run-all -nl docs:*",
41
+ "docs": "npm-run-all -nl 'docs:*'",
42
42
  "docs:clean": "rimraf api-docs/",
43
43
  "docs:api": "typedoc --readme none --out api-docs --entryPoints \"src/**/*\"",
44
44
  "docs:serve": "http-server api-docs -o modules/client",
45
- "test": "npm-run-all --print-name --print-label test-node:* test-browser:*",
45
+ "test": "npm-run-all -nl 'test-node:*' 'test-browser:*'",
46
46
  "test-node:jsdom": "vitest run",
47
47
  "test-browser:chromium": "BROWSER_NAME=chromium vitest run",
48
48
  "test-browser:firefox": "BROWSER_NAME=firefox vitest run",
@@ -57,24 +57,28 @@
57
57
  "bin-packer": "1.7.0",
58
58
  "mdast-util-from-markdown": "^2.0.2",
59
59
  "papaparse": "^5.5.3",
60
- "solid-js": "^1.9.7",
60
+ "solid-js": "^1.9.10",
61
61
  "temporal-polyfill": "^0.3.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@babel/core": "^7.28.0",
65
- "@getodk/tree-sitter-xpath": "0.2.1",
66
- "@getodk/xpath": "0.9.0",
67
- "@playwright/test": "^1.55.1",
68
- "@types/papaparse": "^5.3.16",
64
+ "@babel/core": "^7.28.5",
65
+ "@getodk/tree-sitter-xpath": "0.2.2",
66
+ "@getodk/xpath": "0.9.2",
67
+ "@playwright/test": "^1.57.0",
68
+ "@types/papaparse": "^5.5.0",
69
69
  "@vitest/browser": "^3.2.4",
70
70
  "babel-plugin-transform-jsbi-to-bigint": "^1.4.2",
71
71
  "http-server": "^14.1.1",
72
- "jsdom": "^26.1.0",
73
- "typedoc": "^0.28.7",
74
- "vite": "^7.0.8",
72
+ "jsdom": "^27.2.0",
73
+ "npm-run-all2": "^8.0.4",
74
+ "rimraf": "^6.0.1",
75
+ "typedoc": "^0.28.14",
76
+ "typescript": "~5.9.3",
77
+ "vite": "^7.2.4",
75
78
  "vite-plugin-dts": "^4.5.4",
76
79
  "vite-plugin-no-bundle": "^4.0.0",
77
- "vitest": "^3.2.4"
80
+ "vitest": "^3.2.4",
81
+ "web-tree-sitter": "0.24.5"
78
82
  },
79
83
  "peerDependencies": {
80
84
  "solid-js": "^1.9.7"
@@ -1,16 +1,18 @@
1
1
  import type { Root } from '../instance/Root.ts';
2
2
  import type { AttributeDefinition } from '../parse/model/AttributeDefinition.ts';
3
+ import type { BaseNode, BaseNodeState } from './BaseNode.ts';
3
4
  import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
4
5
  import type { InstanceState } from './serialization/InstanceState.ts';
5
6
 
6
- export interface AttributeNodeState {
7
+ export interface AttributeNodeState extends BaseNodeState {
7
8
  get value(): string;
9
+ get relevant(): boolean;
8
10
  }
9
11
 
10
12
  /**
11
13
  * Base interface for common/shared aspects of attributes.
12
14
  */
13
- export interface AttributeNode {
15
+ export interface AttributeNode extends BaseNode {
14
16
  /**
15
17
  * Specifies the node's general type. This can be useful for narrowing types,
16
18
  * e.g. those of children.
@@ -30,10 +32,6 @@ export interface AttributeNode {
30
32
  */
31
33
  readonly root: Root;
32
34
 
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
35
  readonly parent: unknown;
38
36
 
39
37
  /**
@@ -1,3 +1,4 @@
1
+ import type { Attribute } from '../instance/Attribute.ts';
1
2
  import type { TokenListParser } from '../lib/TokenListParser.ts';
2
3
  import type { AnyNodeDefinition } from '../parse/model/NodeDefinition.ts';
3
4
  import type { NodeAppearances } from './NodeAppearances.ts';
@@ -129,6 +130,11 @@ export interface BaseNodeState {
129
130
  * is an internal engine consideration.
130
131
  */
131
132
  get value(): unknown;
133
+
134
+ /**
135
+ * Nodes can own attributes, which have a literal or reference value.
136
+ */
137
+ get attributes(): readonly Attribute[];
132
138
  }
133
139
 
134
140
  /**
@@ -1,6 +1,21 @@
1
1
  import type { InstanceAttachmentsConfig } from '../attachments/InstanceAttachmentsConfig.ts';
2
2
  import type { OpaqueReactiveObjectFactory } from '../OpaqueReactiveObjectFactory.ts';
3
3
 
4
+ /**
5
+ * @see https://getodk.github.io/xforms-spec/#preload-attributes
6
+ */
7
+ export interface PreloadProperties {
8
+ /**
9
+ * The unique identifier for this device. If not provided, then an identifier will be
10
+ * generated during the first page load and stored in localstorage and reused for
11
+ * subsequent form loads.
12
+ */
13
+ readonly deviceID?: string;
14
+ readonly email?: string;
15
+ readonly username?: string;
16
+ readonly phoneNumber?: string;
17
+ }
18
+
4
19
  export interface FormInstanceConfig {
5
20
  /**
6
21
  * A client may specify a generic function for constructing stateful objects.
@@ -18,4 +33,6 @@ export interface FormInstanceConfig {
18
33
  readonly stateFactory?: OpaqueReactiveObjectFactory;
19
34
 
20
35
  readonly instanceAttachments?: InstanceAttachmentsConfig;
36
+
37
+ readonly preloadProperties?: PreloadProperties;
21
38
  }
@@ -204,4 +204,4 @@ export interface NullValidationState {
204
204
  export type NodeValidationState =
205
205
  | AncestorNodeValidationState
206
206
  | LeafNodeValidationState
207
- | NullValidationState;
207
+ | NullValidationState; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents
@@ -41,6 +41,7 @@ export class FormInstance<Mode extends FormInstanceInitializationMode>
41
41
  const config: InstanceConfig = {
42
42
  clientStateFactory: instanceConfig.stateFactory ?? identity,
43
43
  computeAttachmentName: instanceConfig.instanceAttachments?.fileNameFactory ?? (() => null),
44
+ preloadProperties: instanceConfig.preloadProperties ?? {},
44
45
  };
45
46
  const primaryInstanceOptions: PrimaryInstanceOptions<Mode> = {
46
47
  ...options.instanceOptions,
@@ -1,9 +1,8 @@
1
1
  import { XPathNodeKindKey } from '@getodk/xpath';
2
2
  import type { Accessor } from 'solid-js';
3
3
  import type { AttributeNode } from '../client/AttributeNode.ts';
4
- import type { ActiveLanguage, InstanceState, NullValidationState } from '../client/index.ts';
4
+ import type { InstanceState, NullValidationState } from '../client/index.ts';
5
5
  import type { XFormsXPathAttribute } from '../integration/xpath/adapter/XFormsXPathNode.ts';
6
- import type { EngineXPathEvaluator } from '../integration/xpath/EngineXPathEvaluator.ts';
7
6
  import type { StaticAttribute } from '../integration/xpath/static-dom/StaticAttribute.ts';
8
7
  import { createAttributeNodeInstanceState } from '../lib/client-reactivity/instance-state/createAttributeNodeInstanceState.ts';
9
8
  import {
@@ -12,7 +11,11 @@ import {
12
11
  type RuntimeValue,
13
12
  } from '../lib/codecs/getSharedValueCodec.ts';
14
13
  import type { RuntimeValueSetter, RuntimeValueState } from '../lib/codecs/ValueCodec.ts';
15
- import { createAttributeValueState } from '../lib/reactivity/createAttributeValueState.ts';
14
+ import {
15
+ createAttributeState,
16
+ type AttributeState,
17
+ } from '../lib/reactivity/createAttributeState.ts';
18
+ import { createInstanceValueState } from '../lib/reactivity/createInstanceValueState.ts';
16
19
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
17
20
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
18
21
  import {
@@ -21,26 +24,23 @@ import {
21
24
  } from '../lib/reactivity/node-state/createSharedNodeState.ts';
22
25
  import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
23
26
  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 { DescendantNode, type DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
28
+ import type { AnyNode } from './hierarchy.ts';
27
29
  import type { AttributeContext } from './internal-api/AttributeContext.ts';
28
30
  import type { DecodeInstanceValue } from './internal-api/InstanceValueContext.ts';
29
31
  import type { ClientReactiveSerializableAttributeNode } from './internal-api/serialization/ClientReactiveSerializableAttributeNode.ts';
30
32
  import type { Root } from './Root.ts';
31
33
 
32
- export interface AttributeStateSpec extends DescendantNodeSharedStateSpec {
34
+ export interface AttributeStateSpec extends DescendantNodeStateSpec<string> {
33
35
  readonly children: null;
34
- readonly attributes: null;
36
+ readonly attributes: Accessor<Attribute[]>;
35
37
  readonly value: SimpleAtomicState<string>;
36
38
  readonly instanceValue: Accessor<string>;
37
- readonly label: null;
38
- readonly hint: null;
39
- readonly valueOptions: null;
39
+ readonly relevant: Accessor<boolean>;
40
40
  }
41
41
 
42
42
  export class Attribute
43
- extends InstanceNode<AttributeDefinition, AttributeStateSpec, AnyParentNode, null>
43
+ extends DescendantNode<AttributeDefinition, AttributeStateSpec, AnyNode, null>
44
44
  implements
45
45
  AttributeNode,
46
46
  ClientReactiveSerializableAttributeNode,
@@ -55,7 +55,7 @@ export class Attribute
55
55
 
56
56
  readonly nodeType = 'attribute';
57
57
  readonly currentState: CurrentState<AttributeStateSpec>;
58
- override readonly instanceState: InstanceState;
58
+ readonly instanceState: InstanceState;
59
59
 
60
60
  readonly appearances = null;
61
61
  readonly nodeOptions = null;
@@ -66,55 +66,33 @@ export class Attribute
66
66
  protected readonly getInstanceValue: Accessor<string>;
67
67
  protected readonly valueState: RuntimeValueState<RuntimeValue<'string'>>;
68
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
- };
69
+ readonly attributeState: AttributeState;
85
70
 
86
- readonly hasReadonlyAncestor: Accessor<boolean> = () => {
87
- const { parent } = this;
88
- return parent.hasReadonlyAncestor() || parent.isReadonly();
71
+ override readonly isAttached: Accessor<boolean> = () => {
72
+ return this.owner.isAttached();
89
73
  };
90
74
 
91
- readonly hasNonRelevantAncestor: Accessor<boolean> = () => {
92
- const { parent } = this;
93
- return parent.hasNonRelevantAncestor() || !parent.isRelevant();
94
- };
75
+ override readonly getXPathValue: () => string;
95
76
 
96
77
  constructor(
97
- parent: AnyParentNode,
78
+ readonly owner: AnyNode,
98
79
  definition: AttributeDefinition,
99
80
  override readonly instanceNode: StaticAttribute
100
81
  ) {
101
- const codec = getSharedValueCodec('string');
82
+ const computeReference = () => {
83
+ return `${this.owner.contextReference()}/@${this.definition.qualifiedName.getPrefixedName()}`;
84
+ };
102
85
 
103
- super(parent.instanceConfig, parent, instanceNode, definition, {
104
- scope: parent.scope,
105
- computeReference: (): string => '@' + this.definition.qualifiedName.getPrefixedName(),
106
- });
86
+ super(owner, instanceNode, definition, { computeReference });
107
87
 
108
- this.root = parent.root;
88
+ const codec = getSharedValueCodec('string');
109
89
 
110
- this.getActiveLanguage = parent.getActiveLanguage;
111
90
  this.validationState = { violations: [] };
112
91
 
113
92
  this.valueType = 'string';
114
- this.evaluator = parent.evaluator;
115
93
  this.decodeInstanceValue = codec.decodeInstanceValue;
116
94
 
117
- const instanceValueState = createAttributeValueState(this);
95
+ const instanceValueState = createInstanceValueState(this);
118
96
  const valueState = codec.createRuntimeValueState(instanceValueState);
119
97
 
120
98
  const [getInstanceValue] = instanceValueState;
@@ -122,26 +100,23 @@ export class Attribute
122
100
 
123
101
  this.getInstanceValue = getInstanceValue;
124
102
  this.setValueState = setValueState;
125
- this.getXPathValue = () => {
126
- return this.getInstanceValue();
127
- };
128
103
  this.valueState = valueState;
129
104
 
130
105
  const state = createSharedNodeState(
131
- this.scope,
106
+ owner.scope,
132
107
  {
108
+ value: this.valueState,
109
+ instanceValue: this.getInstanceValue,
110
+ relevant: this.owner.isRelevant,
111
+
112
+ readonly: () => true,
133
113
  reference: this.contextReference,
134
- readonly: this.isReadonly,
135
- relevant: this.isRelevant,
136
114
  required: () => false,
137
-
138
- label: null,
139
- hint: null,
140
115
  children: null,
141
- valueOptions: null,
142
- value: this.valueState,
143
- instanceValue: this.getInstanceValue,
144
- attributes: null,
116
+ label: () => null,
117
+ hint: () => null,
118
+ attributes: () => [],
119
+ valueOptions: () => [],
145
120
  },
146
121
  this.instanceConfig
147
122
  );
@@ -150,6 +125,11 @@ export class Attribute
150
125
  this.engineState = state.engineState;
151
126
  this.currentState = state.currentState;
152
127
  this.instanceState = createAttributeNodeInstanceState(this);
128
+ this.attributeState = createAttributeState(this.scope);
129
+
130
+ this.getXPathValue = () => {
131
+ return this.getInstanceValue();
132
+ };
153
133
  }
154
134
 
155
135
  setValue(value: string): Root {
@@ -158,6 +138,10 @@ export class Attribute
158
138
  return this.root;
159
139
  }
160
140
 
141
+ override getAttributes(): readonly Attribute[] {
142
+ return [];
143
+ }
144
+
161
145
  getChildren(): readonly [] {
162
146
  return [];
163
147
  }
@@ -50,13 +50,13 @@ export class Group
50
50
  ClientReactiveSerializableParentNode<GeneralChildNode>
51
51
  {
52
52
  private readonly childrenState: ChildrenState<GeneralChildNode>;
53
- private readonly attributeState: AttributeState;
54
53
 
55
54
  override readonly [XPathNodeKindKey] = 'element';
56
55
 
57
56
  // InstanceNode
58
57
  protected readonly state: SharedNodeState<GroupStateSpec>;
59
58
  protected override engineState: EngineState<GroupStateSpec>;
59
+ readonly attributeState: AttributeState;
60
60
 
61
61
  // GroupNode
62
62
  readonly nodeType = 'group';
@@ -76,10 +76,9 @@ export class Group
76
76
  this.appearances = definition.bodyElement?.appearances ?? null;
77
77
 
78
78
  const childrenState = createChildrenState<Group, GeneralChildNode>(this);
79
- const attributeState = createAttributeState(this.scope);
79
+ this.attributeState = createAttributeState(this.scope);
80
80
 
81
81
  this.childrenState = childrenState;
82
- this.attributeState = attributeState;
83
82
 
84
83
  const state = createSharedNodeState(
85
84
  this.scope,
@@ -92,7 +91,7 @@ export class Group
92
91
  label: createNodeLabel(this, definition),
93
92
  hint: null,
94
93
  children: childrenState.childIds,
95
- attributes: attributeState.getAttributes,
94
+ attributes: this.attributeState.getAttributes,
96
95
  valueOptions: null,
97
96
  value: null,
98
97
  },
@@ -108,7 +107,7 @@ export class Group
108
107
  );
109
108
 
110
109
  childrenState.setChildren(buildChildren(this));
111
- attributeState.setAttributes(buildAttributes(this));
110
+ this.attributeState.setAttributes(buildAttributes(this));
112
111
  this.validationState = createAggregatedViolations(this, this.instanceConfig);
113
112
  this.instanceState = createParentNodeInstanceState(this);
114
113
  }
@@ -117,7 +116,7 @@ export class Group
117
116
  return this.childrenState.getChildren();
118
117
  }
119
118
 
120
- getAttributes(): readonly Attribute[] {
119
+ override getAttributes(): readonly Attribute[] {
121
120
  return this.attributeState.getAttributes();
122
121
  }
123
122
  }
@@ -13,6 +13,10 @@ import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPat
13
13
  import type { StaticLeafElement } from '../integration/xpath/static-dom/StaticElement.ts';
14
14
  import type { RuntimeInputValue, RuntimeValue } from '../lib/codecs/getSharedValueCodec.ts';
15
15
  import { getSharedValueCodec } from '../lib/codecs/getSharedValueCodec.ts';
16
+ import {
17
+ createAttributeState,
18
+ type AttributeState,
19
+ } from '../lib/reactivity/createAttributeState.ts';
16
20
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
17
21
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
18
22
  import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
@@ -21,6 +25,8 @@ import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
21
25
  import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
22
26
  import type { InputControlDefinition } from '../parse/body/control/InputControlDefinition.ts';
23
27
  import { ValueNode, type ValueNodeStateSpec } from './abstract/ValueNode.ts';
28
+ import { buildAttributes } from './attachments/buildAttributes.ts';
29
+ import type { Attribute } from './Attribute.ts';
24
30
  import type { GeneralParentNode } from './hierarchy.ts';
25
31
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
26
32
  import type { ClientReactiveSerializableValueNode } from './internal-api/serialization/ClientReactiveSerializableValueNode.ts';
@@ -69,6 +75,7 @@ const nodeOptionsFactoryByType: NodeOptionsFactoryByType = {
69
75
  interface InputControlStateSpec<V extends ValueType> extends ValueNodeStateSpec<RuntimeValue<V>> {
70
76
  readonly label: Accessor<TextRange<'label'> | null>;
71
77
  readonly hint: Accessor<TextRange<'hint'> | null>;
78
+ readonly attributes: Accessor<readonly Attribute[]>;
72
79
  readonly valueOptions: null;
73
80
  }
74
81
 
@@ -100,6 +107,7 @@ export class InputControl<V extends ValueType = ValueType>
100
107
  // InstanceNode
101
108
  protected readonly state: SharedNodeState<InputControlStateSpec<V>>;
102
109
  protected readonly engineState: EngineState<InputControlStateSpec<V>>;
110
+ readonly attributeState: AttributeState;
103
111
 
104
112
  // InputNode
105
113
  readonly nodeType = 'input';
@@ -118,6 +126,7 @@ export class InputControl<V extends ValueType = ValueType>
118
126
 
119
127
  this.appearances = definition.bodyElement.appearances;
120
128
  this.nodeOptions = nodeOptionsFactoryByType[definition.valueType](definition.bodyElement);
129
+ this.attributeState = createAttributeState(this.scope);
121
130
 
122
131
  const state = createSharedNodeState(
123
132
  this.scope,
@@ -130,7 +139,7 @@ export class InputControl<V extends ValueType = ValueType>
130
139
  label: createNodeLabel(this, definition),
131
140
  hint: createFieldHint(this, definition),
132
141
  children: null,
133
- attributes: null,
142
+ attributes: this.attributeState.getAttributes,
134
143
  valueOptions: null,
135
144
  value: this.valueState,
136
145
  instanceValue: this.getInstanceValue,
@@ -138,6 +147,8 @@ export class InputControl<V extends ValueType = ValueType>
138
147
  this.instanceConfig
139
148
  );
140
149
 
150
+ this.attributeState.setAttributes(buildAttributes(this));
151
+
141
152
  this.state = state;
142
153
  this.engineState = state.engineState;
143
154
  this.currentState = state.currentState;
@@ -148,6 +159,10 @@ export class InputControl<V extends ValueType = ValueType>
148
159
 
149
160
  return this.root;
150
161
  }
162
+
163
+ override getAttributes(): readonly Attribute[] {
164
+ return this.attributeState.getAttributes();
165
+ }
151
166
  }
152
167
 
153
168
  export type AnyInputControl =
@@ -5,11 +5,17 @@ import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPat
5
5
  import type { StaticLeafElement } from '../integration/xpath/static-dom/StaticElement.ts';
6
6
  import type { RuntimeInputValue, RuntimeValue } from '../lib/codecs/getSharedValueCodec.ts';
7
7
  import { getSharedValueCodec } from '../lib/codecs/getSharedValueCodec.ts';
8
+ import {
9
+ createAttributeState,
10
+ type AttributeState,
11
+ } from '../lib/reactivity/createAttributeState.ts';
8
12
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
9
13
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
10
14
  import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
11
15
  import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
16
+ import type { Attribute } from './Attribute.ts';
12
17
  import { ValueNode, type ValueNodeStateSpec } from './abstract/ValueNode.ts';
18
+ import { buildAttributes } from './attachments/buildAttributes.ts';
13
19
  import type { GeneralParentNode } from './hierarchy.ts';
14
20
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
15
21
  import type { ValidationContext } from './internal-api/ValidationContext.ts';
@@ -49,6 +55,7 @@ export class ModelValue<V extends ValueType = ValueType>
49
55
  // InstanceNode
50
56
  protected readonly state: SharedNodeState<ModelValueStateSpec<V>>;
51
57
  protected readonly engineState: EngineState<ModelValueStateSpec<V>>;
58
+ readonly attributeState: AttributeState;
52
59
 
53
60
  // ModelValueNode
54
61
  readonly nodeType = 'model-value';
@@ -65,6 +72,8 @@ export class ModelValue<V extends ValueType = ValueType>
65
72
 
66
73
  super(parent, instanceNode, definition, codec);
67
74
 
75
+ this.attributeState = createAttributeState(this.scope);
76
+
68
77
  const state = createSharedNodeState(
69
78
  this.scope,
70
79
  {
@@ -76,7 +85,7 @@ export class ModelValue<V extends ValueType = ValueType>
76
85
  label: null,
77
86
  hint: null,
78
87
  children: null,
79
- attributes: null,
88
+ attributes: this.attributeState.getAttributes,
80
89
  valueOptions: null,
81
90
  value: this.valueState,
82
91
  instanceValue: this.getInstanceValue,
@@ -84,10 +93,16 @@ export class ModelValue<V extends ValueType = ValueType>
84
93
  this.instanceConfig
85
94
  );
86
95
 
96
+ this.attributeState.setAttributes(buildAttributes(this));
97
+
87
98
  this.state = state;
88
99
  this.engineState = state.engineState;
89
100
  this.currentState = state.currentState;
90
101
  }
102
+
103
+ override getAttributes(): readonly Attribute[] {
104
+ return this.attributeState.getAttributes();
105
+ }
91
106
  }
92
107
 
93
108
  export type AnyModelValue =
@@ -8,6 +8,10 @@ import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPat
8
8
  import type { StaticLeafElement } from '../integration/xpath/static-dom/StaticElement.ts';
9
9
  import { getNoteCodec } from '../lib/codecs/getNoteCodec.ts';
10
10
  import type { NoteInputValue, NoteRuntimeValue } from '../lib/codecs/NoteCodec.ts';
11
+ import {
12
+ createAttributeState,
13
+ type AttributeState,
14
+ } from '../lib/reactivity/createAttributeState.ts';
11
15
  import { createNoteReadonlyThunk } from '../lib/reactivity/createNoteReadonlyThunk.ts';
12
16
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
13
17
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
@@ -18,6 +22,8 @@ import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
18
22
  import { createNoteText, type ComputedNoteText } from '../lib/reactivity/text/createNoteText.ts';
19
23
  import type { NoteNodeDefinition } from '../parse/model/NoteNodeDefinition.ts';
20
24
  import { ValueNode, type ValueNodeStateSpec } from './abstract/ValueNode.ts';
25
+ import { buildAttributes } from './attachments/buildAttributes.ts';
26
+ import type { Attribute } from './Attribute.ts';
21
27
  import type { GeneralParentNode } from './hierarchy.ts';
22
28
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
23
29
  import type { ClientReactiveSerializableValueNode } from './internal-api/serialization/ClientReactiveSerializableValueNode.ts';
@@ -46,6 +52,7 @@ export class Note<V extends ValueType = ValueType>
46
52
  // InstanceNode
47
53
  protected readonly state: SharedNodeState<NoteStateSpec<V>>;
48
54
  protected readonly engineState: EngineState<NoteStateSpec<V>>;
55
+ readonly attributeState: AttributeState;
49
56
 
50
57
  // NoteNode
51
58
  readonly nodeType = 'note';
@@ -66,6 +73,7 @@ export class Note<V extends ValueType = ValueType>
66
73
 
67
74
  const isReadonly = createNoteReadonlyThunk(this, definition);
68
75
  const noteTextComputation = createNoteText(this, definition.noteTextDefinition);
76
+ this.attributeState = createAttributeState(this.scope);
69
77
 
70
78
  let noteText: ComputedNoteText;
71
79
  let label: Accessor<TextRange<'label', 'form'> | null>;
@@ -105,7 +113,7 @@ export class Note<V extends ValueType = ValueType>
105
113
  noteText,
106
114
 
107
115
  children: null,
108
- attributes: null,
116
+ attributes: this.attributeState.getAttributes,
109
117
  valueOptions: null,
110
118
  value: this.valueState,
111
119
  instanceValue: this.getInstanceValue,
@@ -113,10 +121,16 @@ export class Note<V extends ValueType = ValueType>
113
121
  this.instanceConfig
114
122
  );
115
123
 
124
+ this.attributeState.setAttributes(buildAttributes(this));
125
+
116
126
  this.state = state;
117
127
  this.engineState = state.engineState;
118
128
  this.currentState = state.currentState;
119
129
  }
130
+
131
+ override getAttributes(): readonly Attribute[] {
132
+ return this.attributeState.getAttributes();
133
+ }
120
134
  }
121
135
 
122
136
  export type AnyNote =
@@ -128,6 +128,7 @@ export class PrimaryInstance<
128
128
  // InstanceNode
129
129
  protected readonly state: SharedNodeState<PrimaryInstanceStateSpec>;
130
130
  protected readonly engineState: EngineState<PrimaryInstanceStateSpec>;
131
+ readonly attributeState: AttributeState;
131
132
 
132
133
  override readonly instanceNode: StaticDocument;
133
134
  readonly getChildren: Accessor<readonly Root[]>;
@@ -161,8 +162,6 @@ export class PrimaryInstance<
161
162
  readonly evaluator: EngineXPathEvaluator;
162
163
  override readonly contextNode = this;
163
164
 
164
- private readonly attributeState: AttributeState;
165
-
166
165
  constructor(options: PrimaryInstanceOptions<Mode>) {
167
166
  const { mode, initialState, scope, model, secondaryInstances, config } = options;
168
167
  const { instance: modelInstance } = model;
@@ -202,10 +201,9 @@ export class PrimaryInstance<
202
201
  this.classes = definition.classes;
203
202
 
204
203
  const childrenState = createChildrenState<this, Root>(this);
205
- const attributeState = createAttributeState(this.scope);
204
+ this.attributeState = createAttributeState(this.scope);
206
205
 
207
206
  this.getChildren = childrenState.getChildren;
208
- this.attributeState = attributeState;
209
207
 
210
208
  const stateSpec: PrimaryInstanceStateSpec = {
211
209
  activeLanguage: getActiveLanguage,
@@ -218,7 +216,7 @@ export class PrimaryInstance<
218
216
  valueOptions: null,
219
217
  value: null,
220
218
  children: childrenState.childIds,
221
- attributes: attributeState.getAttributes,
219
+ attributes: this.attributeState.getAttributes,
222
220
  };
223
221
 
224
222
  const state = createSharedNodeState(scope, stateSpec, config);
@@ -239,10 +237,14 @@ export class PrimaryInstance<
239
237
  this.instanceState = createPrimaryInstanceState(this);
240
238
 
241
239
  childrenState.setChildren([root]);
242
- attributeState.setAttributes(buildAttributes(this));
240
+ this.attributeState.setAttributes(buildAttributes(this));
243
241
  setIsAttached(true);
244
242
  }
245
243
 
244
+ override getAttributes(): readonly Attribute[] {
245
+ return this.attributeState.getAttributes();
246
+ }
247
+
246
248
  // PrimaryInstanceDocument
247
249
  /**
248
250
  * @todo Note that this method's signature is intentionally derived from
@@ -292,8 +294,4 @@ export class PrimaryInstance<
292
294
 
293
295
  return Promise.resolve(result);
294
296
  }
295
-
296
- getAttributes(): readonly Attribute[] {
297
- return this.attributeState.getAttributes();
298
- }
299
297
  }