@geospatial-sdk/core 0.0.5-dev.0 → 0.0.5-dev.0.sha-bfaf14b

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 (67) hide show
  1. package/README.md +38 -5
  2. package/dist/constant/projections.d.ts +2 -0
  3. package/dist/constant/projections.d.ts.map +1 -0
  4. package/dist/constant/projections.js +1 -0
  5. package/dist/index.d.ts +7 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -3
  8. package/dist/model/events.d.ts +86 -0
  9. package/dist/model/events.d.ts.map +1 -0
  10. package/dist/model/events.js +37 -0
  11. package/dist/model/index.d.ts +9 -2
  12. package/dist/model/index.d.ts.map +1 -1
  13. package/dist/model/index.js +9 -2
  14. package/dist/model/map-context-diff.d.ts +11 -3
  15. package/dist/model/map-context-diff.d.ts.map +1 -1
  16. package/dist/model/map-context.d.ts +174 -22
  17. package/dist/model/map-context.d.ts.map +1 -1
  18. package/dist/model/resolved-map-state.d.ts +49 -0
  19. package/dist/model/resolved-map-state.d.ts.map +1 -0
  20. package/dist/model/resolved-map-state.js +1 -0
  21. package/dist/model/style.d.ts +3 -0
  22. package/dist/model/style.d.ts.map +1 -0
  23. package/dist/model/style.js +1 -0
  24. package/dist/utils/hash.d.ts +2 -0
  25. package/dist/utils/hash.d.ts.map +1 -0
  26. package/dist/utils/hash.js +28 -0
  27. package/dist/utils/index.d.ts +12 -4
  28. package/dist/utils/index.d.ts.map +1 -1
  29. package/dist/utils/index.js +12 -4
  30. package/dist/utils/map-context-diff.d.ts +1 -1
  31. package/dist/utils/map-context-diff.d.ts.map +1 -1
  32. package/dist/utils/map-context-diff.js +19 -11
  33. package/dist/utils/map-context-layer.d.ts +13 -0
  34. package/dist/utils/map-context-layer.d.ts.map +1 -0
  35. package/dist/utils/map-context-layer.js +44 -0
  36. package/dist/utils/map-context.d.ts +48 -4
  37. package/dist/utils/map-context.d.ts.map +1 -1
  38. package/dist/utils/map-context.js +80 -29
  39. package/dist/utils/style.d.ts +4 -0
  40. package/dist/utils/style.d.ts.map +1 -0
  41. package/dist/utils/style.js +68 -0
  42. package/dist/utils/view.d.ts +25 -0
  43. package/dist/utils/view.d.ts.map +1 -0
  44. package/dist/utils/view.js +126 -0
  45. package/lib/constant/projections.ts +1 -0
  46. package/lib/index.ts +8 -3
  47. package/lib/model/events.ts +113 -0
  48. package/lib/model/index.ts +10 -2
  49. package/lib/model/map-context-diff.ts +12 -3
  50. package/lib/model/map-context.ts +219 -26
  51. package/lib/model/resolved-map-state.ts +67 -0
  52. package/lib/model/style.ts +3 -0
  53. package/lib/utils/freeze.test.ts +1 -1
  54. package/lib/utils/hash.test.ts +180 -0
  55. package/lib/utils/hash.ts +28 -0
  56. package/lib/utils/index.ts +20 -4
  57. package/lib/utils/map-context-diff.test.ts +240 -10
  58. package/lib/utils/map-context-diff.ts +23 -14
  59. package/lib/utils/map-context-layer.test.ts +357 -0
  60. package/lib/utils/map-context-layer.ts +66 -0
  61. package/lib/utils/map-context.test.ts +136 -272
  62. package/lib/utils/map-context.ts +108 -39
  63. package/lib/utils/style.ts +71 -0
  64. package/lib/utils/url.test.ts +1 -1
  65. package/lib/utils/view.test.ts +146 -0
  66. package/lib/utils/view.ts +163 -0
  67. package/package.json +20 -3
@@ -0,0 +1,180 @@
1
+ import { getHash } from "./hash.js";
2
+
3
+ describe("getHash", () => {
4
+ it("generates a hash representing the deep value of an object", () => {
5
+ const hashA = getHash({ a: 1, b: 2, c: "abcd", d: ["a", "b", "c"] });
6
+ const hashB = getHash({ a: 1, b: 200, c: "abcd", d: ["a", "b", "c"] });
7
+ expect(hashB).not.toEqual(hashA);
8
+ });
9
+ it("returns a stable hash regardless of properties order", () => {
10
+ const hashA = getHash({ a: 1, b: 2, c: "abcd" });
11
+ const hashB = getHash({ c: "abcd", b: 2, a: 1 });
12
+ expect(hashB).toEqual(hashA);
13
+ });
14
+ it("takes into account array order", () => {
15
+ const hashA = getHash({ a: ["a", "b", "c"] });
16
+ const hashB = getHash({ a: ["b", "a", "c"] });
17
+ expect(hashB).not.toEqual(hashA);
18
+ });
19
+ it("ignores properties on demand", () => {
20
+ const hashA = getHash({ a: 1, b: 2, c: "abcd", d: ["a", "b", "c"] }, ["b"]);
21
+ const hashB = getHash({ c: "abcd", b: 2000, a: 1, d: ["a", "b", "c"] }, [
22
+ "b",
23
+ ]);
24
+ expect(hashB).toEqual(hashA);
25
+ });
26
+ it("stable for identical Date values", () => {
27
+ const hashA = getHash(new Date("2020-01-01T00:00:00.000Z"));
28
+ const hashB = getHash(new Date("2020-01-01T00:00:00.000Z"));
29
+ expect(hashB).toEqual(hashA);
30
+ });
31
+ it("different for different Date values", () => {
32
+ const hashA = getHash(new Date("2020-01-01T00:00:00.000Z"));
33
+ const hashB = getHash(new Date("2021-06-15T12:30:00.000Z"));
34
+ expect(hashB).not.toEqual(hashA);
35
+ });
36
+ it("different when a nested Date value changes", () => {
37
+ const hashA = getHash({ time: new Date("2020-01-01T00:00:00.000Z") });
38
+ const hashB = getHash({ time: new Date("2021-06-15T12:30:00.000Z") });
39
+ expect(hashB).not.toEqual(hashA);
40
+ });
41
+ it("stable for null", () => {
42
+ const hashA = getHash(null);
43
+ const hashB = getHash(null);
44
+ expect(hashB).toEqual(hashA);
45
+ });
46
+ it("different when casting null to string", () => {
47
+ const hashA = getHash(null);
48
+ const hashB = getHash("null");
49
+ expect(hashB).not.toEqual(hashA);
50
+ });
51
+ it("stable with identical GeoJSON geometry", () => {
52
+ const hashA = getHash({
53
+ geometry: {
54
+ type: "Polygon",
55
+ properties: {},
56
+ coordinates: [
57
+ [
58
+ [-10, -10],
59
+ [-10, 20],
60
+ [10, 20],
61
+ [10, -10],
62
+ [-10, -10],
63
+ ],
64
+ ],
65
+ },
66
+ });
67
+ const hashB = getHash({
68
+ geometry: {
69
+ type: "Polygon",
70
+ properties: {},
71
+ coordinates: [
72
+ [
73
+ [-10, -10],
74
+ [-10, 20],
75
+ [10, 20],
76
+ [10, -10],
77
+ [-10, -10],
78
+ ],
79
+ ],
80
+ },
81
+ });
82
+ expect(hashB).toEqual(hashA);
83
+ });
84
+ it("stable with identical GeoJSON collection", () => {
85
+ const hashA = getHash({
86
+ geometry: {
87
+ type: "FeatureCollection",
88
+ features: [{ type: "Feature", properties: {}, geometry: null }],
89
+ },
90
+ });
91
+ const hashB = getHash({
92
+ geometry: {
93
+ type: "FeatureCollection",
94
+ features: [{ type: "Feature", properties: {}, geometry: null }],
95
+ },
96
+ });
97
+ expect(hashB).toEqual(hashA);
98
+ });
99
+ it("different if GeoJSON geometry properties are not in the same order", () => {
100
+ const hashA = getHash({
101
+ geometry: {
102
+ coordinates: [
103
+ [
104
+ [-10, -10],
105
+ [-10, 20],
106
+ [10, 20],
107
+ [10, -10],
108
+ [-10, -10],
109
+ ],
110
+ ],
111
+ type: "Polygon",
112
+ properties: {},
113
+ },
114
+ });
115
+ const hashB = getHash({
116
+ geometry: {
117
+ type: "Polygon",
118
+ properties: {},
119
+ coordinates: [
120
+ [
121
+ [-10, -10],
122
+ [-10, 20],
123
+ [10, 20],
124
+ [10, -10],
125
+ [-10, -10],
126
+ ],
127
+ ],
128
+ },
129
+ });
130
+ expect(hashB).not.toEqual(hashA);
131
+ });
132
+ it("stable with bigint values", () => {
133
+ const hashA = getHash({
134
+ aa: "bb",
135
+ cc: 9007199254740991n,
136
+ });
137
+ const hashB = getHash({
138
+ aa: "bb",
139
+ cc: 9007199254740991n,
140
+ });
141
+ expect(hashB).toEqual(hashA);
142
+ });
143
+ it("supports bigint in geojson", () => {
144
+ const hash = getHash({
145
+ type: "FeatureCollection",
146
+ features: [
147
+ {
148
+ type: "Feature",
149
+ properties: {
150
+ adresse: "Façade Esplanade",
151
+ code_insee: 59350n,
152
+ dtdate: 1785767040000,
153
+ etat: "OUVERT",
154
+ geom: "POINT (703523.791143476 7060856.34054855)",
155
+ latitude: 50.644637,
156
+ longitude: 3.049734,
157
+ nbr_libre: 138n,
158
+ nbr_total: 270n,
159
+ nom: "Petit Paradis",
160
+ txt_aff: "95",
161
+ ville: "LILLE",
162
+ },
163
+ geometry: {
164
+ coordinates: [
165
+ [
166
+ [2.789133, 50.49973],
167
+ [3.272498, 50.49973],
168
+ [3.272498, 50.794577],
169
+ [2.789133, 50.794577],
170
+ [2.789133, 50.49973],
171
+ ],
172
+ ],
173
+ type: "Polygon",
174
+ },
175
+ },
176
+ ],
177
+ });
178
+ expect(hash).toBeTruthy();
179
+ });
180
+ });
@@ -0,0 +1,28 @@
1
+ function isGeoJsonGeometry(object: object) {
2
+ return "type" in object && ("coordinates" in object || "features" in object);
3
+ }
4
+
5
+ const bigintReplacer = (key: string, value: unknown) =>
6
+ typeof value === "bigint" ? value.toString() : value;
7
+
8
+ export function getHash(input: unknown, ignoreKeys: string[] = []): string {
9
+ if (input instanceof Date) {
10
+ return input.toISOString();
11
+ }
12
+ if (input instanceof Object && isGeoJsonGeometry(input)) {
13
+ return JSON.stringify(input, bigintReplacer); // do not compute an actual hash as it will take too long
14
+ } else if (input instanceof Object) {
15
+ const obj: Record<string, string> = {};
16
+ const keys = Object.keys(input).sort();
17
+ for (const key of keys) {
18
+ if (ignoreKeys.includes(key)) continue;
19
+ obj[key] = getHash(input[key as keyof typeof input]);
20
+ }
21
+ const hash = JSON.stringify(obj, bigintReplacer)
22
+ .split("")
23
+ .reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
24
+ return (hash >>> 0).toString();
25
+ } else {
26
+ return JSON.stringify(input, bigintReplacer);
27
+ }
28
+ }
@@ -1,4 +1,20 @@
1
- export * from "./url";
2
- export * from "./freeze";
3
- export { computeMapContextDiff } from "./map-context-diff";
4
- export { getLayerPosition } from "./map-context";
1
+ /**
2
+ * @group Utils
3
+ * @packageDocumentation
4
+ */
5
+
6
+ export * from "./url.js";
7
+ export * from "./freeze.js";
8
+ export * from "./hash.js";
9
+ export { computeMapContextDiff } from "./map-context-diff.js";
10
+ export {
11
+ getLayerPosition,
12
+ addLayerToContext,
13
+ removeLayerFromContext,
14
+ replaceLayerInContext,
15
+ changeLayerPositionInContext,
16
+ updateLayerInContext,
17
+ } from "./map-context.js";
18
+ export { updateLayer } from "./map-context-layer.js";
19
+ export { createViewFromLayer } from "./view.js";
20
+ export * from "./style.js";
@@ -1,5 +1,5 @@
1
- import { MapContext, MapContextDiff, MapContextLayer } from "../model";
2
- import { computeMapContextDiff } from "./map-context-diff";
1
+ import { MapContext, MapContextDiff, MapContextLayer } from "../model/index.js";
2
+ import { computeMapContextDiff } from "./map-context-diff.js";
3
3
  import {
4
4
  SAMPLE_CONTEXT,
5
5
  SAMPLE_LAYER1,
@@ -7,7 +7,10 @@ import {
7
7
  SAMPLE_LAYER3,
8
8
  SAMPLE_LAYER4,
9
9
  SAMPLE_LAYER5,
10
- } from "../../fixtures/map-context.fixtures";
10
+ SAMPLE_LAYER6,
11
+ SAMPLE_LAYER7,
12
+ } from "../../fixtures/map-context.fixtures.js";
13
+ import { beforeEach, describe, expect, it } from "vitest";
11
14
 
12
15
  describe("Context diff utils", () => {
13
16
  describe("computeMapContextDiff", () => {
@@ -33,7 +36,6 @@ describe("Context diff utils", () => {
33
36
  layersChanged: [],
34
37
  layersRemoved: [],
35
38
  layersReordered: [],
36
- viewChanges: {},
37
39
  });
38
40
  });
39
41
  });
@@ -65,7 +67,6 @@ describe("Context diff utils", () => {
65
67
  layersChanged: [],
66
68
  layersRemoved: [],
67
69
  layersReordered: [],
68
- viewChanges: {},
69
70
  });
70
71
  });
71
72
  });
@@ -97,7 +98,6 @@ describe("Context diff utils", () => {
97
98
  },
98
99
  ],
99
100
  layersReordered: [],
100
- viewChanges: {},
101
101
  });
102
102
  });
103
103
  });
@@ -123,16 +123,17 @@ describe("Context diff utils", () => {
123
123
  layersChanged: [
124
124
  {
125
125
  layer: contextNew.layers[0],
126
+ previousLayer: contextOld.layers[0],
126
127
  position: 0,
127
128
  },
128
129
  {
129
130
  layer: contextNew.layers[1],
131
+ previousLayer: contextOld.layers[1],
130
132
  position: 1,
131
133
  },
132
134
  ],
133
135
  layersRemoved: [],
134
136
  layersReordered: [],
135
- viewChanges: {},
136
137
  });
137
138
  });
138
139
  });
@@ -167,7 +168,6 @@ describe("Context diff utils", () => {
167
168
  previousPosition: 0,
168
169
  },
169
170
  ],
170
- viewChanges: {},
171
171
  });
172
172
  });
173
173
  });
@@ -211,7 +211,122 @@ describe("Context diff utils", () => {
211
211
  previousPosition: 0,
212
212
  },
213
213
  ],
214
- viewChanges: {},
214
+ });
215
+ });
216
+ });
217
+ });
218
+
219
+ describe("view changes", () => {
220
+ describe("both values", () => {
221
+ beforeEach(() => {
222
+ contextOld = {
223
+ ...SAMPLE_CONTEXT,
224
+ view: {
225
+ center: [0, 0],
226
+ zoom: 1,
227
+ },
228
+ };
229
+ contextNew = {
230
+ ...SAMPLE_CONTEXT,
231
+ view: {
232
+ center: [1, 1],
233
+ zoom: 2,
234
+ },
235
+ };
236
+ });
237
+ it("outputs the correct diff", () => {
238
+ diff = computeMapContextDiff(contextNew, contextOld);
239
+ expect(diff).toEqual({
240
+ layersAdded: [],
241
+ layersChanged: [],
242
+ layersRemoved: [],
243
+ layersReordered: [],
244
+ viewChanges: { ...contextNew.view },
245
+ });
246
+ expect(diff.viewChanges).not.toBe(contextNew.view); // the object reference should be different
247
+ });
248
+ });
249
+ describe("value to null", () => {
250
+ beforeEach(() => {
251
+ contextOld = {
252
+ ...SAMPLE_CONTEXT,
253
+ view: {
254
+ center: [0, 0],
255
+ zoom: 1,
256
+ },
257
+ };
258
+ contextNew = {
259
+ ...SAMPLE_CONTEXT,
260
+ view: null,
261
+ };
262
+ });
263
+ it("outputs the correct diff", () => {
264
+ diff = computeMapContextDiff(contextNew, contextOld);
265
+ expect(diff).toEqual({
266
+ layersAdded: [],
267
+ layersChanged: [],
268
+ layersRemoved: [],
269
+ layersReordered: [],
270
+ viewChanges: null,
271
+ });
272
+ });
273
+ });
274
+ describe("null to value", () => {
275
+ beforeEach(() => {
276
+ contextOld = {
277
+ ...SAMPLE_CONTEXT,
278
+ view: null,
279
+ };
280
+ contextNew = {
281
+ ...SAMPLE_CONTEXT,
282
+ view: {
283
+ center: [0, 0],
284
+ zoom: 1,
285
+ },
286
+ };
287
+ });
288
+ it("outputs the correct diff", () => {
289
+ diff = computeMapContextDiff(contextNew, contextOld);
290
+ expect(diff).toEqual({
291
+ layersAdded: [],
292
+ layersChanged: [],
293
+ layersRemoved: [],
294
+ layersReordered: [],
295
+ viewChanges: {
296
+ center: [0, 0],
297
+ zoom: 1,
298
+ },
299
+ });
300
+ });
301
+ });
302
+ describe("same content but different reference", () => {
303
+ beforeEach(() => {
304
+ contextOld = {
305
+ ...SAMPLE_CONTEXT,
306
+ view: {
307
+ center: [5, 10],
308
+ zoom: 3,
309
+ },
310
+ };
311
+ contextNew = {
312
+ ...SAMPLE_CONTEXT,
313
+ view: {
314
+ center: [5, 10],
315
+ zoom: 3,
316
+ },
317
+ };
318
+ });
319
+ it("output the correct diff", () => {
320
+ diff = computeMapContextDiff(contextNew, contextOld);
321
+ expect(diff).toEqual({
322
+ layersAdded: [],
323
+ layersChanged: [],
324
+ layersRemoved: [],
325
+ layersReordered: [],
326
+ viewChanges: {
327
+ center: [5, 10],
328
+ zoom: 3,
329
+ },
215
330
  });
216
331
  });
217
332
  });
@@ -228,6 +343,9 @@ describe("Context diff utils", () => {
228
343
  contextNew = {
229
344
  ...SAMPLE_CONTEXT,
230
345
  layers: [SAMPLE_LAYER2, changedLayer, SAMPLE_LAYER5],
346
+ view: {
347
+ extent: [0, 1, 2, 3],
348
+ },
231
349
  };
232
350
  });
233
351
  it("outputs the correct diff", () => {
@@ -242,6 +360,7 @@ describe("Context diff utils", () => {
242
360
  layersChanged: [
243
361
  {
244
362
  layer: changedLayer,
363
+ previousLayer: SAMPLE_LAYER3,
245
364
  position: 1,
246
365
  },
247
366
  ],
@@ -267,7 +386,118 @@ describe("Context diff utils", () => {
267
386
  previousPosition: 1,
268
387
  },
269
388
  ],
270
- viewChanges: {},
389
+ viewChanges: {
390
+ extent: [0, 1, 2, 3],
391
+ },
392
+ });
393
+ });
394
+ });
395
+
396
+ describe("combined changes (2)", () => {
397
+ beforeEach(() => {
398
+ contextOld = {
399
+ ...SAMPLE_CONTEXT,
400
+ layers: [
401
+ SAMPLE_LAYER1,
402
+ SAMPLE_LAYER5,
403
+ SAMPLE_LAYER3,
404
+ SAMPLE_LAYER4,
405
+ SAMPLE_LAYER6,
406
+ ],
407
+ };
408
+ contextNew = {
409
+ ...SAMPLE_CONTEXT,
410
+ layers: [SAMPLE_LAYER3, SAMPLE_LAYER1],
411
+ };
412
+ });
413
+ it("outputs the correct diff", async () => {
414
+ diff = computeMapContextDiff(contextNew, contextOld);
415
+ expect(diff).toEqual({
416
+ layersAdded: [],
417
+ layersChanged: [],
418
+ layersRemoved: [
419
+ {
420
+ layer: SAMPLE_LAYER5,
421
+ position: 1,
422
+ },
423
+ {
424
+ layer: SAMPLE_LAYER4,
425
+ position: 3,
426
+ },
427
+ {
428
+ layer: SAMPLE_LAYER6,
429
+ position: 4,
430
+ },
431
+ ],
432
+ layersReordered: [
433
+ {
434
+ layer: SAMPLE_LAYER3,
435
+ newPosition: 0,
436
+ previousPosition: 1,
437
+ },
438
+ {
439
+ layer: SAMPLE_LAYER1,
440
+ newPosition: 1,
441
+ previousPosition: 0,
442
+ },
443
+ ],
444
+ });
445
+ });
446
+ });
447
+
448
+ describe("combined changes (3)", () => {
449
+ beforeEach(() => {
450
+ contextOld = {
451
+ ...SAMPLE_CONTEXT,
452
+ layers: [SAMPLE_LAYER5, SAMPLE_LAYER1, SAMPLE_LAYER2],
453
+ };
454
+ contextNew = {
455
+ ...SAMPLE_CONTEXT,
456
+ layers: [
457
+ SAMPLE_LAYER2,
458
+ SAMPLE_LAYER3,
459
+ SAMPLE_LAYER6,
460
+ SAMPLE_LAYER1,
461
+ SAMPLE_LAYER7,
462
+ ],
463
+ };
464
+ });
465
+ it("outputs the correct diff", async () => {
466
+ diff = computeMapContextDiff(contextNew, contextOld);
467
+ expect(diff).toEqual({
468
+ layersAdded: [
469
+ {
470
+ layer: SAMPLE_LAYER3,
471
+ position: 1,
472
+ },
473
+ {
474
+ layer: SAMPLE_LAYER6,
475
+ position: 2,
476
+ },
477
+ {
478
+ layer: SAMPLE_LAYER7,
479
+ position: 4,
480
+ },
481
+ ],
482
+ layersChanged: [],
483
+ layersRemoved: [
484
+ {
485
+ layer: SAMPLE_LAYER5,
486
+ position: 0,
487
+ },
488
+ ],
489
+ layersReordered: [
490
+ {
491
+ layer: SAMPLE_LAYER2,
492
+ previousPosition: 3,
493
+ newPosition: 0,
494
+ },
495
+ {
496
+ layer: SAMPLE_LAYER1,
497
+ previousPosition: 0,
498
+ newPosition: 3,
499
+ },
500
+ ],
271
501
  });
272
502
  });
273
503
  });
@@ -2,11 +2,11 @@ import {
2
2
  MapContext,
3
3
  MapContextDiff,
4
4
  MapContextLayer,
5
+ MapContextLayerChanged,
5
6
  MapContextLayerPositioned,
6
7
  MapContextLayerReordered,
7
- MapContextView,
8
- } from "../model";
9
- import { isLayerSame, isLayerSameAndUnchanged } from "./map-context";
8
+ } from "../model/index.js";
9
+ import { isLayerSame, isLayerSameAndUnchanged } from "./map-context-layer.js";
10
10
 
11
11
  /**
12
12
  * The following logic is produced by identifying layers in both context
@@ -43,11 +43,10 @@ export function computeMapContextDiff(
43
43
  return -1;
44
44
  }
45
45
 
46
- const layersChanged: MapContextLayerPositioned[] = [];
46
+ const layersChanged: MapContextLayerChanged[] = [];
47
47
  const layersReordered: MapContextLayerReordered[] = [];
48
48
  const layersRemoved: MapContextLayerPositioned[] = [];
49
49
  const layersAdded: MapContextLayerPositioned[] = [];
50
- const viewChanges: MapContextView = {};
51
50
 
52
51
  // loop on prev context layers (for removed layers)
53
52
  for (let i = 0; i < previousContext.layers.length; i++) {
@@ -68,35 +67,45 @@ export function computeMapContextDiff(
68
67
  } else {
69
68
  const prevLayer = previousContext.layers[prevPosition];
70
69
  if (!isLayerSameAndUnchanged(layer, prevLayer)) {
71
- layersChanged.push({ layer, position: i });
70
+ layersChanged.push({ layer, previousLayer: prevLayer, position: i });
72
71
  }
73
72
  }
74
73
  }
75
74
 
76
- // look for moved layers
77
- const prevLayersFiltered = previousContext.layers.filter(
75
+ // look for moved layers (ignore removed and added ones)
76
+ const movedLayersInPrevious = previousContext.layers.filter(
78
77
  (l) => !layersRemoved.find(({ layer }) => l === layer),
79
78
  );
80
- const nextLayersFiltered = nextContext.layers.filter(
79
+ const movedLayersInNext = nextContext.layers.filter(
81
80
  (l) => !layersAdded.find(({ layer }) => l === layer),
82
81
  );
83
- for (let i = 0; i < nextLayersFiltered.length; i++) {
84
- const layer = nextLayersFiltered[i];
85
- const prevPosition = getLayerPosition(layer, prevLayersFiltered);
82
+ for (let i = 0; i < movedLayersInNext.length; i++) {
83
+ const layer = movedLayersInNext[i];
84
+ let prevPosition = getLayerPosition(layer, movedLayersInPrevious);
86
85
  if (i !== prevPosition) {
86
+ // shift prevPosition of a move layer when others layers were added before them
87
+ for (const { position: addedPos } of layersAdded) {
88
+ if (addedPos <= prevPosition) prevPosition++;
89
+ }
87
90
  layersReordered.push({
88
91
  layer,
89
92
  newPosition: getLayerPosition(layer, nextContext.layers),
90
- previousPosition: getLayerPosition(layer, previousContext.layers),
93
+ previousPosition: prevPosition,
91
94
  });
92
95
  }
93
96
  }
94
97
 
98
+ let viewChanges =
99
+ nextContext.view !== previousContext.view ? nextContext.view : undefined;
100
+ if (viewChanges !== null && viewChanges !== undefined) {
101
+ viewChanges = { ...viewChanges }; // copy the view to avoid unexpected mutations
102
+ }
103
+
95
104
  return {
96
105
  layersAdded,
97
106
  layersChanged,
98
107
  layersRemoved,
99
108
  layersReordered,
100
- viewChanges,
109
+ ...(viewChanges !== undefined && { viewChanges }),
101
110
  };
102
111
  }