@lionweb/class-core 0.6.13-beta.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 (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +157 -0
  3. package/dist/base-types.d.ts +167 -0
  4. package/dist/base-types.d.ts.map +1 -0
  5. package/dist/base-types.js +147 -0
  6. package/dist/base-types.js.map +1 -0
  7. package/dist/convenience.d.ts +49 -0
  8. package/dist/convenience.d.ts.map +1 -0
  9. package/dist/convenience.js +75 -0
  10. package/dist/convenience.js.map +1 -0
  11. package/dist/deltas/appliers.d.ts +42 -0
  12. package/dist/deltas/appliers.d.ts.map +1 -0
  13. package/dist/deltas/appliers.js +274 -0
  14. package/dist/deltas/appliers.js.map +1 -0
  15. package/dist/deltas/base.d.ts +6 -0
  16. package/dist/deltas/base.d.ts.map +1 -0
  17. package/dist/deltas/base.js +18 -0
  18. package/dist/deltas/base.js.map +1 -0
  19. package/dist/deltas/handlers.d.ts +17 -0
  20. package/dist/deltas/handlers.d.ts.map +1 -0
  21. package/dist/deltas/handlers.js +43 -0
  22. package/dist/deltas/handlers.js.map +1 -0
  23. package/dist/deltas/index.d.ts +7 -0
  24. package/dist/deltas/index.d.ts.map +1 -0
  25. package/dist/deltas/index.js +23 -0
  26. package/dist/deltas/index.js.map +1 -0
  27. package/dist/deltas/inverters.d.ts +7 -0
  28. package/dist/deltas/inverters.d.ts.map +1 -0
  29. package/dist/deltas/inverters.js +82 -0
  30. package/dist/deltas/inverters.js.map +1 -0
  31. package/dist/deltas/serialization/base.d.ts +7 -0
  32. package/dist/deltas/serialization/base.d.ts.map +1 -0
  33. package/dist/deltas/serialization/base.js +18 -0
  34. package/dist/deltas/serialization/base.js.map +1 -0
  35. package/dist/deltas/serialization/deserializer.g.d.ts +5 -0
  36. package/dist/deltas/serialization/deserializer.g.d.ts.map +1 -0
  37. package/dist/deltas/serialization/deserializer.g.js +164 -0
  38. package/dist/deltas/serialization/deserializer.g.js.map +1 -0
  39. package/dist/deltas/serialization/index.d.ts +5 -0
  40. package/dist/deltas/serialization/index.d.ts.map +1 -0
  41. package/dist/deltas/serialization/index.js +21 -0
  42. package/dist/deltas/serialization/index.js.map +1 -0
  43. package/dist/deltas/serialization/serializer-helpers.d.ts +12 -0
  44. package/dist/deltas/serialization/serializer-helpers.d.ts.map +1 -0
  45. package/dist/deltas/serialization/serializer-helpers.js +28 -0
  46. package/dist/deltas/serialization/serializer-helpers.js.map +1 -0
  47. package/dist/deltas/serialization/serializer.g.d.ts +4 -0
  48. package/dist/deltas/serialization/serializer.g.d.ts.map +1 -0
  49. package/dist/deltas/serialization/serializer.g.js +208 -0
  50. package/dist/deltas/serialization/serializer.g.js.map +1 -0
  51. package/dist/deltas/serialization/types.g.d.ts +147 -0
  52. package/dist/deltas/serialization/types.g.d.ts.map +1 -0
  53. package/dist/deltas/serialization/types.g.js +18 -0
  54. package/dist/deltas/serialization/types.g.js.map +1 -0
  55. package/dist/deltas/types.g.d.ts +149 -0
  56. package/dist/deltas/types.g.d.ts.map +1 -0
  57. package/dist/deltas/types.g.js +180 -0
  58. package/dist/deltas/types.g.js.map +1 -0
  59. package/dist/deserializer.d.ts +27 -0
  60. package/dist/deserializer.d.ts.map +1 -0
  61. package/dist/deserializer.js +168 -0
  62. package/dist/deserializer.js.map +1 -0
  63. package/dist/duplicator.d.ts +19 -0
  64. package/dist/duplicator.d.ts.map +1 -0
  65. package/dist/duplicator.js +87 -0
  66. package/dist/duplicator.js.map +1 -0
  67. package/dist/id-mapping.d.ts +19 -0
  68. package/dist/id-mapping.d.ts.map +1 -0
  69. package/dist/id-mapping.js +44 -0
  70. package/dist/id-mapping.js.map +1 -0
  71. package/dist/index.d.ts +11 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +28 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/linking.d.ts +20 -0
  76. package/dist/linking.d.ts.map +1 -0
  77. package/dist/linking.js +18 -0
  78. package/dist/linking.js.map +1 -0
  79. package/dist/lionCore_builtins.g.d.ts +35 -0
  80. package/dist/lionCore_builtins.g.d.ts.map +1 -0
  81. package/dist/lionCore_builtins.g.js +96 -0
  82. package/dist/lionCore_builtins.g.js.map +1 -0
  83. package/dist/serializer.d.ts +17 -0
  84. package/dist/serializer.d.ts.map +1 -0
  85. package/dist/serializer.js +70 -0
  86. package/dist/serializer.js.map +1 -0
  87. package/dist/textualizer.d.ts +7 -0
  88. package/dist/textualizer.d.ts.map +1 -0
  89. package/dist/textualizer.js +82 -0
  90. package/dist/textualizer.js.map +1 -0
  91. package/dist/value-managers/annotations.d.ts +33 -0
  92. package/dist/value-managers/annotations.d.ts.map +1 -0
  93. package/dist/value-managers/annotations.js +192 -0
  94. package/dist/value-managers/annotations.js.map +1 -0
  95. package/dist/value-managers/base.d.ts +53 -0
  96. package/dist/value-managers/base.d.ts.map +1 -0
  97. package/dist/value-managers/base.js +102 -0
  98. package/dist/value-managers/base.js.map +1 -0
  99. package/dist/value-managers/containments.d.ts +58 -0
  100. package/dist/value-managers/containments.d.ts.map +1 -0
  101. package/dist/value-managers/containments.js +408 -0
  102. package/dist/value-managers/containments.js.map +1 -0
  103. package/dist/value-managers/index.d.ts +6 -0
  104. package/dist/value-managers/index.d.ts.map +1 -0
  105. package/dist/value-managers/index.js +22 -0
  106. package/dist/value-managers/index.js.map +1 -0
  107. package/dist/value-managers/properties.d.ts +24 -0
  108. package/dist/value-managers/properties.d.ts.map +1 -0
  109. package/dist/value-managers/properties.js +191 -0
  110. package/dist/value-managers/properties.js.map +1 -0
  111. package/dist/value-managers/references.d.ts +58 -0
  112. package/dist/value-managers/references.d.ts.map +1 -0
  113. package/dist/value-managers/references.js +346 -0
  114. package/dist/value-managers/references.js.map +1 -0
  115. package/package.json +35 -0
  116. package/src/base-types.ts +353 -0
  117. package/src/convenience.ts +101 -0
  118. package/src/deltas/appliers.ts +317 -0
  119. package/src/deltas/base.ts +23 -0
  120. package/src/deltas/handlers.ts +64 -0
  121. package/src/deltas/index.ts +23 -0
  122. package/src/deltas/inverters.ts +111 -0
  123. package/src/deltas/serialization/base.ts +26 -0
  124. package/src/deltas/serialization/deserializer.g.ts +193 -0
  125. package/src/deltas/serialization/index.ts +22 -0
  126. package/src/deltas/serialization/serializer-helpers.ts +36 -0
  127. package/src/deltas/serialization/serializer.g.ts +272 -0
  128. package/src/deltas/serialization/types.g.ts +209 -0
  129. package/src/deltas/types.g.ts +231 -0
  130. package/src/deserializer.ts +234 -0
  131. package/src/duplicator.ts +111 -0
  132. package/src/id-mapping.ts +58 -0
  133. package/src/index.ts +29 -0
  134. package/src/linking.ts +39 -0
  135. package/src/lionCore_builtins.g.ts +141 -0
  136. package/src/serializer.ts +86 -0
  137. package/src/textualizer.ts +104 -0
  138. package/src/value-managers/annotations.ts +139 -0
  139. package/src/value-managers/base.ts +123 -0
  140. package/src/value-managers/containments.ts +323 -0
  141. package/src/value-managers/index.ts +22 -0
  142. package/src/value-managers/properties.ts +127 -0
  143. package/src/value-managers/references.ts +268 -0
@@ -0,0 +1,139 @@
1
+ // Copyright 2025 TRUMPF Laser SE and other contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License")
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
16
+ // SPDX-License-Identifier: Apache-2.0
17
+
18
+ import {action, observable} from "mobx";
19
+
20
+ import {INodeBase, removeFromParent} from "../base-types.js";
21
+ import {checkIndex, ValueManager} from "./base.js";
22
+ import {
23
+ AnnotationAddedDelta,
24
+ AnnotationDeletedDelta,
25
+ AnnotationMovedFromOtherParentDelta,
26
+ AnnotationMovedInSameParentDelta,
27
+ AnnotationReplacedDelta
28
+ } from "../deltas/index.js";
29
+
30
+
31
+ /**
32
+ * A value manager for annotations.
33
+ * Annotations (of any type) are simply contained by a parent (which is an {@link INodeBase}),
34
+ * not in the context of a(ny) {@link Feature feature}.
35
+ */
36
+ export class AnnotationsValueManager extends ValueManager {
37
+
38
+ constructor(container: INodeBase) {
39
+ super(container);
40
+ }
41
+
42
+ private readonly annotations = observable.array<INodeBase>([], {deep: false});
43
+
44
+ get(): INodeBase[] {
45
+ return this.annotations.slice();
46
+ }
47
+
48
+ @action addDirectly(newAnnotation: INodeBase) {
49
+ this.annotations.push(newAnnotation);
50
+ newAnnotation.attachTo(this.container, null);
51
+ }
52
+
53
+ @action add(newAnnotation: INodeBase) {
54
+ this.insertAtIndex(newAnnotation, this.annotations.length);
55
+ }
56
+
57
+ /**
58
+ * @return `false` if the given `newAnnotation` wasn't contained before,
59
+ * otherwise an array tuple with the old – i.e.: previous – parent of the inserted annotation, and the index it was contained at.
60
+ */
61
+ @action insertAtIndexDirectly(newAnnotation: INodeBase, index: number): false | [oldParent: INodeBase, oldIndex: number] {
62
+ checkIndex(index, this.annotations.length, true);
63
+ this.annotations.splice(index, 0, newAnnotation);
64
+ const oldParent = newAnnotation.parent;
65
+ if (oldParent === undefined) {
66
+ newAnnotation.attachTo(this.container, null);
67
+ return false;
68
+ } else {
69
+ const oldIndex = removeFromParent(oldParent, newAnnotation);
70
+ newAnnotation.attachTo(this.container, null);
71
+ return [oldParent, oldIndex];
72
+ }
73
+ }
74
+
75
+ @action insertAtIndex(newAnnotation: INodeBase, index: number) {
76
+ const oldParentage = this.insertAtIndexDirectly(newAnnotation, index);
77
+ if (oldParentage === false) {
78
+ this.emitDelta(() => new AnnotationAddedDelta(this.container, index, newAnnotation));
79
+ } else {
80
+ this.emitDelta(() => new AnnotationMovedFromOtherParentDelta(oldParentage[0], oldParentage[1], this.container, index, newAnnotation));
81
+ }
82
+ }
83
+
84
+ /**
85
+ * @return the moved annotation, or `undefined` if the indices coincide.
86
+ */
87
+ @action moveDirectly(oldIndex: number, newIndex: number): INodeBase | undefined {
88
+ checkIndex(oldIndex, this.annotations.length, false);
89
+ checkIndex(newIndex, this.annotations.length, false);
90
+ if (oldIndex !== newIndex) {
91
+ const [annotation] = this.annotations.splice(oldIndex, 1);
92
+ this.annotations.splice(newIndex, 0, annotation);
93
+ return annotation;
94
+ }
95
+ return undefined;
96
+ }
97
+
98
+ @action move(oldIndex: number, newIndex: number) {
99
+ const annotation = this.moveDirectly(oldIndex, newIndex);
100
+ if (annotation !== undefined) {
101
+ this.emitDelta(() => new AnnotationMovedInSameParentDelta(this.container, oldIndex, newIndex, annotation));
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @return the replaced annotation.
107
+ */
108
+ @action replaceAtIndexDirectly(newAnnotation: INodeBase, index: number): INodeBase {
109
+ checkIndex(index, this.annotations.length, false);
110
+ const replacedAnnotation = this.annotations[index];
111
+ this.annotations.splice(index, 1, newAnnotation);
112
+ replacedAnnotation.detach();
113
+ newAnnotation.attachTo(this.container, null);
114
+ return replacedAnnotation;
115
+ }
116
+
117
+ @action replaceAtIndex(newAnnotation: INodeBase, index: number) {
118
+ const replacedAnnotation = this.replaceAtIndexDirectly(newAnnotation, index);
119
+ this.emitDelta(() => new AnnotationReplacedDelta(this.container, index, replacedAnnotation, newAnnotation));
120
+ }
121
+
122
+ @action removeDirectly(annotationToRemove: INodeBase): number {
123
+ const index = this.annotations.indexOf(annotationToRemove);
124
+ if (index > -1) {
125
+ this.annotations.splice(index, 1);
126
+ }
127
+ annotationToRemove.detach();
128
+ return index;
129
+ }
130
+
131
+ @action remove(annotationToRemove: INodeBase) {
132
+ const removeIndex = this.removeDirectly(annotationToRemove);
133
+ if (removeIndex > -1) {
134
+ this.emitDelta(() => new AnnotationDeletedDelta(this.container, removeIndex, annotationToRemove));
135
+ }
136
+ }
137
+
138
+ }
139
+
@@ -0,0 +1,123 @@
1
+ // Copyright 2025 TRUMPF Laser SE and other contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License")
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
16
+ // SPDX-License-Identifier: Apache-2.0
17
+
18
+ import {Feature, featureMetaType, Link} from "@lionweb/core";
19
+ import {INodeBase} from "../base-types.js";
20
+ import {IDelta} from "../deltas/index.js";
21
+
22
+
23
+ /**
24
+ * Abstract super/base type for managers of values on classifiers.
25
+ */
26
+ export abstract class ValueManager {
27
+
28
+ protected constructor(protected readonly container: INodeBase) {
29
+ }
30
+
31
+ /**
32
+ * Emits a delta if a {@link DeltaHandler} is registered with the container.
33
+ * @param deltaThunk a thunk that generates the delta, and is only called when a delta handler is registered with the container.
34
+ */
35
+ emitDelta(deltaThunk: () => IDelta) {
36
+ if (this.container.handleDelta) {
37
+ this.container.handleDelta(deltaThunk());
38
+ }
39
+ }
40
+
41
+ }
42
+
43
+
44
+ /**
45
+ * Abstract super/base type for managers of values of features on classifiers.
46
+ * All subtypes have "<requiredness><multiplicity>ValueManager" as names,
47
+ * where <requiredness> = "" (for abstract “inter-types”) or "Optional" or "Required",
48
+ * and <multiplicity> = "Single" or "Multiple".
49
+ */
50
+ export abstract class FeatureValueManager<FT extends Feature> extends ValueManager {
51
+
52
+ protected constructor(protected readonly feature: FT, container: INodeBase) {
53
+ super(container);
54
+ }
55
+
56
+
57
+ /**
58
+ * Checks whether the feature's requiredness matches the expected one.
59
+ */
60
+ checkRequired(expectRequired: boolean) {
61
+ if (this.feature.optional === expectRequired) { // (double negation)
62
+ throw new Error(`${featureMetaType(this.feature).toLowerCase()} "${this.feature.name}" (with key="${this.feature.key}") should be ${expectRequired ? "required" : "optional"}`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Checks whether the feature's multiplicity matches the expected one.
68
+ */
69
+ checkMultiple(expectMultiple: boolean) {
70
+ if ((this.feature as unknown as Link).multiple !== expectMultiple) {
71
+ throw new Error(`${featureMetaType(this.feature).toLowerCase()} "${this.feature.name}" (with key="${this.feature.key}") should be ${expectMultiple ? "multi" : "single"}-valued`);
72
+ }
73
+ }
74
+
75
+
76
+ /**
77
+ * @return whether the feature's value is (has been) set.
78
+ */
79
+ abstract isSet(): boolean;
80
+
81
+ /**
82
+ * @throws when the feature's value is unset, but has been attempted to read.
83
+ */
84
+ throwOnReadOfUnset(): never {
85
+ throw new Error(`can't read required ${featureMetaType(this.feature).toLowerCase()} "${this.feature.name}" that's unset on instance of ${this.container.classifier.language.name}.${this.container.classifier.name} with id=${this.container.id}`);
86
+ }
87
+
88
+ /**
89
+ * @throws when the feature's value has been attempted to unset, but the feature is required.
90
+ */
91
+ throwOnUnset(): never {
92
+ throw new Error(`can't unset required ${featureMetaType(this.feature).toLowerCase()} "${this.feature.name}" on instance of ${this.container.classifier.language.name}.${this.container.classifier.name} with id=${this.container.id}`);
93
+ }
94
+
95
+ }
96
+
97
+
98
+ /**
99
+ * Checks whether the given index is valid within the given parameters of the context.
100
+ * @param index the supposed index of an element to retrieve/insert at/to
101
+ * @param nElements the number of elements in an array
102
+ * @param inserting whether the intention is to insert an element to the array
103
+ */
104
+ export const checkIndex = (index: number, nElements: number, inserting: boolean) => {
105
+ if (!Number.isInteger(index)) {
106
+ throw new Error(`an array index must be an integer, but got: ${index}`);
107
+ }
108
+ if (index < 0) {
109
+ throw new Error(`an array index must be a non-negative integer, but got: ${index}`);
110
+ }
111
+ if (inserting) {
112
+ if (index > nElements) {
113
+ throw new Error(`the largest valid insert index for an array with ${nElements} element${nElements === 1 ? "": "s"} is ${nElements}, but got: ${index}`);
114
+ }
115
+ } else {
116
+ if (nElements === 0) {
117
+ throw new Error(`an empty array has no valid indices (got: ${index})`);
118
+ } else if (index + 1 > nElements) {
119
+ throw new Error(`the largest valid index for an array with ${nElements} element${nElements === 1 ? "": "s"} is ${nElements - 1}, but got: ${index}`);
120
+ }
121
+ }
122
+ }
123
+
@@ -0,0 +1,323 @@
1
+ // Copyright 2025 TRUMPF Laser SE and other contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License")
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
16
+ // SPDX-License-Identifier: Apache-2.0
17
+
18
+ import {Containment} from "@lionweb/core";
19
+ import {action, observable} from "mobx";
20
+
21
+ import {INodeBase, removeFromParent} from "../base-types.js";
22
+ import {checkIndex, FeatureValueManager} from "./base.js";
23
+ import {
24
+ ChildAddedDelta,
25
+ ChildDeletedDelta,
26
+ ChildMovedDelta,
27
+ ChildMovedInSameContainmentDelta,
28
+ ChildReplacedDelta
29
+ } from "../deltas/index.js";
30
+
31
+
32
+ /**
33
+ * An instance manages the value of a particular containment feature on a particular instance of a classifier.
34
+ */
35
+ export abstract class ContainmentValueManager<T extends INodeBase> extends FeatureValueManager<Containment> {
36
+
37
+ get containment(): Containment {
38
+ return this.feature;
39
+ }
40
+
41
+ abstract getDirectly(): T | undefined | (T | undefined)[];
42
+
43
+ /**
44
+ * Adds the given child to the containment.
45
+ * For a single-valued containment, this assumes that it contained no child before,
46
+ * or else it throws.
47
+ */
48
+ abstract addDirectly(newChild: T): void;
49
+
50
+ }
51
+
52
+
53
+ export abstract class SingleContainmentValueManager<T extends INodeBase> extends ContainmentValueManager<T> {
54
+
55
+ protected constructor(containment: Containment, container: INodeBase) {
56
+ super(containment, container);
57
+ this.checkMultiple(false);
58
+ }
59
+
60
+ private readonly child = observable.box<T | undefined>(undefined, {deep: false});
61
+
62
+ getDirectly(): T | undefined {
63
+ return this.child.get();
64
+ }
65
+
66
+ isSet(): boolean {
67
+ return this.child.get() !== undefined;
68
+ }
69
+
70
+ @action setDirectly(newChild: T | undefined) {
71
+ this.child.set(newChild);
72
+ }
73
+
74
+ @action addDirectly(newChild: T) {
75
+ const oldChild = this.getDirectly();
76
+ if (oldChild !== undefined) {
77
+ throw new Error(`replacing a child using addDirectly on a value manager for a single-valued containment isn't allowed`); // FIXME unit test this
78
+ }
79
+ this.child.set(newChild);
80
+ }
81
+
82
+ }
83
+
84
+
85
+ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends SingleContainmentValueManager<T> {
86
+
87
+ constructor(containment: Containment, container: INodeBase) {
88
+ super(containment, container);
89
+ this.checkRequired(false);
90
+ }
91
+
92
+ get(): T | undefined {
93
+ return this.getDirectly();
94
+ }
95
+
96
+ @action set(newChild: T | undefined) {
97
+ const oldChild = this.getDirectly();
98
+ if (oldChild === undefined) {
99
+ if (newChild === undefined) {
100
+ // do nothing: nothing's changed
101
+ } else {
102
+ if (newChild.parent && newChild.containment) {
103
+ const oldParent = newChild.parent;
104
+ removeFromParent(oldParent, newChild);
105
+ this.emitDelta(() => new ChildMovedDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
106
+ } else {
107
+ this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
108
+ }
109
+ this.setDirectly(newChild);
110
+ newChild.attachTo(this.container, this.feature);
111
+ }
112
+ } else {
113
+ if (newChild === undefined) {
114
+ oldChild.detach();
115
+ this.setDirectly(undefined);
116
+ this.emitDelta(() => new ChildDeletedDelta(this.container, this.feature, 0, oldChild));
117
+ } else {
118
+ if (oldChild === newChild) {
119
+ // do nothing: nothing's changed
120
+ } else {
121
+ if (oldChild.parent && oldChild.containment && oldChild.parent === this.container && oldChild.containment === this.feature) {
122
+ // FIXME oldChild.parent COULD be this.container
123
+ this.emitDelta(() => new ChildDeletedDelta(this.container, this.feature, 0, oldChild));
124
+ }
125
+ oldChild.detach();
126
+ if (newChild.parent && newChild.containment) {
127
+ removeFromParent(newChild.parent, newChild);
128
+ }
129
+ this.setDirectly(newChild);
130
+ newChild.attachTo(this.container, this.feature);
131
+ this.emitDelta(() => new ChildReplacedDelta(this.container, this.feature, 0, oldChild, newChild));
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ }
138
+
139
+
140
+ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends SingleContainmentValueManager<T> {
141
+
142
+ constructor(containment: Containment, container: INodeBase) {
143
+ super(containment, container);
144
+ this.checkRequired(true);
145
+ }
146
+
147
+ get(): T {
148
+ const child = this.getDirectly();
149
+ if (child === undefined) {
150
+ this.throwOnReadOfUnset();
151
+ }
152
+ return child;
153
+ }
154
+
155
+ @action set(newChild: T | undefined) {
156
+ const oldChild = this.getDirectly();
157
+ if (oldChild === undefined) {
158
+ if (newChild === undefined) {
159
+ // do nothing: nothing's changed
160
+ } else {
161
+ if (newChild.parent && newChild.containment) {
162
+ const oldParent = newChild.parent;
163
+ removeFromParent(oldParent, newChild);
164
+ this.emitDelta(() => new ChildMovedDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
165
+ } else {
166
+ this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
167
+ }
168
+ this.setDirectly(newChild);
169
+ newChild.attachTo(this.container, this.feature);
170
+ }
171
+ } else {
172
+ if (newChild === undefined) {
173
+ this.throwOnUnset();
174
+ } else {
175
+ if (oldChild === newChild) {
176
+ // do nothing: nothing's changed
177
+ } else {
178
+ if (oldChild.parent && oldChild.containment && oldChild.parent === this.container && oldChild.containment === this.feature) {
179
+ // FIXME oldChild.parent COULD be this.container
180
+ this.emitDelta(() => new ChildDeletedDelta(this.container, this.feature, 0, oldChild));
181
+ }
182
+ oldChild.detach();
183
+ if (newChild.parent && newChild.containment) {
184
+ removeFromParent(newChild.parent, newChild);
185
+ }
186
+ this.setDirectly(newChild);
187
+ newChild.attachTo(this.container, this.feature);
188
+ this.emitDelta(() => new ChildReplacedDelta(this.container, this.feature, 0, oldChild, newChild));
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ }
195
+
196
+
197
+ export abstract class MultiContainmentValueManager<T extends INodeBase> extends ContainmentValueManager<T> {
198
+
199
+ protected constructor(containment: Containment, container: INodeBase) {
200
+ super(containment, container);
201
+ this.checkMultiple(true);
202
+ }
203
+
204
+ private readonly children = observable.array<T>([], {deep: false});
205
+
206
+ getDirectly(): T[] {
207
+ return this.children;
208
+ }
209
+
210
+ get(): T[] {
211
+ return this.getDirectly().slice();
212
+ }
213
+
214
+ isSet(): boolean {
215
+ return this.children.length > 0;
216
+ }
217
+
218
+ @action addDirectly(newChild: T) {
219
+ this.children.push(newChild);
220
+ }
221
+
222
+ @action add(newChild: T): void {
223
+ this.insertAtIndex(newChild, this.getDirectly().length);
224
+ }
225
+
226
+ @action insertAtIndexDirectly(newChild: T, index: number) {
227
+ checkIndex(index, this.children.length, true);
228
+ this.children.splice(index, 0, newChild);
229
+ }
230
+
231
+ @action insertAtIndex(newChild: T, index: number) {
232
+ this.insertAtIndexDirectly(newChild, index);
233
+ if (newChild.parent === undefined && newChild.containment === undefined) {
234
+ this.emitDelta(() => new ChildAddedDelta(this.container, this.containment, index, newChild));
235
+ } else {
236
+ const oldIndex = removeFromParent(newChild.parent, newChild);
237
+ this.emitDelta(() => new ChildMovedDelta(newChild.parent!, newChild.containment!, oldIndex, this.container, this.containment, index, newChild));
238
+ newChild.detach();
239
+ }
240
+ newChild.attachTo(this.container, this.containment);
241
+ }
242
+
243
+ @action removeDirectly(childToRemove: T): number {
244
+ const children = this.getDirectly();
245
+ const index = children.findIndex((child) => child === childToRemove);
246
+ if (index > -1) {
247
+ children.splice(index, 1);
248
+ return index;
249
+ }
250
+ return -1;
251
+ }
252
+
253
+ @action moveDirectly(oldIndex: number, newIndex: number): T | undefined {
254
+ checkIndex(oldIndex, this.children.length, false);
255
+ checkIndex(newIndex, this.children.length, false);
256
+ if (oldIndex !== newIndex) {
257
+ const [child] = this.children.splice(oldIndex, 1);
258
+ this.children.splice(newIndex, 0, child);
259
+ return child;
260
+ }
261
+ return undefined;
262
+ }
263
+
264
+ @action move(oldIndex: number, newIndex: number) {
265
+ const child = this.moveDirectly(oldIndex, newIndex);
266
+ if (child !== undefined) {
267
+ this.emitDelta(() => new ChildMovedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, child));
268
+ }
269
+ }
270
+
271
+ }
272
+
273
+
274
+ export class OptionalMultiContainmentValueManager<T extends INodeBase> extends MultiContainmentValueManager<T> {
275
+
276
+ constructor(containment: Containment, container: INodeBase) {
277
+ super(containment, container);
278
+ this.checkRequired(false);
279
+ }
280
+
281
+ @action remove(childToRemove: T) {
282
+ const children = this.getDirectly();
283
+ const index = children.findIndex((child) => child === childToRemove);
284
+ if (index > -1) {
285
+ children.splice(index, 1);
286
+ childToRemove.detach();
287
+ this.emitDelta(() => new ChildDeletedDelta(this.container, this.containment, index, childToRemove));
288
+ }
289
+ }
290
+
291
+ }
292
+
293
+
294
+ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends MultiContainmentValueManager<T> {
295
+
296
+ constructor(containment: Containment, container: INodeBase) {
297
+ super(containment, container);
298
+ this.checkRequired(true);
299
+ }
300
+
301
+ get(): T[] {
302
+ const children = this.getDirectly();
303
+ if (children.length === 0) {
304
+ this.throwOnReadOfUnset();
305
+ }
306
+ return children;
307
+ }
308
+
309
+ @action remove(childToRemove: T) {
310
+ const children = this.getDirectly();
311
+ const index = children.findIndex((child) => child === childToRemove);
312
+ if (index > -1) {
313
+ if (children.length === 1) {
314
+ this.throwOnUnset();
315
+ }
316
+ children.splice(index, 1);
317
+ childToRemove.detach();
318
+ this.emitDelta(() => new ChildDeletedDelta(this.container, this.containment, index, childToRemove));
319
+ }
320
+ }
321
+
322
+ }
323
+
@@ -0,0 +1,22 @@
1
+ // Copyright 2025 TRUMPF Laser SE and other contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License")
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
16
+ // SPDX-License-Identifier: Apache-2.0
17
+
18
+ export * from "./annotations.js";
19
+ export * from "./base.js";
20
+ export * from "./properties.js";
21
+ export * from "./containments.js";
22
+ export * from "./references.js";