@getodk/xforms-engine 0.1.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.
- package/README.md +44 -0
- package/dist/.vite/manifest.json +7 -0
- package/dist/XFormDOM.d.ts +31 -0
- package/dist/XFormDataType.d.ts +26 -0
- package/dist/XFormDefinition.d.ts +14 -0
- package/dist/body/BodyDefinition.d.ts +52 -0
- package/dist/body/BodyElementDefinition.d.ts +32 -0
- package/dist/body/RepeatDefinition.d.ts +15 -0
- package/dist/body/UnsupportedBodyElementDefinition.d.ts +10 -0
- package/dist/body/control/ControlDefinition.d.ts +16 -0
- package/dist/body/control/InputDefinition.d.ts +5 -0
- package/dist/body/control/select/ItemDefinition.d.ts +13 -0
- package/dist/body/control/select/ItemsetDefinition.d.ts +16 -0
- package/dist/body/control/select/ItemsetNodesetContext.d.ts +11 -0
- package/dist/body/control/select/ItemsetNodesetExpression.d.ts +5 -0
- package/dist/body/control/select/ItemsetValueExpression.d.ts +6 -0
- package/dist/body/control/select/SelectDefinition.d.ts +23 -0
- package/dist/body/group/BaseGroupDefinition.d.ts +46 -0
- package/dist/body/group/LogicalGroupDefinition.d.ts +6 -0
- package/dist/body/group/PresentationGroupDefinition.d.ts +11 -0
- package/dist/body/group/RepeatGroupDefinition.d.ts +12 -0
- package/dist/body/group/StructuralGroupDefinition.d.ts +6 -0
- package/dist/body/text/HintDefinition.d.ts +11 -0
- package/dist/body/text/LabelDefinition.d.ts +20 -0
- package/dist/body/text/TextElementDefinition.d.ts +32 -0
- package/dist/body/text/TextElementOutputPart.d.ts +12 -0
- package/dist/body/text/TextElementPart.d.ts +12 -0
- package/dist/body/text/TextElementReferencePart.d.ts +6 -0
- package/dist/body/text/TextElementStaticPart.d.ts +6 -0
- package/dist/client/BaseNode.d.ts +138 -0
- package/dist/client/EngineConfig.d.ts +78 -0
- package/dist/client/FormLanguage.d.ts +63 -0
- package/dist/client/GroupNode.d.ts +24 -0
- package/dist/client/OpaqueReactiveObjectFactory.d.ts +70 -0
- package/dist/client/RepeatInstanceNode.d.ts +28 -0
- package/dist/client/RepeatRangeNode.d.ts +94 -0
- package/dist/client/RootNode.d.ts +31 -0
- package/dist/client/SelectNode.d.ts +60 -0
- package/dist/client/StringNode.d.ts +41 -0
- package/dist/client/SubtreeNode.d.ts +52 -0
- package/dist/client/TextRange.d.ts +55 -0
- package/dist/client/hierarchy.d.ts +48 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/node-types.d.ts +1 -0
- package/dist/expression/DependencyContext.d.ts +12 -0
- package/dist/expression/DependentExpression.d.ts +43 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +37622 -0
- package/dist/index.js.map +1 -0
- package/dist/instance/Group.d.ts +31 -0
- package/dist/instance/RepeatInstance.d.ts +60 -0
- package/dist/instance/RepeatRange.d.ts +81 -0
- package/dist/instance/Root.d.ts +70 -0
- package/dist/instance/SelectField.d.ts +45 -0
- package/dist/instance/StringField.d.ts +39 -0
- package/dist/instance/Subtree.d.ts +30 -0
- package/dist/instance/abstract/DescendantNode.d.ts +76 -0
- package/dist/instance/abstract/InstanceNode.d.ts +107 -0
- package/dist/instance/children.d.ts +2 -0
- package/dist/instance/hierarchy.d.ts +12 -0
- package/dist/instance/identity.d.ts +7 -0
- package/dist/instance/index.d.ts +8 -0
- package/dist/instance/internal-api/EvaluationContext.d.ts +34 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +8 -0
- package/dist/instance/internal-api/SubscribableDependency.d.ts +59 -0
- package/dist/instance/internal-api/TranslationContext.d.ts +4 -0
- package/dist/instance/internal-api/ValueContext.d.ts +22 -0
- package/dist/instance/resource.d.ts +10 -0
- package/dist/instance/text/FormattedTextStub.d.ts +1 -0
- package/dist/instance/text/TextChunk.d.ts +11 -0
- package/dist/instance/text/TextRange.d.ts +10 -0
- package/dist/lib/dom/query.d.ts +20 -0
- package/dist/lib/reactivity/createChildrenState.d.ts +36 -0
- package/dist/lib/reactivity/createComputedExpression.d.ts +12 -0
- package/dist/lib/reactivity/createSelectItems.d.ts +16 -0
- package/dist/lib/reactivity/createValueState.d.ts +44 -0
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +18 -0
- package/dist/lib/reactivity/node-state/createClientState.d.ts +9 -0
- package/dist/lib/reactivity/node-state/createCurrentState.d.ts +6 -0
- package/dist/lib/reactivity/node-state/createEngineState.d.ts +5 -0
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +22 -0
- package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +6 -0
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +139 -0
- package/dist/lib/reactivity/node-state/representations.d.ts +25 -0
- package/dist/lib/reactivity/scope.d.ts +23 -0
- package/dist/lib/reactivity/text/createFieldHint.d.ts +5 -0
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +5 -0
- package/dist/lib/reactivity/text/createTextRange.d.ts +19 -0
- package/dist/lib/reactivity/types.d.ts +21 -0
- package/dist/lib/unique-id.d.ts +27 -0
- package/dist/lib/xpath/analysis.d.ts +22 -0
- package/dist/model/BindComputation.d.ts +30 -0
- package/dist/model/BindDefinition.d.ts +31 -0
- package/dist/model/BindElement.d.ts +6 -0
- package/dist/model/DescendentNodeDefinition.d.ts +25 -0
- package/dist/model/ModelBindMap.d.ts +15 -0
- package/dist/model/ModelDefinition.d.ts +10 -0
- package/dist/model/NodeDefinition.d.ts +74 -0
- package/dist/model/RepeatInstanceDefinition.d.ts +15 -0
- package/dist/model/RepeatSequenceDefinition.d.ts +19 -0
- package/dist/model/RepeatTemplateDefinition.d.ts +29 -0
- package/dist/model/RootDefinition.d.ts +24 -0
- package/dist/model/SubtreeDefinition.d.ts +14 -0
- package/dist/model/ValueNodeDefinition.d.ts +15 -0
- package/dist/solid.js +37273 -0
- package/dist/solid.js.map +1 -0
- package/package.json +87 -0
- package/src/XFormDOM.ts +224 -0
- package/src/XFormDataType.ts +64 -0
- package/src/XFormDefinition.ts +40 -0
- package/src/body/BodyDefinition.ts +202 -0
- package/src/body/BodyElementDefinition.ts +62 -0
- package/src/body/RepeatDefinition.ts +54 -0
- package/src/body/UnsupportedBodyElementDefinition.ts +17 -0
- package/src/body/control/ControlDefinition.ts +42 -0
- package/src/body/control/InputDefinition.ts +9 -0
- package/src/body/control/select/ItemDefinition.ts +31 -0
- package/src/body/control/select/ItemsetDefinition.ts +36 -0
- package/src/body/control/select/ItemsetNodesetContext.ts +26 -0
- package/src/body/control/select/ItemsetNodesetExpression.ts +8 -0
- package/src/body/control/select/ItemsetValueExpression.ts +11 -0
- package/src/body/control/select/SelectDefinition.ts +74 -0
- package/src/body/group/BaseGroupDefinition.ts +137 -0
- package/src/body/group/LogicalGroupDefinition.ts +11 -0
- package/src/body/group/PresentationGroupDefinition.ts +28 -0
- package/src/body/group/RepeatGroupDefinition.ts +91 -0
- package/src/body/group/StructuralGroupDefinition.ts +11 -0
- package/src/body/text/HintDefinition.ts +26 -0
- package/src/body/text/LabelDefinition.ts +54 -0
- package/src/body/text/TextElementDefinition.ts +97 -0
- package/src/body/text/TextElementOutputPart.ts +27 -0
- package/src/body/text/TextElementPart.ts +31 -0
- package/src/body/text/TextElementReferencePart.ts +21 -0
- package/src/body/text/TextElementStaticPart.ts +26 -0
- package/src/client/BaseNode.ts +180 -0
- package/src/client/EngineConfig.ts +83 -0
- package/src/client/FormLanguage.ts +77 -0
- package/src/client/GroupNode.ts +33 -0
- package/src/client/OpaqueReactiveObjectFactory.ts +100 -0
- package/src/client/README.md +39 -0
- package/src/client/RepeatInstanceNode.ts +41 -0
- package/src/client/RepeatRangeNode.ts +100 -0
- package/src/client/RootNode.ts +36 -0
- package/src/client/SelectNode.ts +69 -0
- package/src/client/StringNode.ts +46 -0
- package/src/client/SubtreeNode.ts +57 -0
- package/src/client/TextRange.ts +63 -0
- package/src/client/hierarchy.ts +63 -0
- package/src/client/index.ts +29 -0
- package/src/client/node-types.ts +10 -0
- package/src/expression/DependencyContext.ts +53 -0
- package/src/expression/DependentExpression.ts +102 -0
- package/src/index.ts +35 -0
- package/src/instance/Group.ts +82 -0
- package/src/instance/RepeatInstance.ts +164 -0
- package/src/instance/RepeatRange.ts +214 -0
- package/src/instance/Root.ts +264 -0
- package/src/instance/SelectField.ts +204 -0
- package/src/instance/StringField.ts +93 -0
- package/src/instance/Subtree.ts +79 -0
- package/src/instance/abstract/DescendantNode.ts +182 -0
- package/src/instance/abstract/InstanceNode.ts +257 -0
- package/src/instance/children.ts +52 -0
- package/src/instance/hierarchy.ts +54 -0
- package/src/instance/identity.ts +11 -0
- package/src/instance/index.ts +37 -0
- package/src/instance/internal-api/EvaluationContext.ts +41 -0
- package/src/instance/internal-api/InstanceConfig.ts +9 -0
- package/src/instance/internal-api/SubscribableDependency.ts +61 -0
- package/src/instance/internal-api/TranslationContext.ts +5 -0
- package/src/instance/internal-api/ValueContext.ts +27 -0
- package/src/instance/resource.ts +75 -0
- package/src/instance/text/FormattedTextStub.ts +8 -0
- package/src/instance/text/TextChunk.ts +20 -0
- package/src/instance/text/TextRange.ts +23 -0
- package/src/lib/dom/query.ts +49 -0
- package/src/lib/reactivity/createChildrenState.ts +60 -0
- package/src/lib/reactivity/createComputedExpression.ts +114 -0
- package/src/lib/reactivity/createSelectItems.ts +163 -0
- package/src/lib/reactivity/createValueState.ts +258 -0
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +121 -0
- package/src/lib/reactivity/node-state/createClientState.ts +51 -0
- package/src/lib/reactivity/node-state/createCurrentState.ts +27 -0
- package/src/lib/reactivity/node-state/createEngineState.ts +18 -0
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +79 -0
- package/src/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.ts +85 -0
- package/src/lib/reactivity/node-state/createSpecifiedState.ts +229 -0
- package/src/lib/reactivity/node-state/representations.ts +64 -0
- package/src/lib/reactivity/scope.ts +106 -0
- package/src/lib/reactivity/text/createFieldHint.ts +16 -0
- package/src/lib/reactivity/text/createNodeLabel.ts +16 -0
- package/src/lib/reactivity/text/createTextRange.ts +155 -0
- package/src/lib/reactivity/types.ts +27 -0
- package/src/lib/unique-id.ts +34 -0
- package/src/lib/xpath/analysis.ts +241 -0
- package/src/model/BindComputation.ts +88 -0
- package/src/model/BindDefinition.ts +104 -0
- package/src/model/BindElement.ts +8 -0
- package/src/model/DescendentNodeDefinition.ts +56 -0
- package/src/model/ModelBindMap.ts +71 -0
- package/src/model/ModelDefinition.ts +19 -0
- package/src/model/NodeDefinition.ts +146 -0
- package/src/model/RepeatInstanceDefinition.ts +39 -0
- package/src/model/RepeatSequenceDefinition.ts +53 -0
- package/src/model/RepeatTemplateDefinition.ts +150 -0
- package/src/model/RootDefinition.ts +121 -0
- package/src/model/SubtreeDefinition.ts +50 -0
- package/src/model/ValueNodeDefinition.ts +39 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AnyDependentExpression } from './DependentExpression.ts';
|
|
2
|
+
|
|
3
|
+
export abstract class DependencyContext {
|
|
4
|
+
abstract get parentReference(): string | null;
|
|
5
|
+
abstract get reference(): string | null;
|
|
6
|
+
|
|
7
|
+
protected readonly dependentExpressions = new Set<AnyDependentExpression>();
|
|
8
|
+
|
|
9
|
+
protected dependencyExpressionsCache: ReadonlySet<string> | null = null;
|
|
10
|
+
|
|
11
|
+
get dependencyExpressions(): ReadonlySet<string> {
|
|
12
|
+
let { dependencyExpressionsCache } = this;
|
|
13
|
+
|
|
14
|
+
if (dependencyExpressionsCache == null) {
|
|
15
|
+
dependencyExpressionsCache = new Set(
|
|
16
|
+
Array.from(this.dependentExpressions).flatMap((expression) => {
|
|
17
|
+
return Array.from(expression.dependencyReferences);
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
this.dependencyExpressionsCache = dependencyExpressionsCache;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return dependencyExpressionsCache;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get isTranslated(): boolean {
|
|
27
|
+
return this._isTranslated;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Note: this is a bit of type system "cleverness" that helped to prevent a
|
|
31
|
+
// bug repeatedly encountered in earlier prototyping. The default value is
|
|
32
|
+
// false (currently backed by _isTranslated; this note should be updated if
|
|
33
|
+
// that changes). Its value can be read at any time, but it may only be
|
|
34
|
+
// overridden with `true`. So if a `DependencyContext` is established as
|
|
35
|
+
// dependent on translations by one `DependentExpression`, another expression
|
|
36
|
+
// which does not depend on translations cannot override that.
|
|
37
|
+
//
|
|
38
|
+
// This doesn't deserve so much explanation in its own right, but it's worth
|
|
39
|
+
// calling out here as a pattern we may find valuable in other cases where
|
|
40
|
+
// interfaces allow writes from outside (which I've generally tried to avoid,
|
|
41
|
+
// or significantly restrict, to avoid bugs like the one described above).
|
|
42
|
+
set isTranslated(value: true) {
|
|
43
|
+
this._isTranslated = value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Update note on `set isTranslated` if this internal storage changes.
|
|
47
|
+
protected _isTranslated = false;
|
|
48
|
+
|
|
49
|
+
registerDependentExpression(expression: AnyDependentExpression): void {
|
|
50
|
+
this.dependencyExpressionsCache = null;
|
|
51
|
+
this.dependentExpressions.add(expression);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { XFormsXPathEvaluator } from '@getodk/xpath';
|
|
2
|
+
import { getNodesetDependencies, isItextFunctionCalled } from '../lib/xpath/analysis.ts';
|
|
3
|
+
import type { DependencyContext } from './DependencyContext.ts';
|
|
4
|
+
|
|
5
|
+
const evaluatorMethodsByResultType = {
|
|
6
|
+
boolean: 'evaluateBoolean',
|
|
7
|
+
nodes: 'evaluateNodes',
|
|
8
|
+
string: 'evaluateString',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
type EvaluatorMethodsByResultType = typeof evaluatorMethodsByResultType;
|
|
12
|
+
|
|
13
|
+
export type DependentExpressionResultType = keyof EvaluatorMethodsByResultType;
|
|
14
|
+
|
|
15
|
+
export type DependentExpressionEvaluatorMethod<Type extends DependentExpressionResultType> =
|
|
16
|
+
EvaluatorMethodsByResultType[Type];
|
|
17
|
+
|
|
18
|
+
export type DependentExpressionResult<Type extends DependentExpressionResultType> = ReturnType<
|
|
19
|
+
XFormsXPathEvaluator[DependentExpressionEvaluatorMethod<Type>]
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
interface SemanticDependencyOptions {
|
|
23
|
+
/**
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
readonly parentContext?: boolean | undefined;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
readonly translations?: boolean | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface DependentExpressionOptions {
|
|
35
|
+
/**
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
readonly ignoreContextReference?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
readonly ignoreNullExpressions?: boolean;
|
|
44
|
+
|
|
45
|
+
readonly semanticDependencies?: SemanticDependencyOptions;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class DependentExpression<Type extends DependentExpressionResultType> {
|
|
49
|
+
readonly dependencyReferences: ReadonlySet<string> = new Set();
|
|
50
|
+
readonly isTranslated: boolean = false;
|
|
51
|
+
readonly evaluatorMethod: DependentExpressionEvaluatorMethod<Type>;
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
context: DependencyContext,
|
|
55
|
+
readonly resultType: Type,
|
|
56
|
+
readonly expression: string,
|
|
57
|
+
options: DependentExpressionOptions = {}
|
|
58
|
+
) {
|
|
59
|
+
this.evaluatorMethod = evaluatorMethodsByResultType[resultType];
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
ignoreContextReference = false,
|
|
63
|
+
ignoreNullExpressions = true,
|
|
64
|
+
semanticDependencies = {
|
|
65
|
+
parentContext: false,
|
|
66
|
+
translations: false,
|
|
67
|
+
},
|
|
68
|
+
} = options;
|
|
69
|
+
|
|
70
|
+
const dependencyReferences = new Set<string>(
|
|
71
|
+
getNodesetDependencies(expression, {
|
|
72
|
+
contextReference: context.reference,
|
|
73
|
+
ignoreContextReference,
|
|
74
|
+
ignoreNullExpressions,
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const parentDependency = semanticDependencies.parentContext ? context.parentReference : null;
|
|
79
|
+
|
|
80
|
+
if (parentDependency != null) {
|
|
81
|
+
dependencyReferences.add(parentDependency);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.dependencyReferences = dependencyReferences;
|
|
85
|
+
|
|
86
|
+
const isTranslated = semanticDependencies.translations && isItextFunctionCalled(expression);
|
|
87
|
+
|
|
88
|
+
if (isTranslated) {
|
|
89
|
+
this.isTranslated = true;
|
|
90
|
+
context.isTranslated = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
context.registerDependentExpression(this);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
toString(): string | null {
|
|
97
|
+
return this.expression;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
export type AnyDependentExpression = DependentExpression<any>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InitializeForm } from './index.ts';
|
|
2
|
+
import { initializeForm as engine__initializeForm } from './instance/index.ts';
|
|
3
|
+
|
|
4
|
+
export const initializeForm: InitializeForm = engine__initializeForm;
|
|
5
|
+
|
|
6
|
+
export type * from './client/EngineConfig.ts';
|
|
7
|
+
export type * from './client/FormLanguage.ts';
|
|
8
|
+
export type * from './client/GroupNode.ts';
|
|
9
|
+
export type * from './client/OpaqueReactiveObjectFactory.ts';
|
|
10
|
+
export type * from './client/RepeatInstanceNode.ts';
|
|
11
|
+
export type * from './client/RepeatRangeNode.ts';
|
|
12
|
+
export type * from './client/RootNode.ts';
|
|
13
|
+
export type * from './client/SelectNode.ts';
|
|
14
|
+
export type * from './client/StringNode.ts';
|
|
15
|
+
export type * from './client/SubtreeNode.ts';
|
|
16
|
+
export type * from './client/TextRange.ts';
|
|
17
|
+
export type {
|
|
18
|
+
AnyChildNode,
|
|
19
|
+
AnyLeafNode,
|
|
20
|
+
AnyNode,
|
|
21
|
+
AnyParentNode,
|
|
22
|
+
GeneralChildNode,
|
|
23
|
+
GeneralParentNode,
|
|
24
|
+
} from './client/hierarchy.ts';
|
|
25
|
+
export type * from './client/index.ts';
|
|
26
|
+
|
|
27
|
+
// TODO: notwithstanding potential conflicts with parallel work on `web-forms`
|
|
28
|
+
// (former `ui-vue`), these are the last remaining references **outside of
|
|
29
|
+
// `xforms-engine`** to anything besides /client/* and the `initializeForm`
|
|
30
|
+
// entrypoint implementation. We'll refine the various `definition` types in due
|
|
31
|
+
// time.
|
|
32
|
+
export type {
|
|
33
|
+
AnySelectDefinition,
|
|
34
|
+
SelectDefinition,
|
|
35
|
+
} from './body/control/select/SelectDefinition.ts';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Accessor } from 'solid-js';
|
|
2
|
+
import type { GroupDefinition, GroupNode } from '../client/GroupNode.ts';
|
|
3
|
+
import type { TextRange } from '../index.ts';
|
|
4
|
+
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
5
|
+
import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
6
|
+
import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
7
|
+
import { materializeCurrentStateChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
8
|
+
import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
|
|
9
|
+
import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
|
|
10
|
+
import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
11
|
+
import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
12
|
+
import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
|
|
13
|
+
import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
|
|
14
|
+
import { DescendantNode } from './abstract/DescendantNode.ts';
|
|
15
|
+
import { buildChildren } from './children.ts';
|
|
16
|
+
import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
|
|
17
|
+
import type { NodeID } from './identity.ts';
|
|
18
|
+
import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
|
|
19
|
+
import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
|
|
20
|
+
|
|
21
|
+
// prettier-ignore
|
|
22
|
+
interface GroupStateSpec extends DescendantNodeSharedStateSpec {
|
|
23
|
+
readonly label: Accessor<TextRange<'label'> | null>;
|
|
24
|
+
readonly hint: null;
|
|
25
|
+
readonly children: Accessor<readonly NodeID[]>;
|
|
26
|
+
readonly valueOptions: null;
|
|
27
|
+
readonly value: null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class Group
|
|
31
|
+
extends DescendantNode<GroupDefinition, GroupStateSpec, GeneralChildNode>
|
|
32
|
+
implements GroupNode, EvaluationContext, SubscribableDependency
|
|
33
|
+
{
|
|
34
|
+
private readonly childrenState: ChildrenState<GeneralChildNode>;
|
|
35
|
+
|
|
36
|
+
// InstanceNode
|
|
37
|
+
protected readonly state: SharedNodeState<GroupStateSpec>;
|
|
38
|
+
protected override engineState: EngineState<GroupStateSpec>;
|
|
39
|
+
|
|
40
|
+
// GroupNode
|
|
41
|
+
readonly currentState: MaterializedChildren<CurrentState<GroupStateSpec>, GeneralChildNode>;
|
|
42
|
+
|
|
43
|
+
readonly nodeType = 'group';
|
|
44
|
+
|
|
45
|
+
constructor(parent: GeneralParentNode, definition: GroupDefinition) {
|
|
46
|
+
super(parent, definition);
|
|
47
|
+
|
|
48
|
+
const childrenState = createChildrenState<Group, GeneralChildNode>(this);
|
|
49
|
+
|
|
50
|
+
this.childrenState = childrenState;
|
|
51
|
+
|
|
52
|
+
const state = createSharedNodeState(
|
|
53
|
+
this.scope,
|
|
54
|
+
{
|
|
55
|
+
...this.buildSharedStateSpec(parent, definition),
|
|
56
|
+
|
|
57
|
+
label: createNodeLabel(this, definition),
|
|
58
|
+
hint: null,
|
|
59
|
+
children: childrenState.childIds,
|
|
60
|
+
valueOptions: null,
|
|
61
|
+
value: null,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
this.state = state;
|
|
69
|
+
this.engineState = state.engineState;
|
|
70
|
+
this.currentState = materializeCurrentStateChildren(state.currentState, childrenState);
|
|
71
|
+
|
|
72
|
+
childrenState.setChildren(buildChildren(this));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected computeReference(parent: GeneralParentNode): string {
|
|
76
|
+
return this.computeChildStepReference(parent);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getChildren(): readonly GeneralChildNode[] {
|
|
80
|
+
return this.childrenState.getChildren();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Accessor } from 'solid-js';
|
|
2
|
+
import { createComputed, createSignal, on } from 'solid-js';
|
|
3
|
+
import type { RepeatDefinition, RepeatInstanceNode } from '../client/RepeatInstanceNode.ts';
|
|
4
|
+
import type { TextRange } from '../index.ts';
|
|
5
|
+
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
6
|
+
import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
7
|
+
import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
8
|
+
import { materializeCurrentStateChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
9
|
+
import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
|
|
10
|
+
import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
|
|
11
|
+
import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
12
|
+
import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
13
|
+
import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
|
|
14
|
+
import type { RepeatRange } from './RepeatRange.ts';
|
|
15
|
+
import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
|
|
16
|
+
import { DescendantNode } from './abstract/DescendantNode.ts';
|
|
17
|
+
import { buildChildren } from './children.ts';
|
|
18
|
+
import type { AnyChildNode, GeneralChildNode } from './hierarchy.ts';
|
|
19
|
+
import type { NodeID } from './identity.ts';
|
|
20
|
+
import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
|
|
21
|
+
import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
|
|
22
|
+
|
|
23
|
+
export type { RepeatDefinition };
|
|
24
|
+
|
|
25
|
+
interface RepeatInstanceStateSpec extends DescendantNodeSharedStateSpec {
|
|
26
|
+
readonly label: Accessor<TextRange<'label'> | null>;
|
|
27
|
+
readonly hint: null;
|
|
28
|
+
readonly children: Accessor<readonly NodeID[]>;
|
|
29
|
+
readonly valueOptions: null;
|
|
30
|
+
readonly value: null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface RepeatInstanceOptions {
|
|
34
|
+
readonly precedingPrimaryInstanceNode: Comment | Element;
|
|
35
|
+
readonly precedingInstance: RepeatInstance | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class RepeatInstance
|
|
39
|
+
extends DescendantNode<RepeatDefinition, RepeatInstanceStateSpec, GeneralChildNode>
|
|
40
|
+
implements RepeatInstanceNode, EvaluationContext, SubscribableDependency
|
|
41
|
+
{
|
|
42
|
+
private readonly childrenState: ChildrenState<GeneralChildNode>;
|
|
43
|
+
private readonly currentIndex: Accessor<number>;
|
|
44
|
+
|
|
45
|
+
// InstanceNode
|
|
46
|
+
protected readonly state: SharedNodeState<RepeatInstanceStateSpec>;
|
|
47
|
+
protected override engineState: EngineState<RepeatInstanceStateSpec>;
|
|
48
|
+
|
|
49
|
+
// RepeatInstanceNode
|
|
50
|
+
readonly nodeType = 'repeat-instance';
|
|
51
|
+
|
|
52
|
+
readonly currentState: MaterializedChildren<
|
|
53
|
+
CurrentState<RepeatInstanceStateSpec>,
|
|
54
|
+
GeneralChildNode
|
|
55
|
+
>;
|
|
56
|
+
|
|
57
|
+
constructor(
|
|
58
|
+
override readonly parent: RepeatRange,
|
|
59
|
+
definition: RepeatDefinition,
|
|
60
|
+
options: RepeatInstanceOptions
|
|
61
|
+
) {
|
|
62
|
+
super(parent, definition);
|
|
63
|
+
|
|
64
|
+
const childrenState = createChildrenState<RepeatInstance, GeneralChildNode>(this);
|
|
65
|
+
|
|
66
|
+
this.childrenState = childrenState;
|
|
67
|
+
|
|
68
|
+
options.precedingPrimaryInstanceNode.after(this.contextNode);
|
|
69
|
+
|
|
70
|
+
const { precedingInstance } = options;
|
|
71
|
+
const precedingIndex = precedingInstance?.currentIndex ?? (() => -1);
|
|
72
|
+
const initialIndex = precedingIndex() + 1;
|
|
73
|
+
const [currentIndex, setCurrentIndex] = createSignal(initialIndex);
|
|
74
|
+
|
|
75
|
+
this.currentIndex = currentIndex;
|
|
76
|
+
|
|
77
|
+
const state = createSharedNodeState(
|
|
78
|
+
this.scope,
|
|
79
|
+
{
|
|
80
|
+
...this.buildSharedStateSpec(parent, definition),
|
|
81
|
+
|
|
82
|
+
label: createNodeLabel(this, definition),
|
|
83
|
+
hint: null,
|
|
84
|
+
children: childrenState.childIds,
|
|
85
|
+
valueOptions: null,
|
|
86
|
+
value: null,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
this.state = state;
|
|
94
|
+
this.engineState = state.engineState;
|
|
95
|
+
this.currentState = materializeCurrentStateChildren(state.currentState, childrenState);
|
|
96
|
+
|
|
97
|
+
// Maintain current index state, updating as the parent range's children
|
|
98
|
+
// state is changed. Notable Solid reactivity nuances:
|
|
99
|
+
//
|
|
100
|
+
// - `createComputed` is the Solid API which is explicitly called out for
|
|
101
|
+
// supporting performing reactive writes. It's also generally considered a
|
|
102
|
+
// "smell", but it seems the most appropriate for a first pass on this.
|
|
103
|
+
// - `on(..., { defer: true })` allows us to *synchronously* delay reactive
|
|
104
|
+
// index updates until after the full form tree is built, where this
|
|
105
|
+
// `RepeatInstance` is being constructed but it hasn't yet been appended
|
|
106
|
+
// to the parent range's reactive `children`.
|
|
107
|
+
// - the same logic for deferring reaction on form init should apply for
|
|
108
|
+
// adding new instances to a live form.
|
|
109
|
+
this.scope.runTask(() => {
|
|
110
|
+
// TODO: even as minimal as this currently is, maybe we should move this
|
|
111
|
+
// into a named function under src/lib/reactivity (for consistency with
|
|
112
|
+
// other reactive implementations of specific XForms semantics)?
|
|
113
|
+
const computeCurrentIndex = parent.getInstanceIndex.bind(parent, this);
|
|
114
|
+
|
|
115
|
+
createComputed(on(computeCurrentIndex, setCurrentIndex, { defer: true }));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
childrenState.setChildren(buildChildren(this));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected computeReference(parent: RepeatRange): string {
|
|
122
|
+
const currentPosition = this.currentIndex() + 1;
|
|
123
|
+
|
|
124
|
+
return `${parent.contextReference}[${currentPosition}]`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
protected override initializeContextNode(parentContextNode: Element, nodeName: string): Element {
|
|
128
|
+
return this.createContextNode(parentContextNode, nodeName);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override subscribe(): void {
|
|
132
|
+
super.subscribe();
|
|
133
|
+
this.currentIndex();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getChildren(): readonly GeneralChildNode[] {
|
|
137
|
+
return this.childrenState.getChildren();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Performs repeat instance node-specific removal logic, then general node
|
|
142
|
+
* removal logic, in that order:
|
|
143
|
+
*
|
|
144
|
+
* 1. At present, before any reactive state change is performed, the repeat
|
|
145
|
+
* instance's {@link contextNode} is removed from the primary instance's
|
|
146
|
+
* XML DOM backing store (which also removes any descendant nodes from that
|
|
147
|
+
* store, as a side effect). This behavior may become unnecessary if/when
|
|
148
|
+
* we phase out use of this XML DOM backing store. This should be peformed
|
|
149
|
+
* first, so that any following reactive logic which evaluates affected
|
|
150
|
+
* XPath expressions will be performed against a state consistent with the
|
|
151
|
+
* repeat instance's removal (and that of its XML DOM descendants).
|
|
152
|
+
*
|
|
153
|
+
* 2. Performs any remaining removal logic as defined in
|
|
154
|
+
* {@link DescendantNode.remove}.
|
|
155
|
+
*
|
|
156
|
+
* These removal steps **must also** occur before any update to the parent
|
|
157
|
+
* {@link RepeatRange}'s reactive children state.
|
|
158
|
+
*/
|
|
159
|
+
override remove(this: AnyChildNode): void {
|
|
160
|
+
this.contextNode.remove();
|
|
161
|
+
|
|
162
|
+
super.remove();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { insertAtIndex } from '@getodk/common/lib/array/insert.ts';
|
|
2
|
+
import type { Accessor } from 'solid-js';
|
|
3
|
+
import type { RepeatRangeNode } from '../client/RepeatRangeNode.ts';
|
|
4
|
+
import type { ChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
5
|
+
import { createChildrenState } from '../lib/reactivity/createChildrenState.ts';
|
|
6
|
+
import type { MaterializedChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
7
|
+
import { materializeCurrentStateChildren } from '../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
8
|
+
import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
|
|
9
|
+
import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
|
|
10
|
+
import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
11
|
+
import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
12
|
+
import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
|
|
13
|
+
import type { RepeatSequenceDefinition } from '../model/RepeatSequenceDefinition.ts';
|
|
14
|
+
import type { RepeatDefinition } from './RepeatInstance.ts';
|
|
15
|
+
import { RepeatInstance } from './RepeatInstance.ts';
|
|
16
|
+
import type { Root } from './Root.ts';
|
|
17
|
+
import type { DescendantNodeSharedStateSpec } from './abstract/DescendantNode.ts';
|
|
18
|
+
import { DescendantNode } from './abstract/DescendantNode.ts';
|
|
19
|
+
import type { GeneralParentNode } from './hierarchy.ts';
|
|
20
|
+
import type { NodeID } from './identity.ts';
|
|
21
|
+
import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
|
|
22
|
+
import type { SubscribableDependency } from './internal-api/SubscribableDependency.ts';
|
|
23
|
+
import type { TextRange } from './text/TextRange.ts';
|
|
24
|
+
|
|
25
|
+
interface RepeatRangeStateSpec extends DescendantNodeSharedStateSpec {
|
|
26
|
+
readonly hint: null;
|
|
27
|
+
readonly label: Accessor<TextRange<'label'> | null>;
|
|
28
|
+
readonly children: Accessor<readonly NodeID[]>;
|
|
29
|
+
readonly valueOptions: null;
|
|
30
|
+
readonly value: null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class RepeatRange
|
|
34
|
+
extends DescendantNode<RepeatSequenceDefinition, RepeatRangeStateSpec, RepeatInstance>
|
|
35
|
+
implements RepeatRangeNode, EvaluationContext, SubscribableDependency
|
|
36
|
+
{
|
|
37
|
+
/**
|
|
38
|
+
* A repeat range doesn't have a corresponding primary instance element of its
|
|
39
|
+
* own, and its instances are appended to the range's parent element. During
|
|
40
|
+
* creation of the initial primary instance state and DOM trees, we _could_
|
|
41
|
+
* reliably append all of the range's instances in order as the definition
|
|
42
|
+
* tree is recursed. But that would fail to handle some instance addition
|
|
43
|
+
* cases afterwards.
|
|
44
|
+
*
|
|
45
|
+
* Most notably, we need to know where in the primary instance tree to append
|
|
46
|
+
* instances created for a range which is currently empty. As a lucky
|
|
47
|
+
* coincidence, this need coincides with the ability to add instances at any
|
|
48
|
+
* arbitrary index within the range. In each case, we can reference a primary
|
|
49
|
+
* instance DOM node which will become the new instance's preceding sibling.
|
|
50
|
+
* Where the range is empty, we use this {@link Comment} node (itself created
|
|
51
|
+
* and appended during range initialization) in lieu of a nonexistent
|
|
52
|
+
* preceding instance's {@link contextNode}.
|
|
53
|
+
*
|
|
54
|
+
* @todo We likely want to remove these during submission serialization.
|
|
55
|
+
* @todo Can we use a
|
|
56
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Range | DOM Range}
|
|
57
|
+
* instead?
|
|
58
|
+
*/
|
|
59
|
+
private readonly anchorNode: Comment;
|
|
60
|
+
|
|
61
|
+
private readonly childrenState: ChildrenState<RepeatInstance>;
|
|
62
|
+
|
|
63
|
+
// InstanceNode
|
|
64
|
+
protected readonly state: SharedNodeState<RepeatRangeStateSpec>;
|
|
65
|
+
protected override engineState: EngineState<RepeatRangeStateSpec>;
|
|
66
|
+
|
|
67
|
+
// RepeatRangeNode
|
|
68
|
+
readonly nodeType = 'repeat-range';
|
|
69
|
+
|
|
70
|
+
readonly currentState: MaterializedChildren<CurrentState<RepeatRangeStateSpec>, RepeatInstance>;
|
|
71
|
+
|
|
72
|
+
constructor(parent: GeneralParentNode, definition: RepeatSequenceDefinition) {
|
|
73
|
+
super(parent, definition);
|
|
74
|
+
|
|
75
|
+
const childrenState = createChildrenState<RepeatRange, RepeatInstance>(this);
|
|
76
|
+
|
|
77
|
+
this.childrenState = childrenState;
|
|
78
|
+
|
|
79
|
+
const state = createSharedNodeState(
|
|
80
|
+
this.scope,
|
|
81
|
+
{
|
|
82
|
+
...this.buildSharedStateSpec(parent, definition),
|
|
83
|
+
|
|
84
|
+
label: createNodeLabel(this, definition),
|
|
85
|
+
hint: null,
|
|
86
|
+
children: childrenState.childIds,
|
|
87
|
+
valueOptions: null,
|
|
88
|
+
value: null,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
this.anchorNode = this.contextNode.ownerDocument.createComment(
|
|
96
|
+
`Begin repeat range: ${definition.nodeset}`
|
|
97
|
+
);
|
|
98
|
+
this.contextNode.append(this.anchorNode);
|
|
99
|
+
|
|
100
|
+
this.state = state;
|
|
101
|
+
this.engineState = state.engineState;
|
|
102
|
+
this.currentState = materializeCurrentStateChildren(state.currentState, childrenState);
|
|
103
|
+
|
|
104
|
+
definition.instances.forEach((instanceDefinition, index) => {
|
|
105
|
+
const afterIndex = index - 1;
|
|
106
|
+
|
|
107
|
+
this.addInstances(afterIndex, 1, instanceDefinition);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getLastIndex(): number {
|
|
112
|
+
return this.engineState.children.length - 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected override initializeContextNode(parentContextNode: Element): Element {
|
|
116
|
+
return parentContextNode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected computeReference(parent: GeneralParentNode): string {
|
|
120
|
+
return this.computeChildStepReference(parent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getInstanceIndex(instance: RepeatInstance): number {
|
|
124
|
+
return this.engineState.children.indexOf(instance.nodeId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
addInstances(
|
|
128
|
+
afterIndex = this.getLastIndex(),
|
|
129
|
+
count = 1,
|
|
130
|
+
definition: RepeatDefinition = this.definition.template
|
|
131
|
+
): Root {
|
|
132
|
+
return this.scope.runTask(() => {
|
|
133
|
+
let precedingInstance: RepeatInstance | null;
|
|
134
|
+
|
|
135
|
+
if (afterIndex === -1) {
|
|
136
|
+
precedingInstance = null;
|
|
137
|
+
} else {
|
|
138
|
+
const instance = this.childrenState.getChildren()[afterIndex];
|
|
139
|
+
|
|
140
|
+
if (instance == null) {
|
|
141
|
+
throw new Error(`No repeat instance at index ${afterIndex}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
precedingInstance = instance;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const precedingPrimaryInstanceNode = precedingInstance?.contextNode ?? this.anchorNode;
|
|
148
|
+
|
|
149
|
+
const newInstance = new RepeatInstance(this, definition, {
|
|
150
|
+
precedingPrimaryInstanceNode,
|
|
151
|
+
precedingInstance,
|
|
152
|
+
});
|
|
153
|
+
const initialIndex = afterIndex + 1;
|
|
154
|
+
|
|
155
|
+
this.childrenState.setChildren((currentInstances) => {
|
|
156
|
+
return insertAtIndex(currentInstances, initialIndex, newInstance);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (count > 1) {
|
|
160
|
+
return this.addInstances(initialIndex, count - 1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return this.root;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Removes the {@link RepeatInstance}s corresponding to the specified range of
|
|
169
|
+
* indexes, and then removes those repeat instances from the repeat range's
|
|
170
|
+
* own children state in that order:
|
|
171
|
+
*
|
|
172
|
+
* 1. Identify the set of {@link RepeatInstance}s to be removed.
|
|
173
|
+
*
|
|
174
|
+
* 2. For each {@link RepeatInstance} pending removal, perform that node's
|
|
175
|
+
* removal logic. @see {@link RepeatInstance.remove} for more detail.
|
|
176
|
+
*
|
|
177
|
+
* 3. Finalize update to the repeat range's own {@link childrenState},
|
|
178
|
+
* omitting those {@link RepeatInstance}s which were removed.
|
|
179
|
+
*
|
|
180
|
+
* This ordering ensures a consistent representation of state is established
|
|
181
|
+
* prior to any downstream reactive updates, and ensures that removed nodes'
|
|
182
|
+
* reactivity is cleaned up.
|
|
183
|
+
*/
|
|
184
|
+
removeInstances(startIndex: number, count = 1): Root {
|
|
185
|
+
return this.scope.runTask(() => {
|
|
186
|
+
this.childrenState.setChildren((currentInstances) => {
|
|
187
|
+
const updatedInstances = currentInstances.slice();
|
|
188
|
+
const removedInstances = updatedInstances.splice(startIndex, count);
|
|
189
|
+
|
|
190
|
+
removedInstances.forEach((instance) => {
|
|
191
|
+
instance.remove();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return updatedInstances;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return this.root;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
override subscribe(): void {
|
|
202
|
+
super.subscribe();
|
|
203
|
+
|
|
204
|
+
// Subscribing to children can support reactive expressions dependent on the
|
|
205
|
+
// repeat range itself (e.g. `count()`).
|
|
206
|
+
this.childrenState.getChildren().forEach((child) => {
|
|
207
|
+
child.subscribe();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getChildren(): readonly RepeatInstance[] {
|
|
212
|
+
return this.childrenState.getChildren();
|
|
213
|
+
}
|
|
214
|
+
}
|