@getodk/xforms-engine 0.15.0 → 0.16.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 (94) hide show
  1. package/dist/client/AttributeNode.d.ts +2 -5
  2. package/dist/client/BaseNode.d.ts +5 -0
  3. package/dist/client/form/FormInstanceConfig.d.ts +15 -0
  4. package/dist/index.js +395 -231
  5. package/dist/index.js.map +1 -1
  6. package/dist/instance/Attribute.d.ts +17 -11
  7. package/dist/instance/Group.d.ts +0 -2
  8. package/dist/instance/InputControl.d.ts +2 -0
  9. package/dist/instance/PrimaryInstance.d.ts +0 -2
  10. package/dist/instance/Root.d.ts +0 -2
  11. package/dist/instance/UploadControl.d.ts +0 -2
  12. package/dist/instance/abstract/InstanceNode.d.ts +2 -2
  13. package/dist/instance/abstract/ValueNode.d.ts +2 -1
  14. package/dist/instance/attachments/buildAttributes.d.ts +6 -2
  15. package/dist/instance/hierarchy.d.ts +1 -2
  16. package/dist/instance/internal-api/AttributeContext.d.ts +6 -0
  17. package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
  18. package/dist/instance/internal-api/InstanceValueContext.d.ts +6 -0
  19. package/dist/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.d.ts +0 -1
  20. package/dist/instance/internal-api/serialization/ClientReactiveSerializableValueNode.d.ts +4 -0
  21. package/dist/instance/repeat/RepeatInstance.d.ts +0 -2
  22. package/dist/lib/codecs/items/SingleValueItemCodec.d.ts +1 -1
  23. package/dist/lib/reactivity/createInstanceValueState.d.ts +4 -1
  24. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +2 -2
  25. package/dist/lib/xml-serialization.d.ts +1 -1
  26. package/dist/parse/XFormDOM.d.ts +3 -0
  27. package/dist/parse/expression/ActionComputationExpression.d.ts +4 -0
  28. package/dist/parse/model/ActionDefinition.d.ts +15 -0
  29. package/dist/parse/model/AttributeDefinition.d.ts +3 -1
  30. package/dist/parse/model/BindPreloadDefinition.d.ts +6 -10
  31. package/dist/parse/model/Event.d.ts +8 -0
  32. package/dist/parse/model/LeafNodeDefinition.d.ts +5 -2
  33. package/dist/parse/model/ModelActionMap.d.ts +9 -0
  34. package/dist/parse/model/ModelDefinition.d.ts +8 -1
  35. package/dist/parse/model/NoteNodeDefinition.d.ts +3 -2
  36. package/dist/parse/model/RangeNodeDefinition.d.ts +2 -1
  37. package/dist/parse/model/RootDefinition.d.ts +1 -0
  38. package/dist/solid.js +395 -231
  39. package/dist/solid.js.map +1 -1
  40. package/package.json +21 -17
  41. package/src/client/AttributeNode.ts +2 -5
  42. package/src/client/BaseNode.ts +6 -0
  43. package/src/client/form/FormInstanceConfig.ts +17 -0
  44. package/src/client/validation.ts +1 -1
  45. package/src/entrypoints/FormInstance.ts +1 -0
  46. package/src/instance/Attribute.ts +45 -45
  47. package/src/instance/Group.ts +1 -10
  48. package/src/instance/InputControl.ts +8 -1
  49. package/src/instance/ModelValue.ts +7 -1
  50. package/src/instance/Note.ts +6 -1
  51. package/src/instance/PrimaryInstance.ts +1 -11
  52. package/src/instance/RangeControl.ts +6 -1
  53. package/src/instance/RankControl.ts +7 -1
  54. package/src/instance/Root.ts +1 -10
  55. package/src/instance/SelectControl.ts +6 -1
  56. package/src/instance/TriggerControl.ts +6 -1
  57. package/src/instance/UploadControl.ts +1 -10
  58. package/src/instance/abstract/DescendantNode.ts +0 -5
  59. package/src/instance/abstract/InstanceNode.ts +2 -2
  60. package/src/instance/abstract/ValueNode.ts +2 -1
  61. package/src/instance/attachments/buildAttributes.ts +11 -4
  62. package/src/instance/children/normalizeChildInitOptions.ts +1 -1
  63. package/src/instance/hierarchy.ts +1 -3
  64. package/src/instance/internal-api/AttributeContext.ts +6 -0
  65. package/src/instance/internal-api/InstanceConfig.ts +6 -1
  66. package/src/instance/internal-api/InstanceValueContext.ts +6 -0
  67. package/src/instance/internal-api/serialization/ClientReactiveSerializableAttributeNode.ts +0 -1
  68. package/src/instance/internal-api/serialization/ClientReactiveSerializableValueNode.ts +4 -0
  69. package/src/instance/repeat/RepeatInstance.ts +1 -10
  70. package/src/lib/client-reactivity/instance-state/createValueNodeInstanceState.ts +2 -1
  71. package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +1 -0
  72. package/src/lib/codecs/NoteCodec.ts +1 -1
  73. package/src/lib/codecs/items/SingleValueItemCodec.ts +1 -3
  74. package/src/lib/reactivity/createInstanceValueState.ts +152 -53
  75. package/src/lib/reactivity/node-state/createSharedNodeState.ts +2 -2
  76. package/src/lib/xml-serialization.ts +9 -1
  77. package/src/parse/XFormDOM.ts +9 -0
  78. package/src/parse/body/GroupElementDefinition.ts +1 -1
  79. package/src/parse/body/control/InputControlDefinition.ts +1 -1
  80. package/src/parse/expression/ActionComputationExpression.ts +12 -0
  81. package/src/parse/model/ActionDefinition.ts +70 -0
  82. package/src/parse/model/AttributeDefinition.ts +3 -2
  83. package/src/parse/model/AttributeDefinitionMap.ts +1 -1
  84. package/src/parse/model/BindDefinition.ts +1 -6
  85. package/src/parse/model/BindPreloadDefinition.ts +44 -12
  86. package/src/parse/model/Event.ts +9 -0
  87. package/src/parse/model/LeafNodeDefinition.ts +5 -1
  88. package/src/parse/model/ModelActionMap.ts +37 -0
  89. package/src/parse/model/ModelDefinition.ts +18 -3
  90. package/src/parse/model/NoteNodeDefinition.ts +5 -2
  91. package/src/parse/model/RangeNodeDefinition.ts +5 -2
  92. package/src/parse/model/RootDefinition.ts +22 -4
  93. package/dist/lib/reactivity/createAttributeValueState.d.ts +0 -15
  94. package/src/lib/reactivity/createAttributeValueState.ts +0 -189
@@ -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;
@@ -1,189 +0,0 @@
1
- import type { Signal } from 'solid-js';
2
- import { createComputed, createMemo, createSignal, untrack } from 'solid-js';
3
- import type { AttributeContext } from '../../instance/internal-api/AttributeContext.ts';
4
- import type { BindComputationExpression } from '../../parse/expression/BindComputationExpression.ts';
5
- import { createComputedExpression } from './createComputedExpression.ts';
6
- import type { SimpleAtomicState, SimpleAtomicStateSetter } from './types.ts';
7
-
8
- const isInstanceFirstLoad = (context: AttributeContext) => {
9
- return context.rootDocument.initializationMode === 'create';
10
- };
11
-
12
- /**
13
- * Special case, does not correspond to any event.
14
- *
15
- * @see {@link shouldPreloadUID}
16
- */
17
- const isEditInitialLoad = (context: AttributeContext) => {
18
- return context.rootDocument.initializationMode === 'edit';
19
- };
20
-
21
- const getInitialValue = (context: AttributeContext): string => {
22
- const sourceNode = context.instanceNode ?? context.definition.template;
23
-
24
- return context.decodeInstanceValue(sourceNode.value);
25
- };
26
-
27
- type BaseValueState = Signal<string>;
28
-
29
- type RelevantValueState = SimpleAtomicState<string>;
30
-
31
- /**
32
- * Wraps {@link baseValueState} in a signal-like interface which:
33
- *
34
- * - produces a blank value for nodes ({@link context}) in a non-relevant state
35
- * - persists, and restores, the most recent non-blank value state when a
36
- * node/context's relevance is restored
37
- */
38
- const createRelevantValueState = (
39
- context: AttributeContext,
40
- baseValueState: BaseValueState
41
- ): RelevantValueState => {
42
- return context.scope.runTask(() => {
43
- const [getRelevantValue, setValue] = baseValueState;
44
-
45
- const getValue = createMemo(() => {
46
- if (context.isRelevant()) {
47
- return getRelevantValue();
48
- }
49
-
50
- return '';
51
- });
52
-
53
- return [getValue, setValue];
54
- });
55
- };
56
-
57
- /**
58
- * For fields with a `readonly` bind expression, prevent downstream
59
- * (client/user) writes when the field is in a `readonly` state.
60
- */
61
- const guardDownstreamReadonlyWrites = (
62
- context: AttributeContext,
63
- baseState: SimpleAtomicState<string>
64
- ): SimpleAtomicState<string> => {
65
- const { readonly } = context.definition.bind;
66
-
67
- if (readonly.isDefaultExpression) {
68
- return baseState;
69
- }
70
-
71
- const [getValue, baseSetValue] = baseState;
72
-
73
- const setValue: SimpleAtomicStateSetter<string> = (value) => {
74
- if (context.isReadonly()) {
75
- const reference = untrack(() => context.contextReference());
76
-
77
- throw new Error(`Cannot write to readonly field: ${reference}`);
78
- }
79
-
80
- return baseSetValue(value);
81
- };
82
-
83
- return [getValue, setValue];
84
- };
85
-
86
- /**
87
- * Per {@link https://getodk.github.io/xforms-spec/#preload-attributes:~:text=concatenation%20of%20%E2%80%98uuid%3A%E2%80%99%20and%20uuid()}
88
- */
89
- const PRELOAD_UID_EXPRESSION = 'concat("uuid:", uuid())';
90
-
91
- /**
92
- * @todo It feels increasingly awkward to keep piling up preload stuff here, but it won't stay that way for long. In the meantime, this seems like the best way to express the cases where `preload="uid"` should be effective, i.e.:
93
- *
94
- * - When an instance is first loaded ({@link isInstanceFirstLoad})
95
- * - When an instance is initially loaded for editing ({@link isEditInitialLoad})
96
- */
97
- const shouldPreloadUID = (context: AttributeContext) => {
98
- return isInstanceFirstLoad(context) || isEditInitialLoad(context);
99
- };
100
-
101
- /**
102
- * @todo This is a temporary one-off, until we support the full range of
103
- * {@link https://getodk.github.io/xforms-spec/#preload-attributes | preloads}.
104
- *
105
- * @todo ALSO, IMPORTANTLY(!): the **call site** for this function is
106
- * semantically where we would expect to trigger a
107
- * {@link https://getodk.github.io/xforms-spec/#event:odk-instance-first-load | odk-instance-first-load event},
108
- * _and compute_ preloads semantically associated with that event.
109
- */
110
- const setPreloadUIDValue = (context: AttributeContext, valueState: RelevantValueState): void => {
111
- const { preload } = context.definition.bind;
112
-
113
- if (preload?.type !== 'uid' || !shouldPreloadUID(context)) {
114
- return;
115
- }
116
-
117
- const preloadUIDValue = context.evaluator.evaluateString(PRELOAD_UID_EXPRESSION, {
118
- contextNode: context.contextNode,
119
- });
120
-
121
- const [, setValue] = valueState;
122
-
123
- setValue(preloadUIDValue);
124
- };
125
-
126
- /**
127
- * Defines a reactive effect which writes the result of `calculate` bind
128
- * computations to the provided value setter, on initialization and any
129
- * subsequent reactive update.
130
- *
131
- * @see {@link setPreloadUIDValue} for important details about spec ordering of
132
- * events and computations.
133
- */
134
- const createCalculation = (
135
- context: AttributeContext,
136
- setRelevantValue: SimpleAtomicStateSetter<string>,
137
- calculateDefinition: BindComputationExpression<'calculate'>
138
- ): void => {
139
- context.scope.runTask(() => {
140
- const calculate = createComputedExpression(context, calculateDefinition, {
141
- defaultValue: '',
142
- });
143
-
144
- createComputed(() => {
145
- if (context.isAttached() && context.isRelevant()) {
146
- const calculated = calculate();
147
- const value = context.decodeInstanceValue(calculated);
148
-
149
- setRelevantValue(value);
150
- }
151
- });
152
- });
153
- };
154
-
155
- export type InstanceValueState = SimpleAtomicState<string>;
156
-
157
- /**
158
- * Provides a consistent interface for value nodes of any type which:
159
- *
160
- * - derives initial state from either an existing instance (e.g. for edits) or
161
- * the node's definition (e.g. initializing a new instance)
162
- * - decodes current primary instance state into the value node's runtime type
163
- * - encodes updated runtime values to store updated instance state
164
- * - initializes reactive computation of `calculate` bind expressions for those
165
- * nodes defined with one
166
- * - prevents downstream writes to nodes in a readonly state
167
- */
168
- export const createAttributeValueState = (context: AttributeContext): InstanceValueState => {
169
- return context.scope.runTask(() => {
170
- const initialValue = getInitialValue(context);
171
- const baseValueState = createSignal(initialValue);
172
- const relevantValueState = createRelevantValueState(context, baseValueState);
173
-
174
- /**
175
- * @see {@link setPreloadUIDValue} for important details about spec ordering of events and computations.
176
- */
177
- setPreloadUIDValue(context, relevantValueState);
178
-
179
- const { calculate } = context.definition.bind;
180
-
181
- if (calculate != null) {
182
- const [, setValue] = relevantValueState;
183
-
184
- createCalculation(context, setValue, calculate);
185
- }
186
-
187
- return guardDownstreamReadonlyWrites(context, relevantValueState);
188
- });
189
- };