@getodk/xforms-engine 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/client/AttributeNode.d.ts +4 -6
  2. package/dist/client/BaseNode.d.ts +5 -0
  3. package/dist/client/form/FormInstanceConfig.d.ts +15 -0
  4. package/dist/index.js +654 -429
  5. package/dist/index.js.map +1 -1
  6. package/dist/instance/Attribute.d.ts +13 -19
  7. package/dist/instance/Group.d.ts +2 -1
  8. package/dist/instance/InputControl.d.ts +5 -0
  9. package/dist/instance/ModelValue.d.ts +4 -0
  10. package/dist/instance/Note.d.ts +4 -0
  11. package/dist/instance/PrimaryInstance.d.ts +3 -2
  12. package/dist/instance/RangeControl.d.ts +4 -0
  13. package/dist/instance/RankControl.d.ts +5 -1
  14. package/dist/instance/Root.d.ts +2 -1
  15. package/dist/instance/SelectControl.d.ts +5 -1
  16. package/dist/instance/TriggerControl.d.ts +4 -0
  17. package/dist/instance/UploadControl.d.ts +3 -2
  18. package/dist/instance/abstract/DescendantNode.d.ts +5 -4
  19. package/dist/instance/abstract/InstanceNode.d.ts +6 -5
  20. package/dist/instance/abstract/ValueNode.d.ts +2 -1
  21. package/dist/instance/attachments/buildAttributes.d.ts +6 -2
  22. package/dist/instance/hierarchy.d.ts +2 -2
  23. package/dist/instance/internal-api/AttributeContext.d.ts +6 -0
  24. package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
  25. package/dist/instance/internal-api/InstanceValueContext.d.ts +6 -0
  26. package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +0 -1
  27. package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +4 -0
  28. package/dist/integration/xpath/adapter/XFormsXPathNode.d.ts +1 -1
  29. package/dist/integration/xpath/adapter/kind.d.ts +5 -3
  30. package/dist/integration/xpath/adapter/traversal.d.ts +3 -3
  31. package/dist/integration/xpath/static-dom/StaticAttribute.d.ts +1 -0
  32. package/dist/lib/codecs/items/SingleValueItemCodec.d.ts +1 -1
  33. package/dist/lib/reactivity/createInstanceValueState.d.ts +4 -1
  34. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +2 -2
  35. package/dist/lib/xml-serialization.d.ts +1 -1
  36. package/dist/parse/XFormDOM.d.ts +3 -0
  37. package/dist/parse/expression/ActionComputationExpression.d.ts +4 -0
  38. package/dist/parse/model/ActionDefinition.d.ts +15 -0
  39. package/dist/parse/model/AttributeDefinition.d.ts +5 -1
  40. package/dist/parse/model/BindPreloadDefinition.d.ts +6 -10
  41. package/dist/parse/model/Event.d.ts +8 -0
  42. package/dist/parse/model/LeafNodeDefinition.d.ts +5 -2
  43. package/dist/parse/model/ModelActionMap.d.ts +9 -0
  44. package/dist/parse/model/ModelDefinition.d.ts +8 -1
  45. package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
  46. package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
  47. package/dist/parse/model/RootDefinition.d.ts +1 -0
  48. package/dist/solid.js +654 -429
  49. package/dist/solid.js.map +1 -1
  50. package/package.json +21 -17
  51. package/src/client/AttributeNode.ts +4 -6
  52. package/src/client/BaseNode.ts +6 -0
  53. package/src/client/form/FormInstanceConfig.ts +17 -0
  54. package/src/client/validation.ts +1 -1
  55. package/src/entrypoints/FormInstance.ts +1 -0
  56. package/src/instance/Attribute.ts +43 -59
  57. package/src/instance/Group.ts +5 -6
  58. package/src/instance/InputControl.ts +16 -1
  59. package/src/instance/ModelValue.ts +16 -1
  60. package/src/instance/Note.ts +15 -1
  61. package/src/instance/PrimaryInstance.ts +8 -10
  62. package/src/instance/RangeControl.ts +15 -1
  63. package/src/instance/RankControl.ts +17 -2
  64. package/src/instance/Root.ts +5 -6
  65. package/src/instance/SelectControl.ts +16 -2
  66. package/src/instance/TriggerControl.ts +15 -1
  67. package/src/instance/UploadControl.ts +9 -8
  68. package/src/instance/abstract/DescendantNode.ts +4 -8
  69. package/src/instance/abstract/InstanceNode.ts +7 -5
  70. package/src/instance/abstract/ValueNode.ts +2 -1
  71. package/src/instance/attachments/buildAttributes.ts +15 -4
  72. package/src/instance/children/childrenInitOptions.ts +2 -1
  73. package/src/instance/children/normalizeChildInitOptions.ts +1 -1
  74. package/src/instance/hierarchy.ts +2 -2
  75. package/src/instance/internal-api/AttributeContext.ts +6 -0
  76. package/src/instance/internal-api/InstanceConfig.ts +6 -1
  77. package/src/instance/internal-api/InstanceValueContext.ts +6 -0
  78. package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +0 -1
  79. package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +4 -0
  80. package/src/instance/repeat/RepeatInstance.ts +3 -4
  81. package/src/integration/xpath/adapter/XFormsXPathNode.ts +1 -0
  82. package/src/integration/xpath/adapter/engineDOMAdapter.ts +2 -2
  83. package/src/integration/xpath/adapter/kind.ts +6 -1
  84. package/src/integration/xpath/adapter/names.ts +1 -0
  85. package/src/integration/xpath/adapter/traversal.ts +5 -6
  86. package/src/integration/xpath/static-dom/StaticAttribute.ts +1 -0
  87. package/src/lib/client-reactivity/instance-state/createValueNodeInstanceState.ts +2 -1
  88. package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +1 -0
  89. package/src/lib/codecs/NoteCodec.ts +1 -1
  90. package/src/lib/codecs/items/SingleValueItemCodec.ts +1 -3
  91. package/src/lib/reactivity/createInstanceValueState.ts +177 -52
  92. package/src/lib/reactivity/node-state/createSharedNodeState.ts +2 -2
  93. package/src/lib/xml-serialization.ts +9 -1
  94. package/src/parse/XFormDOM.ts +9 -0
  95. package/src/parse/body/GroupElementDefinition.ts +1 -1
  96. package/src/parse/body/control/InputControlDefinition.ts +1 -1
  97. package/src/parse/expression/ActionComputationExpression.ts +12 -0
  98. package/src/parse/model/ActionDefinition.ts +70 -0
  99. package/src/parse/model/AttributeDefinition.ts +10 -2
  100. package/src/parse/model/AttributeDefinitionMap.ts +1 -1
  101. package/src/parse/model/BindDefinition.ts +1 -6
  102. package/src/parse/model/BindPreloadDefinition.ts +44 -12
  103. package/src/parse/model/Event.ts +9 -0
  104. package/src/parse/model/LeafNodeDefinition.ts +5 -1
  105. package/src/parse/model/ModelActionMap.ts +37 -0
  106. package/src/parse/model/ModelDefinition.ts +18 -3
  107. package/src/parse/model/NoteNodeDefinition.ts +5 -2
  108. package/src/parse/model/RangeNodeDefinition.ts +5 -2
  109. package/src/parse/model/RootDefinition.ts +22 -4
  110. package/dist/lib/reactivity/createAttributeValueState.d.ts +0 -15
  111. package/src/lib/reactivity/createAttributeValueState.ts +0 -189
@@ -0,0 +1,70 @@
1
+ import { isTextNode } from '@getodk/common/lib/dom/predicates.ts';
2
+ import { ActionComputationExpression } from '../expression/ActionComputationExpression.ts';
3
+ import { type XFormEvent, XFORM_EVENT } from './Event.ts';
4
+ import type { ModelDefinition } from './ModelDefinition.ts';
5
+
6
+ export class ActionDefinition {
7
+ static getRef(model: ModelDefinition, setValueElement: Element): string | null {
8
+ if (setValueElement.hasAttribute('ref')) {
9
+ return setValueElement.getAttribute('ref') ?? null;
10
+ }
11
+ if (setValueElement.hasAttribute('bind')) {
12
+ const bindId = setValueElement.getAttribute('bind');
13
+ const bindDefinition = Array.from(model.binds.values()).find((definition) => {
14
+ return definition.bindElement.getAttribute('id') === bindId;
15
+ });
16
+ return bindDefinition?.nodeset ?? null;
17
+ }
18
+ return null;
19
+ }
20
+
21
+ static getValue(element: Element): string {
22
+ if (element.hasAttribute('value')) {
23
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
24
+ return element.getAttribute('value') || "''";
25
+ }
26
+ if (element.firstChild && isTextNode(element.firstChild)) {
27
+ // use the text content as the literal value
28
+ return `'${element.firstChild.nodeValue}'`;
29
+ }
30
+ return "''";
31
+ }
32
+
33
+ static isKnownEvent = (event: XFormEvent): event is XFormEvent => {
34
+ return Object.values(XFORM_EVENT).includes(event);
35
+ };
36
+
37
+ static getEvents(element: Element): XFormEvent[] {
38
+ const events = element.getAttribute('event')?.split(' ') ?? [];
39
+ const unknownEvents = events.filter((event) => !this.isKnownEvent(event as XFormEvent));
40
+ if (unknownEvents.length) {
41
+ throw new Error(
42
+ `An action was registered for unsupported events: ${unknownEvents.join(', ')}`
43
+ );
44
+ }
45
+ return events as XFormEvent[];
46
+ }
47
+
48
+ readonly ref: string;
49
+ readonly events: XFormEvent[];
50
+ readonly computation: ActionComputationExpression<'string'>;
51
+ readonly source: string | undefined;
52
+
53
+ constructor(
54
+ model: ModelDefinition,
55
+ readonly element: Element,
56
+ source?: string
57
+ ) {
58
+ const ref = ActionDefinition.getRef(model, element);
59
+ if (!ref) {
60
+ throw new Error(
61
+ 'Invalid setvalue element - you must define either "ref" or "bind" attribute'
62
+ );
63
+ }
64
+ this.ref = ref;
65
+ this.events = ActionDefinition.getEvents(element);
66
+ const value = ActionDefinition.getValue(element);
67
+ this.computation = new ActionComputationExpression('string', value);
68
+ this.source = source;
69
+ }
70
+ }
@@ -7,6 +7,7 @@ import {
7
7
  import { QualifiedName } from '../../lib/names/QualifiedName.ts';
8
8
  import { escapeXMLText, serializeAttributeXML } from '../../lib/xml-serialization.ts';
9
9
  import type { BindDefinition } from './BindDefinition.ts';
10
+ import type { ModelDefinition } from './ModelDefinition.ts';
10
11
  import { NodeDefinition } from './NodeDefinition.ts';
11
12
  import type { RootDefinition } from './RootDefinition.ts';
12
13
 
@@ -18,6 +19,7 @@ export class AttributeDefinition
18
19
 
19
20
  readonly value: string;
20
21
  readonly type = 'attribute';
22
+ readonly valueType = 'string';
21
23
  readonly namespaceDeclarations: NamespaceDeclarationMap;
22
24
  readonly bodyElement = null;
23
25
  readonly root: RootDefinition;
@@ -29,7 +31,7 @@ export class AttributeDefinition
29
31
  readonly qualifiedName: QualifiedName;
30
32
 
31
33
  constructor(
32
- root: RootDefinition,
34
+ readonly model: ModelDefinition,
33
35
  bind: BindDefinition,
34
36
  readonly template: StaticAttribute
35
37
  ) {
@@ -37,7 +39,7 @@ export class AttributeDefinition
37
39
 
38
40
  const { value } = template;
39
41
 
40
- this.root = root;
42
+ this.root = model.root;
41
43
 
42
44
  this.value = value;
43
45
  this.qualifiedName = template.qualifiedName;
@@ -55,4 +57,10 @@ export class AttributeDefinition
55
57
  serializeAttributeXML(): string {
56
58
  return this.serializedXML;
57
59
  }
60
+
61
+ toJSON() {
62
+ const { bind, bodyElement, parent, root, ...rest } = this;
63
+
64
+ return rest;
65
+ }
58
66
  }
@@ -28,7 +28,7 @@ export class AttributeDefinitionMap extends Map<QualifiedName, AttributeDefiniti
28
28
  const nonNamespaceAttributes = instanceNode.attributes.filter(isNonNamespaceAttribute);
29
29
  const definitions = nonNamespaceAttributes.map((attribute) => {
30
30
  const bind = model.binds.getOrCreateBindDefinition(attribute.nodeset);
31
- return new AttributeDefinition(model.root, bind, attribute);
31
+ return new AttributeDefinition(model, bind, attribute);
32
32
  });
33
33
  return new this(definitions);
34
34
  }
@@ -38,10 +38,7 @@ export class BindDefinition<T extends BindType = BindType> extends DependencyCon
38
38
  // https://github.com/getodk/collect/issues/3758 mentions deprecation.
39
39
  readonly saveIncomplete: BindComputationExpression<'saveIncomplete'>;
40
40
 
41
- // TODO: these are deferred until prioritized
42
- // readonly preload: string | null;
43
- // readonly preloadParams: string | null;
44
- // readonly 'max-pixels': string | null;
41
+ // TODO: deferred until prioritized: readonly 'max-pixels': string | null;
45
42
 
46
43
  protected _parentBind: BindDefinition | null | undefined;
47
44
 
@@ -95,8 +92,6 @@ export class BindDefinition<T extends BindType = BindType> extends DependencyCon
95
92
  this.constraintMsg = MessageDefinition.from(this, 'constraintMsg');
96
93
  this.requiredMsg = MessageDefinition.from(this, 'requiredMsg');
97
94
 
98
- // this.preload = BindComputation.forExpression(this, 'preload');
99
- // this.preloadParams = BindComputation.forExpression(this, 'preloadParams');
100
95
  // this['max-pixels'] = BindComputation.forExpression(this, 'max-pixels');
101
96
  }
102
97
 
@@ -1,10 +1,18 @@
1
1
  import { JAVAROSA_NAMESPACE_URI } from '@getodk/common/constants/xmlns.ts';
2
2
  import type { PartiallyKnownString } from '@getodk/common/types/string/PartiallyKnownString.ts';
3
+ import type { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
4
+ import type { InstanceValueContext } from '../../instance/internal-api/InstanceValueContext.ts';
3
5
  import type { BindElement } from './BindElement.ts';
6
+ import { XFORM_EVENT, type XFormEvent } from './Event.ts';
4
7
 
5
- type PartiallyKnownPreloadParameter<Known extends string> =
6
- // eslint-disable-next-line @typescript-eslint/sort-type-constituents
7
- PartiallyKnownString<NonNullable<Known>> | Extract<Known, null>;
8
+ /**
9
+ * Per {@link https://getodk.github.io/xforms-spec/#preload-attributes:~:text=concatenation%20of%20%E2%80%98uuid%3A%E2%80%99%20and%20uuid()}
10
+ */
11
+ const PRELOAD_UID_EXPRESSION = 'concat("uuid:", uuid())';
12
+
13
+ type PartiallyKnownPreloadParameter<Known extends string> = PartiallyKnownString<
14
+ NonNullable<Known>
15
+ >;
8
16
 
9
17
  interface PreloadParametersByType {
10
18
  readonly uid: string | null;
@@ -61,15 +69,6 @@ const getPreloadInput = (bindElement: BindElement): AnyPreloadInput | null => {
61
69
  *
62
70
  * - {@link type}, a `jr:preload`
63
71
  * - {@link parameter}, an associated `jr:preloadParams` value
64
- *
65
- * @todo It would probably make sense for the _definition_ to also convey:
66
- *
67
- * 1. Which {@link https://getodk.github.io/xforms-spec/#events | event} the
68
- * preload is semantically associated with (noting that the spec may be a tad
69
- * overzealous about this association).
70
- *
71
- * 2. The constant XPath expression (or other computation?) expressed by the
72
- * combined {@link type} and {@link parameter}.
73
72
  */
74
73
  export class BindPreloadDefinition<Type extends PreloadType> implements PreloadInput<Type> {
75
74
  static from(bindElement: BindElement): AnyBindPreloadDefinition | null {
@@ -84,10 +83,43 @@ export class BindPreloadDefinition<Type extends PreloadType> implements PreloadI
84
83
 
85
84
  readonly type: Type;
86
85
  readonly parameter: PreloadParameter<Type>;
86
+ readonly event: XFormEvent;
87
+
88
+ getValue(context: AttributeContext | InstanceValueContext): string | undefined {
89
+ if (this.type === 'uid') {
90
+ return context.evaluator.evaluateString(PRELOAD_UID_EXPRESSION);
91
+ }
92
+ if (this.type === 'timestamp') {
93
+ return context.evaluator.evaluateString('now()');
94
+ }
95
+ if (this.type === 'date') {
96
+ return context.evaluator.evaluateString('today()');
97
+ }
98
+ if (this.type === 'property') {
99
+ const properties = context.instanceConfig.preloadProperties;
100
+ if (this.parameter === 'deviceid') {
101
+ return properties.deviceID;
102
+ }
103
+ if (this.parameter === 'email') {
104
+ return properties.email;
105
+ }
106
+ if (this.parameter === 'phonenumber') {
107
+ return properties.phoneNumber;
108
+ }
109
+ if (this.parameter === 'username') {
110
+ return properties.username;
111
+ }
112
+ }
113
+ return;
114
+ }
87
115
 
88
116
  private constructor(input: PreloadInput<Type>) {
89
117
  this.type = input.type;
90
118
  this.parameter = input.parameter;
119
+ this.event =
120
+ this.type === 'timestamp' && this.parameter === 'end'
121
+ ? XFORM_EVENT.xformsRevalidate
122
+ : XFORM_EVENT.odkInstanceFirstLoad;
91
123
  }
92
124
  }
93
125
 
@@ -0,0 +1,9 @@
1
+ export const XFORM_EVENT = {
2
+ odkInstanceLoad: 'odk-instance-load',
3
+ odkInstanceFirstLoad: 'odk-instance-first-load',
4
+ odkNewRepeat: 'odk-new-repeat',
5
+ xformsRevalidate: 'xforms-revalidate',
6
+ xformsValueChanged: 'xforms-value-changed',
7
+ } as const;
8
+
9
+ export type XFormEvent = (typeof XFORM_EVENT)[keyof typeof XFORM_EVENT];
@@ -6,8 +6,10 @@ import {
6
6
  } from '../../lib/names/NamespaceDeclarationMap.ts';
7
7
  import { QualifiedName } from '../../lib/names/QualifiedName.ts';
8
8
  import type { AnyBodyElementDefinition, ControlElementDefinition } from '../body/BodyDefinition.ts';
9
+ import { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
9
10
  import type { BindDefinition } from './BindDefinition.ts';
10
11
  import { DescendentNodeDefinition } from './DescendentNodeDefinition.ts';
12
+ import type { ModelDefinition } from './ModelDefinition.ts';
11
13
  import type { ParentNodeDefinition } from './NodeDefinition.ts';
12
14
 
13
15
  export class LeafNodeDefinition<V extends ValueType = ValueType>
@@ -20,9 +22,10 @@ export class LeafNodeDefinition<V extends ValueType = ValueType>
20
22
  readonly namespaceDeclarations: NamespaceDeclarationMap;
21
23
  readonly qualifiedName: QualifiedName;
22
24
  readonly children = null;
23
- readonly attributes = null;
25
+ readonly attributes: AttributeDefinitionMap;
24
26
 
25
27
  constructor(
28
+ readonly model: ModelDefinition,
26
29
  parent: ParentNodeDefinition,
27
30
  bind: BindDefinition,
28
31
  bodyElement: AnyBodyElementDefinition | null,
@@ -37,6 +40,7 @@ export class LeafNodeDefinition<V extends ValueType = ValueType>
37
40
  this.valueType = bind.type.resolved satisfies ValueType as V;
38
41
  this.qualifiedName = template.qualifiedName;
39
42
  this.namespaceDeclarations = new NamespaceDeclarationMap(this);
43
+ this.attributes = AttributeDefinitionMap.from(model, template);
40
44
  }
41
45
 
42
46
  toJSON() {
@@ -0,0 +1,37 @@
1
+ import { ActionDefinition } from './ActionDefinition.ts';
2
+ import { XFORM_EVENT } from './Event.ts';
3
+ import type { ModelDefinition } from './ModelDefinition.ts';
4
+
5
+ const REPEAT_REGEX = /(\[[^\]]*\])/gm;
6
+
7
+ export class ModelActionMap extends Map<string, ActionDefinition> {
8
+ static fromModel(model: ModelDefinition): ModelActionMap {
9
+ return new this(model);
10
+ }
11
+
12
+ static getKey(ref: string): string {
13
+ return ref.replace(REPEAT_REGEX, '');
14
+ }
15
+
16
+ protected constructor(model: ModelDefinition) {
17
+ super(
18
+ model.form.xformDOM.setValues.map((setValueElement) => {
19
+ const action = new ActionDefinition(model, setValueElement);
20
+ if (action.events.includes(XFORM_EVENT.odkNewRepeat)) {
21
+ throw new Error('Model contains "setvalue" element with "odk-new-repeat" event');
22
+ }
23
+ const key = ModelActionMap.getKey(action.ref);
24
+ return [key, action];
25
+ })
26
+ );
27
+ }
28
+
29
+ override get(ref: string): ActionDefinition | undefined {
30
+ return super.get(ModelActionMap.getKey(ref));
31
+ }
32
+
33
+ add(action: ActionDefinition) {
34
+ const key = ModelActionMap.getKey(action.ref);
35
+ this.set(key, action);
36
+ }
37
+ }
@@ -6,6 +6,7 @@ import { parseStaticDocumentFromDOMSubtree } from '../shared/parseStaticDocument
6
6
  import type { XFormDefinition } from '../XFormDefinition.ts';
7
7
  import { generateItextChunks, type ChunkExpressionsByItextId } from './generateItextChunks.ts';
8
8
  import { ItextTranslationsDefinition } from './ItextTranslationsDefinition.ts';
9
+ import { ModelActionMap } from './ModelActionMap.ts';
9
10
  import { ModelBindMap } from './ModelBindMap.ts';
10
11
  import type { AnyNodeDefinition } from './NodeDefinition.ts';
11
12
  import type { NodeDefinitionMap } from './nodeDefinitionMap.ts';
@@ -13,18 +14,23 @@ import { nodeDefinitionMap } from './nodeDefinitionMap.ts';
13
14
  import { RootDefinition } from './RootDefinition.ts';
14
15
  import { SubmissionDefinition } from './SubmissionDefinition.ts';
15
16
 
17
+ type XformsRevalidateListener = () => void;
18
+
16
19
  export class ModelDefinition {
17
20
  readonly binds: ModelBindMap;
21
+ readonly actions: ModelActionMap;
18
22
  readonly root: RootDefinition;
19
23
  readonly nodes: NodeDefinitionMap;
20
24
  readonly instance: StaticDocument;
21
25
  readonly itextTranslations: ItextTranslationsDefinition;
22
26
  readonly itextChunks: Map<string, ChunkExpressionsByItextId>;
27
+ readonly xformsRevalidateListeners: Map<string, XformsRevalidateListener>;
23
28
 
24
29
  constructor(readonly form: XFormDefinition) {
25
30
  const submission = new SubmissionDefinition(form.xformDOM);
26
31
 
27
32
  this.binds = ModelBindMap.fromModel(this);
33
+ this.actions = ModelActionMap.fromModel(this);
28
34
  this.instance = parseStaticDocumentFromDOMSubtree(form.xformDOM.primaryInstanceRoot, {
29
35
  nodesetPrefix: '/',
30
36
  });
@@ -32,6 +38,7 @@ export class ModelDefinition {
32
38
  this.nodes = nodeDefinitionMap(this.root);
33
39
  this.itextTranslations = ItextTranslationsDefinition.from(form.xformDOM);
34
40
  this.itextChunks = generateItextChunks(form.xformDOM.itextTranslationElements);
41
+ this.xformsRevalidateListeners = new Map();
35
42
  }
36
43
 
37
44
  getNodeDefinition(nodeset: string): AnyNodeDefinition {
@@ -54,10 +61,12 @@ export class ModelDefinition {
54
61
  return definition;
55
62
  }
56
63
 
57
- toJSON() {
58
- const { form, ...rest } = this;
64
+ registerXformsRevalidateListener(ref: string, listener: XformsRevalidateListener) {
65
+ this.xformsRevalidateListeners.set(ref, listener);
66
+ }
59
67
 
60
- return rest;
68
+ triggerXformsRevalidateListeners() {
69
+ this.xformsRevalidateListeners.forEach((listener: XformsRevalidateListener) => listener());
61
70
  }
62
71
 
63
72
  getTranslationChunks(
@@ -67,4 +76,10 @@ export class ModelDefinition {
67
76
  const languageMap = this.itextChunks.get(activeLanguage.language);
68
77
  return languageMap?.get(itextId) ?? [];
69
78
  }
79
+
80
+ toJSON() {
81
+ const { form, ...rest } = this;
82
+
83
+ return rest;
84
+ }
70
85
  }
@@ -9,6 +9,7 @@ import type { HintDefinition } from '../text/HintDefinition.ts';
9
9
  import type { LabelDefinition } from '../text/LabelDefinition.ts';
10
10
  import type { BindDefinition } from './BindDefinition.ts';
11
11
  import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
12
+ import type { ModelDefinition } from './ModelDefinition.ts';
12
13
  import type { ParentNodeDefinition } from './NodeDefinition.ts';
13
14
 
14
15
  // prettier-ignore
@@ -41,6 +42,7 @@ export type NoteTextDefinition =
41
42
  */
42
43
  export class NoteNodeDefinition<V extends ValueType = ValueType> extends LeafNodeDefinition<V> {
43
44
  static from<V extends ValueType>(
45
+ model: ModelDefinition,
44
46
  parent: ParentNodeDefinition,
45
47
  bind: BindDefinition<V>,
46
48
  bodyElement: AnyBodyElementDefinition | null,
@@ -57,16 +59,17 @@ export class NoteNodeDefinition<V extends ValueType = ValueType> extends LeafNod
57
59
  return null;
58
60
  }
59
61
 
60
- return new this(parent, bind, bodyElement, noteTextDefinition, node);
62
+ return new this(model, parent, bind, bodyElement, noteTextDefinition, node);
61
63
  }
62
64
 
63
65
  constructor(
66
+ model: ModelDefinition,
64
67
  parent: ParentNodeDefinition,
65
68
  override readonly bind: NoteBindDefinition<V>,
66
69
  override readonly bodyElement: InputControlDefinition,
67
70
  readonly noteTextDefinition: NoteTextDefinition,
68
71
  template: StaticLeafElement
69
72
  ) {
70
- super(parent, bind, bodyElement, template);
73
+ super(model, parent, bind, bodyElement, template);
71
74
  }
72
75
  }
@@ -9,6 +9,7 @@ import type {
9
9
  } from '../body/control/RangeControlDefinition.ts';
10
10
  import type { BindDefinition } from './BindDefinition.ts';
11
11
  import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
12
+ import type { ModelDefinition } from './ModelDefinition.ts';
12
13
  import type { ParentNodeDefinition } from './NodeDefinition.ts';
13
14
 
14
15
  const RANGE_VALUE_TYPES = ['decimal', 'int'] as const;
@@ -89,6 +90,7 @@ export class RangeNodeDefinition<V extends RangeValueType = RangeValueType>
89
90
  implements RangeLeafNodeDefinition<V>
90
91
  {
91
92
  static from<V extends ValueType>(
93
+ model: ModelDefinition,
92
94
  parent: ParentNodeDefinition,
93
95
  bind: BindDefinition<V>,
94
96
  bodyElement: RangeControlDefinition,
@@ -96,18 +98,19 @@ export class RangeNodeDefinition<V extends RangeValueType = RangeValueType>
96
98
  ): RangeNodeDefinition<Extract<V, RangeValueType>> {
97
99
  assertRangeBindDefinition(bind);
98
100
 
99
- return new this(parent, bind, bodyElement, node);
101
+ return new this(model, parent, bind, bodyElement, node);
100
102
  }
101
103
 
102
104
  readonly bounds: RangeNodeBoundsDefinition<V>;
103
105
 
104
106
  private constructor(
107
+ model: ModelDefinition,
105
108
  parent: ParentNodeDefinition,
106
109
  override readonly bind: BindDefinition<V>,
107
110
  override readonly bodyElement: RangeControlDefinition,
108
111
  node: StaticLeafElement
109
112
  ) {
110
- super(parent, bind, bodyElement, node);
113
+ super(model, parent, bind, bodyElement, node);
111
114
 
112
115
  this.bounds = RangeNodeBoundsDefinition.from(bodyElement.bounds, bind);
113
116
  }
@@ -1,8 +1,9 @@
1
1
  import type { StaticElement } from '../../integration/xpath/static-dom/StaticElement.ts';
2
2
  import { NamespaceDeclarationMap } from '../../lib/names/NamespaceDeclarationMap.ts';
3
3
  import { QualifiedName } from '../../lib/names/QualifiedName.ts';
4
- import type { BodyClassList } from '../body/BodyDefinition.ts';
4
+ import type { AnyBodyElementDefinition, BodyClassList } from '../body/BodyDefinition.ts';
5
5
  import type { XFormDefinition } from '../XFormDefinition.ts';
6
+ import { ActionDefinition } from './ActionDefinition.ts';
6
7
  import { AttributeDefinitionMap } from './AttributeDefinitionMap.ts';
7
8
  import { GroupDefinition } from './GroupDefinition.ts';
8
9
  import { LeafNodeDefinition } from './LeafNodeDefinition.ts';
@@ -56,6 +57,19 @@ export class RootDefinition extends NodeDefinition<'root'> {
56
57
  this.children = this.buildSubtree(this, template);
57
58
  }
58
59
 
60
+ private mapActions(bodyElement: AnyBodyElementDefinition) {
61
+ const source = bodyElement.reference;
62
+ if (!source) {
63
+ return;
64
+ }
65
+ for (const child of bodyElement.element.children) {
66
+ if (child.nodeName === 'setvalue') {
67
+ const action = new ActionDefinition(this.model, child, source);
68
+ this.model.actions.add(action);
69
+ }
70
+ }
71
+ }
72
+
59
73
  buildSubtree(parent: ParentNodeDefinition, node: StaticElement): readonly ChildNodeDefinition[] {
60
74
  const { form, model } = this;
61
75
  const { body } = form;
@@ -86,6 +100,10 @@ export class RootDefinition extends NodeDefinition<'root'> {
86
100
  const bodyElement = body.getBodyElement(nodeset);
87
101
  const [firstChild, ...restChildren] = children;
88
102
 
103
+ if (bodyElement) {
104
+ this.mapActions(bodyElement);
105
+ }
106
+
89
107
  if (bodyElement?.type === 'repeat') {
90
108
  return RepeatDefinition.from(model, parent, bind, bodyElement, children);
91
109
  }
@@ -98,12 +116,12 @@ export class RootDefinition extends NodeDefinition<'root'> {
98
116
 
99
117
  if (element.isLeafElement()) {
100
118
  if (bodyElement?.type === 'range') {
101
- return RangeNodeDefinition.from(parent, bind, bodyElement, element);
119
+ return RangeNodeDefinition.from(model, parent, bind, bodyElement, element);
102
120
  }
103
121
 
104
122
  return (
105
- NoteNodeDefinition.from(parent, bind, bodyElement, element) ??
106
- new LeafNodeDefinition(parent, bind, bodyElement, element)
123
+ NoteNodeDefinition.from(model, parent, bind, bodyElement, element) ??
124
+ new LeafNodeDefinition(model, parent, bind, bodyElement, element)
107
125
  );
108
126
  }
109
127
 
@@ -1,15 +0,0 @@
1
- import { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
2
- import { SimpleAtomicState } from './types.ts';
3
- export type InstanceValueState = SimpleAtomicState<string>;
4
- /**
5
- * Provides a consistent interface for value nodes of any type which:
6
- *
7
- * - derives initial state from either an existing instance (e.g. for edits) or
8
- * the node's definition (e.g. initializing a new instance)
9
- * - decodes current primary instance state into the value node's runtime type
10
- * - encodes updated runtime values to store updated instance state
11
- * - initializes reactive computation of `calculate` bind expressions for those
12
- * nodes defined with one
13
- * - prevents downstream writes to nodes in a readonly state
14
- */
15
- export declare const createAttributeValueState: (context: AttributeContext) => InstanceValueState;