gridstack 7.2.3 → 8.0.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 (196) hide show
  1. package/README.md +9 -3
  2. package/dist/dd-base-impl.d.ts +20 -20
  3. package/dist/dd-base-impl.js +31 -35
  4. package/dist/dd-base-impl.js.map +1 -1
  5. package/dist/dd-draggable.d.ts +28 -28
  6. package/dist/dd-draggable.js +336 -345
  7. package/dist/dd-draggable.js.map +1 -1
  8. package/dist/dd-droppable.d.ts +26 -26
  9. package/dist/dd-droppable.js +146 -148
  10. package/dist/dd-droppable.js.map +1 -1
  11. package/dist/dd-element.d.ts +27 -27
  12. package/dist/dd-element.js +90 -94
  13. package/dist/dd-element.js.map +1 -1
  14. package/dist/dd-gridstack.d.ts +34 -34
  15. package/dist/dd-gridstack.js +127 -124
  16. package/dist/dd-gridstack.js.map +1 -1
  17. package/dist/dd-manager.d.ts +22 -22
  18. package/dist/dd-manager.js +9 -13
  19. package/dist/dd-manager.js.map +1 -1
  20. package/dist/dd-resizable-handle.d.ts +14 -14
  21. package/dist/dd-resizable-handle.js +102 -105
  22. package/dist/dd-resizable-handle.js.map +1 -1
  23. package/dist/dd-resizable.d.ts +28 -28
  24. package/dist/dd-resizable.js +290 -296
  25. package/dist/dd-resizable.js.map +1 -1
  26. package/dist/dd-touch.d.ts +33 -33
  27. package/dist/dd-touch.js +173 -181
  28. package/dist/dd-touch.js.map +1 -1
  29. package/dist/es5/dd-base-impl.d.ts +20 -20
  30. package/dist/es5/dd-base-impl.js +40 -40
  31. package/dist/es5/dd-base-impl.js.map +1 -1
  32. package/dist/es5/dd-draggable.d.ts +28 -28
  33. package/dist/es5/dd-draggable.js +366 -368
  34. package/dist/es5/dd-draggable.js.map +1 -1
  35. package/dist/es5/dd-droppable.d.ts +26 -26
  36. package/dist/es5/dd-droppable.js +181 -179
  37. package/dist/es5/dd-droppable.js.map +1 -1
  38. package/dist/es5/dd-element.d.ts +27 -27
  39. package/dist/es5/dd-element.js +95 -95
  40. package/dist/es5/dd-element.js.map +1 -1
  41. package/dist/es5/dd-gridstack.d.ts +34 -34
  42. package/dist/es5/dd-gridstack.js +144 -144
  43. package/dist/es5/dd-gridstack.js.map +1 -1
  44. package/dist/es5/dd-manager.d.ts +22 -22
  45. package/dist/es5/dd-manager.js +16 -16
  46. package/dist/es5/dd-manager.js.map +1 -1
  47. package/dist/es5/dd-resizable-handle.d.ts +14 -14
  48. package/dist/es5/dd-resizable-handle.js +105 -106
  49. package/dist/es5/dd-resizable-handle.js.map +1 -1
  50. package/dist/es5/dd-resizable.d.ts +28 -28
  51. package/dist/es5/dd-resizable.js +317 -318
  52. package/dist/es5/dd-resizable.js.map +1 -1
  53. package/dist/es5/dd-touch.d.ts +33 -33
  54. package/dist/es5/dd-touch.js +185 -184
  55. package/dist/es5/dd-touch.js.map +1 -1
  56. package/dist/es5/gridstack-all.js +1 -1
  57. package/dist/es5/gridstack-all.js.LICENSE.txt +1 -1
  58. package/dist/es5/gridstack-all.js.map +1 -1
  59. package/dist/es5/gridstack-engine.d.ts +102 -100
  60. package/dist/es5/gridstack-engine.js +1000 -976
  61. package/dist/es5/gridstack-engine.js.map +1 -1
  62. package/dist/es5/gridstack-poly.js +1 -1
  63. package/dist/es5/gridstack.d.ts +389 -376
  64. package/dist/es5/gridstack.js +2252 -2227
  65. package/dist/es5/gridstack.js.map +1 -1
  66. package/dist/es5/types.d.ts +279 -279
  67. package/dist/es5/types.js +47 -35
  68. package/dist/es5/types.js.map +1 -1
  69. package/dist/es5/utils.d.ts +95 -91
  70. package/dist/es5/utils.js +591 -566
  71. package/dist/es5/utils.js.map +1 -1
  72. package/dist/gridstack-all.js +1 -1
  73. package/dist/gridstack-all.js.LICENSE.txt +1 -1
  74. package/dist/gridstack-all.js.map +1 -1
  75. package/dist/gridstack-engine.d.ts +102 -100
  76. package/dist/gridstack-engine.js +950 -936
  77. package/dist/gridstack-engine.js.map +1 -1
  78. package/dist/gridstack-extra.css +0 -390
  79. package/dist/gridstack-extra.min.css +1 -1
  80. package/dist/gridstack.css +3 -97
  81. package/dist/gridstack.d.ts +389 -376
  82. package/dist/gridstack.js +2155 -2151
  83. package/dist/gridstack.js.map +1 -1
  84. package/dist/gridstack.min.css +1 -1
  85. package/dist/ng/README.md +154 -0
  86. package/dist/ng/gridstack-item.component.d.ts +29 -0
  87. package/dist/ng/gridstack-item.component.js +65 -0
  88. package/dist/ng/gridstack-item.component.js.map +1 -0
  89. package/dist/ng/gridstack.component.d.ts +118 -0
  90. package/dist/ng/gridstack.component.js +245 -0
  91. package/dist/ng/gridstack.component.js.map +1 -0
  92. package/dist/src/gridstack-extra.scss +0 -2
  93. package/dist/src/gridstack.scss +6 -9
  94. package/dist/types.d.ts +279 -279
  95. package/dist/types.js +44 -35
  96. package/dist/types.js.map +1 -1
  97. package/dist/utils.d.ts +95 -91
  98. package/dist/utils.js +539 -524
  99. package/dist/utils.js.map +1 -1
  100. package/{dist → dist_save}/angular/gridstack-item.component.ts +22 -4
  101. package/{dist → dist_save}/angular/gridstack.component.ts +57 -30
  102. package/dist_save/dd-base-impl.d.ts +20 -0
  103. package/dist_save/dd-base-impl.js +36 -0
  104. package/dist_save/dd-base-impl.js.map +1 -0
  105. package/dist_save/dd-draggable.d.ts +28 -0
  106. package/dist_save/dd-draggable.js +343 -0
  107. package/dist_save/dd-draggable.js.map +1 -0
  108. package/dist_save/dd-droppable.d.ts +26 -0
  109. package/dist_save/dd-droppable.js +149 -0
  110. package/dist_save/dd-droppable.js.map +1 -0
  111. package/dist_save/dd-element.d.ts +27 -0
  112. package/dist_save/dd-element.js +95 -0
  113. package/dist_save/dd-element.js.map +1 -0
  114. package/dist_save/dd-gridstack.d.ts +34 -0
  115. package/dist_save/dd-gridstack.js +125 -0
  116. package/dist_save/dd-gridstack.js.map +1 -0
  117. package/dist_save/dd-manager.d.ts +22 -0
  118. package/dist_save/dd-manager.js +14 -0
  119. package/dist_save/dd-manager.js.map +1 -0
  120. package/dist_save/dd-resizable-handle.d.ts +14 -0
  121. package/dist_save/dd-resizable-handle.js +106 -0
  122. package/dist_save/dd-resizable-handle.js.map +1 -0
  123. package/dist_save/dd-resizable.d.ts +28 -0
  124. package/dist_save/dd-resizable.js +294 -0
  125. package/dist_save/dd-resizable.js.map +1 -0
  126. package/dist_save/dd-touch.d.ts +33 -0
  127. package/dist_save/dd-touch.js +183 -0
  128. package/dist_save/dd-touch.js.map +1 -0
  129. package/dist_save/es5/dd-base-impl.d.ts +20 -0
  130. package/dist_save/es5/dd-base-impl.js +41 -0
  131. package/dist_save/es5/dd-base-impl.js.map +1 -0
  132. package/dist_save/es5/dd-draggable.d.ts +28 -0
  133. package/dist_save/es5/dd-draggable.js +366 -0
  134. package/dist_save/es5/dd-draggable.js.map +1 -0
  135. package/dist_save/es5/dd-droppable.d.ts +26 -0
  136. package/dist_save/es5/dd-droppable.js +180 -0
  137. package/dist_save/es5/dd-droppable.js.map +1 -0
  138. package/dist_save/es5/dd-element.d.ts +27 -0
  139. package/dist_save/es5/dd-element.js +96 -0
  140. package/dist_save/es5/dd-element.js.map +1 -0
  141. package/dist_save/es5/dd-gridstack.d.ts +34 -0
  142. package/dist_save/es5/dd-gridstack.js +145 -0
  143. package/dist_save/es5/dd-gridstack.js.map +1 -0
  144. package/dist_save/es5/dd-manager.d.ts +22 -0
  145. package/dist_save/es5/dd-manager.js +17 -0
  146. package/dist_save/es5/dd-manager.js.map +1 -0
  147. package/dist_save/es5/dd-resizable-handle.d.ts +14 -0
  148. package/dist_save/es5/dd-resizable-handle.js +107 -0
  149. package/dist_save/es5/dd-resizable-handle.js.map +1 -0
  150. package/dist_save/es5/dd-resizable.d.ts +28 -0
  151. package/dist_save/es5/dd-resizable.js +316 -0
  152. package/dist_save/es5/dd-resizable.js.map +1 -0
  153. package/dist_save/es5/dd-touch.d.ts +33 -0
  154. package/dist_save/es5/dd-touch.js +186 -0
  155. package/dist_save/es5/dd-touch.js.map +1 -0
  156. package/dist_save/es5/gridstack-all.js +3 -0
  157. package/dist_save/es5/gridstack-all.js.LICENSE.txt +7 -0
  158. package/dist_save/es5/gridstack-all.js.map +1 -0
  159. package/dist_save/es5/gridstack-engine.d.ts +102 -0
  160. package/dist_save/es5/gridstack-engine.js +997 -0
  161. package/dist_save/es5/gridstack-engine.js.map +1 -0
  162. package/dist_save/es5/gridstack-poly.js +356 -0
  163. package/dist_save/es5/gridstack.d.ts +376 -0
  164. package/dist_save/es5/gridstack.js +2238 -0
  165. package/dist_save/es5/gridstack.js.map +1 -0
  166. package/dist_save/es5/types.d.ts +284 -0
  167. package/dist_save/es5/types.js +36 -0
  168. package/dist_save/es5/types.js.map +1 -0
  169. package/dist_save/es5/utils.d.ts +95 -0
  170. package/dist_save/es5/utils.js +590 -0
  171. package/dist_save/es5/utils.js.map +1 -0
  172. package/dist_save/gridstack-all.js +3 -0
  173. package/dist_save/gridstack-all.js.LICENSE.txt +7 -0
  174. package/dist_save/gridstack-all.js.map +1 -0
  175. package/dist_save/gridstack-engine.d.ts +102 -0
  176. package/dist_save/gridstack-engine.js +956 -0
  177. package/dist_save/gridstack-engine.js.map +1 -0
  178. package/dist_save/gridstack-extra.css +433 -0
  179. package/dist_save/gridstack-extra.min.css +1 -0
  180. package/dist_save/gridstack.css +226 -0
  181. package/dist_save/gridstack.d.ts +376 -0
  182. package/dist_save/gridstack.js +2162 -0
  183. package/dist_save/gridstack.js.map +1 -0
  184. package/dist_save/gridstack.min.css +1 -0
  185. package/dist_save/src/gridstack-extra.scss +27 -0
  186. package/dist_save/src/gridstack.scss +131 -0
  187. package/dist_save/types.d.ts +284 -0
  188. package/dist_save/types.js +36 -0
  189. package/dist_save/types.js.map +1 -0
  190. package/dist_save/utils.d.ts +95 -0
  191. package/dist_save/utils.js +548 -0
  192. package/dist_save/utils.js.map +1 -0
  193. package/doc/CHANGES.md +19 -0
  194. package/doc/README.md +17 -1
  195. package/package.json +25 -24
  196. /package/{dist → dist_save}/angular/README.md +0 -0
@@ -1,937 +1,951 @@
1
- "use strict";
2
- /**
3
- * gridstack-engine.ts 7.2.3
4
- * Copyright (c) 2021-2022 Alain Dumesny - see GridStack root license
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.GridStackEngine = void 0;
8
- const utils_1 = require("./utils");
9
- /**
10
- * Defines the GridStack engine that does most no DOM grid manipulation.
11
- * See GridStack methods and vars for descriptions.
12
- *
13
- * NOTE: values should not be modified directly - call the main GridStack API instead
14
- */
15
- class GridStackEngine {
16
- constructor(opts = {}) {
17
- this.addedNodes = [];
18
- this.removedNodes = [];
19
- this.column = opts.column || 12;
20
- this.maxRow = opts.maxRow;
21
- this._float = opts.float;
22
- this.nodes = opts.nodes || [];
23
- this.onChange = opts.onChange;
24
- }
25
- batchUpdate(flag = true) {
26
- if (!!this.batchMode === flag)
27
- return this;
28
- this.batchMode = flag;
29
- if (flag) {
30
- this._prevFloat = this._float;
31
- this._float = true; // let things go anywhere for now... will restore and possibly reposition later
32
- this.saveInitial(); // since begin update (which is called multiple times) won't do this
33
- }
34
- else {
35
- this._float = this._prevFloat;
36
- delete this._prevFloat;
37
- this._packNodes()._notify();
38
- }
39
- return this;
40
- }
41
- // use entire row for hitting area (will use bottom reverse sorted first) if we not actively moving DOWN and didn't already skip
42
- _useEntireRowArea(node, nn) {
43
- return !this.float && !this._hasLocked && (!node._moving || node._skipDown || nn.y <= node.y);
44
- }
45
- /** @internal fix collision on given 'node', going to given new location 'nn', with optional 'collide' node already found.
46
- * return true if we moved. */
47
- _fixCollisions(node, nn = node, collide, opt = {}) {
48
- this.sortNodes(-1); // from last to first, so recursive collision move items in the right order
49
- collide = collide || this.collide(node, nn); // REAL area collide for swap and skip if none...
50
- if (!collide)
51
- return false;
52
- // swap check: if we're actively moving in gravity mode, see if we collide with an object the same size
53
- if (node._moving && !opt.nested && !this.float) {
54
- if (this.swap(node, collide))
55
- return true;
56
- }
57
- // during while() collisions MAKE SURE to check entire row so larger items don't leap frog small ones (push them all down starting last in grid)
58
- let area = nn;
59
- if (this._useEntireRowArea(node, nn)) {
60
- area = { x: 0, w: this.column, y: nn.y, h: nn.h };
61
- collide = this.collide(node, area, opt.skip); // force new hit
62
- }
63
- let didMove = false;
64
- let newOpt = { nested: true, pack: false };
65
- while (collide = collide || this.collide(node, area, opt.skip)) { // could collide with more than 1 item... so repeat for each
66
- let moved;
67
- // if colliding with a locked item OR moving down with top gravity (and collide could move up) -> skip past the collide,
68
- // but remember that skip down so we only do this once (and push others otherwise).
69
- if (collide.locked || node._moving && !node._skipDown && nn.y > node.y && !this.float &&
70
- // can take space we had, or before where we're going
71
- (!this.collide(collide, Object.assign(Object.assign({}, collide), { y: node.y }), node) || !this.collide(collide, Object.assign(Object.assign({}, collide), { y: nn.y - collide.h }), node))) {
72
- node._skipDown = (node._skipDown || nn.y > node.y);
73
- moved = this.moveNode(node, Object.assign(Object.assign(Object.assign({}, nn), { y: collide.y + collide.h }), newOpt));
74
- if (collide.locked && moved) {
75
- utils_1.Utils.copyPos(nn, node); // moving after lock become our new desired location
76
- }
77
- else if (!collide.locked && moved && opt.pack) {
78
- // we moved after and will pack: do it now and keep the original drop location, but past the old collide to see what else we might push way
79
- this._packNodes();
80
- nn.y = collide.y + collide.h;
81
- utils_1.Utils.copyPos(node, nn);
82
- }
83
- didMove = didMove || moved;
84
- }
85
- else {
86
- // move collide down *after* where we will be, ignoring where we are now (don't collide with us)
87
- moved = this.moveNode(collide, Object.assign(Object.assign(Object.assign({}, collide), { y: nn.y + nn.h, skip: node }), newOpt));
88
- }
89
- if (!moved) {
90
- return didMove;
91
- } // break inf loop if we couldn't move after all (ex: maxRow, fixed)
92
- collide = undefined;
93
- }
94
- return didMove;
95
- }
96
- /** return the nodes that intercept the given node. Optionally a different area can be used, as well as a second node to skip */
97
- collide(skip, area = skip, skip2) {
98
- return this.nodes.find(n => n !== skip && n !== skip2 && utils_1.Utils.isIntercepted(n, area));
99
- }
100
- collideAll(skip, area = skip, skip2) {
101
- return this.nodes.filter(n => n !== skip && n !== skip2 && utils_1.Utils.isIntercepted(n, area));
102
- }
103
- /** does a pixel coverage collision based on where we started, returning the node that has the most coverage that is >50% mid line */
104
- directionCollideCoverage(node, o, collides) {
105
- if (!o.rect || !node._rect)
106
- return;
107
- let r0 = node._rect; // where started
108
- let r = Object.assign({}, o.rect); // where we are
109
- // update dragged rect to show where it's coming from (above or below, etc...)
110
- if (r.y > r0.y) {
111
- r.h += r.y - r0.y;
112
- r.y = r0.y;
113
- }
114
- else {
115
- r.h += r0.y - r.y;
116
- }
117
- if (r.x > r0.x) {
118
- r.w += r.x - r0.x;
119
- r.x = r0.x;
120
- }
121
- else {
122
- r.w += r0.x - r.x;
123
- }
124
- let collide;
125
- collides.forEach(n => {
126
- if (n.locked || !n._rect)
127
- return;
128
- let r2 = n._rect; // overlapping target
129
- let yOver = Number.MAX_VALUE, xOver = Number.MAX_VALUE, overMax = 0.5; // need >50%
130
- // depending on which side we started from, compute the overlap % of coverage
131
- // (ex: from above/below we only compute the max horizontal line coverage)
132
- if (r0.y < r2.y) { // from above
133
- yOver = ((r.y + r.h) - r2.y) / r2.h;
134
- }
135
- else if (r0.y + r0.h > r2.y + r2.h) { // from below
136
- yOver = ((r2.y + r2.h) - r.y) / r2.h;
137
- }
138
- if (r0.x < r2.x) { // from the left
139
- xOver = ((r.x + r.w) - r2.x) / r2.w;
140
- }
141
- else if (r0.x + r0.w > r2.x + r2.w) { // from the right
142
- xOver = ((r2.x + r2.w) - r.x) / r2.w;
143
- }
144
- let over = Math.min(xOver, yOver);
145
- if (over > overMax) {
146
- overMax = over;
147
- collide = n;
148
- }
149
- });
150
- o.collide = collide; // save it so we don't have to find it again
151
- return collide;
152
- }
153
- /** does a pixel coverage returning the node that has the most coverage by area */
154
- /*
155
- protected collideCoverage(r: GridStackPosition, collides: GridStackNode[]): {collide: GridStackNode, over: number} {
156
- let collide: GridStackNode;
157
- let overMax = 0;
158
- collides.forEach(n => {
159
- if (n.locked || !n._rect) return;
160
- let over = Utils.areaIntercept(r, n._rect);
161
- if (over > overMax) {
162
- overMax = over;
163
- collide = n;
164
- }
165
- });
166
- return {collide, over: overMax};
167
- }
168
- */
169
- /** called to cache the nodes pixel rectangles used for collision detection during drag */
170
- cacheRects(w, h, top, right, bottom, left) {
171
- this.nodes.forEach(n => n._rect = {
172
- y: n.y * h + top,
173
- x: n.x * w + left,
174
- w: n.w * w - left - right,
175
- h: n.h * h - top - bottom
176
- });
177
- return this;
178
- }
179
- /** called to possibly swap between 2 nodes (same size or column, not locked, touching), returning true if successful */
180
- swap(a, b) {
181
- if (!b || b.locked || !a || a.locked)
182
- return false;
183
- function _doSwap() {
184
- let x = b.x, y = b.y;
185
- b.x = a.x;
186
- b.y = a.y; // b -> a position
187
- if (a.h != b.h) {
188
- a.x = x;
189
- a.y = b.y + b.h; // a -> goes after b
190
- }
191
- else if (a.w != b.w) {
192
- a.x = b.x + b.w;
193
- a.y = y; // a -> goes after b
194
- }
195
- else {
196
- a.x = x;
197
- a.y = y; // a -> old b position
198
- }
199
- a._dirty = b._dirty = true;
200
- return true;
201
- }
202
- let touching; // remember if we called it (vs undefined)
203
- // same size and same row or column, and touching
204
- if (a.w === b.w && a.h === b.h && (a.x === b.x || a.y === b.y) && (touching = utils_1.Utils.isTouching(a, b)))
205
- return _doSwap();
206
- if (touching === false)
207
- return; // IFF ran test and fail, bail out
208
- // check for taking same columns (but different height) and touching
209
- if (a.w === b.w && a.x === b.x && (touching || (touching = utils_1.Utils.isTouching(a, b)))) {
210
- if (b.y < a.y) {
211
- let t = a;
212
- a = b;
213
- b = t;
214
- } // swap a <-> b vars so a is first
215
- return _doSwap();
216
- }
217
- if (touching === false)
218
- return;
219
- // check if taking same row (but different width) and touching
220
- if (a.h === b.h && a.y === b.y && (touching || (touching = utils_1.Utils.isTouching(a, b)))) {
221
- if (b.x < a.x) {
222
- let t = a;
223
- a = b;
224
- b = t;
225
- } // swap a <-> b vars so a is first
226
- return _doSwap();
227
- }
228
- return false;
229
- }
230
- isAreaEmpty(x, y, w, h) {
231
- let nn = { x: x || 0, y: y || 0, w: w || 1, h: h || 1 };
232
- return !this.collide(nn);
233
- }
234
- /** re-layout grid items to reclaim any empty space */
235
- compact() {
236
- if (this.nodes.length === 0)
237
- return this;
238
- this.batchUpdate()
239
- .sortNodes();
240
- let copyNodes = this.nodes;
241
- this.nodes = []; // pretend we have no nodes to conflict layout to start with...
242
- copyNodes.forEach(node => {
243
- if (!node.locked) {
244
- node.autoPosition = true;
245
- }
246
- this.addNode(node, false); // 'false' for add event trigger
247
- node._dirty = true; // will force attr update
248
- });
249
- return this.batchUpdate(false);
250
- }
251
- /** enable/disable floating widgets (default: `false`) See [example](http://gridstackjs.com/demo/float.html) */
252
- set float(val) {
253
- if (this._float === val)
254
- return;
255
- this._float = val || false;
256
- if (!val) {
257
- this._packNodes()._notify();
258
- }
259
- }
260
- /** float getter method */
261
- get float() { return this._float || false; }
262
- /** sort the nodes array from first to last, or reverse. Called during collision/placement to force an order */
263
- sortNodes(dir) {
264
- this.nodes = utils_1.Utils.sort(this.nodes, dir, this.column);
265
- return this;
266
- }
267
- /** @internal called to top gravity pack the items back OR revert back to original Y positions when floating */
268
- _packNodes() {
269
- if (this.batchMode) {
270
- return this;
271
- }
272
- this.sortNodes(); // first to last
273
- if (this.float) {
274
- // restore original Y pos
275
- this.nodes.forEach(n => {
276
- if (n._updating || n._orig === undefined || n.y === n._orig.y)
277
- return;
278
- let newY = n.y;
279
- while (newY > n._orig.y) {
280
- --newY;
281
- let collide = this.collide(n, { x: n.x, y: newY, w: n.w, h: n.h });
282
- if (!collide) {
283
- n._dirty = true;
284
- n.y = newY;
285
- }
286
- }
287
- });
288
- }
289
- else {
290
- // top gravity pack
291
- this.nodes.forEach((n, i) => {
292
- if (n.locked)
293
- return;
294
- while (n.y > 0) {
295
- let newY = i === 0 ? 0 : n.y - 1;
296
- let canBeMoved = i === 0 || !this.collide(n, { x: n.x, y: newY, w: n.w, h: n.h });
297
- if (!canBeMoved)
298
- break;
299
- // Note: must be dirty (from last position) for GridStack::OnChange CB to update positions
300
- // and move items back. The user 'change' CB should detect changes from the original
301
- // starting position instead.
302
- n._dirty = (n.y !== newY);
303
- n.y = newY;
304
- }
305
- });
306
- }
307
- return this;
308
- }
309
- /**
310
- * given a random node, makes sure it's coordinates/values are valid in the current grid
311
- * @param node to adjust
312
- * @param resizing if out of bound, resize down or move into the grid to fit ?
313
- */
314
- prepareNode(node, resizing) {
315
- node = node || {};
316
- node._id = node._id || GridStackEngine._idSeq++;
317
- // if we're missing position, have the grid position us automatically (before we set them to 0,0)
318
- if (node.x === undefined || node.y === undefined || node.x === null || node.y === null) {
319
- node.autoPosition = true;
320
- }
321
- // assign defaults for missing required fields
322
- let defaults = { x: 0, y: 0, w: 1, h: 1 };
323
- utils_1.Utils.defaults(node, defaults);
324
- if (!node.autoPosition) {
325
- delete node.autoPosition;
326
- }
327
- if (!node.noResize) {
328
- delete node.noResize;
329
- }
330
- if (!node.noMove) {
331
- delete node.noMove;
332
- }
333
- // check for NaN (in case messed up strings were passed. can't do parseInt() || defaults.x above as 0 is valid #)
334
- if (typeof node.x == 'string') {
335
- node.x = Number(node.x);
336
- }
337
- if (typeof node.y == 'string') {
338
- node.y = Number(node.y);
339
- }
340
- if (typeof node.w == 'string') {
341
- node.w = Number(node.w);
342
- }
343
- if (typeof node.h == 'string') {
344
- node.h = Number(node.h);
345
- }
346
- if (isNaN(node.x)) {
347
- node.x = defaults.x;
348
- node.autoPosition = true;
349
- }
350
- if (isNaN(node.y)) {
351
- node.y = defaults.y;
352
- node.autoPosition = true;
353
- }
354
- if (isNaN(node.w)) {
355
- node.w = defaults.w;
356
- }
357
- if (isNaN(node.h)) {
358
- node.h = defaults.h;
359
- }
360
- return this.nodeBoundFix(node, resizing);
361
- }
362
- /** part2 of preparing a node to fit inside our grid - checks for x,y,w from grid dimensions */
363
- nodeBoundFix(node, resizing) {
364
- let before = node._orig || utils_1.Utils.copyPos({}, node);
365
- if (node.maxW) {
366
- node.w = Math.min(node.w, node.maxW);
367
- }
368
- if (node.maxH) {
369
- node.h = Math.min(node.h, node.maxH);
370
- }
371
- if (node.minW && node.minW <= this.column) {
372
- node.w = Math.max(node.w, node.minW);
373
- }
374
- if (node.minH) {
375
- node.h = Math.max(node.h, node.minH);
376
- }
377
- // if user loaded a larger than allowed widget for current # of columns (or force 1 column mode),
378
- // remember it's position & width so we can restore back (1 -> 12 column) #1655 #1985
379
- // IFF we're not in the middle of column resizing!
380
- const saveOrig = this.column === 1 || node.x + node.w > this.column;
381
- if (saveOrig && this.column < 12 && !this._inColumnResize && !node.autoPosition && node._id && this.findCacheLayout(node, 12) === -1) {
382
- let copy = Object.assign({}, node); // need _id + positions
383
- copy.x = Math.min(11, copy.x);
384
- copy.w = Math.min(12, copy.w);
385
- this.cacheOneLayout(copy, 12);
386
- }
387
- if (node.w > this.column) {
388
- node.w = this.column;
389
- }
390
- else if (node.w < 1) {
391
- node.w = 1;
392
- }
393
- if (this.maxRow && node.h > this.maxRow) {
394
- node.h = this.maxRow;
395
- }
396
- else if (node.h < 1) {
397
- node.h = 1;
398
- }
399
- if (node.x < 0) {
400
- node.x = 0;
401
- }
402
- if (node.y < 0) {
403
- node.y = 0;
404
- }
405
- if (node.x + node.w > this.column) {
406
- if (resizing) {
407
- node.w = this.column - node.x;
408
- }
409
- else {
410
- node.x = this.column - node.w;
411
- }
412
- }
413
- if (this.maxRow && node.y + node.h > this.maxRow) {
414
- if (resizing) {
415
- node.h = this.maxRow - node.y;
416
- }
417
- else {
418
- node.y = this.maxRow - node.h;
419
- }
420
- }
421
- if (!utils_1.Utils.samePos(node, before)) {
422
- node._dirty = true;
423
- }
424
- return node;
425
- }
426
- /** returns a list of modified nodes from their original values */
427
- getDirtyNodes(verify) {
428
- // compare original x,y,w,h instead as _dirty can be a temporary state
429
- if (verify) {
430
- return this.nodes.filter(n => n._dirty && !utils_1.Utils.samePos(n, n._orig));
431
- }
432
- return this.nodes.filter(n => n._dirty);
433
- }
434
- /** @internal call this to call onChange callback with dirty nodes so DOM can be updated */
435
- _notify(removedNodes) {
436
- if (this.batchMode || !this.onChange)
437
- return this;
438
- let dirtyNodes = (removedNodes || []).concat(this.getDirtyNodes());
439
- this.onChange(dirtyNodes);
440
- return this;
441
- }
442
- /** @internal remove dirty and last tried info */
443
- cleanNodes() {
444
- if (this.batchMode)
445
- return this;
446
- this.nodes.forEach(n => {
447
- delete n._dirty;
448
- delete n._lastTried;
449
- });
450
- return this;
451
- }
452
- /** @internal called to save initial position/size to track real dirty state.
453
- * Note: should be called right after we call change event (so next API is can detect changes)
454
- * as well as right before we start move/resize/enter (so we can restore items to prev values) */
455
- saveInitial() {
456
- this.nodes.forEach(n => {
457
- n._orig = utils_1.Utils.copyPos({}, n);
458
- delete n._dirty;
459
- });
460
- this._hasLocked = this.nodes.some(n => n.locked);
461
- return this;
462
- }
463
- /** @internal restore all the nodes back to initial values (called when we leave) */
464
- restoreInitial() {
465
- this.nodes.forEach(n => {
466
- if (utils_1.Utils.samePos(n, n._orig))
467
- return;
468
- utils_1.Utils.copyPos(n, n._orig);
469
- n._dirty = true;
470
- });
471
- this._notify();
472
- return this;
473
- }
474
- /** find the first available empty spot for the given node width/height, updating the x,y attributes. return true if found */
475
- findEmptyPosition(node) {
476
- this.sortNodes();
477
- let found = false;
478
- for (let i = 0; !found; ++i) {
479
- let x = i % this.column;
480
- let y = Math.floor(i / this.column);
481
- if (x + node.w > this.column) {
482
- continue;
483
- }
484
- let box = { x, y, w: node.w, h: node.h };
485
- if (!this.nodes.find(n => utils_1.Utils.isIntercepted(box, n))) {
486
- node.x = x;
487
- node.y = y;
488
- found = true;
489
- }
490
- }
491
- return found;
492
- }
493
- /** call to add the given node to our list, fixing collision and re-packing */
494
- addNode(node, triggerAddEvent = false) {
495
- let dup = this.nodes.find(n => n._id === node._id);
496
- if (dup)
497
- return dup; // prevent inserting twice! return it instead.
498
- // skip prepareNode if we're in middle of column resize (not new) but do check for bounds!
499
- node = this._inColumnResize ? this.nodeBoundFix(node) : this.prepareNode(node);
500
- delete node._temporaryRemoved;
501
- delete node._removeDOM;
502
- if (node.autoPosition && this.findEmptyPosition(node)) {
503
- delete node.autoPosition; // found our slot
504
- }
505
- this.nodes.push(node);
506
- if (triggerAddEvent) {
507
- this.addedNodes.push(node);
508
- }
509
- this._fixCollisions(node);
510
- if (!this.batchMode) {
511
- this._packNodes()._notify();
512
- }
513
- return node;
514
- }
515
- removeNode(node, removeDOM = true, triggerEvent = false) {
516
- if (!this.nodes.find(n => n === node)) {
517
- // TEST console.log(`Error: GridStackEngine.removeNode() node._id=${node._id} not found!`)
518
- return this;
519
- }
520
- if (triggerEvent) { // we wait until final drop to manually track removed items (rather than during drag)
521
- this.removedNodes.push(node);
522
- }
523
- if (removeDOM)
524
- node._removeDOM = true; // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
525
- // don't use 'faster' .splice(findIndex(),1) in case node isn't in our list, or in multiple times.
526
- this.nodes = this.nodes.filter(n => n !== node);
527
- return this._packNodes()
528
- ._notify([node]);
529
- }
530
- removeAll(removeDOM = true) {
531
- delete this._layouts;
532
- if (this.nodes.length === 0)
533
- return this;
534
- removeDOM && this.nodes.forEach(n => n._removeDOM = true); // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
535
- this.removedNodes = this.nodes;
536
- this.nodes = [];
537
- return this._notify(this.removedNodes);
538
- }
539
- /** checks if item can be moved (layout constrain) vs moveNode(), returning true if was able to move.
540
- * In more complicated cases (maxRow) it will attempt at moving the item and fixing
541
- * others in a clone first, then apply those changes if still within specs. */
542
- moveNodeCheck(node, o) {
543
- // if (node.locked) return false;
544
- if (!this.changedPosConstrain(node, o))
545
- return false;
546
- o.pack = true;
547
- // simpler case: move item directly...
548
- if (!this.maxRow) {
549
- return this.moveNode(node, o);
550
- }
551
- // complex case: create a clone with NO maxRow (will check for out of bounds at the end)
552
- let clonedNode;
553
- let clone = new GridStackEngine({
554
- column: this.column,
555
- float: this.float,
556
- nodes: this.nodes.map(n => {
557
- if (n === node) {
558
- clonedNode = Object.assign({}, n);
559
- return clonedNode;
560
- }
561
- return Object.assign({}, n);
562
- })
563
- });
564
- if (!clonedNode)
565
- return false;
566
- // check if we're covering 50% collision and could move
567
- let canMove = clone.moveNode(clonedNode, o) && clone.getRow() <= this.maxRow;
568
- // else check if we can force a swap (float=true, or different shapes) on non-resize
569
- if (!canMove && !o.resizing && o.collide) {
570
- let collide = o.collide.el.gridstackNode; // find the source node the clone collided with at 50%
571
- if (this.swap(node, collide)) { // swaps and mark dirty
572
- this._notify();
573
- return true;
574
- }
575
- }
576
- if (!canMove)
577
- return false;
578
- // if clone was able to move, copy those mods over to us now instead of caller trying to do this all over!
579
- // Note: we can't use the list directly as elements and other parts point to actual node, so copy content
580
- clone.nodes.filter(n => n._dirty).forEach(c => {
581
- let n = this.nodes.find(a => a._id === c._id);
582
- if (!n)
583
- return;
584
- utils_1.Utils.copyPos(n, c);
585
- n._dirty = true;
586
- });
587
- this._notify();
588
- return true;
589
- }
590
- /** return true if can fit in grid height constrain only (always true if no maxRow) */
591
- willItFit(node) {
592
- delete node._willFitPos;
593
- if (!this.maxRow)
594
- return true;
595
- // create a clone with NO maxRow and check if still within size
596
- let clone = new GridStackEngine({
597
- column: this.column,
598
- float: this.float,
599
- nodes: this.nodes.map(n => { return Object.assign({}, n); })
600
- });
601
- let n = Object.assign({}, node); // clone node so we don't mod any settings on it but have full autoPosition and min/max as well! #1687
602
- this.cleanupNode(n);
603
- delete n.el;
604
- delete n._id;
605
- delete n.content;
606
- delete n.grid;
607
- clone.addNode(n);
608
- if (clone.getRow() <= this.maxRow) {
609
- node._willFitPos = utils_1.Utils.copyPos({}, n);
610
- return true;
611
- }
612
- return false;
613
- }
614
- /** true if x,y or w,h are different after clamping to min/max */
615
- changedPosConstrain(node, p) {
616
- // first make sure w,h are set for caller
617
- p.w = p.w || node.w;
618
- p.h = p.h || node.h;
619
- if (node.x !== p.x || node.y !== p.y)
620
- return true;
621
- // check constrained w,h
622
- if (node.maxW) {
623
- p.w = Math.min(p.w, node.maxW);
624
- }
625
- if (node.maxH) {
626
- p.h = Math.min(p.h, node.maxH);
627
- }
628
- if (node.minW) {
629
- p.w = Math.max(p.w, node.minW);
630
- }
631
- if (node.minH) {
632
- p.h = Math.max(p.h, node.minH);
633
- }
634
- return (node.w !== p.w || node.h !== p.h);
635
- }
636
- /** return true if the passed in node was actually moved (checks for no-op and locked) */
637
- moveNode(node, o) {
638
- var _a, _b;
639
- if (!node || /*node.locked ||*/ !o)
640
- return false;
641
- let wasUndefinedPack;
642
- if (o.pack === undefined) {
643
- wasUndefinedPack = o.pack = true;
644
- }
645
- // constrain the passed in values and check if we're still changing our node
646
- if (typeof o.x !== 'number') {
647
- o.x = node.x;
648
- }
649
- if (typeof o.y !== 'number') {
650
- o.y = node.y;
651
- }
652
- if (typeof o.w !== 'number') {
653
- o.w = node.w;
654
- }
655
- if (typeof o.h !== 'number') {
656
- o.h = node.h;
657
- }
658
- let resizing = (node.w !== o.w || node.h !== o.h);
659
- let nn = utils_1.Utils.copyPos({}, node, true); // get min/max out first, then opt positions next
660
- utils_1.Utils.copyPos(nn, o);
661
- nn = this.nodeBoundFix(nn, resizing);
662
- utils_1.Utils.copyPos(o, nn);
663
- if (utils_1.Utils.samePos(node, o))
664
- return false;
665
- let prevPos = utils_1.Utils.copyPos({}, node);
666
- // check if we will need to fix collision at our new location
667
- let collides = this.collideAll(node, nn, o.skip);
668
- let needToMove = true;
669
- if (collides.length) {
670
- let activeDrag = node._moving && !o.nested;
671
- // check to make sure we actually collided over 50% surface area while dragging
672
- let collide = activeDrag ? this.directionCollideCoverage(node, o, collides) : collides[0];
673
- // if we're enabling creation of sub-grids on the fly, see if we're covering 80% of either one, if we didn't already do that
674
- if (activeDrag && collide && ((_b = (_a = node.grid) === null || _a === void 0 ? void 0 : _a.opts) === null || _b === void 0 ? void 0 : _b.subGridDynamic) && !node.grid._isTemp) {
675
- let over = utils_1.Utils.areaIntercept(o.rect, collide._rect);
676
- let a1 = utils_1.Utils.area(o.rect);
677
- let a2 = utils_1.Utils.area(collide._rect);
678
- let perc = over / (a1 < a2 ? a1 : a2);
679
- if (perc > .8) {
680
- collide.grid.makeSubGrid(collide.el, undefined, node);
681
- collide = undefined;
682
- }
683
- }
684
- if (collide) {
685
- needToMove = !this._fixCollisions(node, nn, collide, o); // check if already moved...
686
- }
687
- else {
688
- needToMove = false; // we didn't cover >50% for a move, skip...
689
- if (wasUndefinedPack)
690
- delete o.pack;
691
- }
692
- }
693
- // now move (to the original ask vs the collision version which might differ) and repack things
694
- if (needToMove) {
695
- node._dirty = true;
696
- utils_1.Utils.copyPos(node, nn);
697
- }
698
- if (o.pack) {
699
- this._packNodes()
700
- ._notify();
701
- }
702
- return !utils_1.Utils.samePos(node, prevPos); // pack might have moved things back
703
- }
704
- getRow() {
705
- return this.nodes.reduce((row, n) => Math.max(row, n.y + n.h), 0);
706
- }
707
- beginUpdate(node) {
708
- if (!node._updating) {
709
- node._updating = true;
710
- delete node._skipDown;
711
- if (!this.batchMode)
712
- this.saveInitial();
713
- }
714
- return this;
715
- }
716
- endUpdate() {
717
- let n = this.nodes.find(n => n._updating);
718
- if (n) {
719
- delete n._updating;
720
- delete n._skipDown;
721
- }
722
- return this;
723
- }
724
- /** saves a copy of the largest column layout (eg 12 even when rendering oneColumnMode) so we don't loose orig layout,
725
- * returning a list of widgets for serialization */
726
- save(saveElement = true) {
727
- var _a;
728
- // use the highest layout for any saved info so we can have full detail on reload #1849
729
- let len = (_a = this._layouts) === null || _a === void 0 ? void 0 : _a.length;
730
- let layout = len && this.column !== (len - 1) ? this._layouts[len - 1] : null;
731
- let list = [];
732
- this.sortNodes();
733
- this.nodes.forEach(n => {
734
- let wl = layout === null || layout === void 0 ? void 0 : layout.find(l => l._id === n._id);
735
- let w = Object.assign({}, n);
736
- // use layout info instead if set
737
- if (wl) {
738
- w.x = wl.x;
739
- w.y = wl.y;
740
- w.w = wl.w;
741
- }
742
- utils_1.Utils.removeInternalForSave(w, !saveElement);
743
- list.push(w);
744
- });
745
- return list;
746
- }
747
- /** @internal called whenever a node is added or moved - updates the cached layouts */
748
- layoutsNodesChange(nodes) {
749
- if (!this._layouts || this._inColumnResize)
750
- return this;
751
- // remove smaller layouts - we will re-generate those on the fly... larger ones need to update
752
- this._layouts.forEach((layout, column) => {
753
- if (!layout || column === this.column)
754
- return this;
755
- if (column < this.column) {
756
- this._layouts[column] = undefined;
757
- }
758
- else {
759
- // we save the original x,y,w (h isn't cached) to see what actually changed to propagate better.
760
- // NOTE: we don't need to check against out of bound scaling/moving as that will be done when using those cache values. #1785
761
- let ratio = column / this.column;
762
- nodes.forEach(node => {
763
- if (!node._orig)
764
- return; // didn't change (newly added ?)
765
- let n = layout.find(l => l._id === node._id);
766
- if (!n)
767
- return; // no cache for new nodes. Will use those values.
768
- // Y changed, push down same amount
769
- // TODO: detect doing item 'swaps' will help instead of move (especially in 1 column mode)
770
- if (node.y !== node._orig.y) {
771
- n.y += (node.y - node._orig.y);
772
- }
773
- // X changed, scale from new position
774
- if (node.x !== node._orig.x) {
775
- n.x = Math.round(node.x * ratio);
776
- }
777
- // width changed, scale from new width
778
- if (node.w !== node._orig.w) {
779
- n.w = Math.round(node.w * ratio);
780
- }
781
- // ...height always carries over from cache
782
- });
783
- }
784
- });
785
- return this;
786
- }
787
- /**
788
- * @internal Called to scale the widget width & position up/down based on the column change.
789
- * Note we store previous layouts (especially original ones) to make it possible to go
790
- * from say 12 -> 1 -> 12 and get back to where we were.
791
- *
792
- * @param prevColumn previous number of columns
793
- * @param column new column number
794
- * @param nodes different sorted list (ex: DOM order) instead of current list
795
- * @param layout specify the type of re-layout that will happen (position, size, etc...).
796
- * Note: items will never be outside of the current column boundaries. default (moveScale). Ignored for 1 column
797
- */
798
- updateNodeWidths(prevColumn, column, nodes, layout = 'moveScale') {
799
- var _a;
800
- if (!this.nodes.length || !column || prevColumn === column)
801
- return this;
802
- // cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data
803
- this.cacheLayout(this.nodes, prevColumn);
804
- this.batchUpdate(); // do this EARLY as it will call saveInitial() so we can detect where we started for _dirty and collision
805
- let newNodes = [];
806
- // if we're going to 1 column and using DOM order rather than default sorting, then generate that layout
807
- let domOrder = false;
808
- if (column === 1 && (nodes === null || nodes === void 0 ? void 0 : nodes.length)) {
809
- domOrder = true;
810
- let top = 0;
811
- nodes.forEach(n => {
812
- n.x = 0;
813
- n.w = 1;
814
- n.y = Math.max(n.y, top);
815
- top = n.y + n.h;
816
- });
817
- newNodes = nodes;
818
- nodes = [];
819
- }
820
- else {
821
- nodes = utils_1.Utils.sort(this.nodes, -1, prevColumn); // current column reverse sorting so we can insert last to front (limit collision)
822
- }
823
- // see if we have cached previous layout IFF we are going up in size (restore) otherwise always
824
- // generate next size down from where we are (looks more natural as you gradually size down).
825
- let cacheNodes = [];
826
- if (column > prevColumn) {
827
- cacheNodes = this._layouts[column] || [];
828
- // ...if not, start with the largest layout (if not already there) as down-scaling is more accurate
829
- // by pretending we came from that larger column by assigning those values as starting point
830
- let lastIndex = this._layouts.length - 1;
831
- if (!cacheNodes.length && prevColumn !== lastIndex && ((_a = this._layouts[lastIndex]) === null || _a === void 0 ? void 0 : _a.length)) {
832
- prevColumn = lastIndex;
833
- this._layouts[lastIndex].forEach(cacheNode => {
834
- let n = nodes.find(n => n._id === cacheNode._id);
835
- if (n) {
836
- // still current, use cache info positions
837
- n.x = cacheNode.x;
838
- n.y = cacheNode.y;
839
- n.w = cacheNode.w;
840
- }
841
- });
842
- }
843
- }
844
- // if we found cache re-use those nodes that are still current
845
- cacheNodes.forEach(cacheNode => {
846
- let j = nodes.findIndex(n => n._id === cacheNode._id);
847
- if (j !== -1) {
848
- // still current, use cache info positions
849
- nodes[j].x = cacheNode.x;
850
- nodes[j].y = cacheNode.y;
851
- nodes[j].w = cacheNode.w;
852
- newNodes.push(nodes[j]);
853
- nodes.splice(j, 1);
854
- }
855
- });
856
- // ...and add any extra non-cached ones
857
- if (nodes.length) {
858
- if (typeof layout === 'function') {
859
- layout(column, prevColumn, newNodes, nodes);
860
- }
861
- else if (!domOrder) {
862
- let ratio = column / prevColumn;
863
- let move = (layout === 'move' || layout === 'moveScale');
864
- let scale = (layout === 'scale' || layout === 'moveScale');
865
- nodes.forEach(node => {
866
- // NOTE: x + w could be outside of the grid, but addNode() below will handle that
867
- node.x = (column === 1 ? 0 : (move ? Math.round(node.x * ratio) : Math.min(node.x, column - 1)));
868
- node.w = ((column === 1 || prevColumn === 1) ? 1 :
869
- scale ? (Math.round(node.w * ratio) || 1) : (Math.min(node.w, column)));
870
- newNodes.push(node);
871
- });
872
- nodes = [];
873
- }
874
- }
875
- // finally re-layout them in reverse order (to get correct placement)
876
- if (!domOrder)
877
- newNodes = utils_1.Utils.sort(newNodes, -1, column);
878
- this._inColumnResize = true; // prevent cache update
879
- this.nodes = []; // pretend we have no nodes to start with (add() will use same structures) to simplify layout
880
- newNodes.forEach(node => {
881
- this.addNode(node, false); // 'false' for add event trigger
882
- delete node._orig; // make sure the commit doesn't try to restore things back to original
883
- });
884
- this.batchUpdate(false);
885
- delete this._inColumnResize;
886
- return this;
887
- }
888
- /**
889
- * call to cache the given layout internally to the given location so we can restore back when column changes size
890
- * @param nodes list of nodes
891
- * @param column corresponding column index to save it under
892
- * @param clear if true, will force other caches to be removed (default false)
893
- */
894
- cacheLayout(nodes, column, clear = false) {
895
- let copy = [];
896
- nodes.forEach((n, i) => {
897
- n._id = n._id || GridStackEngine._idSeq++; // make sure we have an id in case this is new layout, else re-use id already set
898
- copy[i] = { x: n.x, y: n.y, w: n.w, _id: n._id }; // only thing we change is x,y,w and id to find it back
899
- });
900
- this._layouts = clear ? [] : this._layouts || []; // use array to find larger quick
901
- this._layouts[column] = copy;
902
- return this;
903
- }
904
- /**
905
- * call to cache the given node layout internally to the given location so we can restore back when column changes size
906
- * @param node single node to cache
907
- * @param column corresponding column index to save it under
908
- */
909
- cacheOneLayout(n, column) {
910
- n._id = n._id || GridStackEngine._idSeq++;
911
- let layout = { x: n.x, y: n.y, w: n.w, _id: n._id };
912
- this._layouts = this._layouts || [];
913
- this._layouts[column] = this._layouts[column] || [];
914
- let index = this.findCacheLayout(n, column);
915
- if (index === -1)
916
- this._layouts[column].push(layout);
917
- else
918
- this._layouts[column][index] = layout;
919
- return this;
920
- }
921
- findCacheLayout(n, column) {
922
- var _a, _b, _c;
923
- return (_c = (_b = (_a = this._layouts) === null || _a === void 0 ? void 0 : _a[column]) === null || _b === void 0 ? void 0 : _b.findIndex(l => l._id === n._id)) !== null && _c !== void 0 ? _c : -1;
924
- }
925
- /** called to remove all internal values but the _id */
926
- cleanupNode(node) {
927
- for (let prop in node) {
928
- if (prop[0] === '_' && prop !== '_id')
929
- delete node[prop];
930
- }
931
- return this;
932
- }
933
- }
934
- exports.GridStackEngine = GridStackEngine;
935
- /** @internal unique global internal _id counter NOT starting at 0 */
936
- GridStackEngine._idSeq = 1;
1
+ /**
2
+ * gridstack-engine.ts 8.0.0
3
+ * Copyright (c) 2021-2022 Alain Dumesny - see GridStack root license
4
+ */
5
+ import { Utils } from './utils';
6
+ /**
7
+ * Defines the GridStack engine that does most no DOM grid manipulation.
8
+ * See GridStack methods and vars for descriptions.
9
+ *
10
+ * NOTE: values should not be modified directly - call the main GridStack API instead
11
+ */
12
+ class GridStackEngine {
13
+ constructor(opts = {}) {
14
+ this.addedNodes = [];
15
+ this.removedNodes = [];
16
+ this.column = opts.column || 12;
17
+ this.maxRow = opts.maxRow;
18
+ this._float = opts.float;
19
+ this.nodes = opts.nodes || [];
20
+ this.onChange = opts.onChange;
21
+ }
22
+ batchUpdate(flag = true) {
23
+ if (!!this.batchMode === flag)
24
+ return this;
25
+ this.batchMode = flag;
26
+ if (flag) {
27
+ this._prevFloat = this._float;
28
+ this._float = true; // let things go anywhere for now... will restore and possibly reposition later
29
+ this.saveInitial(); // since begin update (which is called multiple times) won't do this
30
+ }
31
+ else {
32
+ this._float = this._prevFloat;
33
+ delete this._prevFloat;
34
+ this._packNodes()._notify();
35
+ }
36
+ return this;
37
+ }
38
+ // use entire row for hitting area (will use bottom reverse sorted first) if we not actively moving DOWN and didn't already skip
39
+ _useEntireRowArea(node, nn) {
40
+ return (!this.float || this.batchMode && !this._prevFloat) && !this._hasLocked && (!node._moving || node._skipDown || nn.y <= node.y);
41
+ }
42
+ /** @internal fix collision on given 'node', going to given new location 'nn', with optional 'collide' node already found.
43
+ * return true if we moved. */
44
+ _fixCollisions(node, nn = node, collide, opt = {}) {
45
+ this.sortNodes(-1); // from last to first, so recursive collision move items in the right order
46
+ collide = collide || this.collide(node, nn); // REAL area collide for swap and skip if none...
47
+ if (!collide)
48
+ return false;
49
+ // swap check: if we're actively moving in gravity mode, see if we collide with an object the same size
50
+ if (node._moving && !opt.nested && !this.float) {
51
+ if (this.swap(node, collide))
52
+ return true;
53
+ }
54
+ // during while() collisions MAKE SURE to check entire row so larger items don't leap frog small ones (push them all down starting last in grid)
55
+ let area = nn;
56
+ if (this._useEntireRowArea(node, nn)) {
57
+ area = { x: 0, w: this.column, y: nn.y, h: nn.h };
58
+ collide = this.collide(node, area, opt.skip); // force new hit
59
+ }
60
+ let didMove = false;
61
+ let newOpt = { nested: true, pack: false };
62
+ while (collide = collide || this.collide(node, area, opt.skip)) { // could collide with more than 1 item... so repeat for each
63
+ let moved;
64
+ // if colliding with a locked item OR moving down with top gravity (and collide could move up) -> skip past the collide,
65
+ // but remember that skip down so we only do this once (and push others otherwise).
66
+ if (collide.locked || node._moving && !node._skipDown && nn.y > node.y && !this.float &&
67
+ // can take space we had, or before where we're going
68
+ (!this.collide(collide, { ...collide, y: node.y }, node) || !this.collide(collide, { ...collide, y: nn.y - collide.h }, node))) {
69
+ node._skipDown = (node._skipDown || nn.y > node.y);
70
+ moved = this.moveNode(node, { ...nn, y: collide.y + collide.h, ...newOpt });
71
+ if (collide.locked && moved) {
72
+ Utils.copyPos(nn, node); // moving after lock become our new desired location
73
+ }
74
+ else if (!collide.locked && moved && opt.pack) {
75
+ // we moved after and will pack: do it now and keep the original drop location, but past the old collide to see what else we might push way
76
+ this._packNodes();
77
+ nn.y = collide.y + collide.h;
78
+ Utils.copyPos(node, nn);
79
+ }
80
+ didMove = didMove || moved;
81
+ }
82
+ else {
83
+ // move collide down *after* where we will be, ignoring where we are now (don't collide with us)
84
+ moved = this.moveNode(collide, { ...collide, y: nn.y + nn.h, skip: node, ...newOpt });
85
+ }
86
+ if (!moved) {
87
+ return didMove;
88
+ } // break inf loop if we couldn't move after all (ex: maxRow, fixed)
89
+ collide = undefined;
90
+ }
91
+ return didMove;
92
+ }
93
+ /** return the nodes that intercept the given node. Optionally a different area can be used, as well as a second node to skip */
94
+ collide(skip, area = skip, skip2) {
95
+ return this.nodes.find(n => n !== skip && n !== skip2 && Utils.isIntercepted(n, area));
96
+ }
97
+ collideAll(skip, area = skip, skip2) {
98
+ return this.nodes.filter(n => n !== skip && n !== skip2 && Utils.isIntercepted(n, area));
99
+ }
100
+ /** does a pixel coverage collision based on where we started, returning the node that has the most coverage that is >50% mid line */
101
+ directionCollideCoverage(node, o, collides) {
102
+ if (!o.rect || !node._rect)
103
+ return;
104
+ let r0 = node._rect; // where started
105
+ let r = { ...o.rect }; // where we are
106
+ // update dragged rect to show where it's coming from (above or below, etc...)
107
+ if (r.y > r0.y) {
108
+ r.h += r.y - r0.y;
109
+ r.y = r0.y;
110
+ }
111
+ else {
112
+ r.h += r0.y - r.y;
113
+ }
114
+ if (r.x > r0.x) {
115
+ r.w += r.x - r0.x;
116
+ r.x = r0.x;
117
+ }
118
+ else {
119
+ r.w += r0.x - r.x;
120
+ }
121
+ let collide;
122
+ collides.forEach(n => {
123
+ if (n.locked || !n._rect)
124
+ return;
125
+ let r2 = n._rect; // overlapping target
126
+ let yOver = Number.MAX_VALUE, xOver = Number.MAX_VALUE, overMax = 0.5; // need >50%
127
+ // depending on which side we started from, compute the overlap % of coverage
128
+ // (ex: from above/below we only compute the max horizontal line coverage)
129
+ if (r0.y < r2.y) { // from above
130
+ yOver = ((r.y + r.h) - r2.y) / r2.h;
131
+ }
132
+ else if (r0.y + r0.h > r2.y + r2.h) { // from below
133
+ yOver = ((r2.y + r2.h) - r.y) / r2.h;
134
+ }
135
+ if (r0.x < r2.x) { // from the left
136
+ xOver = ((r.x + r.w) - r2.x) / r2.w;
137
+ }
138
+ else if (r0.x + r0.w > r2.x + r2.w) { // from the right
139
+ xOver = ((r2.x + r2.w) - r.x) / r2.w;
140
+ }
141
+ let over = Math.min(xOver, yOver);
142
+ if (over > overMax) {
143
+ overMax = over;
144
+ collide = n;
145
+ }
146
+ });
147
+ o.collide = collide; // save it so we don't have to find it again
148
+ return collide;
149
+ }
150
+ /** does a pixel coverage returning the node that has the most coverage by area */
151
+ /*
152
+ protected collideCoverage(r: GridStackPosition, collides: GridStackNode[]): {collide: GridStackNode, over: number} {
153
+ let collide: GridStackNode;
154
+ let overMax = 0;
155
+ collides.forEach(n => {
156
+ if (n.locked || !n._rect) return;
157
+ let over = Utils.areaIntercept(r, n._rect);
158
+ if (over > overMax) {
159
+ overMax = over;
160
+ collide = n;
161
+ }
162
+ });
163
+ return {collide, over: overMax};
164
+ }
165
+ */
166
+ /** called to cache the nodes pixel rectangles used for collision detection during drag */
167
+ cacheRects(w, h, top, right, bottom, left) {
168
+ this.nodes.forEach(n => n._rect = {
169
+ y: n.y * h + top,
170
+ x: n.x * w + left,
171
+ w: n.w * w - left - right,
172
+ h: n.h * h - top - bottom
173
+ });
174
+ return this;
175
+ }
176
+ /** called to possibly swap between 2 nodes (same size or column, not locked, touching), returning true if successful */
177
+ swap(a, b) {
178
+ if (!b || b.locked || !a || a.locked)
179
+ return false;
180
+ function _doSwap() {
181
+ let x = b.x, y = b.y;
182
+ b.x = a.x;
183
+ b.y = a.y; // b -> a position
184
+ if (a.h != b.h) {
185
+ a.x = x;
186
+ a.y = b.y + b.h; // a -> goes after b
187
+ }
188
+ else if (a.w != b.w) {
189
+ a.x = b.x + b.w;
190
+ a.y = y; // a -> goes after b
191
+ }
192
+ else {
193
+ a.x = x;
194
+ a.y = y; // a -> old b position
195
+ }
196
+ a._dirty = b._dirty = true;
197
+ return true;
198
+ }
199
+ let touching; // remember if we called it (vs undefined)
200
+ // same size and same row or column, and touching
201
+ if (a.w === b.w && a.h === b.h && (a.x === b.x || a.y === b.y) && (touching = Utils.isTouching(a, b)))
202
+ return _doSwap();
203
+ if (touching === false)
204
+ return; // IFF ran test and fail, bail out
205
+ // check for taking same columns (but different height) and touching
206
+ if (a.w === b.w && a.x === b.x && (touching || (touching = Utils.isTouching(a, b)))) {
207
+ if (b.y < a.y) {
208
+ let t = a;
209
+ a = b;
210
+ b = t;
211
+ } // swap a <-> b vars so a is first
212
+ return _doSwap();
213
+ }
214
+ if (touching === false)
215
+ return;
216
+ // check if taking same row (but different width) and touching
217
+ if (a.h === b.h && a.y === b.y && (touching || (touching = Utils.isTouching(a, b)))) {
218
+ if (b.x < a.x) {
219
+ let t = a;
220
+ a = b;
221
+ b = t;
222
+ } // swap a <-> b vars so a is first
223
+ return _doSwap();
224
+ }
225
+ return false;
226
+ }
227
+ isAreaEmpty(x, y, w, h) {
228
+ let nn = { x: x || 0, y: y || 0, w: w || 1, h: h || 1 };
229
+ return !this.collide(nn);
230
+ }
231
+ /** re-layout grid items to reclaim any empty space */
232
+ compact() {
233
+ if (this.nodes.length === 0)
234
+ return this;
235
+ this.batchUpdate()
236
+ .sortNodes();
237
+ let copyNodes = this.nodes;
238
+ this.nodes = []; // pretend we have no nodes to conflict layout to start with...
239
+ copyNodes.forEach(node => {
240
+ if (!node.locked) {
241
+ node.autoPosition = true;
242
+ }
243
+ this.addNode(node, false); // 'false' for add event trigger
244
+ node._dirty = true; // will force attr update
245
+ });
246
+ return this.batchUpdate(false);
247
+ }
248
+ /** enable/disable floating widgets (default: `false`) See [example](http://gridstackjs.com/demo/float.html) */
249
+ set float(val) {
250
+ if (this._float === val)
251
+ return;
252
+ this._float = val || false;
253
+ if (!val) {
254
+ this._packNodes()._notify();
255
+ }
256
+ }
257
+ /** float getter method */
258
+ get float() { return this._float || false; }
259
+ /** sort the nodes array from first to last, or reverse. Called during collision/placement to force an order */
260
+ sortNodes(dir) {
261
+ this.nodes = Utils.sort(this.nodes, dir, this.column);
262
+ return this;
263
+ }
264
+ /** @internal called to top gravity pack the items back OR revert back to original Y positions when floating */
265
+ _packNodes() {
266
+ if (this.batchMode) {
267
+ return this;
268
+ }
269
+ this.sortNodes(); // first to last
270
+ if (this.float) {
271
+ // restore original Y pos
272
+ this.nodes.forEach(n => {
273
+ if (n._updating || n._orig === undefined || n.y === n._orig.y)
274
+ return;
275
+ let newY = n.y;
276
+ while (newY > n._orig.y) {
277
+ --newY;
278
+ let collide = this.collide(n, { x: n.x, y: newY, w: n.w, h: n.h });
279
+ if (!collide) {
280
+ n._dirty = true;
281
+ n.y = newY;
282
+ }
283
+ }
284
+ });
285
+ }
286
+ else {
287
+ // top gravity pack
288
+ this.nodes.forEach((n, i) => {
289
+ if (n.locked)
290
+ return;
291
+ while (n.y > 0) {
292
+ let newY = i === 0 ? 0 : n.y - 1;
293
+ let canBeMoved = i === 0 || !this.collide(n, { x: n.x, y: newY, w: n.w, h: n.h });
294
+ if (!canBeMoved)
295
+ break;
296
+ // Note: must be dirty (from last position) for GridStack::OnChange CB to update positions
297
+ // and move items back. The user 'change' CB should detect changes from the original
298
+ // starting position instead.
299
+ n._dirty = (n.y !== newY);
300
+ n.y = newY;
301
+ }
302
+ });
303
+ }
304
+ return this;
305
+ }
306
+ /**
307
+ * given a random node, makes sure it's coordinates/values are valid in the current grid
308
+ * @param node to adjust
309
+ * @param resizing if out of bound, resize down or move into the grid to fit ?
310
+ */
311
+ prepareNode(node, resizing) {
312
+ node = node || {};
313
+ node._id = node._id ?? GridStackEngine._idSeq++;
314
+ // if we're missing position, have the grid position us automatically (before we set them to 0,0)
315
+ if (node.x === undefined || node.y === undefined || node.x === null || node.y === null) {
316
+ node.autoPosition = true;
317
+ }
318
+ // assign defaults for missing required fields
319
+ let defaults = { x: 0, y: 0, w: 1, h: 1 };
320
+ Utils.defaults(node, defaults);
321
+ if (!node.autoPosition) {
322
+ delete node.autoPosition;
323
+ }
324
+ if (!node.noResize) {
325
+ delete node.noResize;
326
+ }
327
+ if (!node.noMove) {
328
+ delete node.noMove;
329
+ }
330
+ Utils.sanitizeMinMax(node);
331
+ // check for NaN (in case messed up strings were passed. can't do parseInt() || defaults.x above as 0 is valid #)
332
+ if (typeof node.x == 'string') {
333
+ node.x = Number(node.x);
334
+ }
335
+ if (typeof node.y == 'string') {
336
+ node.y = Number(node.y);
337
+ }
338
+ if (typeof node.w == 'string') {
339
+ node.w = Number(node.w);
340
+ }
341
+ if (typeof node.h == 'string') {
342
+ node.h = Number(node.h);
343
+ }
344
+ if (isNaN(node.x)) {
345
+ node.x = defaults.x;
346
+ node.autoPosition = true;
347
+ }
348
+ if (isNaN(node.y)) {
349
+ node.y = defaults.y;
350
+ node.autoPosition = true;
351
+ }
352
+ if (isNaN(node.w)) {
353
+ node.w = defaults.w;
354
+ }
355
+ if (isNaN(node.h)) {
356
+ node.h = defaults.h;
357
+ }
358
+ return this.nodeBoundFix(node, resizing);
359
+ }
360
+ /** part2 of preparing a node to fit inside our grid - checks for x,y,w from grid dimensions */
361
+ nodeBoundFix(node, resizing) {
362
+ let before = node._orig || Utils.copyPos({}, node);
363
+ if (node.maxW) {
364
+ node.w = Math.min(node.w, node.maxW);
365
+ }
366
+ if (node.maxH) {
367
+ node.h = Math.min(node.h, node.maxH);
368
+ }
369
+ if (node.minW && node.minW <= this.column) {
370
+ node.w = Math.max(node.w, node.minW);
371
+ }
372
+ if (node.minH) {
373
+ node.h = Math.max(node.h, node.minH);
374
+ }
375
+ // if user loaded a larger than allowed widget for current # of columns (or force 1 column mode),
376
+ // remember it's position & width so we can restore back (1 -> 12 column) #1655 #1985
377
+ // IFF we're not in the middle of column resizing!
378
+ const saveOrig = this.column === 1 || node.x + node.w > this.column;
379
+ if (saveOrig && this.column < 12 && !this._inColumnResize && node._id && this.findCacheLayout(node, 12) === -1) {
380
+ let copy = { ...node }; // need _id + positions
381
+ if (copy.autoPosition) {
382
+ delete copy.x;
383
+ delete copy.y;
384
+ }
385
+ else
386
+ copy.x = Math.min(11, copy.x);
387
+ copy.w = Math.min(12, copy.w);
388
+ this.cacheOneLayout(copy, 12);
389
+ }
390
+ if (node.w > this.column) {
391
+ node.w = this.column;
392
+ }
393
+ else if (node.w < 1) {
394
+ node.w = 1;
395
+ }
396
+ if (this.maxRow && node.h > this.maxRow) {
397
+ node.h = this.maxRow;
398
+ }
399
+ else if (node.h < 1) {
400
+ node.h = 1;
401
+ }
402
+ if (node.x < 0) {
403
+ node.x = 0;
404
+ }
405
+ if (node.y < 0) {
406
+ node.y = 0;
407
+ }
408
+ if (node.x + node.w > this.column) {
409
+ if (resizing) {
410
+ node.w = this.column - node.x;
411
+ }
412
+ else {
413
+ node.x = this.column - node.w;
414
+ }
415
+ }
416
+ if (this.maxRow && node.y + node.h > this.maxRow) {
417
+ if (resizing) {
418
+ node.h = this.maxRow - node.y;
419
+ }
420
+ else {
421
+ node.y = this.maxRow - node.h;
422
+ }
423
+ }
424
+ if (!Utils.samePos(node, before)) {
425
+ node._dirty = true;
426
+ }
427
+ return node;
428
+ }
429
+ /** returns a list of modified nodes from their original values */
430
+ getDirtyNodes(verify) {
431
+ // compare original x,y,w,h instead as _dirty can be a temporary state
432
+ if (verify) {
433
+ return this.nodes.filter(n => n._dirty && !Utils.samePos(n, n._orig));
434
+ }
435
+ return this.nodes.filter(n => n._dirty);
436
+ }
437
+ /** @internal call this to call onChange callback with dirty nodes so DOM can be updated */
438
+ _notify(removedNodes) {
439
+ if (this.batchMode || !this.onChange)
440
+ return this;
441
+ let dirtyNodes = (removedNodes || []).concat(this.getDirtyNodes());
442
+ this.onChange(dirtyNodes);
443
+ return this;
444
+ }
445
+ /** @internal remove dirty and last tried info */
446
+ cleanNodes() {
447
+ if (this.batchMode)
448
+ return this;
449
+ this.nodes.forEach(n => {
450
+ delete n._dirty;
451
+ delete n._lastTried;
452
+ });
453
+ return this;
454
+ }
455
+ /** @internal called to save initial position/size to track real dirty state.
456
+ * Note: should be called right after we call change event (so next API is can detect changes)
457
+ * as well as right before we start move/resize/enter (so we can restore items to prev values) */
458
+ saveInitial() {
459
+ this.nodes.forEach(n => {
460
+ n._orig = Utils.copyPos({}, n);
461
+ delete n._dirty;
462
+ });
463
+ this._hasLocked = this.nodes.some(n => n.locked);
464
+ return this;
465
+ }
466
+ /** @internal restore all the nodes back to initial values (called when we leave) */
467
+ restoreInitial() {
468
+ this.nodes.forEach(n => {
469
+ if (Utils.samePos(n, n._orig))
470
+ return;
471
+ Utils.copyPos(n, n._orig);
472
+ n._dirty = true;
473
+ });
474
+ this._notify();
475
+ return this;
476
+ }
477
+ /** find the first available empty spot for the given node width/height, updating the x,y attributes. return true if found.
478
+ * optionally you can pass your own existing node list and column count, otherwise defaults to that engine data.
479
+ */
480
+ findEmptyPosition(node, nodeList = this.nodes, column = this.column) {
481
+ nodeList = Utils.sort(nodeList, -1, column);
482
+ let found = false;
483
+ for (let i = 0; !found; ++i) {
484
+ let x = i % column;
485
+ let y = Math.floor(i / column);
486
+ if (x + node.w > column) {
487
+ continue;
488
+ }
489
+ let box = { x, y, w: node.w, h: node.h };
490
+ if (!nodeList.find(n => Utils.isIntercepted(box, n))) {
491
+ node.x = x;
492
+ node.y = y;
493
+ delete node.autoPosition;
494
+ found = true;
495
+ }
496
+ }
497
+ return found;
498
+ }
499
+ /** call to add the given node to our list, fixing collision and re-packing */
500
+ addNode(node, triggerAddEvent = false) {
501
+ let dup = this.nodes.find(n => n._id === node._id);
502
+ if (dup)
503
+ return dup; // prevent inserting twice! return it instead.
504
+ // skip prepareNode if we're in middle of column resize (not new) but do check for bounds!
505
+ node = this._inColumnResize ? this.nodeBoundFix(node) : this.prepareNode(node);
506
+ delete node._temporaryRemoved;
507
+ delete node._removeDOM;
508
+ if (node.autoPosition && this.findEmptyPosition(node)) {
509
+ delete node.autoPosition; // found our slot
510
+ }
511
+ this.nodes.push(node);
512
+ if (triggerAddEvent) {
513
+ this.addedNodes.push(node);
514
+ }
515
+ this._fixCollisions(node);
516
+ if (!this.batchMode) {
517
+ this._packNodes()._notify();
518
+ }
519
+ return node;
520
+ }
521
+ removeNode(node, removeDOM = true, triggerEvent = false) {
522
+ if (!this.nodes.find(n => n === node)) {
523
+ // TEST console.log(`Error: GridStackEngine.removeNode() node._id=${node._id} not found!`)
524
+ return this;
525
+ }
526
+ if (triggerEvent) { // we wait until final drop to manually track removed items (rather than during drag)
527
+ this.removedNodes.push(node);
528
+ }
529
+ if (removeDOM)
530
+ node._removeDOM = true; // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
531
+ // don't use 'faster' .splice(findIndex(),1) in case node isn't in our list, or in multiple times.
532
+ this.nodes = this.nodes.filter(n => n !== node);
533
+ return this._packNodes()
534
+ ._notify([node]);
535
+ }
536
+ removeAll(removeDOM = true) {
537
+ delete this._layouts;
538
+ if (!this.nodes.length)
539
+ return this;
540
+ removeDOM && this.nodes.forEach(n => n._removeDOM = true); // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
541
+ this.removedNodes = this.nodes;
542
+ this.nodes = [];
543
+ return this._notify(this.removedNodes);
544
+ }
545
+ /** checks if item can be moved (layout constrain) vs moveNode(), returning true if was able to move.
546
+ * In more complicated cases (maxRow) it will attempt at moving the item and fixing
547
+ * others in a clone first, then apply those changes if still within specs. */
548
+ moveNodeCheck(node, o) {
549
+ // if (node.locked) return false;
550
+ if (!this.changedPosConstrain(node, o))
551
+ return false;
552
+ o.pack = true;
553
+ // simpler case: move item directly...
554
+ if (!this.maxRow) {
555
+ return this.moveNode(node, o);
556
+ }
557
+ // complex case: create a clone with NO maxRow (will check for out of bounds at the end)
558
+ let clonedNode;
559
+ let clone = new GridStackEngine({
560
+ column: this.column,
561
+ float: this.float,
562
+ nodes: this.nodes.map(n => {
563
+ if (n === node) {
564
+ clonedNode = { ...n };
565
+ return clonedNode;
566
+ }
567
+ return { ...n };
568
+ })
569
+ });
570
+ if (!clonedNode)
571
+ return false;
572
+ // check if we're covering 50% collision and could move
573
+ let canMove = clone.moveNode(clonedNode, o) && clone.getRow() <= this.maxRow;
574
+ // else check if we can force a swap (float=true, or different shapes) on non-resize
575
+ if (!canMove && !o.resizing && o.collide) {
576
+ let collide = o.collide.el.gridstackNode; // find the source node the clone collided with at 50%
577
+ if (this.swap(node, collide)) { // swaps and mark dirty
578
+ this._notify();
579
+ return true;
580
+ }
581
+ }
582
+ if (!canMove)
583
+ return false;
584
+ // if clone was able to move, copy those mods over to us now instead of caller trying to do this all over!
585
+ // Note: we can't use the list directly as elements and other parts point to actual node, so copy content
586
+ clone.nodes.filter(n => n._dirty).forEach(c => {
587
+ let n = this.nodes.find(a => a._id === c._id);
588
+ if (!n)
589
+ return;
590
+ Utils.copyPos(n, c);
591
+ n._dirty = true;
592
+ });
593
+ this._notify();
594
+ return true;
595
+ }
596
+ /** return true if can fit in grid height constrain only (always true if no maxRow) */
597
+ willItFit(node) {
598
+ delete node._willFitPos;
599
+ if (!this.maxRow)
600
+ return true;
601
+ // create a clone with NO maxRow and check if still within size
602
+ let clone = new GridStackEngine({
603
+ column: this.column,
604
+ float: this.float,
605
+ nodes: this.nodes.map(n => { return { ...n }; })
606
+ });
607
+ let n = { ...node }; // clone node so we don't mod any settings on it but have full autoPosition and min/max as well! #1687
608
+ this.cleanupNode(n);
609
+ delete n.el;
610
+ delete n._id;
611
+ delete n.content;
612
+ delete n.grid;
613
+ clone.addNode(n);
614
+ if (clone.getRow() <= this.maxRow) {
615
+ node._willFitPos = Utils.copyPos({}, n);
616
+ return true;
617
+ }
618
+ return false;
619
+ }
620
+ /** true if x,y or w,h are different after clamping to min/max */
621
+ changedPosConstrain(node, p) {
622
+ // first make sure w,h are set for caller
623
+ p.w = p.w || node.w;
624
+ p.h = p.h || node.h;
625
+ if (node.x !== p.x || node.y !== p.y)
626
+ return true;
627
+ // check constrained w,h
628
+ if (node.maxW) {
629
+ p.w = Math.min(p.w, node.maxW);
630
+ }
631
+ if (node.maxH) {
632
+ p.h = Math.min(p.h, node.maxH);
633
+ }
634
+ if (node.minW) {
635
+ p.w = Math.max(p.w, node.minW);
636
+ }
637
+ if (node.minH) {
638
+ p.h = Math.max(p.h, node.minH);
639
+ }
640
+ return (node.w !== p.w || node.h !== p.h);
641
+ }
642
+ /** return true if the passed in node was actually moved (checks for no-op and locked) */
643
+ moveNode(node, o) {
644
+ if (!node || /*node.locked ||*/ !o)
645
+ return false;
646
+ let wasUndefinedPack;
647
+ if (o.pack === undefined) {
648
+ wasUndefinedPack = o.pack = true;
649
+ }
650
+ // constrain the passed in values and check if we're still changing our node
651
+ if (typeof o.x !== 'number') {
652
+ o.x = node.x;
653
+ }
654
+ if (typeof o.y !== 'number') {
655
+ o.y = node.y;
656
+ }
657
+ if (typeof o.w !== 'number') {
658
+ o.w = node.w;
659
+ }
660
+ if (typeof o.h !== 'number') {
661
+ o.h = node.h;
662
+ }
663
+ let resizing = (node.w !== o.w || node.h !== o.h);
664
+ let nn = Utils.copyPos({}, node, true); // get min/max out first, then opt positions next
665
+ Utils.copyPos(nn, o);
666
+ nn = this.nodeBoundFix(nn, resizing);
667
+ Utils.copyPos(o, nn);
668
+ if (Utils.samePos(node, o))
669
+ return false;
670
+ let prevPos = Utils.copyPos({}, node);
671
+ // check if we will need to fix collision at our new location
672
+ let collides = this.collideAll(node, nn, o.skip);
673
+ let needToMove = true;
674
+ if (collides.length) {
675
+ let activeDrag = node._moving && !o.nested;
676
+ // check to make sure we actually collided over 50% surface area while dragging
677
+ let collide = activeDrag ? this.directionCollideCoverage(node, o, collides) : collides[0];
678
+ // if we're enabling creation of sub-grids on the fly, see if we're covering 80% of either one, if we didn't already do that
679
+ if (activeDrag && collide && node.grid?.opts?.subGridDynamic && !node.grid._isTemp) {
680
+ let over = Utils.areaIntercept(o.rect, collide._rect);
681
+ let a1 = Utils.area(o.rect);
682
+ let a2 = Utils.area(collide._rect);
683
+ let perc = over / (a1 < a2 ? a1 : a2);
684
+ if (perc > .8) {
685
+ collide.grid.makeSubGrid(collide.el, undefined, node);
686
+ collide = undefined;
687
+ }
688
+ }
689
+ if (collide) {
690
+ needToMove = !this._fixCollisions(node, nn, collide, o); // check if already moved...
691
+ }
692
+ else {
693
+ needToMove = false; // we didn't cover >50% for a move, skip...
694
+ if (wasUndefinedPack)
695
+ delete o.pack;
696
+ }
697
+ }
698
+ // now move (to the original ask vs the collision version which might differ) and repack things
699
+ if (needToMove) {
700
+ node._dirty = true;
701
+ Utils.copyPos(node, nn);
702
+ }
703
+ if (o.pack) {
704
+ this._packNodes()
705
+ ._notify();
706
+ }
707
+ return !Utils.samePos(node, prevPos); // pack might have moved things back
708
+ }
709
+ getRow() {
710
+ return this.nodes.reduce((row, n) => Math.max(row, n.y + n.h), 0);
711
+ }
712
+ beginUpdate(node) {
713
+ if (!node._updating) {
714
+ node._updating = true;
715
+ delete node._skipDown;
716
+ if (!this.batchMode)
717
+ this.saveInitial();
718
+ }
719
+ return this;
720
+ }
721
+ endUpdate() {
722
+ let n = this.nodes.find(n => n._updating);
723
+ if (n) {
724
+ delete n._updating;
725
+ delete n._skipDown;
726
+ }
727
+ return this;
728
+ }
729
+ /** saves a copy of the largest column layout (eg 12 even when rendering oneColumnMode) so we don't loose orig layout,
730
+ * returning a list of widgets for serialization */
731
+ save(saveElement = true, saveCB) {
732
+ // use the highest layout for any saved info so we can have full detail on reload #1849
733
+ let len = this._layouts?.length;
734
+ let layout = len && this.column !== (len - 1) ? this._layouts[len - 1] : null;
735
+ let list = [];
736
+ this.sortNodes();
737
+ this.nodes.forEach(n => {
738
+ let wl = layout?.find(l => l._id === n._id);
739
+ let w = { ...n };
740
+ // use layout info instead if set
741
+ if (wl) {
742
+ w.x = wl.x;
743
+ w.y = wl.y;
744
+ w.w = wl.w;
745
+ }
746
+ Utils.removeInternalForSave(w, !saveElement);
747
+ if (saveCB)
748
+ saveCB(n, w);
749
+ list.push(w);
750
+ });
751
+ return list;
752
+ }
753
+ /** @internal called whenever a node is added or moved - updates the cached layouts */
754
+ layoutsNodesChange(nodes) {
755
+ if (!this._layouts || this._inColumnResize)
756
+ return this;
757
+ // remove smaller layouts - we will re-generate those on the fly... larger ones need to update
758
+ this._layouts.forEach((layout, column) => {
759
+ if (!layout || column === this.column)
760
+ return this;
761
+ if (column < this.column) {
762
+ this._layouts[column] = undefined;
763
+ }
764
+ else {
765
+ // we save the original x,y,w (h isn't cached) to see what actually changed to propagate better.
766
+ // NOTE: we don't need to check against out of bound scaling/moving as that will be done when using those cache values. #1785
767
+ let ratio = column / this.column;
768
+ nodes.forEach(node => {
769
+ if (!node._orig)
770
+ return; // didn't change (newly added ?)
771
+ let n = layout.find(l => l._id === node._id);
772
+ if (!n)
773
+ return; // no cache for new nodes. Will use those values.
774
+ // Y changed, push down same amount
775
+ // TODO: detect doing item 'swaps' will help instead of move (especially in 1 column mode)
776
+ if (node.y !== node._orig.y) {
777
+ n.y += (node.y - node._orig.y);
778
+ }
779
+ // X changed, scale from new position
780
+ if (node.x !== node._orig.x) {
781
+ n.x = Math.round(node.x * ratio);
782
+ }
783
+ // width changed, scale from new width
784
+ if (node.w !== node._orig.w) {
785
+ n.w = Math.round(node.w * ratio);
786
+ }
787
+ // ...height always carries over from cache
788
+ });
789
+ }
790
+ });
791
+ return this;
792
+ }
793
+ /**
794
+ * @internal Called to scale the widget width & position up/down based on the column change.
795
+ * Note we store previous layouts (especially original ones) to make it possible to go
796
+ * from say 12 -> 1 -> 12 and get back to where we were.
797
+ *
798
+ * @param prevColumn previous number of columns
799
+ * @param column new column number
800
+ * @param nodes different sorted list (ex: DOM order) instead of current list
801
+ * @param layout specify the type of re-layout that will happen (position, size, etc...).
802
+ * Note: items will never be outside of the current column boundaries. default (moveScale). Ignored for 1 column
803
+ */
804
+ updateNodeWidths(prevColumn, column, nodes, layout = 'moveScale') {
805
+ if (!this.nodes.length || !column || prevColumn === column)
806
+ return this;
807
+ // cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data
808
+ this.cacheLayout(this.nodes, prevColumn);
809
+ this.batchUpdate(); // do this EARLY as it will call saveInitial() so we can detect where we started for _dirty and collision
810
+ let newNodes = [];
811
+ // if we're going to 1 column and using DOM order rather than default sorting, then generate that layout
812
+ let domOrder = false;
813
+ if (column === 1 && nodes?.length) {
814
+ domOrder = true;
815
+ let top = 0;
816
+ nodes.forEach(n => {
817
+ n.x = 0;
818
+ n.w = 1;
819
+ n.y = Math.max(n.y, top);
820
+ top = n.y + n.h;
821
+ });
822
+ newNodes = nodes;
823
+ nodes = [];
824
+ }
825
+ else {
826
+ nodes = Utils.sort(this.nodes, -1, prevColumn); // current column reverse sorting so we can insert last to front (limit collision)
827
+ }
828
+ // see if we have cached previous layout IFF we are going up in size (restore) otherwise always
829
+ // generate next size down from where we are (looks more natural as you gradually size down).
830
+ let cacheNodes = [];
831
+ if (column > prevColumn) {
832
+ cacheNodes = this._layouts[column] || [];
833
+ // ...if not, start with the largest layout (if not already there) as down-scaling is more accurate
834
+ // by pretending we came from that larger column by assigning those values as starting point
835
+ let lastIndex = this._layouts.length - 1;
836
+ if (!cacheNodes.length && prevColumn !== lastIndex && this._layouts[lastIndex]?.length) {
837
+ prevColumn = lastIndex;
838
+ this._layouts[lastIndex].forEach(cacheNode => {
839
+ let n = nodes.find(n => n._id === cacheNode._id);
840
+ if (n) {
841
+ // still current, use cache info positions
842
+ n.x = cacheNode.x;
843
+ n.y = cacheNode.y;
844
+ n.w = cacheNode.w;
845
+ }
846
+ });
847
+ }
848
+ }
849
+ // if we found cache re-use those nodes that are still current
850
+ cacheNodes.forEach(cacheNode => {
851
+ let j = nodes.findIndex(n => n._id === cacheNode._id);
852
+ if (j !== -1) {
853
+ // still current, use cache info positions
854
+ if (cacheNode.autoPosition || isNaN(cacheNode.x) || isNaN(cacheNode.y)) {
855
+ this.findEmptyPosition(cacheNode, newNodes);
856
+ }
857
+ if (!cacheNode.autoPosition) {
858
+ nodes[j].x = cacheNode.x;
859
+ nodes[j].y = cacheNode.y;
860
+ nodes[j].w = cacheNode.w;
861
+ newNodes.push(nodes[j]);
862
+ }
863
+ nodes.splice(j, 1);
864
+ }
865
+ });
866
+ // ...and add any extra non-cached ones
867
+ if (nodes.length) {
868
+ if (typeof layout === 'function') {
869
+ layout(column, prevColumn, newNodes, nodes);
870
+ }
871
+ else if (!domOrder) {
872
+ let ratio = column / prevColumn;
873
+ let move = (layout === 'move' || layout === 'moveScale');
874
+ let scale = (layout === 'scale' || layout === 'moveScale');
875
+ nodes.forEach(node => {
876
+ // NOTE: x + w could be outside of the grid, but addNode() below will handle that
877
+ node.x = (column === 1 ? 0 : (move ? Math.round(node.x * ratio) : Math.min(node.x, column - 1)));
878
+ node.w = ((column === 1 || prevColumn === 1) ? 1 :
879
+ scale ? (Math.round(node.w * ratio) || 1) : (Math.min(node.w, column)));
880
+ newNodes.push(node);
881
+ });
882
+ nodes = [];
883
+ }
884
+ }
885
+ // finally re-layout them in reverse order (to get correct placement)
886
+ if (!domOrder)
887
+ newNodes = Utils.sort(newNodes, -1, column);
888
+ this._inColumnResize = true; // prevent cache update
889
+ this.nodes = []; // pretend we have no nodes to start with (add() will use same structures) to simplify layout
890
+ newNodes.forEach(node => {
891
+ this.addNode(node, false); // 'false' for add event trigger
892
+ delete node._orig; // make sure the commit doesn't try to restore things back to original
893
+ });
894
+ this.batchUpdate(false);
895
+ delete this._inColumnResize;
896
+ return this;
897
+ }
898
+ /**
899
+ * call to cache the given layout internally to the given location so we can restore back when column changes size
900
+ * @param nodes list of nodes
901
+ * @param column corresponding column index to save it under
902
+ * @param clear if true, will force other caches to be removed (default false)
903
+ */
904
+ cacheLayout(nodes, column, clear = false) {
905
+ let copy = [];
906
+ nodes.forEach((n, i) => {
907
+ n._id = n._id ?? GridStackEngine._idSeq++; // make sure we have an id in case this is new layout, else re-use id already set
908
+ copy[i] = { x: n.x, y: n.y, w: n.w, _id: n._id }; // only thing we change is x,y,w and id to find it back
909
+ });
910
+ this._layouts = clear ? [] : this._layouts || []; // use array to find larger quick
911
+ this._layouts[column] = copy;
912
+ return this;
913
+ }
914
+ /**
915
+ * call to cache the given node layout internally to the given location so we can restore back when column changes size
916
+ * @param node single node to cache
917
+ * @param column corresponding column index to save it under
918
+ */
919
+ cacheOneLayout(n, column) {
920
+ n._id = n._id ?? GridStackEngine._idSeq++;
921
+ let l = { x: n.x, y: n.y, w: n.w, _id: n._id };
922
+ if (n.autoPosition) {
923
+ delete l.x;
924
+ delete l.y;
925
+ l.autoPosition = true;
926
+ }
927
+ this._layouts = this._layouts || [];
928
+ this._layouts[column] = this._layouts[column] || [];
929
+ let index = this.findCacheLayout(n, column);
930
+ if (index === -1)
931
+ this._layouts[column].push(l);
932
+ else
933
+ this._layouts[column][index] = l;
934
+ return this;
935
+ }
936
+ findCacheLayout(n, column) {
937
+ return this._layouts?.[column]?.findIndex(l => l._id === n._id) ?? -1;
938
+ }
939
+ /** called to remove all internal values but the _id */
940
+ cleanupNode(node) {
941
+ for (let prop in node) {
942
+ if (prop[0] === '_' && prop !== '_id')
943
+ delete node[prop];
944
+ }
945
+ return this;
946
+ }
947
+ }
948
+ /** @internal unique global internal _id counter */
949
+ GridStackEngine._idSeq = 0;
950
+ export { GridStackEngine };
937
951
  //# sourceMappingURL=gridstack-engine.js.map