@getodk/xforms-engine 0.16.1 → 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 (80) hide show
  1. package/dist/client/InputNode.d.ts +8 -4
  2. package/dist/client/MarkdownNode.d.ts +3 -0
  3. package/dist/client/NoteNode.d.ts +6 -2
  4. package/dist/client/form/FormInstanceConfig.d.ts +4 -0
  5. package/dist/client/form/LoadFormResult.d.ts +5 -14
  6. package/dist/client/form/ResetFormInstance.d.ts +13 -0
  7. package/dist/entrypoints/FormResult/BaseFormResult.d.ts +1 -0
  8. package/dist/entrypoints/FormResult/BaseInstantiableFormResult.d.ts +2 -0
  9. package/dist/entrypoints/FormResult/FormFailureResult.d.ts +2 -0
  10. package/dist/entrypoints/createPotentiallyClientOwnedReactiveScope.d.ts +19 -0
  11. package/dist/index.js +21681 -25500
  12. package/dist/index.js.map +1 -1
  13. package/dist/instance/PrimaryInstance.d.ts +4 -1
  14. package/dist/instance/internal-api/AttributeContext.d.ts +1 -0
  15. package/dist/instance/internal-api/InstanceConfig.d.ts +2 -1
  16. package/dist/instance/internal-api/InstanceValueContext.d.ts +1 -0
  17. package/dist/instance/markdown/MarkdownNode.d.ts +14 -9
  18. package/dist/integration/xpath/static-dom/StaticDocument.d.ts +2 -0
  19. package/dist/lib/codecs/{Geopoint/Geopoint.d.ts → geolocation/Geolocation.d.ts} +11 -15
  20. package/dist/lib/codecs/geolocation/Geopoint.d.ts +7 -0
  21. package/dist/lib/codecs/geolocation/Geoshape.d.ts +7 -0
  22. package/dist/lib/codecs/geolocation/Geotrace.d.ts +7 -0
  23. package/dist/lib/codecs/geolocation/createGeolocationValueCodec.d.ts +3 -0
  24. package/dist/lib/codecs/getSharedValueCodec.d.ts +7 -5
  25. package/dist/lib/reactivity/text/createTextRange.d.ts +0 -2
  26. package/dist/parse/XFormDOM.d.ts +7 -1
  27. package/dist/parse/body/appearance/inputAppearanceParser.d.ts +1 -1
  28. package/dist/parse/model/ActionDefinition.d.ts +1 -1
  29. package/dist/parse/model/BindPreloadDefinition.d.ts +2 -1
  30. package/dist/parse/model/ModelActionMap.d.ts +3 -2
  31. package/dist/parse/model/ModelDefinition.d.ts +3 -5
  32. package/dist/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.d.ts +0 -17
  33. package/dist/parse/model/SecondaryInstance/sources/external-instance-csv-parser.d.ts +8 -0
  34. package/dist/parse/model/TranslationDefinitionMap.d.ts +4 -0
  35. package/dist/solid.js +21407 -25226
  36. package/dist/solid.js.map +1 -1
  37. package/package.json +2 -2
  38. package/src/client/InputNode.ts +11 -3
  39. package/src/client/MarkdownNode.ts +3 -0
  40. package/src/client/NoteNode.ts +9 -1
  41. package/src/client/form/FormInstanceConfig.ts +6 -0
  42. package/src/client/form/LoadFormResult.ts +5 -17
  43. package/src/client/form/ResetFormInstance.ts +17 -0
  44. package/src/entrypoints/FormInstance.ts +2 -0
  45. package/src/entrypoints/FormResult/BaseFormResult.ts +1 -0
  46. package/src/entrypoints/FormResult/BaseInstantiableFormResult.ts +10 -1
  47. package/src/entrypoints/FormResult/FormFailureResult.ts +3 -0
  48. package/src/entrypoints/createPotentiallyClientOwnedReactiveScope.ts +30 -0
  49. package/src/entrypoints/loadForm.ts +1 -31
  50. package/src/instance/InputControl.ts +3 -5
  51. package/src/instance/PrimaryInstance.ts +17 -2
  52. package/src/instance/attachments/buildAttributes.ts +21 -1
  53. package/src/instance/internal-api/AttributeContext.ts +1 -0
  54. package/src/instance/internal-api/InstanceConfig.ts +3 -0
  55. package/src/instance/internal-api/InstanceValueContext.ts +1 -0
  56. package/src/instance/markdown/MarkdownNode.ts +19 -7
  57. package/src/instance/text/markdownFormat.ts +4 -3
  58. package/src/integration/xpath/static-dom/StaticDocument.ts +2 -0
  59. package/src/lib/codecs/{Geopoint/Geopoint.ts → geolocation/Geolocation.ts} +43 -24
  60. package/src/lib/codecs/geolocation/Geopoint.ts +15 -0
  61. package/src/lib/codecs/geolocation/Geoshape.ts +36 -0
  62. package/src/lib/codecs/geolocation/Geotrace.ts +36 -0
  63. package/src/lib/codecs/geolocation/createGeolocationValueCodec.ts +18 -0
  64. package/src/lib/codecs/getSharedValueCodec.ts +37 -11
  65. package/src/lib/reactivity/createInstanceValueState.ts +64 -34
  66. package/src/lib/reactivity/text/createTextRange.ts +71 -45
  67. package/src/parse/XFormDOM.ts +22 -2
  68. package/src/parse/model/ActionDefinition.ts +6 -6
  69. package/src/parse/model/BindDefinition.ts +1 -1
  70. package/src/parse/model/BindPreloadDefinition.ts +21 -14
  71. package/src/parse/model/ModelActionMap.ts +30 -13
  72. package/src/parse/model/ModelDefinition.ts +5 -10
  73. package/src/parse/model/RootDefinition.ts +2 -1
  74. package/src/parse/model/SecondaryInstance/sources/CSVExternalSecondaryInstance.ts +2 -184
  75. package/src/parse/model/SecondaryInstance/sources/external-instance-csv-parser.ts +185 -0
  76. package/src/parse/model/TranslationDefinitionMap.ts +23 -0
  77. package/dist/lib/codecs/Geopoint/GeopointValueCodec.d.ts +0 -5
  78. package/dist/parse/model/generateItextChunks.d.ts +0 -5
  79. package/src/lib/codecs/Geopoint/GeopointValueCodec.ts +0 -20
  80. package/src/parse/model/generateItextChunks.ts +0 -61
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getodk/xforms-engine",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "XForms engine for ODK Web Forms",
6
6
  "type": "module",
@@ -63,7 +63,7 @@
63
63
  "devDependencies": {
64
64
  "@babel/core": "^7.28.5",
65
65
  "@getodk/tree-sitter-xpath": "0.2.2",
66
- "@getodk/xpath": "0.9.2",
66
+ "@getodk/xpath": "0.10.0",
67
67
  "@playwright/test": "^1.57.0",
68
68
  "@types/papaparse": "^5.5.0",
69
69
  "@vitest/browser": "^3.2.4",
@@ -49,8 +49,8 @@ interface InputNodeOptionsByValueType {
49
49
  readonly time: null;
50
50
  readonly dateTime: null;
51
51
  readonly geopoint: GeoInputNodeOptions;
52
- readonly geotrace: GeoInputNodeOptions;
53
- readonly geoshape: GeoInputNodeOptions;
52
+ readonly geotrace: null;
53
+ readonly geoshape: null;
54
54
  readonly binary: null;
55
55
  readonly barcode: null;
56
56
  readonly intent: null;
@@ -86,12 +86,16 @@ export type IntInputValue = InputValue<'int'>;
86
86
  export type DecimalInputValue = InputValue<'decimal'>;
87
87
  export type DateInputValue = InputValue<'date'>;
88
88
  export type GeopointInputValue = InputValue<'geopoint'>;
89
+ export type GeoshapeInputValue = InputValue<'geoshape'>;
90
+ export type GeotraceInputValue = InputValue<'geotrace'>;
89
91
 
90
92
  export type StringInputNode = InputNode<'string'>;
91
93
  export type IntInputNode = InputNode<'int'>;
92
94
  export type DecimalInputNode = InputNode<'decimal'>;
93
95
  export type DateInputNode = InputNode<'date'>;
94
96
  export type GeopointInputNode = InputNode<'geopoint'>;
97
+ export type GeoshapeInputNode = InputNode<'geoshape'>;
98
+ export type GeotraceInputNode = InputNode<'geotrace'>;
95
99
 
96
100
  // prettier-ignore
97
101
  type SupportedInputValueType =
@@ -100,7 +104,9 @@ type SupportedInputValueType =
100
104
  | 'int'
101
105
  | 'decimal'
102
106
  | 'date'
103
- | 'geopoint';
107
+ | 'geopoint'
108
+ | 'geoshape'
109
+ | 'geotrace';
104
110
 
105
111
  type TemporaryStringValueType = Exclude<ValueType, SupportedInputValueType>;
106
112
 
@@ -114,4 +120,6 @@ export type AnyInputNode =
114
120
  | DecimalInputNode
115
121
  | DateInputNode
116
122
  | GeopointInputNode
123
+ | GeoshapeInputNode
124
+ | GeotraceInputNode
117
125
  | TemporaryStringValueInputNode;
@@ -16,17 +16,20 @@ export type ElementName =
16
16
  export type MarkdownNode = ChildMarkdownNode | HtmlMarkdownNode | ParentMarkdownNode;
17
17
 
18
18
  export interface ParentMarkdownNode {
19
+ readonly id: string;
19
20
  readonly role: 'parent';
20
21
  readonly elementName: string;
21
22
  readonly children: MarkdownNode[];
22
23
  }
23
24
 
24
25
  export interface ChildMarkdownNode {
26
+ readonly id: string;
25
27
  readonly role: 'child';
26
28
  readonly value: string;
27
29
  }
28
30
 
29
31
  export interface HtmlMarkdownNode {
32
+ readonly id: string;
30
33
  readonly role: 'html';
31
34
  readonly unsafeHtml: string;
32
35
  }
@@ -83,12 +83,16 @@ export type IntNoteValue = NoteValue<'int'>;
83
83
  export type DecimalNoteValue = NoteValue<'decimal'>;
84
84
  export type DateNoteValue = NoteValue<'date'>;
85
85
  export type GeopointNoteValue = NoteValue<'geopoint'>;
86
+ export type GeoshapeNoteValue = NoteValue<'geoshape'>;
87
+ export type GeotraceNoteValue = NoteValue<'geotrace'>;
86
88
 
87
89
  export type StringNoteNode = NoteNode<'string'>;
88
90
  export type IntNoteNode = NoteNode<'int'>;
89
91
  export type DecimalNoteNode = NoteNode<'decimal'>;
90
92
  export type DateNoteNode = NoteNode<'date'>;
91
93
  export type GeopointNoteNode = NoteNode<'geopoint'>;
94
+ export type GeoshapeNoteNode = NoteNode<'geoshape'>;
95
+ export type GeotraceNoteNode = NoteNode<'geotrace'>;
92
96
 
93
97
  // prettier-ignore
94
98
  type SupportedNoteValueType =
@@ -97,7 +101,9 @@ type SupportedNoteValueType =
97
101
  | 'int'
98
102
  | 'decimal'
99
103
  | 'date'
100
- | 'geopoint';
104
+ | 'geopoint'
105
+ | 'geoshape'
106
+ | 'geotrace';
101
107
 
102
108
  type TemporaryStringValueType = Exclude<ValueType, SupportedNoteValueType>;
103
109
 
@@ -111,4 +117,6 @@ export type AnyNoteNode =
111
117
  | DecimalNoteNode
112
118
  | DateNoteNode
113
119
  | GeopointNoteNode
120
+ | GeoshapeNoteNode
121
+ | GeotraceNoteNode
114
122
  | TemporaryStringValueNoteNode;
@@ -16,6 +16,10 @@ export interface PreloadProperties {
16
16
  readonly phoneNumber?: string;
17
17
  }
18
18
 
19
+ export interface GeolocationProvider {
20
+ getLocation(): Promise<string>;
21
+ }
22
+
19
23
  export interface FormInstanceConfig {
20
24
  /**
21
25
  * A client may specify a generic function for constructing stateful objects.
@@ -35,4 +39,6 @@ export interface FormInstanceConfig {
35
39
  readonly instanceAttachments?: InstanceAttachmentsConfig;
36
40
 
37
41
  readonly preloadProperties?: PreloadProperties;
42
+
43
+ readonly geolocationProvider?: GeolocationProvider;
38
44
  }
@@ -3,6 +3,7 @@ import type { AnyFunction } from '@getodk/common/types/helpers.js';
3
3
  import type { LoadFormFailureError } from '../../error/LoadFormFailureError.ts';
4
4
  import type { CreateFormInstance } from './CreateFormInstance.ts';
5
5
  import type { EditFormInstance } from './EditFormInstance.ts';
6
+ import type { ResetFormInstance } from './ResetFormInstance.ts';
6
7
  import type { RestoreFormInstance } from './RestoreFormInstance.ts';
7
8
 
8
9
  // Re-export for client access
@@ -44,6 +45,7 @@ interface BaseLoadFormResult {
44
45
  readonly warnings: LoadFormWarnings | null;
45
46
  readonly error: LoadFormFailureError | null;
46
47
  readonly createInstance: FallibleLoadFormResultMethod<CreateFormInstance>;
48
+ readonly resetInstance: FallibleLoadFormResultMethod<ResetFormInstance>;
47
49
  readonly editInstance: FallibleLoadFormResultMethod<EditFormInstance>;
48
50
  readonly restoreInstance: FallibleLoadFormResultMethod<RestoreFormInstance>;
49
51
  }
@@ -53,6 +55,7 @@ export interface LoadFormSuccessResult extends BaseLoadFormResult {
53
55
  readonly warnings: null;
54
56
  readonly error: null;
55
57
  readonly createInstance: CreateFormInstance;
58
+ readonly resetInstance: ResetFormInstance;
56
59
  readonly editInstance: EditFormInstance;
57
60
  readonly restoreInstance: RestoreFormInstance;
58
61
  }
@@ -62,6 +65,7 @@ export interface LoadFormWarningResult extends BaseLoadFormResult {
62
65
  readonly warnings: LoadFormWarnings;
63
66
  readonly error: null;
64
67
  readonly createInstance: CreateFormInstance;
68
+ readonly resetInstance: ResetFormInstance;
65
69
  readonly editInstance: EditFormInstance;
66
70
  readonly restoreInstance: RestoreFormInstance;
67
71
  }
@@ -70,25 +74,9 @@ export interface LoadFormFailureResult extends BaseLoadFormResult {
70
74
  readonly status: 'failure';
71
75
  readonly warnings: LoadFormWarnings | null;
72
76
  readonly error: LoadFormFailureError;
73
-
74
- /**
75
- * @example A temporary demo integration was built during development of this
76
- * interface.
77
- *
78
- * @see
79
- * {@link https://github.com/getodk/web-forms/pull/345/commits/9ef36355d89dd1450d3a87c3a55506bb9b0fc414}
80
- */
81
77
  readonly createInstance: FailedLoadFormResultMethod<CreateFormInstance>;
82
-
78
+ readonly resetInstance: FailedLoadFormResultMethod<ResetFormInstance>;
83
79
  readonly editInstance: FailedLoadFormResultMethod<EditFormInstance>;
84
-
85
- /**
86
- * @example A temporary demo integration was built during development of this
87
- * interface.
88
- *
89
- * @see
90
- * {@link https://github.com/getodk/web-forms/pull/345/commits/9ef36355d89dd1450d3a87c3a55506bb9b0fc414}
91
- */
92
80
  readonly restoreInstance: FailedLoadFormResultMethod<RestoreFormInstance>;
93
81
  }
94
82
 
@@ -0,0 +1,17 @@
1
+ import type { RootNode } from '../RootNode.ts';
2
+ import type { CreatedFormInstance } from './CreateFormInstance.ts';
3
+ import type { FormInstanceConfig } from './FormInstanceConfig.ts';
4
+ import type { LoadForm } from './LoadForm.ts';
5
+ import type { LoadFormResult } from './LoadFormResult.ts';
6
+
7
+ /**
8
+ * @todo This is fallible! Client-facing interfaces will need to account for
9
+ * this. We've begun addressing fallibility _at the interface level_ in
10
+ * {@link LoadForm} (with {@link LoadFormResult}). We'll eventually have a more
11
+ * general interface pattern for this, and we'll apply it here as well. The baby
12
+ * step approach in {@link LoadFormResult} is impractical here due to
13
+ * engine-internal designs, and revising that is currently out of scope. As
14
+ * such, explicit interface-level documentation of fallibility is deferred here,
15
+ * on {@link RootNode} itself, and into any of its sub-interfaces.
16
+ */
17
+ export type ResetFormInstance = (config?: FormInstanceConfig) => CreatedFormInstance;
@@ -42,6 +42,7 @@ export class FormInstance<Mode extends FormInstanceInitializationMode>
42
42
  clientStateFactory: instanceConfig.stateFactory ?? identity,
43
43
  computeAttachmentName: instanceConfig.instanceAttachments?.fileNameFactory ?? (() => null),
44
44
  preloadProperties: instanceConfig.preloadProperties ?? {},
45
+ geolocationProvider: instanceConfig.geolocationProvider,
45
46
  };
46
47
  const primaryInstanceOptions: PrimaryInstanceOptions<Mode> = {
47
48
  ...options.instanceOptions,
@@ -49,6 +50,7 @@ export class FormInstance<Mode extends FormInstanceInitializationMode>
49
50
  initialState,
50
51
  config,
51
52
  };
53
+
52
54
  const { root } = new PrimaryInstance(primaryInstanceOptions);
53
55
 
54
56
  this.mode = mode;
@@ -29,6 +29,7 @@ export abstract class BaseFormResult<Status extends FormResultStatus> {
29
29
  readonly error: BaseFormResultProperty<Status, 'error'>;
30
30
 
31
31
  abstract readonly createInstance: BaseFormResultProperty<Status, 'createInstance'>;
32
+ abstract readonly resetInstance: BaseFormResultProperty<Status, 'resetInstance'>;
32
33
  abstract readonly editInstance: BaseFormResultProperty<Status, 'editInstance'>;
33
34
  abstract readonly restoreInstance: BaseFormResultProperty<Status, 'restoreInstance'>;
34
35
 
@@ -5,6 +5,7 @@ import type {
5
5
  } from '../../client/form/EditFormInstance.ts';
6
6
  import type { FormInstanceConfig } from '../../client/form/FormInstanceConfig.ts';
7
7
  import type { FormResultStatus } from '../../client/form/LoadFormResult.ts';
8
+ import type { ResetFormInstance } from '../../client/form/ResetFormInstance.ts';
8
9
  import type {
9
10
  RestoreFormInstance,
10
11
  RestoreFormInstanceInput,
@@ -13,7 +14,8 @@ import { ErrorProductionDesignPendingError } from '../../error/ErrorProductionDe
13
14
  import { InitialInstanceState } from '../../instance/input/InitialInstanceState.ts';
14
15
  import type { BasePrimaryInstanceOptions } from '../../instance/PrimaryInstance.ts';
15
16
  import type { FormResource } from '../../instance/resource.ts';
16
- import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
17
+ import { type ReactiveScope } from '../../lib/reactivity/scope.ts';
18
+ import { createPotentiallyClientOwnedReactiveScope } from '../createPotentiallyClientOwnedReactiveScope.ts';
17
19
  import type { InstantiableFormResult } from '../FormInstance.ts';
18
20
  import { FormInstance } from '../FormInstance.ts';
19
21
  import type { BaseFormResultProperty } from './BaseFormResult.ts';
@@ -37,6 +39,7 @@ export abstract class BaseInstantiableFormResult<
37
39
  Status extends InstantiableFormResultStatus,
38
40
  > extends BaseFormResult<Status> {
39
41
  readonly createInstance: CreateFormInstance;
42
+ readonly resetInstance: ResetFormInstance;
40
43
  readonly editInstance: EditFormInstance;
41
44
  readonly restoreInstance: RestoreFormInstance;
42
45
 
@@ -60,6 +63,12 @@ export abstract class BaseInstantiableFormResult<
60
63
  });
61
64
  };
62
65
 
66
+ this.resetInstance = (instanceConfig: FormInstanceConfig = {}) => {
67
+ instanceOptions.scope.dispose();
68
+ instanceOptions.scope = createPotentiallyClientOwnedReactiveScope();
69
+ return this.createInstance(instanceConfig);
70
+ };
71
+
63
72
  this.editInstance = async (
64
73
  input: EditFormInstanceInput,
65
74
  instanceConfig: FormInstanceConfig = {}
@@ -6,6 +6,7 @@ import type {
6
6
  LoadFormFailureResult,
7
7
  LoadFormWarnings,
8
8
  } from '../../client/form/LoadFormResult.ts';
9
+ import type { ResetFormInstance } from '../../client/form/ResetFormInstance.ts';
9
10
  import type { RestoreFormInstance } from '../../client/form/RestoreFormInstance.ts';
10
11
  import { LoadFormFailureError } from '../../error/LoadFormFailureError.ts';
11
12
  import { BaseFormResult } from './BaseFormResult.ts';
@@ -25,6 +26,7 @@ const failedFormResultMethodFactory = <T extends AnyFunction>(
25
26
 
26
27
  export class FormFailureResult extends BaseFormResult<'failure'> implements LoadFormFailureResult {
27
28
  readonly createInstance: FailedLoadFormResultMethod<CreateFormInstance>;
29
+ readonly resetInstance: FailedLoadFormResultMethod<ResetFormInstance>;
28
30
  readonly editInstance: FailedLoadFormResultMethod<EditFormInstance>;
29
31
  readonly restoreInstance: FailedLoadFormResultMethod<RestoreFormInstance>;
30
32
 
@@ -38,6 +40,7 @@ export class FormFailureResult extends BaseFormResult<'failure'> implements Load
38
40
  });
39
41
 
40
42
  this.createInstance = failedFormResultMethodFactory(error);
43
+ this.resetInstance = failedFormResultMethodFactory(error);
41
44
  this.editInstance = failedFormResultMethodFactory(error);
42
45
  this.restoreInstance = failedFormResultMethodFactory(error);
43
46
  }
@@ -0,0 +1,30 @@
1
+ import { getOwner, type Owner } from 'solid-js';
2
+ import { createReactiveScope, type ReactiveScope } from '../lib/reactivity/scope';
3
+
4
+ /**
5
+ * Creates a {@link ReactiveScope | reactive scope} from which all form
6
+ * instances derive, and:
7
+ *
8
+ * - if a client loads a form within a Solid reactive context, the scope will be
9
+ * disposed along with the client's reactive context; OR
10
+ * - if a client loads a form outside a Solid reactive context (typically: if a
11
+ * client does not use Solid reactivity), the scope will disposed if and when
12
+ * the engine drops access to the loaded form
13
+ *
14
+ * **IMPORTANT:** this **MUST** be called synchronously. If it is called in an
15
+ * `async` function, it **MUST** be called before any `await` expression; if it
16
+ * is called in any other flow with mixed synchrony, it must be called before
17
+ * yielding to the event loop. Failing to do this will cause the engine to lose
18
+ * access to a client's Solid reactive context, potentially leaking form
19
+ * reactivity indefinitely.
20
+ */
21
+ export const createPotentiallyClientOwnedReactiveScope = (): ReactiveScope => {
22
+ /**
23
+ * A {@link clientOwner | client owner} is the owner of a client's Solid
24
+ * reactive context, if one exists. If none exists, the {@link ReactiveScope}
25
+ * is fully owned by the engine.
26
+ */
27
+ const clientOwner: Owner | null = getOwner();
28
+
29
+ return createReactiveScope({ owner: clientOwner });
30
+ };
@@ -1,5 +1,3 @@
1
- import type { Owner } from 'solid-js';
2
- import { getOwner } from 'solid-js';
3
1
  import { MISSING_RESOURCE_BEHAVIOR } from '../client/constants.ts';
4
2
  import type { FormResource } from '../client/form/FormResource.ts';
5
3
  import type { LoadForm, LoadFormOptions } from '../client/form/LoadForm.ts';
@@ -7,40 +5,12 @@ import type { LoadFormResult } from '../client/form/LoadFormResult.ts';
7
5
  import { LoadFormFailureError } from '../error/LoadFormFailureError.ts';
8
6
  import { retrieveFormDefinition } from '../instance/resource.ts';
9
7
  import type { ReactiveScope } from '../lib/reactivity/scope.ts';
10
- import { createReactiveScope } from '../lib/reactivity/scope.ts';
11
8
  import { XFormDOM } from '../parse/XFormDOM.ts';
12
9
  import { XFormDefinition } from '../parse/XFormDefinition.ts';
13
10
  import { SecondaryInstancesDefinition } from '../parse/model/SecondaryInstance/SecondaryInstancesDefinition.ts';
14
11
  import { FormFailureResult } from './FormResult/FormFailureResult.ts';
15
12
  import { FormSuccessResult } from './FormResult/FormSuccessResult.ts';
16
-
17
- /**
18
- * Creates a {@link ReactiveScope | reactive scope} from which all form
19
- * instances derive, and:
20
- *
21
- * - if a client loads a form within a Solid reactive context, the scope will be
22
- * disposed along with the client's reactive context; OR
23
- * - if a client loads a form outside a Solid reactive context (typically: if a
24
- * client does not use Solid reactivity), the scope will disposed if and when
25
- * the engine drops access to the loaded form
26
- *
27
- * **IMPORTANT:** this **MUST** be called synchronously. If it is called in an
28
- * `async` function, it **MUST** be called before any `await` expression; if it
29
- * is called in any other flow with mixed synchrony, it must be called before
30
- * yielding to the event loop. Failing to do this will cause the engine to lose
31
- * access to a client's Solid reactive context, potentially leaking form
32
- * reactivity indefinitely.
33
- */
34
- const createPotentiallyClientOwnedReactiveScope = (): ReactiveScope => {
35
- /**
36
- * A {@link clientOwner | client owner} is the owner of a client's Solid
37
- * reactive context, if one exists. If none exists, the {@link ReactiveScope}
38
- * is fully owned by the engine.
39
- */
40
- const clientOwner: Owner | null = getOwner();
41
-
42
- return createReactiveScope({ owner: clientOwner });
43
- };
13
+ import { createPotentiallyClientOwnedReactiveScope } from './createPotentiallyClientOwnedReactiveScope.ts';
44
14
 
45
15
  type GlobalFetch = typeof globalThis.fetch;
46
16
 
@@ -41,9 +41,7 @@ const stringInputNodeOptions = (control: InputControlDefinition): InputNodeOptio
41
41
  rows: control.rows,
42
42
  });
43
43
 
44
- const geoInputNodeOptions = (
45
- control: InputControlDefinition
46
- ): InputNodeOptions<'geopoint' | 'geoshape' | 'geotrace'> => ({
44
+ const geoInputNodeOptions = (control: InputControlDefinition): InputNodeOptions<'geopoint'> => ({
47
45
  accuracyThreshold: control.accuracyThreshold,
48
46
  unacceptableAccuracyThreshold: control.unacceptableAccuracyThreshold,
49
47
  });
@@ -65,8 +63,8 @@ const nodeOptionsFactoryByType: NodeOptionsFactoryByType = {
65
63
  time: () => null,
66
64
  dateTime: () => null,
67
65
  geopoint: geoInputNodeOptions,
68
- geotrace: geoInputNodeOptions,
69
- geoshape: geoInputNodeOptions,
66
+ geotrace: () => null,
67
+ geoshape: () => null,
70
68
  binary: () => null,
71
69
  barcode: () => null,
72
70
  intent: () => null,
@@ -1,6 +1,7 @@
1
- import { XPathNodeKindKey } from '@getodk/xpath';
1
+ import { clearCache, XPathNodeKindKey } from '@getodk/xpath';
2
2
  import type { Accessor } from 'solid-js';
3
3
  import { createSignal } from 'solid-js';
4
+ import type { GeolocationProvider } from '../client';
4
5
  import type { FormInstanceInitializationMode } from '../client/form/FormInstance.ts';
5
6
  import type { ActiveLanguage, FormLanguage, FormLanguages } from '../client/FormLanguage.ts';
6
7
  import type { FormNodeID } from '../client/identity.ts';
@@ -87,6 +88,7 @@ interface PrimaryInstanceStateSpec {
87
88
 
88
89
  interface PrimaryInstanceStateInputByMode {
89
90
  readonly create: null;
91
+ readonly reset: null;
90
92
  readonly edit: InitialInstanceState;
91
93
  readonly restore: InitialInstanceState;
92
94
  }
@@ -95,7 +97,7 @@ export type PrimaryInstanceInitialState<Mode extends FormInstanceInitializationM
95
97
  PrimaryInstanceStateInputByMode[Mode];
96
98
 
97
99
  export interface BasePrimaryInstanceOptions {
98
- readonly scope: ReactiveScope;
100
+ scope: ReactiveScope;
99
101
  readonly model: ModelDefinition;
100
102
  readonly secondaryInstances: SecondaryInstancesDefinition;
101
103
  }
@@ -137,6 +139,7 @@ export class PrimaryInstance<
137
139
  readonly hasNonRelevantAncestor = () => false;
138
140
  readonly isRelevant = () => true;
139
141
 
142
+ private geolocationProvider: GeolocationProvider | undefined;
140
143
  // TranslationContext (support)
141
144
  private readonly setActiveLanguage: SimpleAtomicStateSetter<FormLanguage>;
142
145
 
@@ -168,6 +171,8 @@ export class PrimaryInstance<
168
171
  const activeInstance = initialState?.document ?? modelInstance;
169
172
  const definition = model.getRootDefinition(activeInstance);
170
173
 
174
+ clearCache();
175
+
171
176
  super(config, null, activeInstance, definition, {
172
177
  scope,
173
178
  computeReference: () => PRIMARY_INSTANCE_REFERENCE,
@@ -177,6 +182,7 @@ export class PrimaryInstance<
177
182
  this.model = model;
178
183
  this.attachments = new InstanceAttachmentsState(initialState?.attachments);
179
184
  this.instanceNode = activeInstance;
185
+ this.geolocationProvider = config.geolocationProvider;
180
186
 
181
187
  const [isAttached, setIsAttached] = createSignal(false);
182
188
 
@@ -294,4 +300,13 @@ export class PrimaryInstance<
294
300
 
295
301
  return Promise.resolve(result);
296
302
  }
303
+
304
+ async getBackgroundGeopoint(): Promise<string> {
305
+ if (!this.geolocationProvider) {
306
+ return '';
307
+ }
308
+
309
+ const location = await this.geolocationProvider.getLocation();
310
+ return location ?? '';
311
+ }
297
312
  }
@@ -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,6 +8,19 @@ 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>
@@ -13,7 +29,11 @@ export function buildAttributes(
13
29
  if (!attributes) {
14
30
  return [];
15
31
  }
32
+ const instanceAttributes = buildInstanceAttributeMap(owner.instanceNode);
16
33
  return Array.from(attributes.values()).map((attributeDefinition) => {
17
- return new Attribute(owner, attributeDefinition, attributeDefinition.template);
34
+ const instanceNode =
35
+ instanceAttributes.get(attributeDefinition.qualifiedName.getPrefixedName()) ??
36
+ attributeDefinition.template;
37
+ return new Attribute(owner, attributeDefinition, instanceNode);
18
38
  });
19
39
  }
@@ -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
  }
@@ -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
  }
@@ -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');