mobx-keystone 2.0.0 → 2.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/CHANGELOG.md +22 -0
- package/dist/mobx-keystone.esm.js +2341 -780
- package/dist/mobx-keystone.esm.mjs +2341 -780
- package/dist/mobx-keystone.umd.js +2346 -782
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
- package/dist/types/parent/arrayPaths.d.ts +1 -0
- package/dist/types/parent/treeOrder.d.ts +1 -0
- package/dist/types/patch/applyPatches.d.ts +2 -1
- package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
- package/dist/types/ref/nodesById.d.ts +1 -0
- package/dist/types/ref/refIdIndex.d.ts +1 -0
- package/dist/types/snapshot/applySnapshot.d.ts +1 -0
- package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
- package/dist/types/tweaker/changeRollback.d.ts +1 -0
- package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
- package/dist/types/utils/ModelPool.d.ts +7 -1
- package/dist/types/utils/chunks.d.ts +1 -0
- package/package.json +2 -2
- package/src/actionMiddlewares/transactionMiddleware.ts +40 -41
- package/src/actionMiddlewares/undoMiddleware.ts +47 -13
- package/src/model/BaseModel.ts +6 -4
- package/src/modelShared/modelDecorator.ts +5 -25
- package/src/parent/arrayPaths.ts +242 -0
- package/src/parent/coreObjectChildren.ts +300 -48
- package/src/parent/onChildAttachedTo.ts +130 -44
- package/src/parent/path.ts +10 -2
- package/src/parent/setParent.ts +27 -15
- package/src/parent/treeOrder.ts +53 -0
- package/src/parent/walkTree.ts +0 -73
- package/src/patch/applyPatches.ts +31 -26
- package/src/ref/core.ts +63 -27
- package/src/ref/modelRefIdIndex.ts +131 -0
- package/src/ref/nodesById.ts +60 -0
- package/src/ref/refIdIndex.ts +209 -0
- package/src/snapshot/applySnapshot.ts +26 -18
- package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
- package/src/snapshot/fromSnapshot.ts +2 -6
- package/src/snapshot/internal.ts +67 -10
- package/src/tweaker/changeRollback.ts +218 -0
- package/src/tweaker/core.ts +33 -20
- package/src/tweaker/treeNodeMetadata.ts +12 -2
- package/src/tweaker/tweak.ts +181 -18
- package/src/tweaker/tweakArray.ts +107 -27
- package/src/tweaker/tweakPlainObject.ts +56 -31
- package/src/tweaker/tweakedChangeListeners.ts +61 -0
- package/src/tweaker/typeChecking.ts +5 -104
- package/src/types/arrayBased/typesArray.ts +10 -11
- package/src/types/createCachedTypeCheck.ts +522 -94
- package/src/types/objectBased/typesRecord.ts +2 -5
- package/src/utils/ModelPool.ts +75 -19
- package/src/utils/chunks.ts +271 -0
- package/src/utils/index.ts +37 -0
|
@@ -2,7 +2,7 @@ import { isObservableObject, keys } from "mobx"
|
|
|
2
2
|
import type { Path } from "../../parent/pathTypes"
|
|
3
3
|
import { copyOwnEnumerableProps, failure, isArray, isObject } from "../../utils"
|
|
4
4
|
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
5
|
-
import {
|
|
5
|
+
import { createChunkedRecordCachedCheck } from "../createCachedTypeCheck"
|
|
6
6
|
import { getTypeInfo } from "../getTypeInfo"
|
|
7
7
|
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
8
8
|
import type { AnyStandardType, AnyType, RecordType } from "../schemas"
|
|
@@ -81,10 +81,7 @@ export function typesRecord<T extends AnyType>(valueType: T): RecordType<T> {
|
|
|
81
81
|
return error ? prependPathElementToTypeCheckError(error, path, key, typeCheckedValue) : null
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const checkRecordValues =
|
|
85
|
-
iterateRecordEntries,
|
|
86
|
-
checkRecordEntry
|
|
87
|
-
)
|
|
84
|
+
const checkRecordValues = createChunkedRecordCachedCheck(iterateRecordEntries, checkRecordEntry)
|
|
88
85
|
|
|
89
86
|
const thisTc: TypeChecker = new TypeChecker(
|
|
90
87
|
TypeCheckerBaseType.Object,
|
package/src/utils/ModelPool.ts
CHANGED
|
@@ -6,21 +6,41 @@ import type { ModelClass } from "../modelShared/BaseModelShared"
|
|
|
6
6
|
import { getModelInfoForName } from "../modelShared/modelInfo"
|
|
7
7
|
import { dataToModelNode } from "../parent/core"
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
type DeepObjectChildrenExtensionAccessor,
|
|
10
|
+
type PinnedDeepObjectChildrenExtensionData,
|
|
10
11
|
registerDeepObjectChildrenExtension,
|
|
11
12
|
} from "../parent/coreObjectChildren"
|
|
13
|
+
import { getOrCreate } from "./mapUtils"
|
|
12
14
|
|
|
13
15
|
type ModelsById = Map<string, AnyModel>
|
|
14
16
|
type ModelsByTypeAndId = Map<string, ModelsById>
|
|
15
17
|
|
|
18
|
+
interface DeepChildrenModels {
|
|
19
|
+
readonly byTypeAndId: ModelsByTypeAndId
|
|
20
|
+
/** Id each model is indexed under, since ids can change later. */
|
|
21
|
+
readonly indexedIds: Map<AnyModel, string>
|
|
22
|
+
/** Whether some model was shadowed by another one with the same type and id. */
|
|
23
|
+
hasDuplicates: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A view of the models under a node, as they were when the pool was created.
|
|
28
|
+
* Must be released once no longer used.
|
|
29
|
+
*/
|
|
16
30
|
export class ModelPool {
|
|
17
|
-
private pool: ReadonlyMap<string, ReadonlyMap<string, AnyModel>>
|
|
31
|
+
private readonly pool: ReadonlyMap<string, ReadonlyMap<string, AnyModel>>
|
|
32
|
+
private readonly pinned: PinnedDeepObjectChildrenExtensionData<DeepChildrenModels>
|
|
18
33
|
|
|
19
34
|
constructor(root: object) {
|
|
20
35
|
// make sure we don't use the sub-data $ object
|
|
21
36
|
root = dataToModelNode(root)
|
|
22
37
|
|
|
23
|
-
this.
|
|
38
|
+
this.pinned = getDeepChildrenModels().pin(root)
|
|
39
|
+
this.pool = this.pinned.data.byTypeAndId
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
release(): void {
|
|
43
|
+
this.pinned.release()
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
findModelByTypeAndId(modelType: string, modelId: string | undefined): AnyModel | undefined {
|
|
@@ -42,33 +62,69 @@ export class ModelPool {
|
|
|
42
62
|
}
|
|
43
63
|
}
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
let deepChildrenModels: DeepObjectChildrenExtensionAccessor<DeepChildrenModels> | undefined
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const getRegisteredDeepChildrenModels = registerDeepObjectChildrenExtension<ModelsByTypeAndId>({
|
|
67
|
+
// Registered on first use to keep ModelPool out of the coreObjectChildren
|
|
68
|
+
// module-initialization cycle.
|
|
69
|
+
function getDeepChildrenModels() {
|
|
70
|
+
deepChildrenModels ??= registerDeepObjectChildrenExtension<DeepChildrenModels>({
|
|
52
71
|
initData() {
|
|
53
|
-
return
|
|
72
|
+
return {
|
|
73
|
+
byTypeAndId: new Map(),
|
|
74
|
+
indexedIds: new Map(),
|
|
75
|
+
hasDuplicates: false,
|
|
76
|
+
}
|
|
54
77
|
},
|
|
55
78
|
|
|
56
|
-
|
|
79
|
+
// last one wins when rebuilding
|
|
80
|
+
addNode(node, data) {
|
|
57
81
|
if (isModel(node)) {
|
|
58
82
|
const id = node[modelIdKey]
|
|
59
83
|
if (id !== undefined) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
const modelsById = getOrCreate(data.byTypeAndId, node[modelTypeKey], () => new Map())
|
|
85
|
+
const previous = modelsById.get(id)
|
|
86
|
+
if (previous) {
|
|
87
|
+
data.hasDuplicates = true
|
|
88
|
+
data.indexedIds.delete(previous)
|
|
65
89
|
}
|
|
66
90
|
modelsById.set(id, node)
|
|
91
|
+
data.indexedIds.set(node, id)
|
|
67
92
|
}
|
|
68
93
|
}
|
|
69
94
|
},
|
|
70
|
-
})
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
addNodeIncrementally(node, data) {
|
|
97
|
+
if (isModel(node)) {
|
|
98
|
+
const id = node[modelIdKey]
|
|
99
|
+
if (id !== undefined) {
|
|
100
|
+
const modelsById = getOrCreate(data.byTypeAndId, node[modelTypeKey], () => new Map())
|
|
101
|
+
if (modelsById.has(id)) {
|
|
102
|
+
// which duplicate wins depends on the deep children order
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
modelsById.set(id, node)
|
|
106
|
+
data.indexedIds.set(node, id)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return true
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
removeNode(node, data) {
|
|
113
|
+
if (isModel(node)) {
|
|
114
|
+
if (data.hasDuplicates) {
|
|
115
|
+
// a shadowed duplicate might need to take its place
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
const id = data.indexedIds.get(node)
|
|
119
|
+
if (id !== undefined) {
|
|
120
|
+
data.indexedIds.delete(node)
|
|
121
|
+
data.byTypeAndId.get(node[modelTypeKey])!.delete(id)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
dependsOnModelIds: true,
|
|
128
|
+
})
|
|
129
|
+
return deepChildrenModels
|
|
74
130
|
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target size of the chunks large collections are split in, so a change only
|
|
3
|
+
* needs to revisit one chunk instead of every item.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export const chunkSize = 128
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface SequenceChunk<T> {
|
|
13
|
+
items: T[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An ordered sequence split in chunks, which splices keep near `chunkSize`:
|
|
18
|
+
* chunks are split above twice that size and merged below a quarter of it.
|
|
19
|
+
* Subclasses decide what a chunk keeps track of through the hooks.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export abstract class ChunkedSequence<T, C extends SequenceChunk<T>> {
|
|
24
|
+
chunks: C[] = []
|
|
25
|
+
|
|
26
|
+
protected abstract createChunk(items: T[]): C
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Called once items were removed from or inserted into a chunk, before
|
|
30
|
+
* resizing it. The items from `from` on changed their position in it.
|
|
31
|
+
*/
|
|
32
|
+
protected abstract onSpliced(chunk: C, from: number): void
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Called when a resize moves items to another chunk, starting at `from`.
|
|
36
|
+
* The chunk they come from was already passed to `onSpliced`.
|
|
37
|
+
*/
|
|
38
|
+
protected abstract onItemsMoved(chunk: C, from: number): void
|
|
39
|
+
|
|
40
|
+
/** Called for each chunk after the spliced ones when a splice changes the length. */
|
|
41
|
+
protected onShifted(_chunk: C): void {}
|
|
42
|
+
|
|
43
|
+
/** Splits items in new chunks, appended to the current ones. */
|
|
44
|
+
protected appendChunks(items: readonly T[]): void {
|
|
45
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
46
|
+
this.chunks.push(this.createChunk(items.slice(i, i + chunkSize)))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Replaces every chunk with new ones for the items. */
|
|
51
|
+
protected reset(items: readonly T[]): void {
|
|
52
|
+
this.chunks = []
|
|
53
|
+
this.appendChunks(items)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Chunk containing the index, and the index within it. Appends go to the last chunk. */
|
|
57
|
+
locate(index: number): [number, number] {
|
|
58
|
+
const chunks = this.chunks
|
|
59
|
+
let c = 0
|
|
60
|
+
let local = index
|
|
61
|
+
while (c < chunks.length - 1 && local >= chunks[c].items.length) {
|
|
62
|
+
local -= chunks[c].items.length
|
|
63
|
+
c++
|
|
64
|
+
}
|
|
65
|
+
return [c, local]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Removes and inserts items, like `Array.prototype.splice`. `onRemoved` is
|
|
70
|
+
* called with each removed item, the chunk it was in and its index there.
|
|
71
|
+
*/
|
|
72
|
+
protected splice(
|
|
73
|
+
index: number,
|
|
74
|
+
removedCount: number,
|
|
75
|
+
added: readonly T[],
|
|
76
|
+
onRemoved?: (item: T, chunk: C, local: number) => void
|
|
77
|
+
): void {
|
|
78
|
+
const chunks = this.chunks
|
|
79
|
+
if (chunks.length === 0) {
|
|
80
|
+
if (added.length === 0) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
chunks.push(this.createChunk([]))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const [first, start] = this.locate(index)
|
|
87
|
+
let last = first
|
|
88
|
+
let remaining = removedCount
|
|
89
|
+
let local = start
|
|
90
|
+
for (let c = first; remaining > 0; c++) {
|
|
91
|
+
const chunk = chunks[c]
|
|
92
|
+
const count = Math.min(remaining, chunk.items.length - local)
|
|
93
|
+
const removed = chunk.items.splice(local, count)
|
|
94
|
+
if (onRemoved) {
|
|
95
|
+
for (let i = 0; i < removed.length; i++) {
|
|
96
|
+
onRemoved(removed[i], chunk, local + i)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
remaining -= count
|
|
100
|
+
local = 0
|
|
101
|
+
last = c
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const firstChunk = chunks[first]
|
|
105
|
+
if (added.length > 0) {
|
|
106
|
+
firstChunk.items = firstChunk.items
|
|
107
|
+
.slice(0, start)
|
|
108
|
+
.concat(added as T[], firstChunk.items.slice(start))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.onSpliced(firstChunk, start + added.length)
|
|
112
|
+
for (let c = first + 1; c <= last; c++) {
|
|
113
|
+
this.onSpliced(chunks[c], 0)
|
|
114
|
+
}
|
|
115
|
+
if (added.length !== removedCount) {
|
|
116
|
+
for (let c = last + 1; c < chunks.length; c++) {
|
|
117
|
+
this.onShifted(chunks[c])
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// backwards, so merging or splitting a chunk does not move the ones left to visit
|
|
122
|
+
for (let c = last; c >= first; c--) {
|
|
123
|
+
this.resize(c)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Splits, merges or drops a changed chunk to keep its size near the target. */
|
|
128
|
+
private resize(c: number) {
|
|
129
|
+
const chunks = this.chunks
|
|
130
|
+
const chunk = chunks[c]
|
|
131
|
+
const length = chunk.items.length
|
|
132
|
+
|
|
133
|
+
if (length > 2 * chunkSize) {
|
|
134
|
+
const pieceCount = Math.ceil(length / chunkSize)
|
|
135
|
+
const pieceSize = Math.ceil(length / pieceCount)
|
|
136
|
+
const pieces: C[] = []
|
|
137
|
+
for (let i = pieceSize; i < length; i += pieceSize) {
|
|
138
|
+
const piece = this.createChunk(chunk.items.slice(i, i + pieceSize))
|
|
139
|
+
this.onItemsMoved(piece, 0)
|
|
140
|
+
pieces.push(piece)
|
|
141
|
+
}
|
|
142
|
+
chunk.items = chunk.items.slice(0, pieceSize)
|
|
143
|
+
this.chunks = chunks.slice(0, c + 1).concat(pieces, chunks.slice(c + 1))
|
|
144
|
+
} else if (length < chunkSize / 4) {
|
|
145
|
+
const previous = chunks[c - 1] as C | undefined
|
|
146
|
+
const next = chunks[c + 1] as C | undefined
|
|
147
|
+
if (previous && previous.items.length + length <= 2 * chunkSize) {
|
|
148
|
+
const from = previous.items.length
|
|
149
|
+
previous.items = previous.items.concat(chunk.items)
|
|
150
|
+
this.onItemsMoved(previous, from)
|
|
151
|
+
chunks.splice(c, 1)
|
|
152
|
+
} else if (next && next.items.length + length <= 2 * chunkSize) {
|
|
153
|
+
next.items = chunk.items.concat(next.items)
|
|
154
|
+
this.onItemsMoved(next, 0)
|
|
155
|
+
chunks.splice(c, 1)
|
|
156
|
+
} else if (length === 0) {
|
|
157
|
+
chunks.splice(c, 1)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* An unordered set split in chunks. Items are added to the last chunk, and a
|
|
165
|
+
* chunk left under a quarter of `chunkSize` by removals is merged into a
|
|
166
|
+
* neighbor (or dropped once empty). Subclasses decide what a chunk keeps track
|
|
167
|
+
* of through the hooks.
|
|
168
|
+
*
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
export abstract class ChunkedSet<T, C extends SequenceChunk<T>> {
|
|
172
|
+
chunks: C[] = []
|
|
173
|
+
/** Chunk of each item, only needed (and kept) once an item is looked up. */
|
|
174
|
+
private chunkOf: Map<T, C> | undefined
|
|
175
|
+
|
|
176
|
+
protected abstract createChunk(items: T[]): C
|
|
177
|
+
|
|
178
|
+
/** Called when items were added to or removed from a chunk. */
|
|
179
|
+
protected abstract onChunkChanged(chunk: C): void
|
|
180
|
+
|
|
181
|
+
/** Called when chunks were added or removed. */
|
|
182
|
+
protected abstract onChunksChanged(): void
|
|
183
|
+
|
|
184
|
+
/** Called for a chunk removed after its items were moved to another one. */
|
|
185
|
+
protected onChunkDropped(_chunk: C): void {}
|
|
186
|
+
|
|
187
|
+
/** Splits items in new chunks, appended to the current ones. */
|
|
188
|
+
protected appendChunks(items: readonly T[]): void {
|
|
189
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
190
|
+
this.chunks.push(this.createChunk(items.slice(i, i + chunkSize)))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Replaces every chunk with new ones for the items. */
|
|
195
|
+
protected reset(items: readonly T[]): void {
|
|
196
|
+
this.chunks = []
|
|
197
|
+
this.chunkOf = undefined
|
|
198
|
+
this.appendChunks(items)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
add(item: T): void {
|
|
202
|
+
let chunk = this.chunks[this.chunks.length - 1] as C | undefined
|
|
203
|
+
if (chunk && chunk.items.length < chunkSize) {
|
|
204
|
+
chunk.items.push(item)
|
|
205
|
+
this.onChunkChanged(chunk)
|
|
206
|
+
} else {
|
|
207
|
+
chunk = this.createChunk([item])
|
|
208
|
+
this.chunks.push(chunk)
|
|
209
|
+
this.onChunksChanged()
|
|
210
|
+
}
|
|
211
|
+
this.chunkOf?.set(item, chunk)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Chunk containing the item, if any. */
|
|
215
|
+
getChunkOf(item: T): C | undefined {
|
|
216
|
+
return this.getChunkOfMap().get(item)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private getChunkOfMap(): Map<T, C> {
|
|
220
|
+
if (!this.chunkOf) {
|
|
221
|
+
this.chunkOf = new Map()
|
|
222
|
+
for (const chunk of this.chunks) {
|
|
223
|
+
for (const i of chunk.items) {
|
|
224
|
+
this.chunkOf.set(i, chunk)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return this.chunkOf
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Returns false if the item was not in the set. */
|
|
232
|
+
delete(item: T): boolean {
|
|
233
|
+
const chunkOf = this.getChunkOfMap()
|
|
234
|
+
const chunk = chunkOf.get(item)
|
|
235
|
+
if (!chunk) {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
chunkOf.delete(item)
|
|
239
|
+
// (the order within a chunk does not matter, so the last item fills the gap)
|
|
240
|
+
const items = chunk.items
|
|
241
|
+
const last = items.pop()!
|
|
242
|
+
if (last !== item) {
|
|
243
|
+
items[items.lastIndexOf(item)] = last
|
|
244
|
+
}
|
|
245
|
+
this.onChunkChanged(chunk)
|
|
246
|
+
if (chunk.items.length >= chunkSize / 4) {
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// merge it into a neighbor, or drop it once empty
|
|
251
|
+
const chunks = this.chunks
|
|
252
|
+
const c = chunks.indexOf(chunk)
|
|
253
|
+
if (chunk.items.length > 0) {
|
|
254
|
+
const neighbor = [chunks[c - 1], chunks[c + 1]].find(
|
|
255
|
+
(n) => n && n.items.length + chunk.items.length <= 2 * chunkSize
|
|
256
|
+
)
|
|
257
|
+
if (!neighbor) {
|
|
258
|
+
return true
|
|
259
|
+
}
|
|
260
|
+
for (const i of chunk.items) {
|
|
261
|
+
chunkOf.set(i, neighbor)
|
|
262
|
+
}
|
|
263
|
+
neighbor.items = neighbor.items.concat(chunk.items)
|
|
264
|
+
this.onChunkChanged(neighbor)
|
|
265
|
+
}
|
|
266
|
+
chunks.splice(c, 1)
|
|
267
|
+
this.onChunkDropped(chunk)
|
|
268
|
+
this.onChunksChanged()
|
|
269
|
+
return true
|
|
270
|
+
}
|
|
271
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -558,6 +558,43 @@ function isStandardDecoratorContext(value: unknown): boolean {
|
|
|
558
558
|
)
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Options for the observable objects of tree nodes. Their values are never
|
|
563
|
+
* functions nor getters, so the shared `observable.ref` annotation (MobX 7
|
|
564
|
+
* `observableRef`) is equivalent to `deep: false` alone, which MobX 6+ would
|
|
565
|
+
* allocate an annotation for on every object.
|
|
566
|
+
*
|
|
567
|
+
* @internal
|
|
568
|
+
*/
|
|
569
|
+
export const nodeObservableOptions = {
|
|
570
|
+
deep: false,
|
|
571
|
+
defaultDecorator:
|
|
572
|
+
// (computed so bundlers do not warn about the export missing in MobX < 7)
|
|
573
|
+
(mobx as any)[["observable", "Ref"].join("")] ?? (mobx.observable as any).ref,
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// the key MobX 6 stores legacy decorator annotations under (null if unknown)
|
|
577
|
+
let storedMobxAnnotationsKey: symbol | null | undefined
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Whether an instance has legacy decorator annotations for MobX 6
|
|
581
|
+
* `makeObservable` to apply. When their key cannot be found it answers true,
|
|
582
|
+
* so `makeObservable` is still called.
|
|
583
|
+
*
|
|
584
|
+
* @internal
|
|
585
|
+
*/
|
|
586
|
+
export function hasStoredMobxAnnotations(instance: object): boolean {
|
|
587
|
+
if (storedMobxAnnotationsKey === undefined) {
|
|
588
|
+
// a legacy decorator call stores its annotation under that key
|
|
589
|
+
const probe = {}
|
|
590
|
+
;(mobx.observable as any)(probe, "probe")
|
|
591
|
+
storedMobxAnnotationsKey = Object.getOwnPropertySymbols(probe)[0] ?? null
|
|
592
|
+
}
|
|
593
|
+
return (
|
|
594
|
+
storedMobxAnnotationsKey === null || (instance as any)[storedMobxAnnotationsKey] !== undefined
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
|
|
561
598
|
/**
|
|
562
599
|
* Calls `makeObservable` across MobX versions. MobX 7 requires an annotations
|
|
563
600
|
* object, while MobX 6 and older versions use decorator metadata when no
|