@getodk/xforms-engine 0.1.1 → 0.3.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/dist/body/BodyDefinition.d.ts +24 -7
- package/dist/body/BodyElementDefinition.d.ts +4 -3
- package/dist/body/RepeatElementDefinition.d.ts +19 -0
- package/dist/body/appearance/inputAppearanceParser.d.ts +4 -0
- package/dist/body/appearance/selectAppearanceParser.d.ts +4 -0
- package/dist/body/appearance/structureElementAppearanceParser.d.ts +4 -0
- package/dist/body/control/ControlDefinition.d.ts +4 -2
- package/dist/body/control/InputDefinition.d.ts +5 -0
- package/dist/body/control/select/ItemDefinition.d.ts +2 -2
- package/dist/body/control/select/ItemsetDefinition.d.ts +5 -4
- package/dist/body/control/select/SelectDefinition.d.ts +11 -1
- package/dist/body/group/BaseGroupDefinition.d.ts +4 -9
- package/dist/body/group/PresentationGroupDefinition.d.ts +1 -1
- package/dist/client/BaseNode.d.ts +74 -3
- package/dist/client/GroupNode.d.ts +7 -2
- package/dist/client/ModelValueNode.d.ts +37 -0
- package/dist/client/NodeAppearances.d.ts +15 -0
- package/dist/client/NoteNode.d.ts +53 -0
- package/dist/client/RootNode.d.ts +21 -0
- package/dist/client/SelectNode.d.ts +8 -3
- package/dist/client/StringNode.d.ts +8 -3
- package/dist/client/SubtreeNode.d.ts +3 -0
- package/dist/client/TextRange.d.ts +85 -2
- package/dist/client/constants.d.ts +9 -0
- package/dist/client/hierarchy.d.ts +14 -9
- package/dist/client/node-types.d.ts +2 -1
- package/dist/client/{RepeatRangeNode.d.ts → repeat/BaseRepeatRangeNode.d.ts} +19 -15
- package/dist/client/{RepeatInstanceNode.d.ts → repeat/RepeatInstanceNode.d.ts} +11 -7
- package/dist/client/repeat/RepeatRangeControlledNode.d.ts +19 -0
- package/dist/client/repeat/RepeatRangeUncontrolledNode.d.ts +20 -0
- package/dist/client/validation.d.ts +163 -0
- package/dist/expression/DependentExpression.d.ts +12 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +3173 -960
- package/dist/index.js.map +1 -1
- package/dist/instance/Group.d.ts +6 -4
- package/dist/instance/ModelValue.d.ts +40 -0
- package/dist/instance/Note.d.ts +42 -0
- package/dist/instance/Root.d.ts +10 -23
- package/dist/instance/SelectField.d.ts +12 -6
- package/dist/instance/StringField.d.ts +13 -7
- package/dist/instance/Subtree.d.ts +3 -1
- package/dist/instance/abstract/DescendantNode.d.ts +16 -9
- package/dist/instance/abstract/InstanceNode.d.ts +28 -29
- package/dist/instance/hierarchy.d.ts +10 -5
- package/dist/instance/internal-api/EvaluationContext.d.ts +5 -4
- package/dist/instance/internal-api/ValidationContext.d.ts +21 -0
- package/dist/instance/internal-api/ValueContext.d.ts +2 -2
- package/dist/instance/repeat/BaseRepeatRange.d.ts +160 -0
- package/dist/instance/{RepeatInstance.d.ts → repeat/RepeatInstance.d.ts} +38 -13
- package/dist/instance/repeat/RepeatRangeControlled.d.ts +16 -0
- package/dist/instance/repeat/RepeatRangeUncontrolled.d.ts +35 -0
- package/dist/instance/text/TextRange.d.ts +4 -4
- package/dist/lib/TokenListParser.d.ts +84 -0
- package/dist/lib/dom/query.d.ts +5 -0
- package/dist/lib/reactivity/createComputedExpression.d.ts +6 -1
- package/dist/lib/reactivity/createNoteReadonlyThunk.d.ts +5 -0
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +2 -1
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +1 -1
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +1 -1
- package/dist/lib/reactivity/text/createFieldHint.d.ts +3 -3
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +2 -2
- package/dist/lib/reactivity/text/createNoteText.d.ts +25 -0
- package/dist/lib/reactivity/text/createTextRange.d.ts +5 -7
- package/dist/lib/reactivity/validation/createAggregatedViolations.d.ts +9 -0
- package/dist/lib/reactivity/validation/createValidation.d.ts +18 -0
- package/dist/model/BindDefinition.d.ts +4 -2
- package/dist/model/BindElement.d.ts +1 -0
- package/dist/model/DescendentNodeDefinition.d.ts +1 -2
- package/dist/model/{ValueNodeDefinition.d.ts → LeafNodeDefinition.d.ts} +3 -4
- package/dist/model/NodeDefinition.d.ts +16 -16
- package/dist/model/RepeatInstanceDefinition.d.ts +5 -6
- package/dist/model/RepeatRangeDefinition.d.ts +30 -0
- package/dist/model/RepeatTemplateDefinition.d.ts +6 -7
- package/dist/model/RootDefinition.d.ts +3 -1
- package/dist/model/SubtreeDefinition.d.ts +2 -2
- package/dist/parse/NoteNodeDefinition.d.ts +31 -0
- package/dist/parse/expression/RepeatCountControlExpression.d.ts +19 -0
- package/dist/parse/text/HintDefinition.d.ts +9 -0
- package/dist/parse/text/ItemLabelDefinition.d.ts +9 -0
- package/dist/parse/text/ItemsetLabelDefinition.d.ts +13 -0
- package/dist/parse/text/LabelDefinition.d.ts +15 -0
- package/dist/parse/text/MessageDefinition.d.ts +15 -0
- package/dist/parse/text/OutputChunkDefinition.d.ts +8 -0
- package/dist/parse/text/ReferenceChunkDefinition.d.ts +8 -0
- package/dist/parse/text/StaticTextChunkDefinition.d.ts +10 -0
- package/dist/parse/text/TranslationChunkDefinition.d.ts +9 -0
- package/dist/parse/text/abstract/TextChunkDefinition.d.ts +18 -0
- package/dist/parse/text/abstract/TextElementDefinition.d.ts +23 -0
- package/dist/parse/text/abstract/TextRangeDefinition.d.ts +35 -0
- package/dist/parse/xpath/dependency-analysis.d.ts +40 -0
- package/dist/parse/xpath/path-resolution.d.ts +70 -0
- package/dist/parse/xpath/predicate-analysis.d.ts +30 -0
- package/dist/parse/xpath/reference-parsing.d.ts +18 -0
- package/dist/parse/xpath/semantic-analysis.d.ts +98 -0
- package/dist/parse/xpath/syntax-traversal.d.ts +69 -0
- package/dist/solid.js +3174 -961
- package/dist/solid.js.map +1 -1
- package/package.json +14 -15
- package/src/XFormDOM.ts +81 -8
- package/src/body/BodyDefinition.ts +38 -23
- package/src/body/BodyElementDefinition.ts +4 -3
- package/src/body/RepeatElementDefinition.ts +58 -0
- package/src/body/appearance/inputAppearanceParser.ts +39 -0
- package/src/body/appearance/selectAppearanceParser.ts +38 -0
- package/src/body/appearance/structureElementAppearanceParser.ts +7 -0
- package/src/body/control/ControlDefinition.ts +8 -3
- package/src/body/control/InputDefinition.ts +13 -0
- package/src/body/control/select/ItemDefinition.ts +3 -3
- package/src/body/control/select/ItemsetDefinition.ts +29 -12
- package/src/body/control/select/ItemsetNodesetExpression.ts +1 -1
- package/src/body/control/select/SelectDefinition.ts +14 -5
- package/src/body/group/BaseGroupDefinition.ts +14 -51
- package/src/body/group/PresentationGroupDefinition.ts +1 -1
- package/src/client/BaseNode.ts +82 -8
- package/src/client/GroupNode.ts +8 -2
- package/src/client/ModelValueNode.ts +40 -0
- package/src/client/NodeAppearances.ts +22 -0
- package/src/client/NoteNode.ts +74 -0
- package/src/client/README.md +1 -0
- package/src/client/RootNode.ts +24 -0
- package/src/client/SelectNode.ts +9 -3
- package/src/client/StringNode.ts +9 -3
- package/src/client/SubtreeNode.ts +3 -0
- package/src/client/TextRange.ts +99 -2
- package/src/client/constants.ts +10 -0
- package/src/client/hierarchy.ts +30 -14
- package/src/client/node-types.ts +8 -1
- package/src/client/{RepeatRangeNode.ts → repeat/BaseRepeatRangeNode.ts} +20 -17
- package/src/client/{RepeatInstanceNode.ts → repeat/RepeatInstanceNode.ts} +13 -7
- package/src/client/repeat/RepeatRangeControlledNode.ts +20 -0
- package/src/client/repeat/RepeatRangeUncontrolledNode.ts +21 -0
- package/src/client/validation.ts +199 -0
- package/src/expression/DependentExpression.ts +45 -27
- package/src/index.ts +15 -8
- package/src/instance/Group.ts +24 -13
- package/src/instance/ModelValue.ts +104 -0
- package/src/instance/Note.ts +142 -0
- package/src/instance/Root.ts +29 -67
- package/src/instance/SelectField.ts +35 -13
- package/src/instance/StringField.ts +40 -13
- package/src/instance/Subtree.ts +19 -10
- package/src/instance/abstract/DescendantNode.ts +50 -49
- package/src/instance/abstract/InstanceNode.ts +89 -92
- package/src/instance/children.ts +47 -10
- package/src/instance/hierarchy.ts +21 -2
- package/src/instance/index.ts +1 -1
- package/src/instance/internal-api/EvaluationContext.ts +5 -6
- package/src/instance/internal-api/ValidationContext.ts +23 -0
- package/src/instance/internal-api/ValueContext.ts +2 -2
- package/src/instance/repeat/BaseRepeatRange.ts +347 -0
- package/src/instance/{RepeatInstance.ts → repeat/RepeatInstance.ts} +85 -36
- package/src/instance/repeat/RepeatRangeControlled.ts +82 -0
- package/src/instance/repeat/RepeatRangeUncontrolled.ts +67 -0
- package/src/instance/text/TextRange.ts +10 -4
- package/src/lib/TokenListParser.ts +156 -0
- package/src/lib/dom/query.ts +13 -0
- package/src/lib/reactivity/createChildrenState.ts +51 -6
- package/src/lib/reactivity/createComputedExpression.ts +23 -25
- package/src/lib/reactivity/createNoteReadonlyThunk.ts +33 -0
- package/src/lib/reactivity/createSelectItems.ts +25 -20
- package/src/lib/reactivity/createValueState.ts +6 -6
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +3 -1
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +1 -1
- package/src/lib/reactivity/text/createFieldHint.ts +9 -7
- package/src/lib/reactivity/text/createNodeLabel.ts +7 -5
- package/src/lib/reactivity/text/createNoteText.ts +72 -0
- package/src/lib/reactivity/text/createTextRange.ts +17 -90
- package/src/lib/reactivity/validation/createAggregatedViolations.ts +70 -0
- package/src/lib/reactivity/validation/createValidation.ts +196 -0
- package/src/model/BindComputation.ts +0 -4
- package/src/model/BindDefinition.ts +8 -6
- package/src/model/BindElement.ts +1 -0
- package/src/model/DescendentNodeDefinition.ts +1 -2
- package/src/model/{ValueNodeDefinition.ts → LeafNodeDefinition.ts} +5 -6
- package/src/model/ModelBindMap.ts +4 -0
- package/src/model/ModelDefinition.ts +1 -1
- package/src/model/NodeDefinition.ts +21 -21
- package/src/model/RepeatInstanceDefinition.ts +8 -13
- package/src/model/RepeatRangeDefinition.ts +94 -0
- package/src/model/RepeatTemplateDefinition.ts +10 -15
- package/src/model/RootDefinition.ts +12 -14
- package/src/model/SubtreeDefinition.ts +3 -3
- package/src/parse/NoteNodeDefinition.ts +70 -0
- package/src/parse/TODO.md +3 -0
- package/src/parse/expression/RepeatCountControlExpression.ts +44 -0
- package/src/parse/text/HintDefinition.ts +25 -0
- package/src/parse/text/ItemLabelDefinition.ts +25 -0
- package/src/parse/text/ItemsetLabelDefinition.ts +44 -0
- package/src/parse/text/LabelDefinition.ts +61 -0
- package/src/parse/text/MessageDefinition.ts +49 -0
- package/src/parse/text/OutputChunkDefinition.ts +25 -0
- package/src/parse/text/ReferenceChunkDefinition.ts +14 -0
- package/src/parse/text/StaticTextChunkDefinition.ts +19 -0
- package/src/parse/text/TranslationChunkDefinition.ts +38 -0
- package/src/parse/text/abstract/TextChunkDefinition.ts +38 -0
- package/src/parse/text/abstract/TextElementDefinition.ts +71 -0
- package/src/parse/text/abstract/TextRangeDefinition.ts +70 -0
- package/src/parse/xpath/dependency-analysis.ts +105 -0
- package/src/parse/xpath/path-resolution.ts +475 -0
- package/src/parse/xpath/predicate-analysis.ts +61 -0
- package/src/parse/xpath/reference-parsing.ts +90 -0
- package/src/parse/xpath/semantic-analysis.ts +466 -0
- package/src/parse/xpath/syntax-traversal.ts +129 -0
- package/dist/body/RepeatDefinition.d.ts +0 -16
- package/dist/body/group/RepeatGroupDefinition.d.ts +0 -13
- package/dist/body/text/HintDefinition.d.ts +0 -11
- package/dist/body/text/LabelDefinition.d.ts +0 -20
- package/dist/body/text/TextElementDefinition.d.ts +0 -33
- package/dist/body/text/TextElementOutputPart.d.ts +0 -13
- package/dist/body/text/TextElementPart.d.ts +0 -13
- package/dist/body/text/TextElementReferencePart.d.ts +0 -7
- package/dist/body/text/TextElementStaticPart.d.ts +0 -7
- package/dist/instance/RepeatRange.d.ts +0 -80
- package/dist/lib/xpath/analysis.d.ts +0 -23
- package/dist/model/RepeatSequenceDefinition.d.ts +0 -20
- package/src/body/RepeatDefinition.ts +0 -54
- package/src/body/group/RepeatGroupDefinition.ts +0 -91
- package/src/body/text/HintDefinition.ts +0 -26
- package/src/body/text/LabelDefinition.ts +0 -54
- package/src/body/text/TextElementDefinition.ts +0 -97
- package/src/body/text/TextElementOutputPart.ts +0 -27
- package/src/body/text/TextElementPart.ts +0 -31
- package/src/body/text/TextElementReferencePart.ts +0 -21
- package/src/body/text/TextElementStaticPart.ts +0 -26
- package/src/instance/RepeatRange.ts +0 -214
- package/src/lib/xpath/analysis.ts +0 -241
- package/src/model/RepeatSequenceDefinition.ts +0 -53
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { insertAtIndex } from '@getodk/common/lib/array/insert.ts';
|
|
2
|
+
import { untrack, type Accessor } from 'solid-js';
|
|
3
|
+
import type { NodeAppearances } from '../../client/NodeAppearances.ts';
|
|
4
|
+
import type { BaseRepeatRangeNode } from '../../client/repeat/BaseRepeatRangeNode.ts';
|
|
5
|
+
import type { TextRange } from '../../client/TextRange.ts';
|
|
6
|
+
import type { AncestorNodeValidationState } from '../../client/validation.ts';
|
|
7
|
+
import type { ChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
8
|
+
import { createChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
9
|
+
import { createComputedExpression } from '../../lib/reactivity/createComputedExpression.ts';
|
|
10
|
+
import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
11
|
+
import { materializeCurrentStateChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
12
|
+
import type { CurrentState } from '../../lib/reactivity/node-state/createCurrentState.ts';
|
|
13
|
+
import type { EngineState } from '../../lib/reactivity/node-state/createEngineState.ts';
|
|
14
|
+
import type { SharedNodeState } from '../../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
15
|
+
import { createSharedNodeState } from '../../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
16
|
+
import { createNodeLabel } from '../../lib/reactivity/text/createNodeLabel.ts';
|
|
17
|
+
import type {
|
|
18
|
+
AnyRepeatRangeDefinition,
|
|
19
|
+
ControlledRepeatRangeDefinition,
|
|
20
|
+
} from '../../model/RepeatRangeDefinition.ts';
|
|
21
|
+
import type {
|
|
22
|
+
AnyDescendantNode,
|
|
23
|
+
DescendantNodeParent,
|
|
24
|
+
DescendantNodeSharedStateSpec,
|
|
25
|
+
} from '../abstract/DescendantNode.ts';
|
|
26
|
+
import { DescendantNode } from '../abstract/DescendantNode.ts';
|
|
27
|
+
import type { RepeatRange } from '../hierarchy.ts';
|
|
28
|
+
import type { NodeID } from '../identity.ts';
|
|
29
|
+
import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
|
|
30
|
+
import type { SubscribableDependency } from '../internal-api/SubscribableDependency.ts';
|
|
31
|
+
import { RepeatInstance, type RepeatDefinition } from './RepeatInstance.ts';
|
|
32
|
+
|
|
33
|
+
interface RepeatRangeStateSpec extends DescendantNodeSharedStateSpec {
|
|
34
|
+
readonly hint: null;
|
|
35
|
+
readonly label: Accessor<TextRange<'label'> | null>;
|
|
36
|
+
readonly children: Accessor<readonly NodeID[]>;
|
|
37
|
+
readonly valueOptions: null;
|
|
38
|
+
readonly value: null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// prettier-ignore
|
|
42
|
+
type BaseRepeatRangeNodeType<Definition extends AnyRepeatRangeDefinition> =
|
|
43
|
+
Definition extends ControlledRepeatRangeDefinition
|
|
44
|
+
? 'repeat-range:controlled'
|
|
45
|
+
: 'repeat-range:uncontrolled';
|
|
46
|
+
|
|
47
|
+
export abstract class BaseRepeatRange<Definition extends AnyRepeatRangeDefinition>
|
|
48
|
+
extends DescendantNode<Definition, RepeatRangeStateSpec, RepeatInstance>
|
|
49
|
+
implements BaseRepeatRangeNode, EvaluationContext, SubscribableDependency
|
|
50
|
+
{
|
|
51
|
+
/**
|
|
52
|
+
* A repeat range doesn't have a corresponding primary instance element of its
|
|
53
|
+
* own, and its instances are appended to the range's parent element. During
|
|
54
|
+
* creation of the initial primary instance state and DOM trees, we _could_
|
|
55
|
+
* reliably append all of the range's instances in order as the definition
|
|
56
|
+
* tree is recursed. But that would fail to handle some instance addition
|
|
57
|
+
* cases afterwards.
|
|
58
|
+
*
|
|
59
|
+
* Most notably, we need to know where in the primary instance tree to append
|
|
60
|
+
* instances created for a range which is currently empty. As a lucky
|
|
61
|
+
* coincidence, this need coincides with the ability to add instances at any
|
|
62
|
+
* arbitrary index within the range. In each case, we can reference a primary
|
|
63
|
+
* instance DOM node which will become the new instance's preceding sibling.
|
|
64
|
+
* Where the range is empty, we use this {@link Comment} node (itself created
|
|
65
|
+
* and appended during range initialization) in lieu of a nonexistent
|
|
66
|
+
* preceding instance's {@link contextNode}.
|
|
67
|
+
*
|
|
68
|
+
* @todo We likely want to remove these during submission serialization.
|
|
69
|
+
* @todo Can we use a
|
|
70
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Range | DOM Range}
|
|
71
|
+
* instead?
|
|
72
|
+
*/
|
|
73
|
+
protected readonly anchorNode: Comment;
|
|
74
|
+
|
|
75
|
+
protected readonly childrenState: ChildrenState<RepeatInstance>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Provides an {@link EvaluationContext} from which to evaluate expressions
|
|
79
|
+
* where some LocationPath sub-expressions may be **relative to the repeat
|
|
80
|
+
* range itself**. This is useful for evaluation of expressions where:
|
|
81
|
+
*
|
|
82
|
+
* - the expression is typically contextualized to any of its
|
|
83
|
+
* {@link RepeatInstance} children, but it presently has none (i.e.
|
|
84
|
+
* `relevant`)
|
|
85
|
+
*
|
|
86
|
+
* - the expression is conceptually intended to be evaluated in the context of
|
|
87
|
+
* the repeat range itself (i.e. `jr:count`)
|
|
88
|
+
*/
|
|
89
|
+
protected readonly selfEvaluationContext: EvaluationContext & {
|
|
90
|
+
readonly contextNode: Comment;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @see {@link isSelfRelevant}
|
|
95
|
+
*/
|
|
96
|
+
protected readonly isEmptyRangeSelfRelevant: Accessor<boolean>;
|
|
97
|
+
|
|
98
|
+
// InstanceNode
|
|
99
|
+
protected readonly state: SharedNodeState<RepeatRangeStateSpec>;
|
|
100
|
+
protected override engineState: EngineState<RepeatRangeStateSpec>;
|
|
101
|
+
|
|
102
|
+
// DescendantNode
|
|
103
|
+
/**
|
|
104
|
+
* @todo Should we special case repeat `readonly` state the same way
|
|
105
|
+
* we do for `relevant`?
|
|
106
|
+
*
|
|
107
|
+
* @see {@link isSelfRelevant}
|
|
108
|
+
*/
|
|
109
|
+
declare isSelfReadonly: Accessor<boolean>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A repeat range does not exist in the primary instance tree. A `relevant`
|
|
113
|
+
* expression applies to each {@link RepeatInstance} child of the repeat
|
|
114
|
+
* range. Determining whether a repeat range itself "is relevant" isn't a
|
|
115
|
+
* concept the spec addresses, but it may be used by clients to determine
|
|
116
|
+
* whether to allow interaction with the range (e.g. by adding a repeat
|
|
117
|
+
* instance, or presenting the range's label when empty).
|
|
118
|
+
*
|
|
119
|
+
* As a naive first pass, it seems like the heuristic for this should be:
|
|
120
|
+
*
|
|
121
|
+
* 1. Does the repeat range have any repeat instance children?
|
|
122
|
+
*
|
|
123
|
+
* - If yes, go to 2.
|
|
124
|
+
* - If no, go to 3.
|
|
125
|
+
*
|
|
126
|
+
* 2. Does one or more of those children return `true` for the node's
|
|
127
|
+
* `relevant` expression (i.e. is the repeat instance "self relevant")?
|
|
128
|
+
*
|
|
129
|
+
* 3. Does the relevant expression return `true` for the repeat range itself
|
|
130
|
+
* (where, at least for now, the context of that evaluation would be the
|
|
131
|
+
* repeat range's {@link anchorNode} to ensure correct relative expressions
|
|
132
|
+
* resolve correctly)?
|
|
133
|
+
*
|
|
134
|
+
* @todo While (3) is proactively implemented, there isn't presently a test
|
|
135
|
+
* exercising it. It felt best for now to surface this for discussion in
|
|
136
|
+
* review to validate that it's going in the right direction.
|
|
137
|
+
*
|
|
138
|
+
* @todo While (2) **is actually tested**, the tests currently in place behave
|
|
139
|
+
* the same way with only the logic for (3), regardless of whether the repeat
|
|
140
|
+
* range actually has any repeat instance children. It's unclear (a) if that's
|
|
141
|
+
* a preferable simplification and (b) how that might affect performance (in
|
|
142
|
+
* theory it could vary depending on form structure and runtime state).
|
|
143
|
+
*/
|
|
144
|
+
override readonly isSelfRelevant: Accessor<boolean> = () => {
|
|
145
|
+
const instances = this.childrenState.getChildren();
|
|
146
|
+
|
|
147
|
+
if (instances.length > 0) {
|
|
148
|
+
return instances.some((instance) => instance.isSelfRelevant());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return this.isEmptyRangeSelfRelevant();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// BaseRepeatRangeNode
|
|
155
|
+
abstract override readonly nodeType: BaseRepeatRangeNodeType<Definition>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @todo RepeatRange*, RepeatInstance* (and RepeatTemplate*) all share the
|
|
159
|
+
* same body element, and thus all share the same definition `bodyElement`. As
|
|
160
|
+
* such, they also all share the same `appearances`. At time of writing,
|
|
161
|
+
* `web-forms` (Vue UI package) treats a `RepeatRangeNode`...
|
|
162
|
+
*
|
|
163
|
+
* - ... as a group, if the node has a label (i.e.
|
|
164
|
+
* `<group><label/><repeat/></group>`)
|
|
165
|
+
* - ... effectively as a fragment containing only its instances, otherwise
|
|
166
|
+
*
|
|
167
|
+
* We now collapse `<group><repeat>` into `<repeat>`, and no longer treat
|
|
168
|
+
* "repeat group" as a concept (after parsing). According to the spec, these
|
|
169
|
+
* appearances **are supposed to** come from that "repeat group" in the form
|
|
170
|
+
* definition. In practice, many forms do define appearances directly on a
|
|
171
|
+
* repeat element. The engine currently produces an error if both are defined
|
|
172
|
+
* simultaneously, but otherwise makes no distinction between appearances in
|
|
173
|
+
* these form definition shapes:
|
|
174
|
+
*
|
|
175
|
+
* ```xml
|
|
176
|
+
* <group ref="/data/rep1" appearance="...">
|
|
177
|
+
* <repeat nodeset="/data/rep1"/>
|
|
178
|
+
* </group>
|
|
179
|
+
*
|
|
180
|
+
* <group ref="/data/rep1">
|
|
181
|
+
* <repeat nodeset="/data/rep1"/ appearance="...">
|
|
182
|
+
* </group>
|
|
183
|
+
*
|
|
184
|
+
* <repeat nodeset="/data/rep1"/ appearance="...">
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* All of the above creates considerable ambiguity about where "repeat
|
|
188
|
+
* appearances" should apply, under which circumstances.
|
|
189
|
+
*/
|
|
190
|
+
abstract override readonly appearances: NodeAppearances<Definition>;
|
|
191
|
+
|
|
192
|
+
readonly currentState: MaterializedChildren<CurrentState<RepeatRangeStateSpec>, RepeatInstance>;
|
|
193
|
+
|
|
194
|
+
abstract override readonly validationState: AncestorNodeValidationState;
|
|
195
|
+
|
|
196
|
+
constructor(parent: DescendantNodeParent<Definition>, definition: Definition) {
|
|
197
|
+
super(parent, definition);
|
|
198
|
+
|
|
199
|
+
const repeatRange = this as AnyDescendantNode as RepeatRange;
|
|
200
|
+
|
|
201
|
+
const childrenState = createChildrenState<RepeatRange, RepeatInstance>(repeatRange);
|
|
202
|
+
|
|
203
|
+
this.childrenState = childrenState;
|
|
204
|
+
|
|
205
|
+
this.anchorNode = this.contextNode.ownerDocument.createComment(
|
|
206
|
+
`Begin repeat range: ${definition.nodeset}`
|
|
207
|
+
);
|
|
208
|
+
this.contextNode.append(this.anchorNode);
|
|
209
|
+
|
|
210
|
+
this.selfEvaluationContext = {
|
|
211
|
+
scope: this.scope,
|
|
212
|
+
evaluator: this.evaluator,
|
|
213
|
+
root: this.root,
|
|
214
|
+
contextReference: this.contextReference,
|
|
215
|
+
contextNode: this.anchorNode,
|
|
216
|
+
|
|
217
|
+
getSubscribableDependenciesByReference: (reference) => {
|
|
218
|
+
return repeatRange.getSubscribableDependenciesByReference(reference);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.isEmptyRangeSelfRelevant = createComputedExpression(
|
|
223
|
+
this.selfEvaluationContext,
|
|
224
|
+
definition.bind.relevant
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const sharedStateOptions = {
|
|
228
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const state = createSharedNodeState(
|
|
232
|
+
this.scope,
|
|
233
|
+
{
|
|
234
|
+
reference: this.contextReference,
|
|
235
|
+
readonly: this.isReadonly,
|
|
236
|
+
relevant: this.isRelevant,
|
|
237
|
+
required: this.isRequired,
|
|
238
|
+
|
|
239
|
+
label: createNodeLabel(this, definition),
|
|
240
|
+
hint: null,
|
|
241
|
+
children: childrenState.childIds,
|
|
242
|
+
valueOptions: null,
|
|
243
|
+
value: null,
|
|
244
|
+
},
|
|
245
|
+
sharedStateOptions
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
this.state = state;
|
|
249
|
+
this.engineState = state.engineState;
|
|
250
|
+
this.currentState = materializeCurrentStateChildren(
|
|
251
|
+
this.scope,
|
|
252
|
+
state.currentState,
|
|
253
|
+
childrenState
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
protected getLastIndex(): number {
|
|
258
|
+
return this.engineState.children.length - 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
protected override initializeContextNode(parentContextNode: Element): Element {
|
|
262
|
+
return parentContextNode;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
getInstanceIndex(instance: RepeatInstance): number {
|
|
266
|
+
return this.engineState.children.indexOf(instance.nodeId);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private createChildren(
|
|
270
|
+
afterIndex: number,
|
|
271
|
+
definitions: readonly RepeatDefinition[]
|
|
272
|
+
): readonly RepeatInstance[] {
|
|
273
|
+
return this.scope.runTask(() => {
|
|
274
|
+
let initialPrecedingInstance: RepeatInstance | null;
|
|
275
|
+
|
|
276
|
+
if (afterIndex === -1) {
|
|
277
|
+
initialPrecedingInstance = null;
|
|
278
|
+
} else {
|
|
279
|
+
const instance = untrack(() => this.childrenState.getChildren()[afterIndex]);
|
|
280
|
+
|
|
281
|
+
if (instance == null) {
|
|
282
|
+
throw new Error(`No repeat instance at index ${afterIndex}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
initialPrecedingInstance = instance;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const repeatRange = this as AnyDescendantNode as RepeatRange;
|
|
289
|
+
|
|
290
|
+
return definitions.reduce<RepeatInstance[]>((acc, definition) => {
|
|
291
|
+
const precedingInstance = acc[acc.length - 1] ?? initialPrecedingInstance;
|
|
292
|
+
const precedingPrimaryInstanceNode = precedingInstance?.contextNode ?? this.anchorNode;
|
|
293
|
+
const newInstance = new RepeatInstance(repeatRange, definition, {
|
|
294
|
+
precedingPrimaryInstanceNode,
|
|
295
|
+
precedingInstance,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
acc.push(newInstance);
|
|
299
|
+
|
|
300
|
+
return acc;
|
|
301
|
+
}, []);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
protected addChildren(
|
|
306
|
+
afterIndex: number,
|
|
307
|
+
definitions: readonly RepeatDefinition[]
|
|
308
|
+
): readonly RepeatInstance[] {
|
|
309
|
+
return this.scope.runTask(() => {
|
|
310
|
+
const initialIndex = afterIndex + 1;
|
|
311
|
+
const newInstances = this.createChildren(afterIndex, definitions);
|
|
312
|
+
|
|
313
|
+
return this.childrenState.setChildren((currentInstances) => {
|
|
314
|
+
return insertAtIndex(currentInstances, initialIndex, newInstances);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
protected removeChildren(startIndex: number, count: number): readonly RepeatInstance[] {
|
|
320
|
+
return this.scope.runTask(() => {
|
|
321
|
+
return this.childrenState.setChildren((currentInstances) => {
|
|
322
|
+
const updatedInstances = currentInstances.slice();
|
|
323
|
+
const removedInstances = updatedInstances.splice(startIndex, count);
|
|
324
|
+
|
|
325
|
+
removedInstances.forEach((instance) => {
|
|
326
|
+
instance.remove();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return updatedInstances;
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
override subscribe(): void {
|
|
335
|
+
super.subscribe();
|
|
336
|
+
|
|
337
|
+
// Subscribing to children can support reactive expressions dependent on the
|
|
338
|
+
// repeat range itself (e.g. `count()`).
|
|
339
|
+
this.childrenState.getChildren().forEach((child) => {
|
|
340
|
+
child.subscribe();
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
getChildren(): readonly RepeatInstance[] {
|
|
345
|
+
return this.childrenState.getChildren();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import type { Accessor } from 'solid-js';
|
|
2
2
|
import { createComputed, createSignal, on } from 'solid-js';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import type {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import type {
|
|
21
|
-
import
|
|
3
|
+
import type {
|
|
4
|
+
RepeatDefinition,
|
|
5
|
+
RepeatInstanceNode,
|
|
6
|
+
RepeatInstanceNodeAppearances,
|
|
7
|
+
} from '../../client/repeat/RepeatInstanceNode.ts';
|
|
8
|
+
import type { TextRange } from '../../client/TextRange.ts';
|
|
9
|
+
import type { AncestorNodeValidationState } from '../../client/validation.ts';
|
|
10
|
+
import type { ChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
11
|
+
import { createChildrenState } from '../../lib/reactivity/createChildrenState.ts';
|
|
12
|
+
import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
13
|
+
import { materializeCurrentStateChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
|
|
14
|
+
import type { CurrentState } from '../../lib/reactivity/node-state/createCurrentState.ts';
|
|
15
|
+
import type { EngineState } from '../../lib/reactivity/node-state/createEngineState.ts';
|
|
16
|
+
import type { SharedNodeState } from '../../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
17
|
+
import { createSharedNodeState } from '../../lib/reactivity/node-state/createSharedNodeState.ts';
|
|
18
|
+
import { createNodeLabel } from '../../lib/reactivity/text/createNodeLabel.ts';
|
|
19
|
+
import { createAggregatedViolations } from '../../lib/reactivity/validation/createAggregatedViolations.ts';
|
|
20
|
+
import type { DescendantNodeSharedStateSpec } from '../abstract/DescendantNode.ts';
|
|
21
|
+
import { DescendantNode } from '../abstract/DescendantNode.ts';
|
|
22
|
+
import { buildChildren } from '../children.ts';
|
|
23
|
+
import type { AnyChildNode, GeneralChildNode, RepeatRange } from '../hierarchy.ts';
|
|
24
|
+
import type { NodeID } from '../identity.ts';
|
|
25
|
+
import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
|
|
26
|
+
import type { SubscribableDependency } from '../internal-api/SubscribableDependency.ts';
|
|
22
27
|
|
|
23
28
|
export type { RepeatDefinition };
|
|
24
29
|
|
|
@@ -46,20 +51,64 @@ export class RepeatInstance
|
|
|
46
51
|
protected readonly state: SharedNodeState<RepeatInstanceStateSpec>;
|
|
47
52
|
protected override engineState: EngineState<RepeatInstanceStateSpec>;
|
|
48
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @todo Should we special case repeat `readonly` inheritance the same way
|
|
56
|
+
* we do for `relevant`?
|
|
57
|
+
*
|
|
58
|
+
* @see {@link hasNonRelevantAncestor}
|
|
59
|
+
*/
|
|
60
|
+
declare readonly hasReadonlyAncestor: Accessor<boolean>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A repeat instance can inherit non-relevance, just like any other node. That
|
|
64
|
+
* inheritance is derived from the repeat instance's parent node in the
|
|
65
|
+
* primary instance XML/DOM tree (and would be semantically expected to do so
|
|
66
|
+
* even if we move away from that implementation detail).
|
|
67
|
+
*
|
|
68
|
+
* Since {@link RepeatInstance.parent} is a {@link RepeatRange}, which is a
|
|
69
|
+
* runtime data model fiction that does not exist in that hierarchy, we pass
|
|
70
|
+
* this call through, allowing the {@link RepeatRange} to check the actual
|
|
71
|
+
* primary instance parent node's relevance state.
|
|
72
|
+
*
|
|
73
|
+
* @todo Should we apply similar reasoning in {@link hasReadonlyAncestor}?
|
|
74
|
+
*/
|
|
75
|
+
override readonly hasNonRelevantAncestor: Accessor<boolean> = () => {
|
|
76
|
+
return this.parent.hasNonRelevantAncestor();
|
|
77
|
+
};
|
|
78
|
+
|
|
49
79
|
// RepeatInstanceNode
|
|
50
80
|
readonly nodeType = 'repeat-instance';
|
|
51
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @see {@link RepeatRange.appearances}
|
|
84
|
+
*/
|
|
85
|
+
readonly appearances: RepeatInstanceNodeAppearances;
|
|
86
|
+
|
|
52
87
|
readonly currentState: MaterializedChildren<
|
|
53
88
|
CurrentState<RepeatInstanceStateSpec>,
|
|
54
89
|
GeneralChildNode
|
|
55
90
|
>;
|
|
91
|
+
readonly validationState: AncestorNodeValidationState;
|
|
56
92
|
|
|
57
93
|
constructor(
|
|
58
94
|
override readonly parent: RepeatRange,
|
|
59
95
|
definition: RepeatDefinition,
|
|
60
96
|
options: RepeatInstanceOptions
|
|
61
97
|
) {
|
|
62
|
-
|
|
98
|
+
const { precedingInstance } = options;
|
|
99
|
+
const precedingIndex = precedingInstance?.currentIndex ?? (() => -1);
|
|
100
|
+
const initialIndex = precedingIndex() + 1;
|
|
101
|
+
const [currentIndex, setCurrentIndex] = createSignal(initialIndex);
|
|
102
|
+
|
|
103
|
+
super(parent, definition, {
|
|
104
|
+
computeReference: (): string => {
|
|
105
|
+
const currentPosition = currentIndex() + 1;
|
|
106
|
+
|
|
107
|
+
return `${parent.contextReference()}[${currentPosition}]`;
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.appearances = definition.bodyElement.appearances;
|
|
63
112
|
|
|
64
113
|
const childrenState = createChildrenState<RepeatInstance, GeneralChildNode>(this);
|
|
65
114
|
|
|
@@ -67,32 +116,37 @@ export class RepeatInstance
|
|
|
67
116
|
|
|
68
117
|
options.precedingPrimaryInstanceNode.after(this.contextNode);
|
|
69
118
|
|
|
70
|
-
const { precedingInstance } = options;
|
|
71
|
-
const precedingIndex = precedingInstance?.currentIndex ?? (() => -1);
|
|
72
|
-
const initialIndex = precedingIndex() + 1;
|
|
73
|
-
const [currentIndex, setCurrentIndex] = createSignal(initialIndex);
|
|
74
|
-
|
|
75
119
|
this.currentIndex = currentIndex;
|
|
76
120
|
|
|
121
|
+
const sharedStateOptions = {
|
|
122
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
123
|
+
};
|
|
124
|
+
|
|
77
125
|
const state = createSharedNodeState(
|
|
78
126
|
this.scope,
|
|
79
127
|
{
|
|
80
|
-
|
|
128
|
+
reference: this.contextReference,
|
|
129
|
+
readonly: this.isReadonly,
|
|
130
|
+
relevant: this.isRelevant,
|
|
131
|
+
required: this.isRequired,
|
|
81
132
|
|
|
133
|
+
// TODO: only-child <group><label>
|
|
82
134
|
label: createNodeLabel(this, definition),
|
|
83
135
|
hint: null,
|
|
84
136
|
children: childrenState.childIds,
|
|
85
137
|
valueOptions: null,
|
|
86
138
|
value: null,
|
|
87
139
|
},
|
|
88
|
-
|
|
89
|
-
clientStateFactory: this.engineConfig.stateFactory,
|
|
90
|
-
}
|
|
140
|
+
sharedStateOptions
|
|
91
141
|
);
|
|
92
142
|
|
|
93
143
|
this.state = state;
|
|
94
144
|
this.engineState = state.engineState;
|
|
95
|
-
this.currentState = materializeCurrentStateChildren(
|
|
145
|
+
this.currentState = materializeCurrentStateChildren(
|
|
146
|
+
this.scope,
|
|
147
|
+
state.currentState,
|
|
148
|
+
childrenState
|
|
149
|
+
);
|
|
96
150
|
|
|
97
151
|
// Maintain current index state, updating as the parent range's children
|
|
98
152
|
// state is changed. Notable Solid reactivity nuances:
|
|
@@ -116,12 +170,7 @@ export class RepeatInstance
|
|
|
116
170
|
});
|
|
117
171
|
|
|
118
172
|
childrenState.setChildren(buildChildren(this));
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
protected computeReference(parent: RepeatRange): string {
|
|
122
|
-
const currentPosition = this.currentIndex() + 1;
|
|
123
|
-
|
|
124
|
-
return `${parent.contextReference}[${currentPosition}]`;
|
|
173
|
+
this.validationState = createAggregatedViolations(this, sharedStateOptions);
|
|
125
174
|
}
|
|
126
175
|
|
|
127
176
|
protected override initializeContextNode(parentContextNode: Element, nodeName: string): Element {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createComputed } from 'solid-js';
|
|
2
|
+
import type { RepeatRangeNodeAppearances } from '../../client/repeat/BaseRepeatRangeNode.ts';
|
|
3
|
+
import type { RepeatRangeControlledNode } from '../../client/repeat/RepeatRangeControlledNode.ts';
|
|
4
|
+
import type { AncestorNodeValidationState } from '../../client/validation.ts';
|
|
5
|
+
import { createComputedExpression } from '../../lib/reactivity/createComputedExpression.ts';
|
|
6
|
+
import { createAggregatedViolations } from '../../lib/reactivity/validation/createAggregatedViolations.ts';
|
|
7
|
+
import type { ControlledRepeatRangeDefinition } from '../../model/RepeatRangeDefinition.ts';
|
|
8
|
+
import type { GeneralParentNode } from '../hierarchy.ts';
|
|
9
|
+
import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
|
|
10
|
+
import type { SubscribableDependency } from '../internal-api/SubscribableDependency.ts';
|
|
11
|
+
import { BaseRepeatRange } from './BaseRepeatRange.ts';
|
|
12
|
+
import type { RepeatDefinition } from './RepeatInstance.ts';
|
|
13
|
+
|
|
14
|
+
export class RepeatRangeControlled
|
|
15
|
+
extends BaseRepeatRange<ControlledRepeatRangeDefinition>
|
|
16
|
+
implements RepeatRangeControlledNode, EvaluationContext, SubscribableDependency
|
|
17
|
+
{
|
|
18
|
+
// RepeatRangeControlledNode
|
|
19
|
+
readonly nodeType = 'repeat-range:controlled';
|
|
20
|
+
|
|
21
|
+
readonly appearances: RepeatRangeNodeAppearances;
|
|
22
|
+
|
|
23
|
+
readonly validationState: AncestorNodeValidationState;
|
|
24
|
+
|
|
25
|
+
constructor(parent: GeneralParentNode, definition: ControlledRepeatRangeDefinition) {
|
|
26
|
+
super(parent, definition);
|
|
27
|
+
|
|
28
|
+
this.appearances = definition.bodyElement.appearances;
|
|
29
|
+
|
|
30
|
+
this.initializeControlledChildrenState(definition);
|
|
31
|
+
|
|
32
|
+
this.validationState = createAggregatedViolations(this, {
|
|
33
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private initializeControlledChildrenState(definition: ControlledRepeatRangeDefinition): void {
|
|
38
|
+
this.scope.runTask(() => {
|
|
39
|
+
const { count, instances, template } = definition;
|
|
40
|
+
const computeCount = createComputedExpression(this.selfEvaluationContext, count);
|
|
41
|
+
|
|
42
|
+
createComputed<number>((previousCount) => {
|
|
43
|
+
let currentCount = computeCount();
|
|
44
|
+
|
|
45
|
+
if (Number.isFinite(currentCount) && currentCount < 0) {
|
|
46
|
+
currentCount = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
currentCount === previousCount ||
|
|
51
|
+
// TODO: the intent of this check is to defer a count update when the
|
|
52
|
+
// count expression produces a blank value. This "feels right" when
|
|
53
|
+
// the count is directly controlled by the user (i.e. entering a
|
|
54
|
+
// number in an input), but probably does not make sense in every
|
|
55
|
+
// scenario! For instance, when a referenced node's relevance changes.
|
|
56
|
+
Number.isNaN(currentCount)
|
|
57
|
+
) {
|
|
58
|
+
return previousCount;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (currentCount > previousCount) {
|
|
62
|
+
const delta = currentCount - previousCount;
|
|
63
|
+
const definitions = Array<RepeatDefinition>(delta)
|
|
64
|
+
.fill(template)
|
|
65
|
+
.map((baseDefinition, index) => {
|
|
66
|
+
const instanceIndex = previousCount + index;
|
|
67
|
+
|
|
68
|
+
return instances[instanceIndex] ?? baseDefinition;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.addChildren(previousCount - 1, definitions);
|
|
72
|
+
} else {
|
|
73
|
+
const delta = previousCount - currentCount;
|
|
74
|
+
|
|
75
|
+
this.removeChildren(currentCount, delta);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return currentCount;
|
|
79
|
+
}, 0);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { RepeatRangeNodeAppearances } from '../../client/repeat/BaseRepeatRangeNode.ts';
|
|
2
|
+
import type { RepeatRangeUncontrolledNode } from '../../client/repeat/RepeatRangeUncontrolledNode.ts';
|
|
3
|
+
import type { AncestorNodeValidationState } from '../../client/validation.ts';
|
|
4
|
+
import { createAggregatedViolations } from '../../lib/reactivity/validation/createAggregatedViolations.ts';
|
|
5
|
+
import type { UncontrolledRepeatRangeDefinition } from '../../model/RepeatRangeDefinition.ts';
|
|
6
|
+
import type { GeneralParentNode } from '../hierarchy.ts';
|
|
7
|
+
import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
|
|
8
|
+
import type { SubscribableDependency } from '../internal-api/SubscribableDependency.ts';
|
|
9
|
+
import type { Root } from '../Root.ts';
|
|
10
|
+
import { BaseRepeatRange } from './BaseRepeatRange.ts';
|
|
11
|
+
import { RepeatInstance } from './RepeatInstance.ts';
|
|
12
|
+
|
|
13
|
+
export class RepeatRangeUncontrolled
|
|
14
|
+
extends BaseRepeatRange<UncontrolledRepeatRangeDefinition>
|
|
15
|
+
implements RepeatRangeUncontrolledNode, EvaluationContext, SubscribableDependency
|
|
16
|
+
{
|
|
17
|
+
// RepeatRangeUncontrolledNode
|
|
18
|
+
readonly nodeType = 'repeat-range:uncontrolled';
|
|
19
|
+
|
|
20
|
+
readonly appearances: RepeatRangeNodeAppearances;
|
|
21
|
+
|
|
22
|
+
readonly validationState: AncestorNodeValidationState;
|
|
23
|
+
|
|
24
|
+
constructor(parent: GeneralParentNode, definition: UncontrolledRepeatRangeDefinition) {
|
|
25
|
+
super(parent, definition);
|
|
26
|
+
|
|
27
|
+
this.appearances = definition.bodyElement.appearances;
|
|
28
|
+
|
|
29
|
+
this.addChildren(-1, definition.instances);
|
|
30
|
+
|
|
31
|
+
this.validationState = createAggregatedViolations(this, {
|
|
32
|
+
clientStateFactory: this.engineConfig.stateFactory,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// RepeatRangeUncontrolledNode
|
|
37
|
+
addInstances(afterIndex = this.getLastIndex(), count = 1): Root {
|
|
38
|
+
const definitions = Array(count).fill(this.definition.template);
|
|
39
|
+
|
|
40
|
+
this.addChildren(afterIndex, definitions);
|
|
41
|
+
|
|
42
|
+
return this.root;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Removes the {@link RepeatInstance}s corresponding to the specified range of
|
|
47
|
+
* indexes, and then removes those repeat instances from the repeat range's
|
|
48
|
+
* own children state in that order:
|
|
49
|
+
*
|
|
50
|
+
* 1. Identify the set of {@link RepeatInstance}s to be removed.
|
|
51
|
+
*
|
|
52
|
+
* 2. For each {@link RepeatInstance} pending removal, perform that node's
|
|
53
|
+
* removal logic. @see {@link RepeatInstance.remove} for more detail.
|
|
54
|
+
*
|
|
55
|
+
* 3. Finalize update to the repeat range's own {@link childrenState},
|
|
56
|
+
* omitting those {@link RepeatInstance}s which were removed.
|
|
57
|
+
*
|
|
58
|
+
* This ordering ensures a consistent representation of state is established
|
|
59
|
+
* prior to any downstream reactive updates, and ensures that removed nodes'
|
|
60
|
+
* reactivity is cleaned up.
|
|
61
|
+
*/
|
|
62
|
+
removeInstances(startIndex: number, count = 1): Root {
|
|
63
|
+
this.removeChildren(startIndex, count);
|
|
64
|
+
|
|
65
|
+
return this.root;
|
|
66
|
+
}
|
|
67
|
+
}
|