@getodk/xforms-engine 0.16.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 (54) hide show
  1. package/dist/client/AttributeNode.d.ts +4 -3
  2. package/dist/index.js +272 -211
  3. package/dist/index.js.map +1 -1
  4. package/dist/instance/Attribute.d.ts +11 -23
  5. package/dist/instance/Group.d.ts +3 -0
  6. package/dist/instance/InputControl.d.ts +3 -0
  7. package/dist/instance/ModelValue.d.ts +4 -0
  8. package/dist/instance/Note.d.ts +4 -0
  9. package/dist/instance/PrimaryInstance.d.ts +3 -0
  10. package/dist/instance/RangeControl.d.ts +4 -0
  11. package/dist/instance/RankControl.d.ts +5 -1
  12. package/dist/instance/Root.d.ts +3 -0
  13. package/dist/instance/SelectControl.d.ts +5 -1
  14. package/dist/instance/TriggerControl.d.ts +4 -0
  15. package/dist/instance/UploadControl.d.ts +3 -0
  16. package/dist/instance/abstract/DescendantNode.d.ts +5 -4
  17. package/dist/instance/abstract/InstanceNode.d.ts +4 -3
  18. package/dist/instance/hierarchy.d.ts +2 -1
  19. package/dist/instance/repeat/RepeatInstance.d.ts +2 -0
  20. package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +1 -1
  21. package/dist/integration/xpath/adapter/kind.d.ts +5 -3
  22. package/dist/integration/xpath/adapter/traversal.d.ts +3 -3
  23. package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +1 -0
  24. package/dist/parse/model/AttributeDefinition.d.ts +2 -0
  25. package/dist/solid.js +272 -211
  26. package/dist/solid.js.map +1 -1
  27. package/package.json +2 -2
  28. package/src/client/AttributeNode.ts +4 -3
  29. package/src/instance/Attribute.ts +38 -54
  30. package/src/instance/Group.ts +12 -4
  31. package/src/instance/InputControl.ts +12 -4
  32. package/src/instance/ModelValue.ts +13 -4
  33. package/src/instance/Note.ts +13 -4
  34. package/src/instance/PrimaryInstance.ts +12 -4
  35. package/src/instance/RangeControl.ts +13 -4
  36. package/src/instance/RankControl.ts +14 -5
  37. package/src/instance/Root.ts +12 -4
  38. package/src/instance/SelectControl.ts +14 -5
  39. package/src/instance/TriggerControl.ts +13 -4
  40. package/src/instance/UploadControl.ts +13 -3
  41. package/src/instance/abstract/DescendantNode.ts +4 -3
  42. package/src/instance/abstract/InstanceNode.ts +5 -3
  43. package/src/instance/attachments/buildAttributes.ts +5 -1
  44. package/src/instance/children/childrenInitOptions.ts +2 -1
  45. package/src/instance/hierarchy.ts +2 -0
  46. package/src/instance/repeat/RepeatInstance.ts +11 -3
  47. package/src/integration/xpath/adapter/XFormsXPathNode.ts +1 -0
  48. package/src/integration/xpath/adapter/engineDOMAdapter.ts +2 -2
  49. package/src/integration/xpath/adapter/kind.ts +6 -1
  50. package/src/integration/xpath/adapter/names.ts +1 -0
  51. package/src/integration/xpath/adapter/traversal.ts +5 -6
  52. package/src/integration/xpath/static-dom/StaticAttribute.ts +1 -0
  53. package/src/lib/reactivity/createInstanceValueState.ts +29 -3
  54. package/src/parse/model/AttributeDefinition.ts +7 -0
@@ -12,7 +12,10 @@ import type { InstanceState } from '../client/serialization/InstanceState.ts';
12
12
  import type { AncestorNodeValidationState } from '../client/validation.ts';
13
13
  import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPathNode.ts';
14
14
  import { createRootInstanceState } from '../lib/client-reactivity/instance-state/createRootInstanceState.ts';
15
- import { createAttributeState } from '../lib/reactivity/createAttributeState.ts';
15
+ import {
16
+ createAttributeState,
17
+ type AttributeState,
18
+ } from '../lib/reactivity/createAttributeState.ts';
16
19
  import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
17
20
  import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
18
21
  import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
@@ -67,6 +70,7 @@ export class Root
67
70
  // DescendantNode
68
71
  protected readonly state: SharedNodeState<RootStateSpec>;
69
72
  protected readonly engineState: EngineState<RootStateSpec>;
73
+ readonly attributeState: AttributeState;
70
74
 
71
75
  override readonly hasReadonlyAncestor = () => false;
72
76
  override readonly isSelfReadonly = () => false;
@@ -99,7 +103,7 @@ export class Root
99
103
  this.classes = parent.classes;
100
104
 
101
105
  const childrenState = createChildrenState<Root, GeneralChildNode>(this);
102
- const attributeState = createAttributeState(this.scope);
106
+ this.attributeState = createAttributeState(this.scope);
103
107
 
104
108
  this.childrenState = childrenState;
105
109
  this.languages = parent.languages;
@@ -117,7 +121,7 @@ export class Root
117
121
  valueOptions: null,
118
122
  value: null,
119
123
  children: childrenState.childIds,
120
- attributes: attributeState.getAttributes,
124
+ attributes: this.attributeState.getAttributes,
121
125
  },
122
126
  this.instanceConfig
123
127
  );
@@ -131,7 +135,7 @@ export class Root
131
135
  );
132
136
 
133
137
  childrenState.setChildren(buildChildren(this));
134
- attributeState.setAttributes(buildAttributes(this));
138
+ this.attributeState.setAttributes(buildAttributes(this));
135
139
  this.validationState = createAggregatedViolations(this, this.instanceConfig);
136
140
  this.instanceState = createRootInstanceState(this);
137
141
  }
@@ -140,6 +144,10 @@ export class Root
140
144
  return this.childrenState.getChildren();
141
145
  }
142
146
 
147
+ override getAttributes(): readonly Attribute[] {
148
+ return this.attributeState.getAttributes();
149
+ }
150
+
143
151
  // RootNode
144
152
  setLanguage(language: FormLanguage): Root {
145
153
  this.rootDocument.setLanguage(language);
@@ -1,4 +1,4 @@
1
- import { type XPathChoiceNode, XPathNodeKindKey } from '@getodk/xpath';
1
+ import { XPathNodeKindKey, type XPathChoiceNode } from '@getodk/xpath';
2
2
  import type { Accessor } from 'solid-js';
3
3
  import { createMemo } from 'solid-js';
4
4
  import type {
@@ -14,7 +14,10 @@ import { SelectValueTypeError } from '../error/SelectValueTypeError.ts';
14
14
  import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPathNode.ts';
15
15
  import type { StaticLeafElement } from '../integration/xpath/static-dom/StaticElement.ts';
16
16
  import { getSelectCodec } from '../lib/codecs/getSelectCodec.ts';
17
- import { createAttributeState } from '../lib/reactivity/createAttributeState.ts';
17
+ import {
18
+ createAttributeState,
19
+ type AttributeState,
20
+ } from '../lib/reactivity/createAttributeState.ts';
18
21
  import { createItemCollection } from '../lib/reactivity/createItemCollection.ts';
19
22
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
20
23
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
@@ -24,6 +27,7 @@ import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
24
27
  import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
25
28
  import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
26
29
  import type { SelectType } from '../parse/body/control/SelectControlDefinition.ts';
30
+ import type { Attribute } from './Attribute.ts';
27
31
  import type { Root } from './Root.ts';
28
32
  import type { ValueNodeStateSpec } from './abstract/ValueNode.ts';
29
33
  import { ValueNode } from './abstract/ValueNode.ts';
@@ -91,6 +95,7 @@ export class SelectControl
91
95
  // InstanceNode
92
96
  protected readonly state: SharedNodeState<SelectControlStateSpec>;
93
97
  protected readonly engineState: EngineState<SelectControlStateSpec>;
98
+ readonly attributeState: AttributeState;
94
99
 
95
100
  // SelectNode
96
101
  readonly nodeType = 'select';
@@ -110,7 +115,7 @@ export class SelectControl
110
115
 
111
116
  this.appearances = definition.bodyElement.appearances;
112
117
  this.selectType = definition.bodyElement.type;
113
- const attributeState = createAttributeState(this.scope);
118
+ this.attributeState = createAttributeState(this.scope);
114
119
 
115
120
  const valueOptions = createItemCollection(this);
116
121
 
@@ -156,7 +161,7 @@ export class SelectControl
156
161
  label: createNodeLabel(this, definition),
157
162
  hint: createFieldHint(this, definition),
158
163
  children: null,
159
- attributes: attributeState.getAttributes,
164
+ attributes: this.attributeState.getAttributes,
160
165
  valueOptions,
161
166
  value: valueState,
162
167
  instanceValue: this.getInstanceValue,
@@ -165,7 +170,7 @@ export class SelectControl
165
170
  this.instanceConfig
166
171
  );
167
172
 
168
- attributeState.setAttributes(buildAttributes(this));
173
+ this.attributeState.setAttributes(buildAttributes(this));
169
174
 
170
175
  this.state = state;
171
176
  this.engineState = state.engineState;
@@ -243,4 +248,8 @@ export class SelectControl
243
248
  const option = this.mapOptionsByValue().get(value);
244
249
  return option?.label?.asString ?? null;
245
250
  }
251
+
252
+ override getAttributes(): readonly Attribute[] {
253
+ return this.attributeState.getAttributes();
254
+ }
246
255
  }
@@ -8,7 +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 type { TriggerInputValue, TriggerRuntimeValue } from '../lib/codecs/TriggerCodec.ts';
10
10
  import { TriggerCodec } from '../lib/codecs/TriggerCodec.ts';
11
- import { createAttributeState } from '../lib/reactivity/createAttributeState.ts';
11
+ import {
12
+ createAttributeState,
13
+ type AttributeState,
14
+ } from '../lib/reactivity/createAttributeState.ts';
12
15
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
13
16
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
14
17
  import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
@@ -16,6 +19,7 @@ import { createSharedNodeState } from '../lib/reactivity/node-state/createShared
16
19
  import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
17
20
  import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
18
21
  import type { UnknownAppearanceDefinition } from '../parse/body/appearance/unknownAppearanceParser.ts';
22
+ import type { Attribute } from './Attribute.ts';
19
23
  import type { Root } from './Root.ts';
20
24
  import { ValueNode, type ValueNodeStateSpec } from './abstract/ValueNode.ts';
21
25
  import { buildAttributes } from './attachments/buildAttributes.ts';
@@ -75,6 +79,7 @@ export class TriggerControl
75
79
  // InstanceNode
76
80
  protected readonly state: SharedNodeState<TriggerControlStateSpec>;
77
81
  protected readonly engineState: EngineState<TriggerControlStateSpec>;
82
+ readonly attributeState: AttributeState;
78
83
 
79
84
  // TriggerNode
80
85
  readonly nodeType = 'trigger';
@@ -90,7 +95,7 @@ export class TriggerControl
90
95
  super(parent, instanceNode, definition, codec);
91
96
 
92
97
  this.appearances = definition.bodyElement.appearances;
93
- const attributeState = createAttributeState(this.scope);
98
+ this.attributeState = createAttributeState(this.scope);
94
99
 
95
100
  const state = createSharedNodeState(
96
101
  this.scope,
@@ -103,7 +108,7 @@ export class TriggerControl
103
108
  label: createNodeLabel(this, definition),
104
109
  hint: createFieldHint(this, definition),
105
110
  children: null,
106
- attributes: attributeState.getAttributes,
111
+ attributes: this.attributeState.getAttributes,
107
112
  valueOptions: null,
108
113
  value: this.valueState,
109
114
  instanceValue: this.getInstanceValue,
@@ -111,13 +116,17 @@ export class TriggerControl
111
116
  this.instanceConfig
112
117
  );
113
118
 
114
- attributeState.setAttributes(buildAttributes(this));
119
+ this.attributeState.setAttributes(buildAttributes(this));
115
120
 
116
121
  this.state = state;
117
122
  this.engineState = state.engineState;
118
123
  this.currentState = state.currentState;
119
124
  }
120
125
 
126
+ override getAttributes(): readonly Attribute[] {
127
+ return this.attributeState.getAttributes();
128
+ }
129
+
121
130
  // TriggerNode
122
131
  setValue(value: TriggerInputValue): Root {
123
132
  this.setValueState(value);
@@ -9,7 +9,10 @@ import { UploadValueTypeError } from '../error/UploadValueTypeError.ts';
9
9
  import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPathNode.ts';
10
10
  import type { StaticLeafElement } from '../integration/xpath/static-dom/StaticElement.ts';
11
11
  import { createValueNodeInstanceState } from '../lib/client-reactivity/instance-state/createValueNodeInstanceState.ts';
12
- import { createAttributeState } from '../lib/reactivity/createAttributeState.ts';
12
+ import {
13
+ createAttributeState,
14
+ type AttributeState,
15
+ } from '../lib/reactivity/createAttributeState.ts';
13
16
  import { createInstanceAttachment } from '../lib/reactivity/createInstanceAttachment.ts';
14
17
  import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
15
18
  import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
@@ -29,6 +32,7 @@ import type {
29
32
  InstanceAttachment,
30
33
  InstanceAttachmentRuntimeValue,
31
34
  } from './attachments/InstanceAttachment.ts';
35
+ import { buildAttributes } from './attachments/buildAttributes.ts';
32
36
  import type { GeneralParentNode } from './hierarchy.ts';
33
37
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
34
38
  import type { InstanceAttachmentContext } from './internal-api/InstanceAttachmentContext.ts';
@@ -106,6 +110,7 @@ export class UploadControl
106
110
  // InstanceNode
107
111
  protected readonly state: SharedNodeState<UploadControlStateSpec>;
108
112
  protected readonly engineState: EngineState<UploadControlStateSpec>;
113
+ readonly attributeState: AttributeState;
109
114
 
110
115
  // InstanceValueContext
111
116
  readonly decodeInstanceValue: DecodeInstanceValue;
@@ -136,7 +141,7 @@ export class UploadControl
136
141
  const instanceAttachment = createInstanceAttachment(this);
137
142
 
138
143
  this.instanceAttachment = instanceAttachment;
139
- const attributeState = createAttributeState(this.scope);
144
+ this.attributeState = createAttributeState(this.scope);
140
145
  this.decodeInstanceValue = instanceAttachment.decodeInstanceValue;
141
146
  this.getXPathValue = instanceAttachment.getInstanceValue;
142
147
 
@@ -153,7 +158,7 @@ export class UploadControl
153
158
  children: null,
154
159
  valueOptions: null,
155
160
  value: instanceAttachment.valueState,
156
- attributes: attributeState.getAttributes,
161
+ attributes: this.attributeState.getAttributes,
157
162
  instanceValue: instanceAttachment.getInstanceValue,
158
163
  },
159
164
  this.instanceConfig
@@ -163,6 +168,7 @@ export class UploadControl
163
168
  this.engineState = state.engineState;
164
169
  this.currentState = state.currentState;
165
170
  this.validation = createValidationState(this, this.instanceConfig);
171
+ this.attributeState.setAttributes(buildAttributes(this));
166
172
  this.instanceState = createValueNodeInstanceState(this);
167
173
  }
168
174
 
@@ -180,6 +186,10 @@ export class UploadControl
180
186
  return [];
181
187
  }
182
188
 
189
+ override getAttributes(): readonly Attribute[] {
190
+ return this.attributeState.getAttributes();
191
+ }
192
+
183
193
  // UploadNode
184
194
  setValue(value: InstanceAttachmentRuntimeValue): Root {
185
195
  this.instanceAttachment.setValue(value);
@@ -11,12 +11,13 @@ import type {
11
11
  } from '../../integration/xpath/adapter/XFormsXPathNode.ts';
12
12
  import { XFORMS_XPATH_NODE_RANGE_KIND } from '../../integration/xpath/adapter/XFormsXPathNode.ts';
13
13
  import type { EngineXPathEvaluator } from '../../integration/xpath/EngineXPathEvaluator.ts';
14
+ import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
14
15
  import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
15
16
  import { createComputedExpression } from '../../lib/reactivity/createComputedExpression.ts';
16
17
  import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
17
18
  import type { AnyNodeDefinition } from '../../parse/model/NodeDefinition.ts';
18
19
  import type { DescendantNodeInitOptions } from '../children/DescendantNodeInitOptions.ts';
19
- import type { AnyChildNode, AnyParentNode, RepeatRange } from '../hierarchy.ts';
20
+ import type { AnyChildNode, AnyNode, RepeatRange } from '../hierarchy.ts';
20
21
  import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
21
22
  import type { RepeatInstance } from '../repeat/RepeatInstance.ts';
22
23
  import type { Root } from '../Root.ts';
@@ -63,7 +64,7 @@ export abstract class DescendantNode<
63
64
  Definition extends DescendantNodeDefinition,
64
65
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
66
  Spec extends DescendantNodeStateSpec<any>,
66
- Parent extends AnyParentNode,
67
+ Parent extends AnyNode,
67
68
  Child extends AnyChildNode | null = null,
68
69
  >
69
70
  extends InstanceNode<Definition, Spec, Parent, Child>
@@ -137,7 +138,7 @@ export abstract class DescendantNode<
137
138
 
138
139
  constructor(
139
140
  override readonly parent: Parent,
140
- override readonly instanceNode: StaticElement | null,
141
+ override readonly instanceNode: StaticAttribute | StaticElement | null,
141
142
  override readonly definition: Definition,
142
143
  options?: DescendantNodeOptions
143
144
  ) {
@@ -28,7 +28,7 @@ import type { AnyNodeDefinition } from '../../parse/model/NodeDefinition.ts';
28
28
  import type { Attribute } from '../Attribute.ts';
29
29
  import type { PrimaryInstance } from '../PrimaryInstance.ts';
30
30
  import type { Root } from '../Root.ts';
31
- import type { AnyChildNode, AnyNode, AnyParentNode } from '../hierarchy.ts';
31
+ import type { AnyChildNode, AnyNode } from '../hierarchy.ts';
32
32
  import { nodeID } from '../identity.ts';
33
33
  import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
34
34
  import type { InstanceConfig } from '../internal-api/InstanceConfig.ts';
@@ -84,7 +84,7 @@ export type InstanceNodeCurrentState<
84
84
  };
85
85
 
86
86
  interface ComputableReferenceNode {
87
- readonly parent: AnyParentNode | null;
87
+ readonly parent: AnyNode | null;
88
88
  readonly definition: AnyNodeDefinition;
89
89
  }
90
90
 
@@ -103,7 +103,7 @@ export abstract class InstanceNode<
103
103
  Definition extends AnyNodeDefinition,
104
104
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
105
  Spec extends InstanceNodeStateSpec<any>,
106
- Parent extends AnyParentNode | null,
106
+ Parent extends AnyNode | null,
107
107
  Child extends AnyChildNode | null = null,
108
108
  >
109
109
  implements BaseEngineNode, XFormsXPathPrimaryInstanceNode, EvaluationContext
@@ -272,4 +272,6 @@ export abstract class InstanceNode<
272
272
  .map((child) => child.getXPathValue())
273
273
  .join('');
274
274
  }
275
+
276
+ abstract getAttributes(): readonly Attribute[];
275
277
  }
@@ -9,7 +9,11 @@ export function buildAttributes(
9
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
10
  owner: AnyNode | InputControl<any> | ModelValue<any> | Note<any> | RangeControl<any>
11
11
  ): Attribute[] {
12
- return Array.from(owner.definition.attributes.values()).map((attributeDefinition) => {
12
+ const attributes = owner.definition.attributes;
13
+ if (!attributes) {
14
+ return [];
15
+ }
16
+ return Array.from(attributes.values()).map((attributeDefinition) => {
13
17
  return new Attribute(owner, attributeDefinition, attributeDefinition.template);
14
18
  });
15
19
  }
@@ -1,3 +1,4 @@
1
+ import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
1
2
  import type { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
2
3
  import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
3
4
  import type { ModelDefinition } from '../../parse/model/ModelDefinition.ts';
@@ -37,7 +38,7 @@ const collectModelChildNodesets = (parentTemplate: StaticElement): readonly stri
37
38
  type InstanceNodesByNodeset = ReadonlyMap<string, readonly [StaticElement, ...StaticElement[]]>;
38
39
 
39
40
  const groupChildElementsByNodeset = (
40
- parent: StaticDocument | StaticElement
41
+ parent: StaticAttribute | StaticDocument | StaticElement
41
42
  ): InstanceNodesByNodeset => {
42
43
  const result = new Map<string, [StaticElement, ...StaticElement[]]>();
43
44
 
@@ -1,3 +1,4 @@
1
+ import type { Attribute } from './Attribute.ts';
1
2
  import type { Group } from './Group.ts';
2
3
  import type { AnyInputControl } from './InputControl.ts';
3
4
  import type { AnyModelValue } from './ModelValue.ts';
@@ -23,6 +24,7 @@ export type AnyNode =
23
24
  | Group
24
25
  | RepeatRange
25
26
  | RepeatInstance
27
+ | Attribute
26
28
  | AnyNote
27
29
  | AnyModelValue
28
30
  | AnyInputControl
@@ -13,7 +13,10 @@ import type { AncestorNodeValidationState } from '../../client/validation.ts';
13
13
  import type { XFormsXPathElement } from '../../integration/xpath/adapter/XFormsXPathNode.ts';
14
14
  import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
15
15
  import { createTemplatedNodeInstanceState } from '../../lib/client-reactivity/instance-state/createTemplatedNodeInstanceState.ts';
16
- import { createAttributeState } from '../../lib/reactivity/createAttributeState.ts';
16
+ import {
17
+ createAttributeState,
18
+ type AttributeState,
19
+ } from '../../lib/reactivity/createAttributeState.ts';
17
20
  import type { ChildrenState } from '../../lib/reactivity/createChildrenState.ts';
18
21
  import { createChildrenState } from '../../lib/reactivity/createChildrenState.ts';
19
22
  import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
@@ -60,6 +63,7 @@ export class RepeatInstance
60
63
  ClientReactiveSerializableTemplatedNode
61
64
  {
62
65
  private readonly childrenState: ChildrenState<GeneralChildNode>;
66
+ private readonly attributeState: AttributeState;
63
67
  private readonly currentIndex: Accessor<number>;
64
68
 
65
69
  override readonly [XPathNodeKindKey] = 'element';
@@ -131,7 +135,7 @@ export class RepeatInstance
131
135
  this.appearances = definition.bodyElement.appearances;
132
136
 
133
137
  const childrenState = createChildrenState<RepeatInstance, GeneralChildNode>(this);
134
- const attributeState = createAttributeState(this.scope);
138
+ this.attributeState = createAttributeState(this.scope);
135
139
 
136
140
  this.childrenState = childrenState;
137
141
  this.currentIndex = currentIndex;
@@ -147,7 +151,7 @@ export class RepeatInstance
147
151
  // TODO: only-child <group><label>
148
152
  label: createNodeLabel(this, definition),
149
153
  hint: null,
150
- attributes: attributeState.getAttributes,
154
+ attributes: this.attributeState.getAttributes,
151
155
  children: childrenState.childIds,
152
156
  valueOptions: null,
153
157
  value: null,
@@ -192,4 +196,8 @@ export class RepeatInstance
192
196
  getChildren(): readonly GeneralChildNode[] {
193
197
  return this.childrenState.getChildren();
194
198
  }
199
+
200
+ override getAttributes(): readonly Attribute[] {
201
+ return this.attributeState.getAttributes();
202
+ }
195
203
  }
@@ -120,6 +120,7 @@ export interface XFormsXPathPrimaryInstanceNode extends XFormsXPathNode {
120
120
  // prettier-ignore
121
121
  export type XFormsXPathPrimaryInstanceDescendantNodeKind =
122
122
  | XFormsXPathNodeRangeKind
123
+ | XPathAttributeKind
123
124
  | XPathElementKind
124
125
  | XPathTextKind;
125
126
 
@@ -10,10 +10,10 @@ import {
10
10
  } from './names.ts';
11
11
  import {
12
12
  compareDocumentOrder,
13
+ getAttributes,
13
14
  getChildElements,
14
15
  getChildNodes,
15
16
  getContainingEngineXPathDocument,
16
- getEngineXPathAttributes,
17
17
  getNamespaceDeclarations,
18
18
  getNextSiblingElement,
19
19
  getNextSiblingNode,
@@ -43,7 +43,7 @@ export const engineDOMAdapter: EngineDOMAdapter = {
43
43
 
44
44
  // XPathTraversalAdapter
45
45
  compareDocumentOrder,
46
- getAttributes: getEngineXPathAttributes,
46
+ getAttributes: getAttributes,
47
47
  getChildElements: getChildElements,
48
48
  getChildNodes: getChildNodes,
49
49
  getContainingDocument: getContainingEngineXPathDocument,
@@ -1,5 +1,6 @@
1
1
  import type { XPathNodeKind } from '@getodk/xpath';
2
2
  import { XPathNodeKindKey } from '@getodk/xpath';
3
+ import type { Attribute } from '../../../instance/Attribute.ts';
3
4
  import type { AnyChildNode, AnyNode, AnyParentNode } from '../../../instance/hierarchy.ts';
4
5
  import type { PrimaryInstance } from '../../../instance/PrimaryInstance.ts';
5
6
  import type { StaticAttribute } from '../static-dom/StaticAttribute.ts';
@@ -7,6 +8,7 @@ import type { StaticDocument } from '../static-dom/StaticDocument.ts';
7
8
  import type { StaticElement } from '../static-dom/StaticElement.ts';
8
9
  import type { StaticText } from '../static-dom/StaticText.ts';
9
10
  import type {
11
+ XFormsXPathAttribute,
10
12
  XFormsXPathComment,
11
13
  XFormsXPathDocument,
12
14
  XFormsXPathElement,
@@ -17,12 +19,15 @@ export type PrimaryInstanceXPathNode = Extract<AnyNode, XFormsXPathPrimaryInstan
17
19
 
18
20
  export type PrimaryInstanceXPathElement = Extract<AnyChildNode, XFormsXPathElement>;
19
21
 
22
+ export type PrimaryInstanceXPathAttribute = Extract<Attribute, XFormsXPathAttribute>;
23
+
20
24
  export type PrimaryInstanceXPathComment = Extract<AnyChildNode, XFormsXPathComment>;
21
25
 
22
26
  // prettier-ignore
23
27
  export type PrimaryInstanceXPathChildNode =
24
28
  // eslint-disable-next-line @typescript-eslint/sort-type-constituents
25
29
  | PrimaryInstanceXPathElement
30
+ | PrimaryInstanceXPathAttribute
26
31
  | PrimaryInstanceXPathComment;
27
32
 
28
33
  // prettier-ignore
@@ -41,7 +46,7 @@ export type EngineXPathComment =
41
46
 
42
47
  // Giving this a type alias anticipates eventually implementing attributes
43
48
  // in primary instance state as well
44
- export type EngineXPathAttribute = StaticAttribute;
49
+ export type EngineXPathAttribute = PrimaryInstanceXPathAttribute | StaticAttribute;
45
50
 
46
51
  export type EngineXPathText = StaticText;
47
52
 
@@ -128,6 +128,7 @@ export const resolveEngineXPathNodeNamespaceURI = (
128
128
  case 'static-text':
129
129
  return resolveNamespaceURIFromStaticNodeContext(node, prefix);
130
130
 
131
+ case 'attribute':
131
132
  case 'group':
132
133
  case 'input':
133
134
  case 'model-value':
@@ -8,7 +8,6 @@ import type {
8
8
  EngineXPathDocument,
9
9
  EngineXPathElement,
10
10
  EngineXPathNode,
11
- EngineXPathParentNode,
12
11
  PrimaryInstanceXPathChildNode,
13
12
  XFormsXPathChildNode,
14
13
  } from './kind.ts';
@@ -18,13 +17,13 @@ export const getContainingEngineXPathDocument = (node: EngineXPathNode): EngineX
18
17
  return node.rootDocument;
19
18
  };
20
19
 
21
- export const getEngineXPathAttributes = (
22
- node: EngineXPathNode
23
- ): readonly EngineXPathAttribute[] => {
20
+ export const getAttributes = (node: EngineXPathNode): readonly EngineXPathAttribute[] => {
24
21
  if (node.nodeType === 'static-element') {
25
22
  return node.attributes;
26
23
  }
27
-
24
+ if (isEngineXPathElement(node)) {
25
+ return node.getAttributes();
26
+ }
28
27
  return [];
29
28
  };
30
29
 
@@ -43,7 +42,7 @@ export const getEngineXPathAttributes = (
43
42
  */
44
43
  export const getNamespaceDeclarations = (): readonly [] => [];
45
44
 
46
- export const getParentNode = (node: EngineXPathNode): EngineXPathParentNode | null => {
45
+ export const getParentNode = (node: EngineXPathNode): EngineXPathNode | null => {
47
46
  if (node.nodeType === 'repeat-instance') {
48
47
  return node.parent.parent;
49
48
  }
@@ -20,6 +20,7 @@ export class StaticAttribute extends StaticNode<'attribute'> implements XFormsXP
20
20
  readonly nodeset: string;
21
21
  readonly attributes = [] as const;
22
22
  readonly children = null;
23
+ readonly childElements = [];
23
24
  readonly value: string;
24
25
 
25
26
  constructor(
@@ -199,6 +199,32 @@ const createCalculation = (
199
199
  });
200
200
  };
201
201
 
202
+ /**
203
+ * Runs the computation without maintaining a reactive listener, so
204
+ * actions that should run only at a specific time are not triggered
205
+ * when referenced elements are updated.
206
+ */
207
+ const createActionCalculation = (
208
+ context: ValueContext,
209
+ setRelevantValue: SimpleAtomicStateSetter<string>,
210
+ computation: ActionComputationExpression<'string'>
211
+ ): void => {
212
+ createComputed(() => {
213
+ if (context.isAttached()) {
214
+ // use untrack so the expression evaluation isn't reactive
215
+ const relevant = untrack(() => context.isRelevant());
216
+ if (!relevant) {
217
+ return;
218
+ }
219
+ const calculated = untrack(() => {
220
+ return context.evaluator.evaluateString(computation.expression, context);
221
+ });
222
+ const value = context.decodeInstanceValue(calculated);
223
+ setRelevantValue(value);
224
+ }
225
+ });
226
+ };
227
+
202
228
  const createValueChangedCalculation = (
203
229
  context: ValueContext,
204
230
  setRelevantValue: SimpleAtomicStateSetter<string>,
@@ -235,17 +261,17 @@ const registerAction = (
235
261
  ) => {
236
262
  if (action.events.includes(XFORM_EVENT.odkInstanceFirstLoad)) {
237
263
  if (isInstanceFirstLoad(context)) {
238
- createCalculation(context, setValue, action.computation);
264
+ createActionCalculation(context, setValue, action.computation);
239
265
  }
240
266
  }
241
267
  if (action.events.includes(XFORM_EVENT.odkInstanceLoad)) {
242
268
  if (!isAddingRepeatChild(context)) {
243
- createCalculation(context, setValue, action.computation);
269
+ createActionCalculation(context, setValue, action.computation);
244
270
  }
245
271
  }
246
272
  if (action.events.includes(XFORM_EVENT.odkNewRepeat)) {
247
273
  if (isAddingRepeatChild(context)) {
248
- createCalculation(context, setValue, action.computation);
274
+ createActionCalculation(context, setValue, action.computation);
249
275
  }
250
276
  }
251
277
  if (action.events.includes(XFORM_EVENT.xformsValueChanged)) {
@@ -19,6 +19,7 @@ export class AttributeDefinition
19
19
 
20
20
  readonly value: string;
21
21
  readonly type = 'attribute';
22
+ readonly valueType = 'string';
22
23
  readonly namespaceDeclarations: NamespaceDeclarationMap;
23
24
  readonly bodyElement = null;
24
25
  readonly root: RootDefinition;
@@ -56,4 +57,10 @@ export class AttributeDefinition
56
57
  serializeAttributeXML(): string {
57
58
  return this.serializedXML;
58
59
  }
60
+
61
+ toJSON() {
62
+ const { bind, bodyElement, parent, root, ...rest } = this;
63
+
64
+ return rest;
65
+ }
59
66
  }