@getodk/xforms-engine 0.16.0 → 0.17.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 (124) hide show
  1. package/dist/client/AttributeNode.d.ts +4 -3
  2. package/dist/client/InputNode.d.ts +8 -4
  3. package/dist/client/MarkdownNode.d.ts +3 -0
  4. package/dist/client/NoteNode.d.ts +6 -2
  5. package/dist/client/form/FormInstanceConfig.d.ts +4 -0
  6. package/dist/client/form/LoadFormResult.d.ts +5 -14
  7. package/dist/client/form/ResetFormInstance.d.ts +13 -0
  8. package/dist/entrypoints/FormResult/BaseFormResult.d.ts +1 -0
  9. package/dist/entrypoints/FormResult/BaseInstantiableFormResult.d.ts +2 -0
  10. package/dist/entrypoints/FormResult/FormFailureResult.d.ts +2 -0
  11. package/dist/entrypoints/createPotentiallyClientOwnedReactiveScope.d.ts +19 -0
  12. package/dist/index.js +22150 -25908
  13. package/dist/index.js.map +1 -1
  14. package/dist/instance/Attribute.d.ts +11 -23
  15. package/dist/instance/Group.d.ts +3 -0
  16. package/dist/instance/InputControl.d.ts +3 -0
  17. package/dist/instance/ModelValue.d.ts +4 -0
  18. package/dist/instance/Note.d.ts +4 -0
  19. package/dist/instance/PrimaryInstance.d.ts +7 -1
  20. package/dist/instance/RangeControl.d.ts +4 -0
  21. package/dist/instance/RankControl.d.ts +5 -1
  22. package/dist/instance/Root.d.ts +3 -0
  23. package/dist/instance/SelectControl.d.ts +5 -1
  24. package/dist/instance/TriggerControl.d.ts +4 -0
  25. package/dist/instance/UploadControl.d.ts +3 -0
  26. package/dist/instance/abstract/DescendantNode.d.ts +5 -4
  27. package/dist/instance/abstract/InstanceNode.d.ts +4 -3
  28. package/dist/instance/hierarchy.d.ts +2 -1
  29. package/dist/instance/internal-api/AttributeContext.d.ts +1 -0
  30. package/dist/instance/internal-api/InstanceConfig.d.ts +2 -1
  31. package/dist/instance/internal-api/InstanceValueContext.d.ts +1 -0
  32. package/dist/instance/markdown/MarkdownNode.d.ts +14 -9
  33. package/dist/instance/repeat/RepeatInstance.d.ts +2 -0
  34. package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +1 -1
  35. package/dist/integration/xpath/adapter/kind.d.ts +5 -3
  36. package/dist/integration/xpath/adapter/traversal.d.ts +3 -3
  37. package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +1 -0
  38. package/dist/integration/xpath/static-dom/StaticDocument.d.ts +2 -0
  39. package/dist/lib/codecs/{Geopoint/Geopoint.d.ts → geolocation/Geolocation.d.ts} +11 -15
  40. package/dist/lib/codecs/geolocation/Geopoint.d.ts +7 -0
  41. package/dist/lib/codecs/geolocation/Geoshape.d.ts +7 -0
  42. package/dist/lib/codecs/geolocation/Geotrace.d.ts +7 -0
  43. package/dist/lib/codecs/geolocation/createGeolocationValueCodec.d.ts +3 -0
  44. package/dist/lib/codecs/getSharedValueCodec.d.ts +7 -5
  45. package/dist/lib/reactivity/text/createTextRange.d.ts +0 -2
  46. package/dist/parse/XFormDOM.d.ts +7 -1
  47. package/dist/parse/body/appearance/inputAppearanceParser.d.ts +1 -1
  48. package/dist/parse/model/ActionDefinition.d.ts +1 -1
  49. package/dist/parse/model/AttributeDefinition.d.ts +2 -0
  50. package/dist/parse/model/BindPreloadDefinition.d.ts +2 -1
  51. package/dist/parse/model/ModelActionMap.d.ts +3 -2
  52. package/dist/parse/model/ModelDefinition.d.ts +3 -5
  53. package/dist/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.d.ts +0 -17
  54. package/dist/parse/model/SecondaryInstance/sources/external-instance-csv-parser.d.ts +8 -0
  55. package/dist/parse/model/TranslationDefinitionMap.d.ts +4 -0
  56. package/dist/solid.js +21608 -25366
  57. package/dist/solid.js.map +1 -1
  58. package/package.json +2 -2
  59. package/src/client/AttributeNode.ts +4 -3
  60. package/src/client/InputNode.ts +11 -3
  61. package/src/client/MarkdownNode.ts +3 -0
  62. package/src/client/NoteNode.ts +9 -1
  63. package/src/client/form/FormInstanceConfig.ts +6 -0
  64. package/src/client/form/LoadFormResult.ts +5 -17
  65. package/src/client/form/ResetFormInstance.ts +17 -0
  66. package/src/entrypoints/FormInstance.ts +2 -0
  67. package/src/entrypoints/FormResult/BaseFormResult.ts +1 -0
  68. package/src/entrypoints/FormResult/BaseInstantiableFormResult.ts +10 -1
  69. package/src/entrypoints/FormResult/FormFailureResult.ts +3 -0
  70. package/src/entrypoints/createPotentiallyClientOwnedReactiveScope.ts +30 -0
  71. package/src/entrypoints/loadForm.ts +1 -31
  72. package/src/instance/Attribute.ts +38 -54
  73. package/src/instance/Group.ts +12 -4
  74. package/src/instance/InputControl.ts +15 -9
  75. package/src/instance/ModelValue.ts +13 -4
  76. package/src/instance/Note.ts +13 -4
  77. package/src/instance/PrimaryInstance.ts +29 -6
  78. package/src/instance/RangeControl.ts +13 -4
  79. package/src/instance/RankControl.ts +14 -5
  80. package/src/instance/Root.ts +12 -4
  81. package/src/instance/SelectControl.ts +14 -5
  82. package/src/instance/TriggerControl.ts +13 -4
  83. package/src/instance/UploadControl.ts +13 -3
  84. package/src/instance/abstract/DescendantNode.ts +4 -3
  85. package/src/instance/abstract/InstanceNode.ts +5 -3
  86. package/src/instance/attachments/buildAttributes.ts +26 -2
  87. package/src/instance/children/childrenInitOptions.ts +2 -1
  88. package/src/instance/hierarchy.ts +2 -0
  89. package/src/instance/internal-api/AttributeContext.ts +1 -0
  90. package/src/instance/internal-api/InstanceConfig.ts +3 -0
  91. package/src/instance/internal-api/InstanceValueContext.ts +1 -0
  92. package/src/instance/markdown/MarkdownNode.ts +19 -7
  93. package/src/instance/repeat/RepeatInstance.ts +11 -3
  94. package/src/instance/text/markdownFormat.ts +4 -3
  95. package/src/integration/xpath/adapter/XFormsXPathNode.ts +1 -0
  96. package/src/integration/xpath/adapter/engineDOMAdapter.ts +2 -2
  97. package/src/integration/xpath/adapter/kind.ts +6 -1
  98. package/src/integration/xpath/adapter/names.ts +1 -0
  99. package/src/integration/xpath/adapter/traversal.ts +5 -6
  100. package/src/integration/xpath/static-dom/StaticAttribute.ts +1 -0
  101. package/src/integration/xpath/static-dom/StaticDocument.ts +2 -0
  102. package/src/lib/codecs/{Geopoint/Geopoint.ts → geolocation/Geolocation.ts} +43 -24
  103. package/src/lib/codecs/geolocation/Geopoint.ts +15 -0
  104. package/src/lib/codecs/geolocation/Geoshape.ts +36 -0
  105. package/src/lib/codecs/geolocation/Geotrace.ts +36 -0
  106. package/src/lib/codecs/geolocation/createGeolocationValueCodec.ts +18 -0
  107. package/src/lib/codecs/getSharedValueCodec.ts +37 -11
  108. package/src/lib/reactivity/createInstanceValueState.ts +90 -34
  109. package/src/lib/reactivity/text/createTextRange.ts +71 -45
  110. package/src/parse/XFormDOM.ts +22 -2
  111. package/src/parse/model/ActionDefinition.ts +6 -6
  112. package/src/parse/model/AttributeDefinition.ts +7 -0
  113. package/src/parse/model/BindDefinition.ts +1 -1
  114. package/src/parse/model/BindPreloadDefinition.ts +21 -14
  115. package/src/parse/model/ModelActionMap.ts +30 -13
  116. package/src/parse/model/ModelDefinition.ts +5 -10
  117. package/src/parse/model/RootDefinition.ts +2 -1
  118. package/src/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.ts +2 -184
  119. package/src/parse/model/SecondaryInstance/sources/external-instance-csv-parser.ts +185 -0
  120. package/src/parse/model/TranslationDefinitionMap.ts +23 -0
  121. package/dist/lib/codecs/Geopoint/GeopointValueCodec.d.ts +0 -5
  122. package/dist/parse/model/generateItextChunks.d.ts +0 -5
  123. package/src/lib/codecs/Geopoint/GeopointValueCodec.ts +0 -20
  124. package/src/parse/model/generateItextChunks.ts +0 -61
@@ -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
  }
@@ -1,3 +1,6 @@
1
+ import type { StaticAttribute } from '../../integration/xpath/static-dom/StaticAttribute.ts';
2
+ import type { StaticDocument } from '../../integration/xpath/static-dom/StaticDocument.ts';
3
+ import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
1
4
  import { Attribute } from '../Attribute';
2
5
  import type { AnyNode } from '../hierarchy.ts';
3
6
  import type { InputControl } from '../InputControl.ts';
@@ -5,11 +8,32 @@ import type { ModelValue } from '../ModelValue.ts';
5
8
  import type { Note } from '../Note.ts';
6
9
  import type { RangeControl } from '../RangeControl.ts';
7
10
 
11
+ function buildInstanceAttributeMap(
12
+ instanceNode: StaticAttribute | StaticDocument | StaticElement | null
13
+ ): Map<string, StaticAttribute> {
14
+ const map = new Map<string, StaticAttribute>();
15
+ if (!instanceNode) {
16
+ return map;
17
+ }
18
+ for (const attribute of instanceNode.attributes) {
19
+ map.set(attribute.qualifiedName.getPrefixedName(), attribute);
20
+ }
21
+ return map;
22
+ }
23
+
8
24
  export function buildAttributes(
9
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
26
  owner: AnyNode | InputControl<any> | ModelValue<any> | Note<any> | RangeControl<any>
11
27
  ): Attribute[] {
12
- return Array.from(owner.definition.attributes.values()).map((attributeDefinition) => {
13
- return new Attribute(owner, attributeDefinition, attributeDefinition.template);
28
+ const attributes = owner.definition.attributes;
29
+ if (!attributes) {
30
+ return [];
31
+ }
32
+ const instanceAttributes = buildInstanceAttributeMap(owner.instanceNode);
33
+ return Array.from(attributes.values()).map((attributeDefinition) => {
34
+ const instanceNode =
35
+ instanceAttributes.get(attributeDefinition.qualifiedName.getPrefixedName()) ??
36
+ attributeDefinition.template;
37
+ return new Attribute(owner, attributeDefinition, instanceNode);
14
38
  });
15
39
  }
@@ -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
@@ -11,6 +11,7 @@ import type { InstanceConfig } from './InstanceConfig.ts';
11
11
  export interface InstanceAttributeContextDocument {
12
12
  readonly initializationMode: FormInstanceInitializationMode;
13
13
  readonly isAttached: Accessor<boolean>;
14
+ getBackgroundGeopoint: Accessor<Promise<string>>;
14
15
  }
15
16
 
16
17
  export type DecodeInstanceValue = (value: string) => string;
@@ -1,6 +1,7 @@
1
1
  import type { InstanceAttachmentFileNameFactory } from '../../client/attachments/InstanceAttachmentsConfig.ts';
2
2
  import type {
3
3
  FormInstanceConfig,
4
+ GeolocationProvider,
4
5
  PreloadProperties,
5
6
  } from '../../client/form/FormInstanceConfig.ts';
6
7
  import type { OpaqueReactiveObjectFactory } from '../../client/OpaqueReactiveObjectFactory.ts';
@@ -14,4 +15,6 @@ export interface InstanceConfig {
14
15
  readonly computeAttachmentName: InstanceAttachmentFileNameFactory;
15
16
 
16
17
  readonly preloadProperties: PreloadProperties;
18
+
19
+ readonly geolocationProvider: GeolocationProvider | undefined;
17
20
  }
@@ -11,6 +11,7 @@ import type { InstanceConfig } from './InstanceConfig.ts';
11
11
  export interface InstanceValueContextDocument {
12
12
  readonly initializationMode: FormInstanceInitializationMode;
13
13
  readonly isAttached: Accessor<boolean>;
14
+ getBackgroundGeopoint: Accessor<Promise<string>>;
14
15
  }
15
16
 
16
17
  export type DecodeInstanceValue = (value: string) => string;
@@ -2,18 +2,26 @@ import {
2
2
  type AnchorMarkdownNode,
3
3
  type ChildMarkdownNode as ClientChildMarkdownNode,
4
4
  type HtmlMarkdownNode as ClientHtmlMarkdownNode,
5
+ type MarkdownNode as ClientMarkdownNode,
5
6
  type ParentMarkdownNode as ClientParentMarkdownNode,
6
7
  type StyledMarkdownNode as ClientStyledMarkdownNode,
7
8
  type ElementName,
8
- type MarkdownNode,
9
9
  type MarkdownProperty,
10
10
  } from '../../client';
11
11
 
12
- abstract class ParentMarkdownNode implements ClientParentMarkdownNode {
12
+ abstract class MarkdownNode {
13
+ readonly id: string;
14
+ constructor() {
15
+ this.id = crypto.randomUUID();
16
+ }
17
+ }
18
+
19
+ abstract class ParentMarkdownNode extends MarkdownNode implements ClientParentMarkdownNode {
13
20
  readonly children;
14
21
  readonly role = 'parent';
15
22
  abstract elementName: ElementName;
16
- constructor(children: MarkdownNode[]) {
23
+ constructor(children: ClientMarkdownNode[]) {
24
+ super();
17
25
  this.children = children;
18
26
  }
19
27
  }
@@ -69,18 +77,20 @@ export class ListItem extends ParentMarkdownNode {
69
77
  export class Anchor extends ParentMarkdownNode implements AnchorMarkdownNode {
70
78
  readonly elementName = 'a';
71
79
  readonly url: string;
72
- constructor(children: MarkdownNode[], url: string) {
80
+ constructor(children: ClientMarkdownNode[], url: string) {
73
81
  super(children);
74
82
  this.url = url;
75
83
  }
76
84
  }
77
85
 
78
86
  abstract class StyledMarkdownNode implements ClientParentMarkdownNode {
87
+ readonly id: string;
79
88
  readonly children;
80
89
  readonly role = 'parent';
81
90
  abstract elementName: ElementName;
82
91
  readonly properties: MarkdownProperty | undefined;
83
- constructor(children: MarkdownNode[], properties: MarkdownProperty | undefined) {
92
+ constructor(children: ClientMarkdownNode[], properties: MarkdownProperty | undefined) {
93
+ this.id = crypto.randomUUID();
84
94
  this.children = children;
85
95
  this.properties = properties;
86
96
  }
@@ -98,18 +108,20 @@ export class Div extends StyledMarkdownNode implements ClientStyledMarkdownNode
98
108
  readonly elementName = 'div';
99
109
  }
100
110
 
101
- export class ChildMarkdownNode implements ClientChildMarkdownNode {
111
+ export class ChildMarkdownNode extends MarkdownNode implements ClientChildMarkdownNode {
102
112
  readonly role = 'child';
103
113
  readonly value: string;
104
114
  constructor(value: string) {
115
+ super();
105
116
  this.value = value;
106
117
  }
107
118
  }
108
119
 
109
- export class Html implements ClientHtmlMarkdownNode {
120
+ export class Html extends MarkdownNode implements ClientHtmlMarkdownNode {
110
121
  readonly role = 'html';
111
122
  readonly unsafeHtml: string;
112
123
  constructor(unsafeHtml: string) {
124
+ super();
113
125
  this.unsafeHtml = unsafeHtml;
114
126
  }
115
127
  }
@@ -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
  }
@@ -42,7 +42,7 @@ const SUPPORTED_HTML_TAGS = Object.entries(HTML_TAG_MAP).map(([tag, type]) => {
42
42
  };
43
43
  });
44
44
 
45
- let outputStrings: Map<string, string>;
45
+ const outputStrings = new Map<string, string>();
46
46
 
47
47
  function validateStyleProperty(name: string | undefined, value: string | undefined): boolean {
48
48
  if (!name || !value) {
@@ -208,7 +208,8 @@ function toOdkMarkdown(str: string): MarkdownNode[] {
208
208
  }
209
209
 
210
210
  export function format(chunks: readonly TextChunk[]): MarkdownNode[] {
211
- outputStrings = new Map<string, string>();
212
211
  const str = escapeEditableChunks(chunks);
213
- return toOdkMarkdown(str);
212
+ const result = toOdkMarkdown(str);
213
+ outputStrings.clear();
214
+ return result;
214
215
  }
@@ -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(
@@ -1,4 +1,5 @@
1
1
  import type { XFormsXPathDocument } from '../adapter/XFormsXPathNode.ts';
2
+ import type { StaticAttribute } from './StaticAttribute.ts';
2
3
  import type { StaticElementOptions } from './StaticElement.ts';
3
4
  import { StaticElement } from './StaticElement.ts';
4
5
  import { StaticParentNode } from './StaticParentNode.ts';
@@ -15,6 +16,7 @@ export class StaticDocument extends StaticParentNode<'document'> implements XFor
15
16
  readonly nodeset: string;
16
17
  readonly children: readonly [root: StaticElement];
17
18
  readonly childElements: readonly [root: StaticElement];
19
+ readonly attributes: readonly StaticAttribute[] = [];
18
20
 
19
21
  constructor(options: StaticDocumentOptions) {
20
22
  super('document');
@@ -1,8 +1,3 @@
1
- /**
2
- * This abstract class defines the minimal behavior for a default geopoint.
3
- * It can be expanded later to support units (e.g., degrees or meters),
4
- * which would also serve as documentation to clarify what each value represents.
5
- */
6
1
  abstract class SemanticValue<Semantic extends string, Value extends number | null> {
7
2
  abstract readonly semantic: Semantic;
8
3
 
@@ -25,26 +20,21 @@ class Accuracy<Value extends number | null = number> extends SemanticValue<'accu
25
20
  readonly semantic = 'accuracy';
26
21
  }
27
22
 
28
- export interface GeopointValue {
23
+ export interface LocationPoint {
29
24
  readonly latitude: number;
30
25
  readonly longitude: number;
31
26
  readonly altitude: number | null;
32
27
  readonly accuracy: number | null;
33
28
  }
34
29
 
35
- export type GeopointRuntimeValue = GeopointValue | null;
36
-
37
- // TODO: Add support for GeoJSONValue
38
- export type GeopointInputValue = GeopointRuntimeValue | string;
39
-
40
- interface GeopointInternalValue {
30
+ interface GeolocationInternalValue {
41
31
  readonly latitude: Latitude;
42
32
  readonly longitude: Longitude;
43
33
  readonly altitude: Altitude<null> | Altitude<number>;
44
34
  readonly accuracy: Accuracy<null> | Accuracy<number>;
45
35
  }
46
36
 
47
- type GeopointTuple =
37
+ type LocationPointTuple =
48
38
  | readonly [
49
39
  latitude: Latitude,
50
40
  longitude: Longitude,
@@ -54,6 +44,8 @@ type GeopointTuple =
54
44
  | readonly [latitude: Latitude, longitude: Longitude, altitude: Altitude]
55
45
  | readonly [latitude: Latitude, longitude: Longitude];
56
46
 
47
+ export const SEGMENT_SEPARATOR = ';';
48
+
57
49
  const DEGREES_MAX = {
58
50
  latitude: 90,
59
51
  longitude: 180,
@@ -61,10 +53,10 @@ const DEGREES_MAX = {
61
53
 
62
54
  type CoordinateType = keyof typeof DEGREES_MAX;
63
55
 
64
- export class Geopoint {
65
- private readonly internalValue: GeopointInternalValue;
56
+ export class Geolocation {
57
+ private readonly internalValue: GeolocationInternalValue;
66
58
 
67
- constructor(coordinates: GeopointValue) {
59
+ constructor(coordinates: LocationPoint) {
68
60
  const { latitude, longitude, altitude, accuracy } = coordinates;
69
61
 
70
62
  this.internalValue = {
@@ -75,7 +67,7 @@ export class Geopoint {
75
67
  };
76
68
  }
77
69
 
78
- getTuple(): GeopointTuple {
70
+ getTuple(): LocationPointTuple {
79
71
  const { latitude, longitude, altitude, accuracy } = this.internalValue;
80
72
 
81
73
  if (accuracy.value != null) {
@@ -89,12 +81,16 @@ export class Geopoint {
89
81
  return [latitude, longitude];
90
82
  }
91
83
 
92
- getRuntimeValue(): GeopointRuntimeValue {
84
+ getRuntimeValue(): LocationPoint | null {
93
85
  const { latitude, longitude, altitude, accuracy } = this.internalValue;
94
86
  const isLatitude = this.isValidDegrees('latitude', latitude.value);
95
87
  const isLongitude = this.isValidDegrees('longitude', longitude.value);
96
88
 
97
- if (!isLatitude || !isLongitude || Geopoint.isNullLocation(latitude.value, longitude.value)) {
89
+ if (
90
+ !isLatitude ||
91
+ !isLongitude ||
92
+ Geolocation.isNullLocation(latitude.value, longitude.value)
93
+ ) {
98
94
  return null;
99
95
  }
100
96
 
@@ -118,8 +114,9 @@ export class Geopoint {
118
114
  return latitude === 0 && longitude === 0;
119
115
  }
120
116
 
121
- static parseString(value: string): GeopointRuntimeValue {
122
- if (value.trim() === '') {
117
+ static parseString(value: string): LocationPoint | null {
118
+ value = value.trim();
119
+ if (value === '') {
123
120
  return null;
124
121
  }
125
122
 
@@ -132,12 +129,12 @@ export class Geopoint {
132
129
  return new this({ latitude, longitude, altitude, accuracy }).getRuntimeValue();
133
130
  }
134
131
 
135
- static toCoordinatesString(value: GeopointInputValue): string {
136
- const decodedValue = typeof value === 'string' ? Geopoint.parseString(value) : value;
132
+ static toCoordinatesString(value: LocationPoint | string | null): string {
133
+ const decodedValue = typeof value === 'string' ? Geolocation.parseString(value) : value;
137
134
 
138
135
  if (
139
136
  decodedValue == null ||
140
- Geopoint.isNullLocation(decodedValue.latitude, decodedValue.longitude)
137
+ Geolocation.isNullLocation(decodedValue.latitude, decodedValue.longitude)
141
138
  ) {
142
139
  return '';
143
140
  }
@@ -147,4 +144,26 @@ export class Geopoint {
147
144
  .map((item) => item.value ?? 0)
148
145
  .join(' ');
149
146
  }
147
+
148
+ static isClosedShape(points: LocationPoint[]) {
149
+ const firstPoint = points[0];
150
+ const lastPoint = points[points.length - 1];
151
+ return (
152
+ firstPoint?.latitude === lastPoint?.latitude && firstPoint?.longitude === lastPoint?.longitude
153
+ );
154
+ }
155
+
156
+ static getSegments(value: string): string[] | null {
157
+ if (value.trim() === '') {
158
+ return null;
159
+ }
160
+
161
+ const parts = value.split(SEGMENT_SEPARATOR);
162
+ // Handles trailing semicolon, which is valid and common in ODK.
163
+ if (parts[parts.length - 1]?.trim() === '') {
164
+ parts.pop();
165
+ }
166
+
167
+ return parts;
168
+ }
150
169
  }