gridstack 12.3.3 → 12.4.1

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 (124) hide show
  1. package/dist/angular/esm2020/lib/base-widget.mjs +2 -2
  2. package/dist/angular/esm2020/lib/gridstack-item.component.mjs +2 -2
  3. package/dist/angular/esm2020/lib/gridstack.component.mjs +4 -2
  4. package/dist/angular/esm2020/lib/gridstack.module.mjs +2 -2
  5. package/dist/angular/esm2020/lib/types.mjs +2 -2
  6. package/dist/angular/fesm2015/gridstack-angular.mjs +6 -4
  7. package/dist/angular/fesm2015/gridstack-angular.mjs.map +1 -1
  8. package/dist/angular/fesm2020/gridstack-angular.mjs +7 -5
  9. package/dist/angular/fesm2020/gridstack-angular.mjs.map +1 -1
  10. package/dist/angular/lib/gridstack-item.component.d.ts +1 -1
  11. package/dist/angular/lib/gridstack.component.d.ts +4 -4
  12. package/dist/angular/lib/types.d.ts +1 -1
  13. package/dist/angular/package.json +1 -1
  14. package/dist/angular/src/base-widget.ts +13 -13
  15. package/dist/angular/src/gridstack-item.component.ts +5 -5
  16. package/dist/angular/src/gridstack.component.ts +34 -32
  17. package/dist/angular/src/gridstack.module.ts +4 -4
  18. package/dist/angular/src/types.ts +3 -2
  19. package/dist/dd-base-impl.d.ts +1 -1
  20. package/dist/dd-base-impl.js +1 -1
  21. package/dist/dd-base-impl.js.map +1 -1
  22. package/dist/dd-draggable.d.ts +1 -1
  23. package/dist/dd-draggable.js +1 -1
  24. package/dist/dd-draggable.js.map +1 -1
  25. package/dist/dd-droppable.d.ts +1 -1
  26. package/dist/dd-droppable.js +7 -1
  27. package/dist/dd-droppable.js.map +1 -1
  28. package/dist/dd-element.d.ts +1 -1
  29. package/dist/dd-element.js +1 -1
  30. package/dist/dd-element.js.map +1 -1
  31. package/dist/dd-gridstack.d.ts +1 -1
  32. package/dist/dd-gridstack.js +1 -1
  33. package/dist/dd-gridstack.js.map +1 -1
  34. package/dist/dd-manager.d.ts +1 -1
  35. package/dist/dd-manager.js +1 -1
  36. package/dist/dd-manager.js.map +1 -1
  37. package/dist/dd-resizable-handle.d.ts +5 -4
  38. package/dist/dd-resizable-handle.js +23 -8
  39. package/dist/dd-resizable-handle.js.map +1 -1
  40. package/dist/dd-resizable.d.ts +3 -5
  41. package/dist/dd-resizable.js +5 -10
  42. package/dist/dd-resizable.js.map +1 -1
  43. package/dist/dd-touch.d.ts +1 -1
  44. package/dist/dd-touch.js +1 -1
  45. package/dist/dd-touch.js.map +1 -1
  46. package/dist/gridstack-all.js +1 -1
  47. package/dist/gridstack-all.js.LICENSE.txt +1 -1
  48. package/dist/gridstack-all.js.map +1 -1
  49. package/dist/gridstack-engine.d.ts +1 -1
  50. package/dist/gridstack-engine.js +7 -1
  51. package/dist/gridstack-engine.js.map +1 -1
  52. package/dist/gridstack.css +1 -1
  53. package/dist/gridstack.d.ts +1 -2
  54. package/dist/gridstack.js +9 -18
  55. package/dist/gridstack.js.map +1 -1
  56. package/dist/types.d.ts +11 -6
  57. package/dist/types.js +1 -1
  58. package/dist/types.js.map +1 -1
  59. package/dist/utils.d.ts +1 -1
  60. package/dist/utils.js +78 -69
  61. package/dist/utils.js.map +1 -1
  62. package/doc/API.md +166 -165
  63. package/package.json +5 -5
  64. package/dist/spec/gridstack-engine-spec.d.ts +0 -1
  65. package/dist/spec/gridstack-engine-spec.js +0 -358
  66. package/dist/spec/gridstack-engine-spec.js.map +0 -1
  67. package/dist/spec/gridstack-spec.d.ts +0 -1
  68. package/dist/spec/gridstack-spec.js +0 -1780
  69. package/dist/spec/gridstack-spec.js.map +0 -1
  70. package/dist/spec/integration/gridstack-integration.spec.d.ts +0 -1
  71. package/dist/spec/integration/gridstack-integration.spec.js +0 -171
  72. package/dist/spec/integration/gridstack-integration.spec.js.map +0 -1
  73. package/dist/spec/regression-spec.d.ts +0 -1
  74. package/dist/spec/regression-spec.js +0 -100
  75. package/dist/spec/regression-spec.js.map +0 -1
  76. package/dist/spec/utils-spec.d.ts +0 -1
  77. package/dist/spec/utils-spec.js +0 -243
  78. package/dist/spec/utils-spec.js.map +0 -1
  79. package/dist/src/dd-base-impl.d.ts +0 -69
  80. package/dist/src/dd-base-impl.js +0 -70
  81. package/dist/src/dd-base-impl.js.map +0 -1
  82. package/dist/src/dd-draggable.d.ts +0 -20
  83. package/dist/src/dd-draggable.js +0 -364
  84. package/dist/src/dd-draggable.js.map +0 -1
  85. package/dist/src/dd-droppable.d.ts +0 -26
  86. package/dist/src/dd-droppable.js +0 -149
  87. package/dist/src/dd-droppable.js.map +0 -1
  88. package/dist/src/dd-element.d.ts +0 -27
  89. package/dist/src/dd-element.js +0 -91
  90. package/dist/src/dd-element.js.map +0 -1
  91. package/dist/src/dd-gridstack.d.ts +0 -82
  92. package/dist/src/dd-gridstack.js +0 -165
  93. package/dist/src/dd-gridstack.js.map +0 -1
  94. package/dist/src/dd-manager.d.ts +0 -43
  95. package/dist/src/dd-manager.js +0 -14
  96. package/dist/src/dd-manager.js.map +0 -1
  97. package/dist/src/dd-resizable-handle.d.ts +0 -18
  98. package/dist/src/dd-resizable-handle.js +0 -113
  99. package/dist/src/dd-resizable-handle.js.map +0 -1
  100. package/dist/src/dd-resizable.d.ts +0 -30
  101. package/dist/src/dd-resizable.js +0 -304
  102. package/dist/src/dd-resizable.js.map +0 -1
  103. package/dist/src/dd-touch.d.ts +0 -33
  104. package/dist/src/dd-touch.js +0 -145
  105. package/dist/src/dd-touch.js.map +0 -1
  106. package/dist/src/gridstack-engine.d.ts +0 -321
  107. package/dist/src/gridstack-engine.js +0 -1272
  108. package/dist/src/gridstack-engine.js.map +0 -1
  109. package/dist/src/gridstack.d.ts +0 -802
  110. package/dist/src/gridstack.js +0 -2872
  111. package/dist/src/gridstack.js.map +0 -1
  112. package/dist/src/gridstack.scss +0 -157
  113. package/dist/src/types.d.ts +0 -427
  114. package/dist/src/types.js +0 -38
  115. package/dist/src/types.js.map +0 -1
  116. package/dist/src/utils.d.ts +0 -283
  117. package/dist/src/utils.js +0 -790
  118. package/dist/src/utils.js.map +0 -1
  119. package/dist/vitest.config.d.ts +0 -2
  120. package/dist/vitest.config.js +0 -74
  121. package/dist/vitest.config.js.map +0 -1
  122. package/dist/vitest.setup.d.ts +0 -1
  123. package/dist/vitest.setup.js +0 -90
  124. package/dist/vitest.setup.js.map +0 -1
@@ -1,2872 +0,0 @@
1
- /*!
2
- * GridStack 12.3.3
3
- * https://gridstackjs.com/
4
- *
5
- * Copyright (c) 2021-2025 Alain Dumesny
6
- * see root license https://github.com/gridstack/gridstack.js/tree/master/LICENSE
7
- */
8
- import { GridStackEngine } from './gridstack-engine';
9
- import { Utils, obsolete } from './utils';
10
- import { gridDefaults } from './types';
11
- /*
12
- * and include D&D by default
13
- * TODO: while we could generate a gridstack-static.js at smaller size - saves about 31k (41k -> 72k)
14
- * I don't know how to generate the DD only code at the remaining 31k to delay load as code depends on Gridstack.ts
15
- * also it caused loading issues in prod - see https://github.com/gridstack/gridstack.js/issues/2039
16
- */
17
- import { DDGridStack } from './dd-gridstack';
18
- import { isTouch } from './dd-touch';
19
- import { DDManager } from './dd-manager';
20
- const dd = new DDGridStack;
21
- // export all dependent file as well to make it easier for users to just import the main file
22
- export * from './types';
23
- export * from './utils';
24
- export * from './gridstack-engine';
25
- export * from './dd-gridstack';
26
- export * from './dd-manager';
27
- export * from './dd-element';
28
- export * from './dd-draggable';
29
- export * from './dd-droppable';
30
- export * from './dd-resizable';
31
- export * from './dd-resizable-handle';
32
- export * from './dd-base-impl';
33
- /**
34
- * Main gridstack class - you will need to call `GridStack.init()` first to initialize your grid.
35
- * Note: your grid elements MUST have the following classes for the CSS layout to work:
36
- * @example
37
- * <div class="grid-stack">
38
- * <div class="grid-stack-item">
39
- * <div class="grid-stack-item-content">Item 1</div>
40
- * </div>
41
- * </div>
42
- */
43
- class GridStack {
44
- /**
45
- * initializing the HTML element, or selector string, into a grid will return the grid. Calling it again will
46
- * simply return the existing instance (ignore any passed options). There is also an initAll() version that support
47
- * multiple grids initialization at once. Or you can use addGrid() to create the entire grid from JSON.
48
- * @param options grid options (optional)
49
- * @param elOrString element or CSS selector (first one used) to convert to a grid (default to '.grid-stack' class selector)
50
- *
51
- * @example
52
- * const grid = GridStack.init();
53
- *
54
- * Note: the HTMLElement (of type GridHTMLElement) will store a `gridstack: GridStack` value that can be retrieve later
55
- * const grid = document.querySelector('.grid-stack').gridstack;
56
- */
57
- static init(options = {}, elOrString = '.grid-stack') {
58
- if (typeof document === 'undefined')
59
- return null; // temp workaround SSR
60
- const el = GridStack.getGridElement(elOrString);
61
- if (!el) {
62
- if (typeof elOrString === 'string') {
63
- console.error('GridStack.initAll() no grid was found with selector "' + elOrString + '" - element missing or wrong selector ?' +
64
- '\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.');
65
- }
66
- else {
67
- console.error('GridStack.init() no grid element was passed.');
68
- }
69
- return null;
70
- }
71
- if (!el.gridstack) {
72
- el.gridstack = new GridStack(el, Utils.cloneDeep(options));
73
- }
74
- return el.gridstack;
75
- }
76
- /**
77
- * Will initialize a list of elements (given a selector) and return an array of grids.
78
- * @param options grid options (optional)
79
- * @param selector elements selector to convert to grids (default to '.grid-stack' class selector)
80
- *
81
- * @example
82
- * const grids = GridStack.initAll();
83
- * grids.forEach(...)
84
- */
85
- static initAll(options = {}, selector = '.grid-stack') {
86
- const grids = [];
87
- if (typeof document === 'undefined')
88
- return grids; // temp workaround SSR
89
- GridStack.getGridElements(selector).forEach(el => {
90
- if (!el.gridstack) {
91
- el.gridstack = new GridStack(el, Utils.cloneDeep(options));
92
- }
93
- grids.push(el.gridstack);
94
- });
95
- if (grids.length === 0) {
96
- console.error('GridStack.initAll() no grid was found with selector "' + selector + '" - element missing or wrong selector ?' +
97
- '\nNote: ".grid-stack" is required for proper CSS styling and drag/drop, and is the default selector.');
98
- }
99
- return grids;
100
- }
101
- /**
102
- * call to create a grid with the given options, including loading any children from JSON structure. This will call GridStack.init(), then
103
- * grid.load() on any passed children (recursively). Great alternative to calling init() if you want entire grid to come from
104
- * JSON serialized data, including options.
105
- * @param parent HTML element parent to the grid
106
- * @param opt grids options used to initialize the grid, and list of children
107
- */
108
- static addGrid(parent, opt = {}) {
109
- if (!parent)
110
- return null;
111
- let el = parent;
112
- if (el.gridstack) {
113
- // already a grid - set option and load data
114
- const grid = el.gridstack;
115
- if (opt)
116
- grid.opts = { ...grid.opts, ...opt };
117
- if (opt.children !== undefined)
118
- grid.load(opt.children);
119
- return grid;
120
- }
121
- // create the grid element, but check if the passed 'parent' already has grid styling and should be used instead
122
- const parentIsGrid = parent.classList.contains('grid-stack');
123
- if (!parentIsGrid || GridStack.addRemoveCB) {
124
- if (GridStack.addRemoveCB) {
125
- el = GridStack.addRemoveCB(parent, opt, true, true);
126
- }
127
- else {
128
- el = Utils.createDiv(['grid-stack', opt.class], parent);
129
- }
130
- }
131
- // create grid class and load any children
132
- const grid = GridStack.init(opt, el);
133
- return grid;
134
- }
135
- /** call this method to register your engine instead of the default one.
136
- * See instead `GridStackOptions.engineClass` if you only need to
137
- * replace just one instance.
138
- */
139
- static registerEngine(engineClass) {
140
- GridStack.engineClass = engineClass;
141
- }
142
- /**
143
- * @internal create placeholder DIV as needed
144
- * @returns the placeholder element for indicating drop zones during drag operations
145
- */
146
- get placeholder() {
147
- if (!this._placeholder) {
148
- this._placeholder = Utils.createDiv([this.opts.placeholderClass, gridDefaults.itemClass, this.opts.itemClass]);
149
- const placeholderChild = Utils.createDiv(['placeholder-content'], this._placeholder);
150
- if (this.opts.placeholderText) {
151
- placeholderChild.textContent = this.opts.placeholderText;
152
- }
153
- }
154
- return this._placeholder;
155
- }
156
- /**
157
- * Construct a grid item from the given element and options
158
- * @param el the HTML element tied to this grid after it's been initialized
159
- * @param opts grid options - public for classes to access, but use methods to modify!
160
- */
161
- constructor(el, opts = {}) {
162
- this.el = el;
163
- this.opts = opts;
164
- /** time to wait for animation (if enabled) to be done so content sizing can happen */
165
- this.animationDelay = 300 + 10;
166
- /** @internal */
167
- this._gsEventHandler = {};
168
- /** @internal extra row added when dragging at the bottom of the grid */
169
- this._extraDragRow = 0;
170
- /** @internal meant to store the scale of the active grid */
171
- this.dragTransform = { xScale: 1, yScale: 1, xOffset: 0, yOffset: 0 };
172
- el.gridstack = this;
173
- this.opts = opts = opts || {}; // handles null/undefined/0
174
- if (!el.classList.contains('grid-stack')) {
175
- this.el.classList.add('grid-stack');
176
- }
177
- // if row property exists, replace minRow and maxRow instead
178
- if (opts.row) {
179
- opts.minRow = opts.maxRow = opts.row;
180
- delete opts.row;
181
- }
182
- const rowAttr = Utils.toNumber(el.getAttribute('gs-row'));
183
- // flag only valid in sub-grids (handled by parent, not here)
184
- if (opts.column === 'auto') {
185
- delete opts.column;
186
- }
187
- // save original setting so we can restore on save
188
- if (opts.alwaysShowResizeHandle !== undefined) {
189
- opts._alwaysShowResizeHandle = opts.alwaysShowResizeHandle;
190
- }
191
- // cleanup responsive opts (must have columnWidth | breakpoints) then sort breakpoints by size (so we can match during resize)
192
- const resp = opts.columnOpts;
193
- if (resp) {
194
- const bk = resp.breakpoints;
195
- if (!resp.columnWidth && !bk?.length) {
196
- delete opts.columnOpts;
197
- }
198
- else {
199
- resp.columnMax = resp.columnMax || 12;
200
- if (bk?.length > 1)
201
- bk.sort((a, b) => (b.w || 0) - (a.w || 0));
202
- }
203
- }
204
- // elements DOM attributes override any passed options (like CSS style) - merge the two together
205
- const defaults = {
206
- ...Utils.cloneDeep(gridDefaults),
207
- column: Utils.toNumber(el.getAttribute('gs-column')) || gridDefaults.column,
208
- minRow: rowAttr ? rowAttr : Utils.toNumber(el.getAttribute('gs-min-row')) || gridDefaults.minRow,
209
- maxRow: rowAttr ? rowAttr : Utils.toNumber(el.getAttribute('gs-max-row')) || gridDefaults.maxRow,
210
- staticGrid: Utils.toBool(el.getAttribute('gs-static')) || gridDefaults.staticGrid,
211
- sizeToContent: Utils.toBool(el.getAttribute('gs-size-to-content')) || undefined,
212
- draggable: {
213
- handle: (opts.handleClass ? '.' + opts.handleClass : (opts.handle ? opts.handle : '')) || gridDefaults.draggable.handle,
214
- },
215
- removableOptions: {
216
- accept: opts.itemClass || gridDefaults.removableOptions.accept,
217
- decline: gridDefaults.removableOptions.decline
218
- },
219
- };
220
- if (el.getAttribute('gs-animate')) { // default to true, but if set to false use that instead
221
- defaults.animate = Utils.toBool(el.getAttribute('gs-animate'));
222
- }
223
- opts = Utils.defaults(opts, defaults);
224
- this._initMargin(); // part of settings defaults...
225
- // Now check if we're loading into !12 column mode FIRST so we don't do un-necessary work (like cellHeight = width / 12 then go 1 column)
226
- this.checkDynamicColumn();
227
- this._updateColumnVar(opts);
228
- if (opts.rtl === 'auto') {
229
- opts.rtl = (el.style.direction === 'rtl');
230
- }
231
- if (opts.rtl) {
232
- this.el.classList.add('grid-stack-rtl');
233
- }
234
- // check if we're been nested, and if so update our style and keep pointer around (used during save)
235
- const parentGridItem = this.el.closest('.' + gridDefaults.itemClass);
236
- const parentNode = parentGridItem?.gridstackNode;
237
- if (parentNode) {
238
- parentNode.subGrid = this;
239
- this.parentGridNode = parentNode;
240
- this.el.classList.add('grid-stack-nested');
241
- parentNode.el.classList.add('grid-stack-sub-grid');
242
- }
243
- this._isAutoCellHeight = (opts.cellHeight === 'auto');
244
- if (this._isAutoCellHeight || opts.cellHeight === 'initial') {
245
- // make the cell content square initially (will use resize/column event to keep it square)
246
- this.cellHeight(undefined);
247
- }
248
- else {
249
- // append unit if any are set
250
- if (typeof opts.cellHeight == 'number' && opts.cellHeightUnit && opts.cellHeightUnit !== gridDefaults.cellHeightUnit) {
251
- opts.cellHeight = opts.cellHeight + opts.cellHeightUnit;
252
- delete opts.cellHeightUnit;
253
- }
254
- const val = opts.cellHeight;
255
- delete opts.cellHeight; // force initial cellHeight() call to set the value
256
- this.cellHeight(val);
257
- }
258
- // see if we need to adjust auto-hide
259
- if (opts.alwaysShowResizeHandle === 'mobile') {
260
- opts.alwaysShowResizeHandle = isTouch;
261
- }
262
- this._setStaticClass();
263
- const engineClass = opts.engineClass || GridStack.engineClass || GridStackEngine;
264
- this.engine = new engineClass({
265
- column: this.getColumn(),
266
- float: opts.float,
267
- maxRow: opts.maxRow,
268
- onChange: (cbNodes) => {
269
- cbNodes.forEach(n => {
270
- const el = n.el;
271
- if (!el)
272
- return;
273
- if (n._removeDOM) {
274
- if (el)
275
- el.remove();
276
- delete n._removeDOM;
277
- }
278
- else {
279
- this._writePosAttr(el, n);
280
- }
281
- });
282
- this._updateContainerHeight();
283
- }
284
- });
285
- if (opts.auto) {
286
- this.batchUpdate(); // prevent in between re-layout #1535 TODO: this only set float=true, need to prevent collision check...
287
- this.engine._loading = true; // loading collision check
288
- this.getGridItems().forEach(el => this._prepareElement(el));
289
- delete this.engine._loading;
290
- this.batchUpdate(false);
291
- }
292
- // load any passed in children as well, which overrides any DOM layout done above
293
- if (opts.children) {
294
- const children = opts.children;
295
- delete opts.children;
296
- if (children.length)
297
- this.load(children); // don't load empty
298
- }
299
- this.setAnimation();
300
- // dynamic grids require pausing during drag to detect over to nest vs push
301
- if (opts.subGridDynamic && !DDManager.pauseDrag)
302
- DDManager.pauseDrag = true;
303
- if (opts.draggable?.pause !== undefined)
304
- DDManager.pauseDrag = opts.draggable.pause;
305
- this._setupRemoveDrop();
306
- this._setupAcceptWidget();
307
- this._updateResizeEvent();
308
- }
309
- _updateColumnVar(opts = this.opts) {
310
- this.el.classList.add('gs-' + opts.column);
311
- if (typeof opts.column === 'number')
312
- this.el.style.setProperty('--gs-column-width', `${100 / opts.column}%`);
313
- }
314
- /**
315
- * add a new widget and returns it.
316
- *
317
- * Widget will be always placed even if result height is more than actual grid height.
318
- * You need to use `willItFit()` before calling addWidget for additional check.
319
- * See also `makeWidget(el)` for DOM element.
320
- *
321
- * @example
322
- * const grid = GridStack.init();
323
- * grid.addWidget({w: 3, content: 'hello'});
324
- *
325
- * @param w GridStackWidget definition. used MakeWidget(el) if you have dom element instead.
326
- */
327
- addWidget(w) {
328
- if (!w)
329
- return;
330
- if (typeof w === 'string') {
331
- console.error('V11: GridStack.addWidget() does not support string anymore. see #2736');
332
- return;
333
- }
334
- if (w.ELEMENT_NODE) {
335
- console.error('V11: GridStack.addWidget() does not support HTMLElement anymore. use makeWidget()');
336
- return this.makeWidget(w);
337
- }
338
- let el;
339
- let node = w;
340
- node.grid = this;
341
- if (node.el) {
342
- el = node.el; // re-use element stored in the node
343
- }
344
- else if (GridStack.addRemoveCB) {
345
- el = GridStack.addRemoveCB(this.el, w, true, false);
346
- }
347
- else {
348
- el = this.createWidgetDivs(node);
349
- }
350
- if (!el)
351
- return;
352
- // if the caller ended up initializing the widget in addRemoveCB, or we stared with one already, skip the rest
353
- node = el.gridstackNode;
354
- if (node && el.parentElement === this.el && this.engine.nodes.find(n => n._id === node._id))
355
- return el;
356
- // Tempting to initialize the passed in opt with default and valid values, but this break knockout demos
357
- // as the actual value are filled in when _prepareElement() calls el.getAttribute('gs-xyz') before adding the node.
358
- // So make sure we load any DOM attributes that are not specified in passed in options (which override)
359
- const domAttr = this._readAttr(el);
360
- Utils.defaults(w, domAttr);
361
- this.engine.prepareNode(w);
362
- // this._writeAttr(el, w); why write possibly incorrect values back when makeWidget() will ?
363
- this.el.appendChild(el);
364
- this.makeWidget(el, w);
365
- return el;
366
- }
367
- /**
368
- * Create the default grid item divs and content (possibly lazy loaded) by using GridStack.renderCB().
369
- *
370
- * @param n GridStackNode definition containing widget configuration
371
- * @returns the created HTML element with proper grid item structure
372
- *
373
- * @example
374
- * const element = grid.createWidgetDivs({ w: 2, h: 1, content: 'Hello World' });
375
- */
376
- createWidgetDivs(n) {
377
- const el = Utils.createDiv(['grid-stack-item', this.opts.itemClass]);
378
- const cont = Utils.createDiv(['grid-stack-item-content'], el);
379
- if (Utils.lazyLoad(n)) {
380
- if (!n.visibleObservable) {
381
- n.visibleObservable = new IntersectionObserver(([entry]) => {
382
- if (entry.isIntersecting) {
383
- n.visibleObservable?.disconnect();
384
- delete n.visibleObservable;
385
- GridStack.renderCB(cont, n);
386
- n.grid?.prepareDragDrop(n.el);
387
- }
388
- });
389
- window.setTimeout(() => n.visibleObservable?.observe(el)); // wait until callee sets position attributes
390
- }
391
- }
392
- else
393
- GridStack.renderCB(cont, n);
394
- return el;
395
- }
396
- /**
397
- * Convert an existing gridItem element into a sub-grid with the given (optional) options, else inherit them
398
- * from the parent's subGrid options.
399
- * @param el gridItem element to convert
400
- * @param ops (optional) sub-grid options, else default to node, then parent settings, else defaults
401
- * @param nodeToAdd (optional) node to add to the newly created sub grid (used when dragging over existing regular item)
402
- * @param saveContent if true (default) the html inside .grid-stack-content will be saved to child widget
403
- * @returns newly created grid
404
- */
405
- makeSubGrid(el, ops, nodeToAdd, saveContent = true) {
406
- let node = el.gridstackNode;
407
- if (!node) {
408
- node = this.makeWidget(el).gridstackNode;
409
- }
410
- if (node.subGrid?.el)
411
- return node.subGrid; // already done
412
- // find the template subGrid stored on a parent as fallback...
413
- let subGridTemplate; // eslint-disable-next-line @typescript-eslint/no-this-alias
414
- let grid = this;
415
- while (grid && !subGridTemplate) {
416
- subGridTemplate = grid.opts?.subGridOpts;
417
- grid = grid.parentGridNode?.grid;
418
- }
419
- //... and set the create options
420
- ops = Utils.cloneDeep({
421
- // by default sub-grid inherit from us | parent, other than id, children, etc...
422
- ...this.opts, id: undefined, children: undefined, column: 'auto', columnOpts: undefined, layout: 'list', subGridOpts: undefined,
423
- ...(subGridTemplate || {}),
424
- ...(ops || node.subGridOpts || {})
425
- });
426
- node.subGridOpts = ops;
427
- // if column special case it set, remember that flag and set default
428
- let autoColumn;
429
- if (ops.column === 'auto') {
430
- autoColumn = true;
431
- ops.column = Math.max(node.w || 1, nodeToAdd?.w || 1);
432
- delete ops.columnOpts; // driven by parent
433
- }
434
- // if we're converting an existing full item, move over the content to be the first sub item in the new grid
435
- let content = node.el.querySelector('.grid-stack-item-content');
436
- let newItem;
437
- let newItemOpt;
438
- if (saveContent) {
439
- this._removeDD(node.el); // remove D&D since it's set on content div
440
- newItemOpt = { ...node, x: 0, y: 0 };
441
- Utils.removeInternalForSave(newItemOpt);
442
- delete newItemOpt.subGridOpts;
443
- if (node.content) {
444
- newItemOpt.content = node.content;
445
- delete node.content;
446
- }
447
- if (GridStack.addRemoveCB) {
448
- newItem = GridStack.addRemoveCB(this.el, newItemOpt, true, false);
449
- }
450
- else {
451
- newItem = Utils.createDiv(['grid-stack-item']);
452
- newItem.appendChild(content);
453
- content = Utils.createDiv(['grid-stack-item-content'], node.el);
454
- }
455
- this.prepareDragDrop(node.el); // ... and restore original D&D
456
- }
457
- // if we're adding an additional item, make the container large enough to have them both
458
- if (nodeToAdd) {
459
- const w = autoColumn ? ops.column : node.w;
460
- const h = node.h + nodeToAdd.h;
461
- const style = node.el.style;
462
- style.transition = 'none'; // show up instantly so we don't see scrollbar with nodeToAdd
463
- this.update(node.el, { w, h });
464
- setTimeout(() => style.transition = null); // recover animation
465
- }
466
- const subGrid = node.subGrid = GridStack.addGrid(content, ops);
467
- if (nodeToAdd?._moving)
468
- subGrid._isTemp = true; // prevent re-nesting as we add over
469
- if (autoColumn)
470
- subGrid._autoColumn = true;
471
- // add the original content back as a child of the newly created grid
472
- if (saveContent) {
473
- subGrid.makeWidget(newItem, newItemOpt);
474
- }
475
- // now add any additional node
476
- if (nodeToAdd) {
477
- if (nodeToAdd._moving) {
478
- // create an artificial event even for the just created grid to receive this item
479
- window.setTimeout(() => Utils.simulateMouseEvent(nodeToAdd._event, 'mouseenter', subGrid.el), 0);
480
- }
481
- else {
482
- subGrid.makeWidget(node.el, node);
483
- }
484
- }
485
- // if sizedToContent, we need to re-calc the size of ourself
486
- this.resizeToContentCheck(false, node);
487
- return subGrid;
488
- }
489
- /**
490
- * called when an item was converted into a nested grid to accommodate a dragged over item, but then item leaves - return back
491
- * to the original grid-item. Also called to remove empty sub-grids when last item is dragged out (since re-creating is simple)
492
- */
493
- removeAsSubGrid(nodeThatRemoved) {
494
- const pGrid = this.parentGridNode?.grid;
495
- if (!pGrid)
496
- return;
497
- pGrid.batchUpdate();
498
- pGrid.removeWidget(this.parentGridNode.el, true, true);
499
- this.engine.nodes.forEach(n => {
500
- // migrate any children over and offsetting by our location
501
- n.x += this.parentGridNode.x;
502
- n.y += this.parentGridNode.y;
503
- pGrid.makeWidget(n.el, n);
504
- });
505
- pGrid.batchUpdate(false);
506
- if (this.parentGridNode)
507
- delete this.parentGridNode.subGrid;
508
- delete this.parentGridNode;
509
- // create an artificial event for the original grid now that this one is gone (got a leave, but won't get enter)
510
- if (nodeThatRemoved) {
511
- window.setTimeout(() => Utils.simulateMouseEvent(nodeThatRemoved._event, 'mouseenter', pGrid.el), 0);
512
- }
513
- }
514
- /**
515
- * saves the current layout returning a list of widgets for serialization which might include any nested grids.
516
- * @param saveContent if true (default) the latest html inside .grid-stack-content will be saved to GridStackWidget.content field, else it will
517
- * be removed.
518
- * @param saveGridOpt if true (default false), save the grid options itself, so you can call the new GridStack.addGrid()
519
- * to recreate everything from scratch. GridStackOptions.children would then contain the widget list instead.
520
- * @param saveCB callback for each node -> widget, so application can insert additional data to be saved into the widget data structure.
521
- * @param column if provided, the grid will be saved for the given column size (IFF we have matching internal saved layout, or current layout).
522
- * Otherwise it will use the largest possible layout (say 12 even if rendering at 1 column) so we can restore to all layouts.
523
- * NOTE: if you want to save to currently display layout, pass this.getColumn() as column.
524
- * NOTE2: nested grids will ALWAYS save to the container size to be in sync with parent.
525
- * @returns list of widgets or full grid option, including .children list of widgets
526
- */
527
- save(saveContent = true, saveGridOpt = false, saveCB = GridStack.saveCB, column) {
528
- // return copied GridStackWidget (with optionally .el) we can modify at will...
529
- const list = this.engine.save(saveContent, saveCB, column);
530
- // check for HTML content and nested grids
531
- list.forEach(n => {
532
- if (saveContent && n.el && !n.subGrid && !saveCB) { // sub-grid are saved differently, not plain content
533
- const itemContent = n.el.querySelector('.grid-stack-item-content');
534
- n.content = itemContent?.innerHTML;
535
- if (!n.content)
536
- delete n.content;
537
- }
538
- else {
539
- if (!saveContent && !saveCB) {
540
- delete n.content;
541
- }
542
- // check for nested grid - make sure it saves to the given container size to be consistent
543
- if (n.subGrid?.el) {
544
- const column = n.w || n.subGrid.getColumn();
545
- const listOrOpt = n.subGrid.save(saveContent, saveGridOpt, saveCB, column);
546
- n.subGridOpts = (saveGridOpt ? listOrOpt : { children: listOrOpt });
547
- delete n.subGrid;
548
- }
549
- }
550
- delete n.el;
551
- });
552
- // check if save entire grid options (needed for recursive) + children...
553
- if (saveGridOpt) {
554
- const o = Utils.cloneDeep(this.opts);
555
- // delete default values that will be recreated on launch
556
- if (o.marginBottom === o.marginTop && o.marginRight === o.marginLeft && o.marginTop === o.marginRight) {
557
- o.margin = o.marginTop;
558
- delete o.marginTop;
559
- delete o.marginRight;
560
- delete o.marginBottom;
561
- delete o.marginLeft;
562
- }
563
- if (o.rtl === (this.el.style.direction === 'rtl')) {
564
- o.rtl = 'auto';
565
- }
566
- if (this._isAutoCellHeight) {
567
- o.cellHeight = 'auto';
568
- }
569
- if (this._autoColumn) {
570
- o.column = 'auto';
571
- }
572
- const origShow = o._alwaysShowResizeHandle;
573
- delete o._alwaysShowResizeHandle;
574
- if (origShow !== undefined) {
575
- o.alwaysShowResizeHandle = origShow;
576
- }
577
- else {
578
- delete o.alwaysShowResizeHandle;
579
- }
580
- Utils.removeInternalAndSame(o, gridDefaults);
581
- o.children = list;
582
- return o;
583
- }
584
- return list;
585
- }
586
- /**
587
- * Load widgets from a list. This will call update() on each (matching by id) or add/remove widgets that are not there.
588
- * Used to restore a grid layout for a saved layout list (see `save()`).
589
- *
590
- * @param items list of widgets definition to update/create
591
- * @param addRemove boolean (default true) or callback method can be passed to control if and how missing widgets can be added/removed, giving
592
- * the user control of insertion.
593
- * @returns the grid instance for chaining
594
- *
595
- * @example
596
- * // Basic usage with saved layout
597
- * const savedLayout = grid.save(); // Save current layout
598
- * // ... later restore it
599
- * grid.load(savedLayout);
600
- *
601
- * // Load with custom add/remove callback
602
- * grid.load(layout, (items, grid, add) => {
603
- * if (add) {
604
- * // Custom logic for adding new widgets
605
- * items.forEach(item => {
606
- * const el = document.createElement('div');
607
- * el.innerHTML = item.content || '';
608
- * grid.addWidget(el, item);
609
- * });
610
- * } else {
611
- * // Custom logic for removing widgets
612
- * items.forEach(item => grid.removeWidget(item.el));
613
- * }
614
- * });
615
- *
616
- * // Load without adding/removing missing widgets
617
- * grid.load(layout, false);
618
- *
619
- * @see {@link http://gridstackjs.com/demo/serialization.html} for complete example
620
- */
621
- load(items, addRemove = GridStack.addRemoveCB || true) {
622
- items = Utils.cloneDeep(items); // so we can mod
623
- const column = this.getColumn();
624
- // make sure size 1x1 (default) is present as it may need to override current sizes
625
- items.forEach(n => { n.w = n.w || n.minW || 1; n.h = n.h || n.minH || 1; });
626
- // sort items. those without coord will be appended last
627
- items = Utils.sort(items);
628
- this.engine.skipCacheUpdate = this._ignoreLayoutsNodeChange = true; // skip layout update
629
- // if we're loading a layout into for example 1 column and items don't fit, make sure to save
630
- // the original wanted layout so we can scale back up correctly #1471
631
- let maxColumn = 0;
632
- items.forEach(n => { maxColumn = Math.max(maxColumn, (n.x || 0) + n.w); });
633
- if (maxColumn > this.engine.defaultColumn)
634
- this.engine.defaultColumn = maxColumn;
635
- if (maxColumn > column) {
636
- // if we're loading (from empty) into a smaller column, check for special responsive layout
637
- if (this.engine.nodes.length === 0 && this.responseLayout) {
638
- this.engine.nodes = items;
639
- this.engine.columnChanged(maxColumn, column, this.responseLayout);
640
- items = this.engine.nodes;
641
- this.engine.nodes = [];
642
- delete this.responseLayout;
643
- }
644
- else
645
- this.engine.cacheLayout(items, maxColumn, true);
646
- }
647
- // if given a different callback, temporally set it as global option so creating will use it
648
- const prevCB = GridStack.addRemoveCB;
649
- if (typeof (addRemove) === 'function')
650
- GridStack.addRemoveCB = addRemove;
651
- const removed = [];
652
- this.batchUpdate();
653
- // if we are loading from empty temporarily remove animation
654
- const blank = !this.engine.nodes.length;
655
- const noAnim = blank && this.opts.animate;
656
- if (noAnim)
657
- this.setAnimation(false);
658
- // see if any items are missing from new layout and need to be removed first
659
- if (!blank && addRemove) {
660
- const copyNodes = [...this.engine.nodes]; // don't loop through array you modify
661
- copyNodes.forEach(n => {
662
- if (!n.id)
663
- return;
664
- const item = Utils.find(items, n.id);
665
- if (!item) {
666
- if (GridStack.addRemoveCB)
667
- GridStack.addRemoveCB(this.el, n, false, false);
668
- removed.push(n); // batch keep track
669
- this.removeWidget(n.el, true, false);
670
- }
671
- });
672
- }
673
- // now add/update the widgets - starting with removing items in the new layout we will reposition
674
- // to reduce collision and add no-coord ones at next available spot
675
- this.engine._loading = true; // help with collision
676
- const updateNodes = [];
677
- this.engine.nodes = this.engine.nodes.filter(n => {
678
- if (Utils.find(items, n.id)) {
679
- updateNodes.push(n);
680
- return false;
681
- } // remove if found from list
682
- return true;
683
- });
684
- items.forEach(w => {
685
- const item = Utils.find(updateNodes, w.id);
686
- if (item) {
687
- // if item sizes to content, re-use the exiting height so it's a better guess at the final size (same if width doesn't change)
688
- if (Utils.shouldSizeToContent(item))
689
- w.h = item.h;
690
- // check if missing coord, in which case find next empty slot with new (or old if missing) sizes
691
- this.engine.nodeBoundFix(w);
692
- if (w.autoPosition || w.x === undefined || w.y === undefined) {
693
- w.w = w.w || item.w;
694
- w.h = w.h || item.h;
695
- this.engine.findEmptyPosition(w);
696
- }
697
- // add back to current list BUT force a collision check if it 'appears' we didn't change to make sure we don't overlap others now
698
- this.engine.nodes.push(item);
699
- if (Utils.samePos(item, w) && this.engine.nodes.length > 1) {
700
- this.moveNode(item, { ...w, forceCollide: true });
701
- Utils.copyPos(w, item); // use possily updated values before update() is called next (no-op since already moved)
702
- }
703
- this.update(item.el, w);
704
- if (w.subGridOpts?.children) { // update any sub grid as well
705
- const sub = item.el.querySelector('.grid-stack');
706
- if (sub && sub.gridstack) {
707
- sub.gridstack.load(w.subGridOpts.children); // TODO: support updating grid options ?
708
- }
709
- }
710
- }
711
- else if (addRemove) {
712
- this.addWidget(w);
713
- }
714
- });
715
- delete this.engine._loading; // done loading
716
- this.engine.removedNodes = removed;
717
- this.batchUpdate(false);
718
- // after commit, clear that flag
719
- delete this._ignoreLayoutsNodeChange;
720
- delete this.engine.skipCacheUpdate;
721
- prevCB ? GridStack.addRemoveCB = prevCB : delete GridStack.addRemoveCB;
722
- if (noAnim)
723
- this.setAnimation(true, true); // delay adding animation back
724
- return this;
725
- }
726
- /**
727
- * use before calling a bunch of `addWidget()` to prevent un-necessary relayouts in between (more efficient)
728
- * and get a single event callback. You will see no changes until `batchUpdate(false)` is called.
729
- */
730
- batchUpdate(flag = true) {
731
- this.engine.batchUpdate(flag);
732
- if (!flag) {
733
- this._updateContainerHeight();
734
- this._triggerRemoveEvent();
735
- this._triggerAddEvent();
736
- this._triggerChangeEvent();
737
- }
738
- return this;
739
- }
740
- /**
741
- * Gets the current cell height in pixels. This takes into account the unit type and converts to pixels if necessary.
742
- *
743
- * @param forcePixel if true, forces conversion to pixels even when cellHeight is specified in other units
744
- * @returns the cell height in pixels
745
- *
746
- * @example
747
- * const height = grid.getCellHeight();
748
- * console.log('Cell height:', height, 'px');
749
- *
750
- * // Force pixel conversion
751
- * const pixelHeight = grid.getCellHeight(true);
752
- */
753
- getCellHeight(forcePixel = false) {
754
- if (this.opts.cellHeight && this.opts.cellHeight !== 'auto' &&
755
- (!forcePixel || !this.opts.cellHeightUnit || this.opts.cellHeightUnit === 'px')) {
756
- return this.opts.cellHeight;
757
- }
758
- // do rem/em/cm/mm to px conversion
759
- if (this.opts.cellHeightUnit === 'rem') {
760
- return this.opts.cellHeight * parseFloat(getComputedStyle(document.documentElement).fontSize);
761
- }
762
- if (this.opts.cellHeightUnit === 'em') {
763
- return this.opts.cellHeight * parseFloat(getComputedStyle(this.el).fontSize);
764
- }
765
- if (this.opts.cellHeightUnit === 'cm') {
766
- // 1cm = 96px/2.54. See https://www.w3.org/TR/css-values-3/#absolute-lengths
767
- return this.opts.cellHeight * (96 / 2.54);
768
- }
769
- if (this.opts.cellHeightUnit === 'mm') {
770
- return this.opts.cellHeight * (96 / 2.54) / 10;
771
- }
772
- // else get first cell height
773
- const el = this.el.querySelector('.' + this.opts.itemClass);
774
- if (el) {
775
- const h = Utils.toNumber(el.getAttribute('gs-h')) || 1; // since we don't write 1 anymore
776
- return Math.round(el.offsetHeight / h);
777
- }
778
- // else do entire grid and # of rows (but doesn't work if min-height is the actual constrain)
779
- const rows = parseInt(this.el.getAttribute('gs-current-row'));
780
- return rows ? Math.round(this.el.getBoundingClientRect().height / rows) : this.opts.cellHeight;
781
- }
782
- /**
783
- * Update current cell height - see `GridStackOptions.cellHeight` for format by updating eh Browser CSS variable.
784
- *
785
- * @param val the cell height. Options:
786
- * - `undefined`: cells content will be made square (match width minus margin)
787
- * - `0`: the CSS will be generated by the application instead
788
- * - number: height in pixels
789
- * - string: height with units (e.g., '70px', '5rem', '2em')
790
- * @returns the grid instance for chaining
791
- *
792
- * @example
793
- * grid.cellHeight(100); // 100px height
794
- * grid.cellHeight('70px'); // explicit pixel height
795
- * grid.cellHeight('5rem'); // relative to root font size
796
- * grid.cellHeight(grid.cellWidth() * 1.2); // aspect ratio
797
- * grid.cellHeight('auto'); // auto-size based on content
798
- */
799
- cellHeight(val) {
800
- // if not called internally, check if we're changing mode
801
- if (val !== undefined) {
802
- if (this._isAutoCellHeight !== (val === 'auto')) {
803
- this._isAutoCellHeight = (val === 'auto');
804
- this._updateResizeEvent();
805
- }
806
- }
807
- if (val === 'initial' || val === 'auto') {
808
- val = undefined;
809
- }
810
- // make item content be square
811
- if (val === undefined) {
812
- const marginDiff = -this.opts.marginRight - this.opts.marginLeft
813
- + this.opts.marginTop + this.opts.marginBottom;
814
- val = this.cellWidth() + marginDiff;
815
- }
816
- const data = Utils.parseHeight(val);
817
- if (this.opts.cellHeightUnit === data.unit && this.opts.cellHeight === data.h) {
818
- return this;
819
- }
820
- this.opts.cellHeightUnit = data.unit;
821
- this.opts.cellHeight = data.h;
822
- // finally update var and container
823
- this.el.style.setProperty('--gs-cell-height', `${this.opts.cellHeight}${this.opts.cellHeightUnit}`);
824
- this._updateContainerHeight();
825
- this.resizeToContentCheck();
826
- return this;
827
- }
828
- /** Gets current cell width. */
829
- /**
830
- * Gets the current cell width in pixels. This is calculated based on the grid container width divided by the number of columns.
831
- *
832
- * @returns the cell width in pixels
833
- *
834
- * @example
835
- * const width = grid.cellWidth();
836
- * console.log('Cell width:', width, 'px');
837
- *
838
- * // Use cell width to calculate widget dimensions
839
- * const widgetWidth = width * 3; // For a 3-column wide widget
840
- */
841
- cellWidth() {
842
- return this._widthOrContainer() / this.getColumn();
843
- }
844
- /** return our expected width (or parent) , and optionally of window for dynamic column check */
845
- _widthOrContainer(forBreakpoint = false) {
846
- // use `offsetWidth` or `clientWidth` (no scrollbar) ?
847
- // https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
848
- return forBreakpoint && this.opts.columnOpts?.breakpointForWindow ? window.innerWidth : (this.el.clientWidth || this.el.parentElement.clientWidth || window.innerWidth);
849
- }
850
- /** checks for dynamic column count for our current size, returning true if changed */
851
- checkDynamicColumn() {
852
- const resp = this.opts.columnOpts;
853
- if (!resp || (!resp.columnWidth && !resp.breakpoints?.length))
854
- return false;
855
- const column = this.getColumn();
856
- let newColumn = column;
857
- const w = this._widthOrContainer(true);
858
- if (resp.columnWidth) {
859
- newColumn = Math.min(Math.round(w / resp.columnWidth) || 1, resp.columnMax);
860
- }
861
- else {
862
- // find the closest breakpoint (already sorted big to small) that matches
863
- newColumn = resp.columnMax;
864
- let i = 0;
865
- while (i < resp.breakpoints.length && w <= resp.breakpoints[i].w) {
866
- newColumn = resp.breakpoints[i++].c || column;
867
- }
868
- }
869
- if (newColumn !== column) {
870
- const bk = resp.breakpoints?.find(b => b.c === newColumn);
871
- this.column(newColumn, bk?.layout || resp.layout);
872
- return true;
873
- }
874
- return false;
875
- }
876
- /**
877
- * Re-layout grid items to reclaim any empty space. This is useful after removing widgets
878
- * or when you want to optimize the layout.
879
- *
880
- * @param layout layout type. Options:
881
- * - 'compact' (default): might re-order items to fill any empty space
882
- * - 'list': keep the widget left->right order the same, even if that means leaving an empty slot if things don't fit
883
- * @param doSort re-sort items first based on x,y position. Set to false to do your own sorting ahead (default: true)
884
- * @returns the grid instance for chaining
885
- *
886
- * @example
887
- * // Compact layout after removing widgets
888
- * grid.removeWidget('.widget-to-remove');
889
- * grid.compact();
890
- *
891
- * // Use list layout (preserve order)
892
- * grid.compact('list');
893
- *
894
- * // Compact without sorting first
895
- * grid.compact('compact', false);
896
- */
897
- compact(layout = 'compact', doSort = true) {
898
- this.engine.compact(layout, doSort);
899
- this._triggerChangeEvent();
900
- return this;
901
- }
902
- /**
903
- * Set the number of columns in the grid. Will update existing widgets to conform to new number of columns,
904
- * as well as cache the original layout so you can revert back to previous positions without loss.
905
- *
906
- * Requires `gridstack-extra.css` or `gridstack-extra.min.css` for [2-11] columns,
907
- * else you will need to generate correct CSS.
908
- * See: https://github.com/gridstack/gridstack.js#change-grid-columns
909
- *
910
- * @param column Integer > 0 (default 12)
911
- * @param layout specify the type of re-layout that will happen. Options:
912
- * - 'moveScale' (default): scale widget positions and sizes
913
- * - 'move': keep widget sizes, only move positions
914
- * - 'scale': keep widget positions, only scale sizes
915
- * - 'none': don't change widget positions or sizes
916
- * Note: items will never be outside of the current column boundaries.
917
- * Ignored for `column=1` as we always want to vertically stack.
918
- * @returns the grid instance for chaining
919
- *
920
- * @example
921
- * // Change to 6 columns with default scaling
922
- * grid.column(6);
923
- *
924
- * // Change to 4 columns, only move positions
925
- * grid.column(4, 'move');
926
- *
927
- * // Single column layout (vertical stack)
928
- * grid.column(1);
929
- */
930
- column(column, layout = 'moveScale') {
931
- if (!column || column < 1 || this.opts.column === column)
932
- return this;
933
- const oldColumn = this.getColumn();
934
- this.opts.column = column;
935
- if (!this.engine) {
936
- // called in constructor, noting else to do but remember that breakpoint layout
937
- this.responseLayout = layout;
938
- return this;
939
- }
940
- this.engine.column = column;
941
- this.el.classList.remove('gs-' + oldColumn);
942
- this._updateColumnVar();
943
- // update the items now
944
- this.engine.columnChanged(oldColumn, column, layout);
945
- if (this._isAutoCellHeight)
946
- this.cellHeight();
947
- this.resizeToContentCheck(true); // wait for width resizing
948
- // and trigger our event last...
949
- this._ignoreLayoutsNodeChange = true; // skip layout update
950
- this._triggerChangeEvent();
951
- delete this._ignoreLayoutsNodeChange;
952
- return this;
953
- }
954
- /**
955
- * Get the number of columns in the grid (default 12).
956
- *
957
- * @returns the current number of columns in the grid
958
- *
959
- * @example
960
- * const columnCount = grid.getColumn(); // returns 12 by default
961
- */
962
- getColumn() { return this.opts.column; }
963
- /**
964
- * Returns an array of grid HTML elements (no placeholder) - used to iterate through our children in DOM order.
965
- * This method excludes placeholder elements and returns only actual grid items.
966
- *
967
- * @returns array of GridItemHTMLElement instances representing all grid items
968
- *
969
- * @example
970
- * const items = grid.getGridItems();
971
- * items.forEach(item => {
972
- * console.log('Item ID:', item.gridstackNode.id);
973
- * });
974
- */
975
- getGridItems() {
976
- return Array.from(this.el.children)
977
- .filter((el) => el.matches('.' + this.opts.itemClass) && !el.matches('.' + this.opts.placeholderClass));
978
- }
979
- /**
980
- * Returns true if change callbacks should be ignored due to column change, sizeToContent, loading, etc.
981
- * This is useful for callers who want to implement dirty flag functionality.
982
- *
983
- * @returns true if change callbacks are currently being ignored
984
- *
985
- * @example
986
- * if (!grid.isIgnoreChangeCB()) {
987
- * // Process the change event
988
- * console.log('Grid layout changed');
989
- * }
990
- */
991
- isIgnoreChangeCB() { return this._ignoreLayoutsNodeChange; }
992
- /**
993
- * Destroys a grid instance. DO NOT CALL any methods or access any vars after this as it will free up members.
994
- * @param removeDOM if `false` grid and items HTML elements will not be removed from the DOM (Optional. Default `true`).
995
- */
996
- destroy(removeDOM = true) {
997
- if (!this.el)
998
- return; // prevent multiple calls
999
- this.offAll();
1000
- this._updateResizeEvent(true);
1001
- this.setStatic(true, false); // permanently removes DD but don't set CSS class (we're going away)
1002
- this.setAnimation(false);
1003
- if (!removeDOM) {
1004
- this.removeAll(removeDOM);
1005
- this.el.removeAttribute('gs-current-row');
1006
- }
1007
- else {
1008
- this.el.parentNode.removeChild(this.el);
1009
- }
1010
- if (this.parentGridNode)
1011
- delete this.parentGridNode.subGrid;
1012
- delete this.parentGridNode;
1013
- delete this.opts;
1014
- delete this._placeholder?.gridstackNode;
1015
- delete this._placeholder;
1016
- delete this.engine;
1017
- delete this.el.gridstack; // remove circular dependency that would prevent a freeing
1018
- delete this.el;
1019
- return this;
1020
- }
1021
- /**
1022
- * Enable/disable floating widgets (default: `false`). When enabled, widgets can float up to fill empty spaces.
1023
- * See [example](http://gridstackjs.com/demo/float.html)
1024
- *
1025
- * @param val true to enable floating, false to disable
1026
- * @returns the grid instance for chaining
1027
- *
1028
- * @example
1029
- * grid.float(true); // Enable floating
1030
- * grid.float(false); // Disable floating (default)
1031
- */
1032
- float(val) {
1033
- if (this.opts.float !== val) {
1034
- this.opts.float = this.engine.float = val;
1035
- this._triggerChangeEvent();
1036
- }
1037
- return this;
1038
- }
1039
- /**
1040
- * Get the current float mode setting.
1041
- *
1042
- * @returns true if floating is enabled, false otherwise
1043
- *
1044
- * @example
1045
- * const isFloating = grid.getFloat();
1046
- * console.log('Floating enabled:', isFloating);
1047
- */
1048
- getFloat() {
1049
- return this.engine.float;
1050
- }
1051
- /**
1052
- * Get the position of the cell under a pixel on screen.
1053
- * @param position the position of the pixel to resolve in
1054
- * absolute coordinates, as an object with top and left properties
1055
- * @param useDocRelative if true, value will be based on document position vs parent position (Optional. Default false).
1056
- * Useful when grid is within `position: relative` element
1057
- *
1058
- * Returns an object with properties `x` and `y` i.e. the column and row in the grid.
1059
- */
1060
- getCellFromPixel(position, useDocRelative = false) {
1061
- const box = this.el.getBoundingClientRect();
1062
- // console.log(`getBoundingClientRect left: ${box.left} top: ${box.top} w: ${box.w} h: ${box.h}`)
1063
- let containerPos;
1064
- if (useDocRelative) {
1065
- containerPos = { top: box.top + document.documentElement.scrollTop, left: box.left };
1066
- // console.log(`getCellFromPixel scrollTop: ${document.documentElement.scrollTop}`)
1067
- }
1068
- else {
1069
- containerPos = { top: this.el.offsetTop, left: this.el.offsetLeft };
1070
- // console.log(`getCellFromPixel offsetTop: ${containerPos.left} offsetLeft: ${containerPos.top}`)
1071
- }
1072
- const relativeLeft = position.left - containerPos.left;
1073
- const relativeTop = position.top - containerPos.top;
1074
- const columnWidth = (box.width / this.getColumn());
1075
- const rowHeight = (box.height / parseInt(this.el.getAttribute('gs-current-row')));
1076
- return { x: Math.floor(relativeLeft / columnWidth), y: Math.floor(relativeTop / rowHeight) };
1077
- }
1078
- /**
1079
- * Returns the current number of rows, which will be at least `minRow` if set.
1080
- * The row count is based on the highest positioned widget in the grid.
1081
- *
1082
- * @returns the current number of rows in the grid
1083
- *
1084
- * @example
1085
- * const rowCount = grid.getRow();
1086
- * console.log('Grid has', rowCount, 'rows');
1087
- */
1088
- getRow() {
1089
- return Math.max(this.engine.getRow(), this.opts.minRow || 0);
1090
- }
1091
- /**
1092
- * Checks if the specified rectangular area is empty (no widgets occupy any part of it).
1093
- *
1094
- * @param x the x coordinate (column) of the area to check
1095
- * @param y the y coordinate (row) of the area to check
1096
- * @param w the width in columns of the area to check
1097
- * @param h the height in rows of the area to check
1098
- * @returns true if the area is completely empty, false if any widget overlaps
1099
- *
1100
- * @example
1101
- * // Check if a 2x2 area at position (1,1) is empty
1102
- * if (grid.isAreaEmpty(1, 1, 2, 2)) {
1103
- * console.log('Area is available for placement');
1104
- * }
1105
- */
1106
- isAreaEmpty(x, y, w, h) {
1107
- return this.engine.isAreaEmpty(x, y, w, h);
1108
- }
1109
- /**
1110
- * If you add elements to your grid by hand (or have some framework creating DOM), you have to tell gridstack afterwards to make them widgets.
1111
- * If you want gridstack to add the elements for you, use `addWidget()` instead.
1112
- * Makes the given element a widget and returns it.
1113
- *
1114
- * @param els widget or single selector to convert.
1115
- * @param options widget definition to use instead of reading attributes or using default sizing values
1116
- * @returns the converted GridItemHTMLElement
1117
- *
1118
- * @example
1119
- * const grid = GridStack.init();
1120
- *
1121
- * // Create HTML content manually, possibly looking like:
1122
- * // <div id="item-1" gs-x="0" gs-y="0" gs-w="3" gs-h="2"></div>
1123
- * grid.el.innerHTML = '<div id="item-1" gs-w="3"></div><div id="item-2"></div>';
1124
- *
1125
- * // Convert existing elements to widgets
1126
- * grid.makeWidget('#item-1'); // Uses gs-* attributes from DOM
1127
- * grid.makeWidget('#item-2', {w: 2, h: 1, content: 'Hello World'});
1128
- *
1129
- * // Or pass DOM element directly
1130
- * const element = document.getElementById('item-3');
1131
- * grid.makeWidget(element, {x: 0, y: 1, w: 4, h: 2});
1132
- */
1133
- makeWidget(els, options) {
1134
- const el = GridStack.getElement(els);
1135
- if (!el || el.gridstackNode)
1136
- return el;
1137
- if (!el.parentElement)
1138
- this.el.appendChild(el);
1139
- this._prepareElement(el, true, options);
1140
- const node = el.gridstackNode;
1141
- this._updateContainerHeight();
1142
- // see if there is a sub-grid to create
1143
- if (node.subGridOpts) {
1144
- this.makeSubGrid(el, node.subGridOpts, undefined, false); // node.subGrid will be used as option in method, no need to pass
1145
- }
1146
- // if we're adding an item into 1 column make sure
1147
- // we don't override the larger 12 column layout that was already saved. #1985
1148
- let resetIgnoreLayoutsNodeChange;
1149
- if (this.opts.column === 1 && !this._ignoreLayoutsNodeChange) {
1150
- resetIgnoreLayoutsNodeChange = this._ignoreLayoutsNodeChange = true;
1151
- }
1152
- this._triggerAddEvent();
1153
- this._triggerChangeEvent();
1154
- if (resetIgnoreLayoutsNodeChange)
1155
- delete this._ignoreLayoutsNodeChange;
1156
- return el;
1157
- }
1158
- on(name, callback) {
1159
- // check for array of names being passed instead
1160
- if (name.indexOf(' ') !== -1) {
1161
- const names = name.split(' ');
1162
- names.forEach(name => this.on(name, callback));
1163
- return this;
1164
- }
1165
- // native CustomEvent handlers - cash the generic handlers so we can easily remove
1166
- if (name === 'change' || name === 'added' || name === 'removed' || name === 'enable' || name === 'disable') {
1167
- const noData = (name === 'enable' || name === 'disable');
1168
- if (noData) {
1169
- this._gsEventHandler[name] = (event) => callback(event);
1170
- }
1171
- else {
1172
- this._gsEventHandler[name] = (event) => { if (event.detail)
1173
- callback(event, event.detail); };
1174
- }
1175
- this.el.addEventListener(name, this._gsEventHandler[name]);
1176
- }
1177
- else if (name === 'drag' || name === 'dragstart' || name === 'dragstop' || name === 'resizestart' || name === 'resize'
1178
- || name === 'resizestop' || name === 'dropped' || name === 'resizecontent') {
1179
- // drag&drop stop events NEED to be call them AFTER we update node attributes so handle them ourself.
1180
- // do same for start event to make it easier...
1181
- this._gsEventHandler[name] = callback;
1182
- }
1183
- else {
1184
- console.error('GridStack.on(' + name + ') event not supported');
1185
- }
1186
- return this;
1187
- }
1188
- /**
1189
- * unsubscribe from the 'on' event GridStackEvent
1190
- * @param name of the event (see possible values) or list of names space separated
1191
- */
1192
- off(name) {
1193
- // check for array of names being passed instead
1194
- if (name.indexOf(' ') !== -1) {
1195
- const names = name.split(' ');
1196
- names.forEach(name => this.off(name));
1197
- return this;
1198
- }
1199
- if (name === 'change' || name === 'added' || name === 'removed' || name === 'enable' || name === 'disable') {
1200
- // remove native CustomEvent handlers
1201
- if (this._gsEventHandler[name]) {
1202
- this.el.removeEventListener(name, this._gsEventHandler[name]);
1203
- }
1204
- }
1205
- delete this._gsEventHandler[name];
1206
- return this;
1207
- }
1208
- /**
1209
- * Remove all event handlers from the grid. This is useful for cleanup when destroying a grid.
1210
- *
1211
- * @returns the grid instance for chaining
1212
- *
1213
- * @example
1214
- * grid.offAll(); // Remove all event listeners
1215
- */
1216
- offAll() {
1217
- Object.keys(this._gsEventHandler).forEach((key) => this.off(key));
1218
- return this;
1219
- }
1220
- /**
1221
- * Removes widget from the grid.
1222
- * @param el widget or selector to modify
1223
- * @param removeDOM if `false` DOM element won't be removed from the tree (Default? true).
1224
- * @param triggerEvent if `false` (quiet mode) element will not be added to removed list and no 'removed' callbacks will be called (Default? true).
1225
- */
1226
- removeWidget(els, removeDOM = true, triggerEvent = true) {
1227
- if (!els) {
1228
- console.error('Error: GridStack.removeWidget(undefined) called');
1229
- return this;
1230
- }
1231
- GridStack.getElements(els).forEach(el => {
1232
- if (el.parentElement && el.parentElement !== this.el)
1233
- return; // not our child!
1234
- let node = el.gridstackNode;
1235
- // For Meteor support: https://github.com/gridstack/gridstack.js/pull/272
1236
- if (!node) {
1237
- node = this.engine.nodes.find(n => el === n.el);
1238
- }
1239
- if (!node)
1240
- return;
1241
- if (removeDOM && GridStack.addRemoveCB) {
1242
- GridStack.addRemoveCB(this.el, node, false, false);
1243
- }
1244
- // remove our DOM data (circular link) and drag&drop permanently
1245
- delete el.gridstackNode;
1246
- this._removeDD(el);
1247
- this.engine.removeNode(node, removeDOM, triggerEvent);
1248
- if (removeDOM && el.parentElement) {
1249
- el.remove(); // in batch mode engine.removeNode doesn't call back to remove DOM
1250
- }
1251
- });
1252
- if (triggerEvent) {
1253
- this._triggerRemoveEvent();
1254
- this._triggerChangeEvent();
1255
- }
1256
- return this;
1257
- }
1258
- /**
1259
- * Removes all widgets from the grid.
1260
- * @param removeDOM if `false` DOM elements won't be removed from the tree (Default? `true`).
1261
- * @param triggerEvent if `false` (quiet mode) element will not be added to removed list and no 'removed' callbacks will be called (Default? true).
1262
- */
1263
- removeAll(removeDOM = true, triggerEvent = true) {
1264
- // always remove our DOM data (circular link) before list gets emptied and drag&drop permanently
1265
- this.engine.nodes.forEach(n => {
1266
- if (removeDOM && GridStack.addRemoveCB) {
1267
- GridStack.addRemoveCB(this.el, n, false, false);
1268
- }
1269
- delete n.el.gridstackNode;
1270
- if (!this.opts.staticGrid)
1271
- this._removeDD(n.el);
1272
- });
1273
- this.engine.removeAll(removeDOM, triggerEvent);
1274
- if (triggerEvent)
1275
- this._triggerRemoveEvent();
1276
- return this;
1277
- }
1278
- /**
1279
- * Toggle the grid animation state. Toggles the `grid-stack-animate` class.
1280
- * @param doAnimate if true the grid will animate.
1281
- * @param delay if true setting will be set on next event loop.
1282
- */
1283
- setAnimation(doAnimate = this.opts.animate, delay) {
1284
- if (delay) {
1285
- // delay, but check to make sure grid (opt) is still around
1286
- setTimeout(() => { if (this.opts)
1287
- this.setAnimation(doAnimate); });
1288
- }
1289
- else if (doAnimate) {
1290
- this.el.classList.add('grid-stack-animate');
1291
- }
1292
- else {
1293
- this.el.classList.remove('grid-stack-animate');
1294
- }
1295
- this.opts.animate = doAnimate;
1296
- return this;
1297
- }
1298
- /** @internal */
1299
- hasAnimationCSS() { return this.el.classList.contains('grid-stack-animate'); }
1300
- /**
1301
- * Toggle the grid static state, which permanently removes/add Drag&Drop support, unlike disable()/enable() that just turns it off/on.
1302
- * Also toggle the grid-stack-static class.
1303
- * @param val if true the grid become static.
1304
- * @param updateClass true (default) if css class gets updated
1305
- * @param recurse true (default) if sub-grids also get updated
1306
- */
1307
- setStatic(val, updateClass = true, recurse = true) {
1308
- if (!!this.opts.staticGrid === val)
1309
- return this;
1310
- val ? this.opts.staticGrid = true : delete this.opts.staticGrid;
1311
- this._setupRemoveDrop();
1312
- this._setupAcceptWidget();
1313
- this.engine.nodes.forEach(n => {
1314
- this.prepareDragDrop(n.el); // either delete or init Drag&drop
1315
- if (n.subGrid && recurse)
1316
- n.subGrid.setStatic(val, updateClass, recurse);
1317
- });
1318
- if (updateClass) {
1319
- this._setStaticClass();
1320
- }
1321
- return this;
1322
- }
1323
- /**
1324
- * Updates the passed in options on the grid (similar to update(widget) for for the grid options).
1325
- * @param options PARTIAL grid options to update - only items specified will be updated.
1326
- * NOTE: not all options updating are currently supported (lot of code, unlikely to change)
1327
- */
1328
- updateOptions(o) {
1329
- const opts = this.opts;
1330
- if (o === opts)
1331
- return this; // nothing to do
1332
- if (o.acceptWidgets !== undefined) {
1333
- opts.acceptWidgets = o.acceptWidgets;
1334
- this._setupAcceptWidget();
1335
- }
1336
- if (o.animate !== undefined)
1337
- this.setAnimation(o.animate);
1338
- if (o.cellHeight)
1339
- this.cellHeight(o.cellHeight);
1340
- if (o.class !== undefined && o.class !== opts.class) {
1341
- if (opts.class)
1342
- this.el.classList.remove(opts.class);
1343
- if (o.class)
1344
- this.el.classList.add(o.class);
1345
- }
1346
- // responsive column take over actual count (keep what we have now)
1347
- if (o.columnOpts) {
1348
- this.opts.columnOpts = o.columnOpts;
1349
- this.checkDynamicColumn();
1350
- }
1351
- else if (o.columnOpts === null && this.opts.columnOpts) {
1352
- delete this.opts.columnOpts;
1353
- this._updateResizeEvent();
1354
- }
1355
- else if (typeof (o.column) === 'number')
1356
- this.column(o.column);
1357
- if (o.margin !== undefined)
1358
- this.margin(o.margin);
1359
- if (o.staticGrid !== undefined)
1360
- this.setStatic(o.staticGrid);
1361
- if (o.disableDrag !== undefined && !o.staticGrid)
1362
- this.enableMove(!o.disableDrag);
1363
- if (o.disableResize !== undefined && !o.staticGrid)
1364
- this.enableResize(!o.disableResize);
1365
- if (o.float !== undefined)
1366
- this.float(o.float);
1367
- if (o.row !== undefined) {
1368
- opts.minRow = opts.maxRow = opts.row = o.row;
1369
- this._updateContainerHeight();
1370
- }
1371
- else {
1372
- if (o.minRow !== undefined) {
1373
- opts.minRow = o.minRow;
1374
- this._updateContainerHeight();
1375
- }
1376
- if (o.maxRow !== undefined)
1377
- opts.maxRow = o.maxRow;
1378
- }
1379
- if (o.children?.length)
1380
- this.load(o.children);
1381
- // TBD if we have a real need for these (more complex code)
1382
- // alwaysShowResizeHandle, draggable, handle, handleClass, itemClass, layout, placeholderClass, placeholderText, resizable, removable, row,...
1383
- return this;
1384
- }
1385
- /**
1386
- * Updates widget position/size and other info. This is used to change widget properties after creation.
1387
- * Can update position, size, content, and other widget properties.
1388
- *
1389
- * Note: If you need to call this on all nodes, use load() instead which will update what changed.
1390
- * Setting the same x,y for multiple items will be indeterministic and likely unwanted.
1391
- *
1392
- * @param els widget element(s) or selector to modify
1393
- * @param opt new widget options (x,y,w,h, etc.). Only those set will be updated.
1394
- * @returns the grid instance for chaining
1395
- *
1396
- * @example
1397
- * // Update widget size and position
1398
- * grid.update('.my-widget', { x: 2, y: 1, w: 3, h: 2 });
1399
- *
1400
- * // Update widget content
1401
- * grid.update(widget, { content: '<p>New content</p>' });
1402
- *
1403
- * // Update multiple properties
1404
- * grid.update('#my-widget', {
1405
- * w: 4,
1406
- * h: 3,
1407
- * noResize: true,
1408
- * locked: true
1409
- * });
1410
- */
1411
- update(els, opt) {
1412
- GridStack.getElements(els).forEach(el => {
1413
- const n = el?.gridstackNode;
1414
- if (!n)
1415
- return;
1416
- const w = { ...Utils.copyPos({}, n), ...Utils.cloneDeep(opt) }; // make a copy we can modify in case they re-use it or multiple items
1417
- this.engine.nodeBoundFix(w);
1418
- delete w.autoPosition;
1419
- // move/resize widget if anything changed
1420
- const keys = ['x', 'y', 'w', 'h'];
1421
- let m;
1422
- if (keys.some(k => w[k] !== undefined && w[k] !== n[k])) {
1423
- m = {};
1424
- keys.forEach(k => {
1425
- m[k] = (w[k] !== undefined) ? w[k] : n[k];
1426
- delete w[k];
1427
- });
1428
- }
1429
- // for a move as well IFF there is any min/max fields set
1430
- if (!m && (w.minW || w.minH || w.maxW || w.maxH)) {
1431
- m = {}; // will use node position but validate values
1432
- }
1433
- // check for content changing
1434
- if (w.content !== undefined) {
1435
- const itemContent = el.querySelector('.grid-stack-item-content');
1436
- if (itemContent && itemContent.textContent !== w.content) {
1437
- n.content = w.content;
1438
- GridStack.renderCB(itemContent, w);
1439
- // restore any sub-grid back
1440
- if (n.subGrid?.el) {
1441
- itemContent.appendChild(n.subGrid.el);
1442
- n.subGrid._updateContainerHeight();
1443
- }
1444
- }
1445
- delete w.content;
1446
- }
1447
- // any remaining fields are assigned, but check for dragging changes, resize constrain
1448
- let changed = false;
1449
- let ddChanged = false;
1450
- for (const key in w) {
1451
- if (key[0] !== '_' && n[key] !== w[key]) {
1452
- n[key] = w[key];
1453
- changed = true;
1454
- ddChanged = ddChanged || (!this.opts.staticGrid && (key === 'noResize' || key === 'noMove' || key === 'locked'));
1455
- }
1456
- }
1457
- Utils.sanitizeMinMax(n);
1458
- // finally move the widget and update attr
1459
- if (m) {
1460
- const widthChanged = (m.w !== undefined && m.w !== n.w);
1461
- this.moveNode(n, m);
1462
- if (widthChanged && n.subGrid) {
1463
- // if we're animating the client size hasn't changed yet, so force a change (not exact size)
1464
- n.subGrid.onResize(this.hasAnimationCSS() ? n.w : undefined);
1465
- }
1466
- else {
1467
- this.resizeToContentCheck(widthChanged, n);
1468
- }
1469
- delete n._orig; // clear out original position now that we moved #2669
1470
- }
1471
- if (m || changed) {
1472
- this._writeAttr(el, n);
1473
- }
1474
- if (ddChanged) {
1475
- this.prepareDragDrop(n.el);
1476
- }
1477
- if (GridStack.updateCB)
1478
- GridStack.updateCB(n); // call user callback so they know widget got updated
1479
- });
1480
- return this;
1481
- }
1482
- moveNode(n, m) {
1483
- const wasUpdating = n._updating;
1484
- if (!wasUpdating)
1485
- this.engine.cleanNodes().beginUpdate(n);
1486
- this.engine.moveNode(n, m);
1487
- this._updateContainerHeight();
1488
- if (!wasUpdating) {
1489
- this._triggerChangeEvent();
1490
- this.engine.endUpdate();
1491
- }
1492
- }
1493
- /**
1494
- * Updates widget height to match the content height to avoid vertical scrollbars or dead space.
1495
- * This automatically adjusts the widget height based on its content size.
1496
- *
1497
- * Note: This assumes only 1 child under resizeToContentParent='.grid-stack-item-content'
1498
- * (sized to gridItem minus padding) that represents the entire content size.
1499
- *
1500
- * @param el the grid item element to resize
1501
- *
1502
- * @example
1503
- * // Resize a widget to fit its content
1504
- * const widget = document.querySelector('.grid-stack-item');
1505
- * grid.resizeToContent(widget);
1506
- *
1507
- * // This is commonly used with dynamic content:
1508
- * widget.querySelector('.content').innerHTML = 'New longer content...';
1509
- * grid.resizeToContent(widget);
1510
- */
1511
- resizeToContent(el) {
1512
- if (!el)
1513
- return;
1514
- el.classList.remove('size-to-content-max');
1515
- if (!el.clientHeight)
1516
- return; // 0 when hidden, skip
1517
- const n = el.gridstackNode;
1518
- if (!n)
1519
- return;
1520
- const grid = n.grid;
1521
- if (!grid || el.parentElement !== grid.el)
1522
- return; // skip if we are not inside a grid
1523
- const cell = grid.getCellHeight(true);
1524
- if (!cell)
1525
- return;
1526
- let height = n.h ? n.h * cell : el.clientHeight; // getBoundingClientRect().height seem to flicker back and forth
1527
- let item;
1528
- if (n.resizeToContentParent)
1529
- item = el.querySelector(n.resizeToContentParent);
1530
- if (!item)
1531
- item = el.querySelector(GridStack.resizeToContentParent);
1532
- if (!item)
1533
- return;
1534
- const padding = el.clientHeight - item.clientHeight; // full - available height to our child (minus border, padding...)
1535
- const itemH = n.h ? n.h * cell - padding : item.clientHeight; // calculated to what cellHeight is or will become (rather than actual to prevent waiting for animation to finish)
1536
- let wantedH;
1537
- if (n.subGrid) {
1538
- // sub-grid - use their actual row count * their cell height, BUT append any content outside of the grid (eg: above text)
1539
- wantedH = n.subGrid.getRow() * n.subGrid.getCellHeight(true);
1540
- const subRec = n.subGrid.el.getBoundingClientRect();
1541
- const parentRec = el.getBoundingClientRect();
1542
- wantedH += subRec.top - parentRec.top;
1543
- }
1544
- else if (n.subGridOpts?.children?.length) {
1545
- // not sub-grid just yet (case above) wait until we do
1546
- return;
1547
- }
1548
- else {
1549
- // NOTE: clientHeight & getBoundingClientRect() is undefined for text and other leaf nodes. use <div> container!
1550
- const child = item.firstElementChild;
1551
- if (!child) {
1552
- console.error(`Error: GridStack.resizeToContent() widget id:${n.id} '${GridStack.resizeToContentParent}'.firstElementChild is null, make sure to have a div like container. Skipping sizing.`);
1553
- return;
1554
- }
1555
- wantedH = child.getBoundingClientRect().height || itemH;
1556
- }
1557
- if (itemH === wantedH)
1558
- return;
1559
- height += wantedH - itemH;
1560
- let h = Math.ceil(height / cell);
1561
- // check for min/max and special sizing
1562
- const softMax = Number.isInteger(n.sizeToContent) ? n.sizeToContent : 0;
1563
- if (softMax && h > softMax) {
1564
- h = softMax;
1565
- el.classList.add('size-to-content-max'); // get v-scroll back
1566
- }
1567
- if (n.minH && h < n.minH)
1568
- h = n.minH;
1569
- else if (n.maxH && h > n.maxH)
1570
- h = n.maxH;
1571
- if (h !== n.h) {
1572
- grid._ignoreLayoutsNodeChange = true;
1573
- grid.moveNode(n, { h });
1574
- delete grid._ignoreLayoutsNodeChange;
1575
- }
1576
- }
1577
- /** call the user resize (so they can do extra work) else our build in version */
1578
- resizeToContentCBCheck(el) {
1579
- if (GridStack.resizeToContentCB)
1580
- GridStack.resizeToContentCB(el);
1581
- else
1582
- this.resizeToContent(el);
1583
- }
1584
- /**
1585
- * Rotate widgets by swapping their width and height. This is typically called when the user presses 'r' during dragging.
1586
- * The rotation swaps the w/h dimensions and adjusts min/max constraints accordingly.
1587
- *
1588
- * @param els widget element(s) or selector to rotate
1589
- * @param relative optional pixel coordinate relative to upper/left corner to rotate around (keeps that cell under cursor)
1590
- * @returns the grid instance for chaining
1591
- *
1592
- * @example
1593
- * // Rotate a specific widget
1594
- * grid.rotate('.my-widget');
1595
- *
1596
- * // Rotate with relative positioning during drag
1597
- * grid.rotate(widget, { left: 50, top: 30 });
1598
- */
1599
- rotate(els, relative) {
1600
- GridStack.getElements(els).forEach(el => {
1601
- const n = el.gridstackNode;
1602
- if (!Utils.canBeRotated(n))
1603
- return;
1604
- const rot = { w: n.h, h: n.w, minH: n.minW, minW: n.minH, maxH: n.maxW, maxW: n.maxH };
1605
- // if given an offset, adjust x/y by column/row bounds when user presses 'r' during dragging
1606
- if (relative) {
1607
- const pivotX = relative.left > 0 ? Math.floor(relative.left / this.cellWidth()) : 0;
1608
- const pivotY = relative.top > 0 ? Math.floor(relative.top / this.opts.cellHeight) : 0;
1609
- rot.x = n.x + pivotX - (n.h - (pivotY + 1));
1610
- rot.y = (n.y + pivotY) - pivotX;
1611
- }
1612
- Object.keys(rot).forEach(k => { if (rot[k] === undefined)
1613
- delete rot[k]; });
1614
- const _orig = n._orig;
1615
- this.update(el, rot);
1616
- n._orig = _orig; // restore as move() will delete it
1617
- });
1618
- return this;
1619
- }
1620
- /**
1621
- * Updates the margins which will set all 4 sides at once - see `GridStackOptions.margin` for format options.
1622
- * Supports CSS string format of 1, 2, or 4 values or a single number.
1623
- *
1624
- * @param value margin value - can be:
1625
- * - Single number: `10` (applies to all sides)
1626
- * - Two values: `'10px 20px'` (top/bottom, left/right)
1627
- * - Four values: `'10px 20px 5px 15px'` (top, right, bottom, left)
1628
- * @returns the grid instance for chaining
1629
- *
1630
- * @example
1631
- * grid.margin(10); // 10px all sides
1632
- * grid.margin('10px 20px'); // 10px top/bottom, 20px left/right
1633
- * grid.margin('5px 10px 15px 20px'); // Different for each side
1634
- */
1635
- margin(value) {
1636
- const isMultiValue = (typeof value === 'string' && value.split(' ').length > 1);
1637
- // check if we can skip... won't check if multi values (too much hassle)
1638
- if (!isMultiValue) {
1639
- const data = Utils.parseHeight(value);
1640
- if (this.opts.marginUnit === data.unit && this.opts.margin === data.h)
1641
- return;
1642
- }
1643
- // re-use existing margin handling
1644
- this.opts.margin = value;
1645
- this.opts.marginTop = this.opts.marginBottom = this.opts.marginLeft = this.opts.marginRight = undefined;
1646
- this._initMargin();
1647
- return this;
1648
- }
1649
- /**
1650
- * Returns the current margin value as a number (undefined if the 4 sides don't match).
1651
- * This only returns a number if all sides have the same margin value.
1652
- *
1653
- * @returns the margin value in pixels, or undefined if sides have different values
1654
- *
1655
- * @example
1656
- * const margin = grid.getMargin();
1657
- * if (margin !== undefined) {
1658
- * console.log('Uniform margin:', margin, 'px');
1659
- * } else {
1660
- * console.log('Margins are different on different sides');
1661
- * }
1662
- */
1663
- getMargin() { return this.opts.margin; }
1664
- /**
1665
- * Returns true if the height of the grid will be less than the vertical
1666
- * constraint. Always returns true if grid doesn't have height constraint.
1667
- * @param node contains x,y,w,h,auto-position options
1668
- *
1669
- * @example
1670
- * if (grid.willItFit(newWidget)) {
1671
- * grid.addWidget(newWidget);
1672
- * } else {
1673
- * alert('Not enough free space to place the widget');
1674
- * }
1675
- */
1676
- willItFit(node) {
1677
- // support legacy call for now
1678
- if (arguments.length > 1) {
1679
- console.warn('gridstack.ts: `willItFit(x,y,w,h,autoPosition)` is deprecated. Use `willItFit({x, y,...})`. It will be removed soon');
1680
- // eslint-disable-next-line prefer-rest-params
1681
- const a = arguments;
1682
- let i = 0, w = { x: a[i++], y: a[i++], w: a[i++], h: a[i++], autoPosition: a[i++] };
1683
- return this.willItFit(w);
1684
- }
1685
- return this.engine.willItFit(node);
1686
- }
1687
- /** @internal */
1688
- _triggerChangeEvent() {
1689
- if (this.engine.batchMode)
1690
- return this;
1691
- const elements = this.engine.getDirtyNodes(true); // verify they really changed
1692
- if (elements && elements.length) {
1693
- if (!this._ignoreLayoutsNodeChange) {
1694
- this.engine.layoutsNodesChange(elements);
1695
- }
1696
- this._triggerEvent('change', elements);
1697
- }
1698
- this.engine.saveInitial(); // we called, now reset initial values & dirty flags
1699
- return this;
1700
- }
1701
- /** @internal */
1702
- _triggerAddEvent() {
1703
- if (this.engine.batchMode)
1704
- return this;
1705
- if (this.engine.addedNodes?.length) {
1706
- if (!this._ignoreLayoutsNodeChange) {
1707
- this.engine.layoutsNodesChange(this.engine.addedNodes);
1708
- }
1709
- // prevent added nodes from also triggering 'change' event (which is called next)
1710
- this.engine.addedNodes.forEach(n => { delete n._dirty; });
1711
- const addedNodes = [...this.engine.addedNodes];
1712
- this.engine.addedNodes = [];
1713
- this._triggerEvent('added', addedNodes);
1714
- }
1715
- return this;
1716
- }
1717
- /** @internal */
1718
- _triggerRemoveEvent() {
1719
- if (this.engine.batchMode)
1720
- return this;
1721
- if (this.engine.removedNodes?.length) {
1722
- const removedNodes = [...this.engine.removedNodes];
1723
- this.engine.removedNodes = [];
1724
- this._triggerEvent('removed', removedNodes);
1725
- }
1726
- return this;
1727
- }
1728
- /** @internal */
1729
- _triggerEvent(type, data) {
1730
- const event = data ? new CustomEvent(type, { bubbles: false, detail: data }) : new Event(type);
1731
- // check if we're nested, and if so call the outermost grid to trigger the event
1732
- // eslint-disable-next-line @typescript-eslint/no-this-alias
1733
- let grid = this;
1734
- while (grid.parentGridNode)
1735
- grid = grid.parentGridNode.grid;
1736
- grid.el.dispatchEvent(event);
1737
- return this;
1738
- }
1739
- /** @internal */
1740
- _updateContainerHeight() {
1741
- if (!this.engine || this.engine.batchMode)
1742
- return this;
1743
- const parent = this.parentGridNode;
1744
- let row = this.getRow() + this._extraDragRow; // this checks for minRow already
1745
- const cellHeight = this.opts.cellHeight;
1746
- const unit = this.opts.cellHeightUnit;
1747
- if (!cellHeight)
1748
- return this;
1749
- // check for css min height (non nested grid). TODO: support mismatch, say: min % while unit is px.
1750
- // If `minRow` was applied, don't override it with this check, and avoid performance issues
1751
- // (reflows) using `getComputedStyle`
1752
- if (!parent && !this.opts.minRow) {
1753
- const cssMinHeight = Utils.parseHeight(getComputedStyle(this.el)['minHeight']);
1754
- if (cssMinHeight.h > 0 && cssMinHeight.unit === unit) {
1755
- const minRow = Math.floor(cssMinHeight.h / cellHeight);
1756
- if (row < minRow) {
1757
- row = minRow;
1758
- }
1759
- }
1760
- }
1761
- this.el.setAttribute('gs-current-row', String(row));
1762
- this.el.style.removeProperty('min-height');
1763
- this.el.style.removeProperty('height');
1764
- if (row) {
1765
- // nested grids have 'insert:0' to fill the space of parent by default, but we may be taller so use min-height for possible scrollbars
1766
- this.el.style[parent ? 'minHeight' : 'height'] = row * cellHeight + unit;
1767
- }
1768
- // if we're a nested grid inside an sizeToContent item, tell it to resize itself too
1769
- if (parent && Utils.shouldSizeToContent(parent)) {
1770
- parent.grid.resizeToContentCBCheck(parent.el);
1771
- }
1772
- return this;
1773
- }
1774
- /** @internal */
1775
- _prepareElement(el, triggerAddEvent = false, node) {
1776
- node = node || this._readAttr(el);
1777
- el.gridstackNode = node;
1778
- node.el = el;
1779
- node.grid = this;
1780
- node = this.engine.addNode(node, triggerAddEvent);
1781
- // write the dom sizes and class
1782
- this._writeAttr(el, node);
1783
- el.classList.add(gridDefaults.itemClass, this.opts.itemClass);
1784
- const sizeToContent = Utils.shouldSizeToContent(node);
1785
- sizeToContent ? el.classList.add('size-to-content') : el.classList.remove('size-to-content');
1786
- if (sizeToContent)
1787
- this.resizeToContentCheck(false, node);
1788
- if (!Utils.lazyLoad(node))
1789
- this.prepareDragDrop(node.el);
1790
- return this;
1791
- }
1792
- /** @internal write position CSS vars and x,y,w,h attributes (not used for CSS but by users) back to element */
1793
- _writePosAttr(el, n) {
1794
- // Avoid overwriting the inline style of the element during drag/resize, but always update the placeholder
1795
- if ((!n._moving && !n._resizing) || this._placeholder === el) {
1796
- // width/height:1 x/y:0 is set by default in the main CSS, so no need to set inlined vars
1797
- el.style.top = n.y ? (n.y === 1 ? `var(--gs-cell-height)` : `calc(${n.y} * var(--gs-cell-height))`) : null;
1798
- el.style.left = n.x ? (n.x === 1 ? `var(--gs-column-width)` : `calc(${n.x} * var(--gs-column-width))`) : null;
1799
- el.style.width = n.w > 1 ? `calc(${n.w} * var(--gs-column-width))` : null;
1800
- el.style.height = n.h > 1 ? `calc(${n.h} * var(--gs-cell-height))` : null;
1801
- }
1802
- // NOTE: those are technically not needed anymore (v12+) as we have CSS vars for everything, but some users depends on them to render item size using CSS
1803
- n.x > 0 ? el.setAttribute('gs-x', String(n.x)) : el.removeAttribute('gs-x');
1804
- n.y > 0 ? el.setAttribute('gs-y', String(n.y)) : el.removeAttribute('gs-y');
1805
- n.w > 1 ? el.setAttribute('gs-w', String(n.w)) : el.removeAttribute('gs-w');
1806
- n.h > 1 ? el.setAttribute('gs-h', String(n.h)) : el.removeAttribute('gs-h');
1807
- return this;
1808
- }
1809
- /** @internal call to write any default attributes back to element */
1810
- _writeAttr(el, node) {
1811
- if (!node)
1812
- return this;
1813
- this._writePosAttr(el, node);
1814
- const attrs /*: GridStackWidget but strings */ = {
1815
- // autoPosition: 'gs-auto-position', // no need to write out as already in node and doesn't affect CSS
1816
- noResize: 'gs-no-resize',
1817
- noMove: 'gs-no-move',
1818
- locked: 'gs-locked',
1819
- id: 'gs-id',
1820
- sizeToContent: 'gs-size-to-content',
1821
- };
1822
- for (const key in attrs) {
1823
- if (node[key]) { // 0 is valid for x,y only but done above already and not in list anyway
1824
- el.setAttribute(attrs[key], String(node[key]));
1825
- }
1826
- else {
1827
- el.removeAttribute(attrs[key]);
1828
- }
1829
- }
1830
- return this;
1831
- }
1832
- /** @internal call to read any default attributes from element */
1833
- _readAttr(el, clearDefaultAttr = true) {
1834
- const n = {};
1835
- n.x = Utils.toNumber(el.getAttribute('gs-x'));
1836
- n.y = Utils.toNumber(el.getAttribute('gs-y'));
1837
- n.w = Utils.toNumber(el.getAttribute('gs-w'));
1838
- n.h = Utils.toNumber(el.getAttribute('gs-h'));
1839
- n.autoPosition = Utils.toBool(el.getAttribute('gs-auto-position'));
1840
- n.noResize = Utils.toBool(el.getAttribute('gs-no-resize'));
1841
- n.noMove = Utils.toBool(el.getAttribute('gs-no-move'));
1842
- n.locked = Utils.toBool(el.getAttribute('gs-locked'));
1843
- const attr = el.getAttribute('gs-size-to-content');
1844
- if (attr) {
1845
- if (attr === 'true' || attr === 'false')
1846
- n.sizeToContent = Utils.toBool(attr);
1847
- else
1848
- n.sizeToContent = parseInt(attr, 10);
1849
- }
1850
- n.id = el.getAttribute('gs-id');
1851
- // read but never written out
1852
- n.maxW = Utils.toNumber(el.getAttribute('gs-max-w'));
1853
- n.minW = Utils.toNumber(el.getAttribute('gs-min-w'));
1854
- n.maxH = Utils.toNumber(el.getAttribute('gs-max-h'));
1855
- n.minH = Utils.toNumber(el.getAttribute('gs-min-h'));
1856
- // v8.x optimization to reduce un-needed attr that don't render or are default CSS
1857
- if (clearDefaultAttr) {
1858
- if (n.w === 1)
1859
- el.removeAttribute('gs-w');
1860
- if (n.h === 1)
1861
- el.removeAttribute('gs-h');
1862
- if (n.maxW)
1863
- el.removeAttribute('gs-max-w');
1864
- if (n.minW)
1865
- el.removeAttribute('gs-min-w');
1866
- if (n.maxH)
1867
- el.removeAttribute('gs-max-h');
1868
- if (n.minH)
1869
- el.removeAttribute('gs-min-h');
1870
- }
1871
- // remove any key not found (null or false which is default, unless sizeToContent=false override)
1872
- for (const key in n) {
1873
- if (!n.hasOwnProperty(key))
1874
- return;
1875
- if (!n[key] && n[key] !== 0 && key !== 'sizeToContent') { // 0 can be valid value (x,y only really)
1876
- delete n[key];
1877
- }
1878
- }
1879
- return n;
1880
- }
1881
- /** @internal */
1882
- _setStaticClass() {
1883
- const classes = ['grid-stack-static'];
1884
- if (this.opts.staticGrid) {
1885
- this.el.classList.add(...classes);
1886
- this.el.setAttribute('gs-static', 'true');
1887
- }
1888
- else {
1889
- this.el.classList.remove(...classes);
1890
- this.el.removeAttribute('gs-static');
1891
- }
1892
- return this;
1893
- }
1894
- /**
1895
- * called when we are being resized - check if the one Column Mode needs to be turned on/off
1896
- * and remember the prev columns we used, or get our count from parent, as well as check for cellHeight==='auto' (square)
1897
- * or `sizeToContent` gridItem options.
1898
- */
1899
- onResize(clientWidth = this.el?.clientWidth) {
1900
- if (!clientWidth)
1901
- return; // return if we're gone or no size yet (will get called again)
1902
- if (this.prevWidth === clientWidth)
1903
- return; // no-op
1904
- this.prevWidth = clientWidth;
1905
- // console.log('onResize ', clientWidth);
1906
- this.batchUpdate();
1907
- // see if we're nested and take our column count from our parent....
1908
- let columnChanged = false;
1909
- if (this._autoColumn && this.parentGridNode) {
1910
- if (this.opts.column !== this.parentGridNode.w) {
1911
- this.column(this.parentGridNode.w, this.opts.layout || 'list');
1912
- columnChanged = true;
1913
- }
1914
- }
1915
- else {
1916
- // else check for dynamic column
1917
- columnChanged = this.checkDynamicColumn();
1918
- }
1919
- // make the cells content square again
1920
- if (this._isAutoCellHeight)
1921
- this.cellHeight();
1922
- // update any nested grids, or items size
1923
- this.engine.nodes.forEach(n => {
1924
- if (n.subGrid)
1925
- n.subGrid.onResize();
1926
- });
1927
- if (!this._skipInitialResize)
1928
- this.resizeToContentCheck(columnChanged); // wait for anim of column changed (DOM reflow before we can size correctly)
1929
- delete this._skipInitialResize;
1930
- this.batchUpdate(false);
1931
- return this;
1932
- }
1933
- /** resizes content for given node (or all) if shouldSizeToContent() is true */
1934
- resizeToContentCheck(delay = false, n = undefined) {
1935
- if (!this.engine)
1936
- return; // we've been deleted in between!
1937
- // update any gridItem height with sizeToContent, but wait for DOM $animation_speed to settle if we changed column count
1938
- // TODO: is there a way to know what the final (post animation) size of the content will be so we can animate the column width and height together rather than sequentially ?
1939
- if (delay && this.hasAnimationCSS())
1940
- return setTimeout(() => this.resizeToContentCheck(false, n), this.animationDelay);
1941
- if (n) {
1942
- if (Utils.shouldSizeToContent(n))
1943
- this.resizeToContentCBCheck(n.el);
1944
- }
1945
- else if (this.engine.nodes.some(n => Utils.shouldSizeToContent(n))) {
1946
- const nodes = [...this.engine.nodes]; // in case order changes while resizing one
1947
- this.batchUpdate();
1948
- nodes.forEach(n => {
1949
- if (Utils.shouldSizeToContent(n))
1950
- this.resizeToContentCBCheck(n.el);
1951
- });
1952
- this._ignoreLayoutsNodeChange = true; // loop through each node will set/reset around each move, so set it here again
1953
- this.batchUpdate(false);
1954
- this._ignoreLayoutsNodeChange = false;
1955
- }
1956
- // call this regardless of shouldSizeToContent because widget might need to stretch to take available space after a resize
1957
- if (this._gsEventHandler['resizecontent'])
1958
- this._gsEventHandler['resizecontent'](null, n ? [n] : this.engine.nodes);
1959
- }
1960
- /** add or remove the grid element size event handler */
1961
- _updateResizeEvent(forceRemove = false) {
1962
- // only add event if we're not nested (parent will call us) and we're auto sizing cells or supporting dynamic column (i.e. doing work)
1963
- // or supporting new sizeToContent option.
1964
- const trackSize = !this.parentGridNode && (this._isAutoCellHeight || this.opts.sizeToContent || this.opts.columnOpts
1965
- || this.engine.nodes.find(n => n.sizeToContent));
1966
- if (!forceRemove && trackSize && !this.resizeObserver) {
1967
- this._sizeThrottle = Utils.throttle(() => this.onResize(), this.opts.cellHeightThrottle);
1968
- this.resizeObserver = new ResizeObserver(() => this._sizeThrottle());
1969
- this.resizeObserver.observe(this.el);
1970
- this._skipInitialResize = true; // makeWidget will originally have called on startup
1971
- }
1972
- else if ((forceRemove || !trackSize) && this.resizeObserver) {
1973
- this.resizeObserver.disconnect();
1974
- delete this.resizeObserver;
1975
- delete this._sizeThrottle;
1976
- }
1977
- return this;
1978
- }
1979
- /** @internal convert a potential selector into actual element */
1980
- static getElement(els = '.grid-stack-item') { return Utils.getElement(els); }
1981
- /** @internal */
1982
- static getElements(els = '.grid-stack-item') { return Utils.getElements(els); }
1983
- /** @internal */
1984
- static getGridElement(els) { return GridStack.getElement(els); }
1985
- /** @internal */
1986
- static getGridElements(els) { return Utils.getElements(els); }
1987
- /** @internal initialize margin top/bottom/left/right and units */
1988
- _initMargin() {
1989
- let data;
1990
- let margin = 0;
1991
- // support passing multiple values like CSS (ex: '5px 10px 0 20px')
1992
- let margins = [];
1993
- if (typeof this.opts.margin === 'string') {
1994
- margins = this.opts.margin.split(' ');
1995
- }
1996
- if (margins.length === 2) { // top/bot, left/right like CSS
1997
- this.opts.marginTop = this.opts.marginBottom = margins[0];
1998
- this.opts.marginLeft = this.opts.marginRight = margins[1];
1999
- }
2000
- else if (margins.length === 4) { // Clockwise like CSS
2001
- this.opts.marginTop = margins[0];
2002
- this.opts.marginRight = margins[1];
2003
- this.opts.marginBottom = margins[2];
2004
- this.opts.marginLeft = margins[3];
2005
- }
2006
- else {
2007
- data = Utils.parseHeight(this.opts.margin);
2008
- this.opts.marginUnit = data.unit;
2009
- margin = this.opts.margin = data.h;
2010
- }
2011
- // see if top/bottom/left/right need to be set as well
2012
- const keys = ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'];
2013
- keys.forEach(k => {
2014
- if (this.opts[k] === undefined) {
2015
- this.opts[k] = margin;
2016
- }
2017
- else {
2018
- data = Utils.parseHeight(this.opts[k]);
2019
- this.opts[k] = data.h;
2020
- delete this.opts.margin;
2021
- }
2022
- });
2023
- this.opts.marginUnit = data.unit; // in case side were spelled out, use those units instead...
2024
- if (this.opts.marginTop === this.opts.marginBottom && this.opts.marginLeft === this.opts.marginRight && this.opts.marginTop === this.opts.marginRight) {
2025
- this.opts.margin = this.opts.marginTop; // makes it easier to check for no-ops in setMargin()
2026
- }
2027
- // finally Update the CSS margin variables (inside the cell height) */
2028
- const style = this.el.style;
2029
- style.setProperty('--gs-item-margin-top', `${this.opts.marginTop}${this.opts.marginUnit}`);
2030
- style.setProperty('--gs-item-margin-bottom', `${this.opts.marginBottom}${this.opts.marginUnit}`);
2031
- style.setProperty('--gs-item-margin-right', `${this.opts.marginRight}${this.opts.marginUnit}`);
2032
- style.setProperty('--gs-item-margin-left', `${this.opts.marginLeft}${this.opts.marginUnit}`);
2033
- return this;
2034
- }
2035
- /* ===========================================================================================
2036
- * drag&drop methods that used to be stubbed out and implemented in dd-gridstack.ts
2037
- * but caused loading issues in prod - see https://github.com/gridstack/gridstack.js/issues/2039
2038
- * ===========================================================================================
2039
- */
2040
- /**
2041
- * Get the global drag & drop implementation instance.
2042
- * This provides access to the underlying drag & drop functionality.
2043
- *
2044
- * @returns the DDGridStack instance used for drag & drop operations
2045
- *
2046
- * @example
2047
- * const dd = GridStack.getDD();
2048
- * // Access drag & drop functionality
2049
- */
2050
- static getDD() {
2051
- return dd;
2052
- }
2053
- /**
2054
- * call to setup dragging in from the outside (say toolbar), by specifying the class selection and options.
2055
- * Called during GridStack.init() as options, but can also be called directly (last param are used) in case the toolbar
2056
- * is dynamically create and needs to be set later.
2057
- * @param dragIn string selector (ex: '.sidebar-item') or list of dom elements
2058
- * @param dragInOptions options - see DDDragOpt. (default: {handle: '.grid-stack-item-content', appendTo: 'body'}
2059
- * @param widgets GridStackWidget def to assign to each element which defines what to create on drop
2060
- * @param root optional root which defaults to document (for shadow dom pass the parent HTMLDocument)
2061
- */
2062
- static setupDragIn(dragIn, dragInOptions, widgets, root = document) {
2063
- if (dragInOptions?.pause !== undefined) {
2064
- DDManager.pauseDrag = dragInOptions.pause;
2065
- }
2066
- dragInOptions = { appendTo: 'body', helper: 'clone', ...(dragInOptions || {}) }; // default to handle:undefined = drag by the whole item
2067
- const els = (typeof dragIn === 'string') ? Utils.getElements(dragIn, root) : dragIn;
2068
- els.forEach((el, i) => {
2069
- if (!dd.isDraggable(el))
2070
- dd.dragIn(el, dragInOptions);
2071
- if (widgets?.[i])
2072
- el.gridstackNode = widgets[i];
2073
- });
2074
- }
2075
- /**
2076
- * Enables/Disables dragging by the user for specific grid elements.
2077
- * For all items and future items, use enableMove() instead. No-op for static grids.
2078
- *
2079
- * Note: If you want to prevent an item from moving due to being pushed around by another
2080
- * during collision, use the 'locked' property instead.
2081
- *
2082
- * @param els widget element(s) or selector to modify
2083
- * @param val if true widget will be draggable, assuming the parent grid isn't noMove or static
2084
- * @returns the grid instance for chaining
2085
- *
2086
- * @example
2087
- * // Make specific widgets draggable
2088
- * grid.movable('.my-widget', true);
2089
- *
2090
- * // Disable dragging for specific widgets
2091
- * grid.movable('#fixed-widget', false);
2092
- */
2093
- movable(els, val) {
2094
- if (this.opts.staticGrid)
2095
- return this; // can't move a static grid!
2096
- GridStack.getElements(els).forEach(el => {
2097
- const n = el.gridstackNode;
2098
- if (!n)
2099
- return;
2100
- val ? delete n.noMove : n.noMove = true;
2101
- this.prepareDragDrop(n.el); // init DD if need be, and adjust
2102
- });
2103
- return this;
2104
- }
2105
- /**
2106
- * Enables/Disables user resizing for specific grid elements.
2107
- * For all items and future items, use enableResize() instead. No-op for static grids.
2108
- *
2109
- * @param els widget element(s) or selector to modify
2110
- * @param val if true widget will be resizable, assuming the parent grid isn't noResize or static
2111
- * @returns the grid instance for chaining
2112
- *
2113
- * @example
2114
- * // Make specific widgets resizable
2115
- * grid.resizable('.my-widget', true);
2116
- *
2117
- * // Disable resizing for specific widgets
2118
- * grid.resizable('#fixed-size-widget', false);
2119
- */
2120
- resizable(els, val) {
2121
- if (this.opts.staticGrid)
2122
- return this; // can't resize a static grid!
2123
- GridStack.getElements(els).forEach(el => {
2124
- const n = el.gridstackNode;
2125
- if (!n)
2126
- return;
2127
- val ? delete n.noResize : n.noResize = true;
2128
- this.prepareDragDrop(n.el); // init DD if need be, and adjust
2129
- });
2130
- return this;
2131
- }
2132
- /**
2133
- * Temporarily disables widgets moving/resizing.
2134
- * If you want a more permanent way (which freezes up resources) use `setStatic(true)` instead.
2135
- *
2136
- * Note: This is a no-op for static grids.
2137
- *
2138
- * This is a shortcut for:
2139
- * ```typescript
2140
- * grid.enableMove(false);
2141
- * grid.enableResize(false);
2142
- * ```
2143
- *
2144
- * @param recurse if true (default), sub-grids also get updated
2145
- * @returns the grid instance for chaining
2146
- *
2147
- * @example
2148
- * // Disable all interactions
2149
- * grid.disable();
2150
- *
2151
- * // Disable only this grid, not sub-grids
2152
- * grid.disable(false);
2153
- */
2154
- disable(recurse = true) {
2155
- if (this.opts.staticGrid)
2156
- return;
2157
- this.enableMove(false, recurse);
2158
- this.enableResize(false, recurse);
2159
- this._triggerEvent('disable');
2160
- return this;
2161
- }
2162
- /**
2163
- * Re-enables widgets moving/resizing - see disable().
2164
- * Note: This is a no-op for static grids.
2165
- *
2166
- * This is a shortcut for:
2167
- * ```typescript
2168
- * grid.enableMove(true);
2169
- * grid.enableResize(true);
2170
- * ```
2171
- *
2172
- * @param recurse if true (default), sub-grids also get updated
2173
- * @returns the grid instance for chaining
2174
- *
2175
- * @example
2176
- * // Re-enable all interactions
2177
- * grid.enable();
2178
- *
2179
- * // Enable only this grid, not sub-grids
2180
- * grid.enable(false);
2181
- */
2182
- enable(recurse = true) {
2183
- if (this.opts.staticGrid)
2184
- return;
2185
- this.enableMove(true, recurse);
2186
- this.enableResize(true, recurse);
2187
- this._triggerEvent('enable');
2188
- return this;
2189
- }
2190
- /**
2191
- * Enables/disables widget moving for all widgets. No-op for static grids.
2192
- * Note: locally defined items (with noMove property) still override this setting.
2193
- *
2194
- * @param doEnable if true widgets will be movable, if false moving is disabled
2195
- * @param recurse if true (default), sub-grids also get updated
2196
- * @returns the grid instance for chaining
2197
- *
2198
- * @example
2199
- * // Enable moving for all widgets
2200
- * grid.enableMove(true);
2201
- *
2202
- * // Disable moving for all widgets
2203
- * grid.enableMove(false);
2204
- *
2205
- * // Enable only this grid, not sub-grids
2206
- * grid.enableMove(true, false);
2207
- */
2208
- enableMove(doEnable, recurse = true) {
2209
- if (this.opts.staticGrid)
2210
- return this; // can't move a static grid!
2211
- doEnable ? delete this.opts.disableDrag : this.opts.disableDrag = true; // FIRST before we update children as grid overrides #1658
2212
- this.engine.nodes.forEach(n => {
2213
- this.prepareDragDrop(n.el);
2214
- if (n.subGrid && recurse)
2215
- n.subGrid.enableMove(doEnable, recurse);
2216
- });
2217
- return this;
2218
- }
2219
- /**
2220
- * Enables/disables widget resizing for all widgets. No-op for static grids.
2221
- * Note: locally defined items (with noResize property) still override this setting.
2222
- *
2223
- * @param doEnable if true widgets will be resizable, if false resizing is disabled
2224
- * @param recurse if true (default), sub-grids also get updated
2225
- * @returns the grid instance for chaining
2226
- *
2227
- * @example
2228
- * // Enable resizing for all widgets
2229
- * grid.enableResize(true);
2230
- *
2231
- * // Disable resizing for all widgets
2232
- * grid.enableResize(false);
2233
- *
2234
- * // Enable only this grid, not sub-grids
2235
- * grid.enableResize(true, false);
2236
- */
2237
- enableResize(doEnable, recurse = true) {
2238
- if (this.opts.staticGrid)
2239
- return this; // can't size a static grid!
2240
- doEnable ? delete this.opts.disableResize : this.opts.disableResize = true; // FIRST before we update children as grid overrides #1658
2241
- this.engine.nodes.forEach(n => {
2242
- this.prepareDragDrop(n.el);
2243
- if (n.subGrid && recurse)
2244
- n.subGrid.enableResize(doEnable, recurse);
2245
- });
2246
- return this;
2247
- }
2248
- /** @internal call when drag (and drop) needs to be cancelled (Esc key) */
2249
- cancelDrag() {
2250
- const n = this._placeholder?.gridstackNode;
2251
- if (!n)
2252
- return;
2253
- if (n._isExternal) {
2254
- // remove any newly inserted nodes (from outside)
2255
- n._isAboutToRemove = true;
2256
- this.engine.removeNode(n);
2257
- }
2258
- else if (n._isAboutToRemove) {
2259
- // restore any temp removed (dragged over trash)
2260
- GridStack._itemRemoving(n.el, false);
2261
- }
2262
- this.engine.restoreInitial();
2263
- }
2264
- /** @internal removes any drag&drop present (called during destroy) */
2265
- _removeDD(el) {
2266
- dd.draggable(el, 'destroy').resizable(el, 'destroy');
2267
- if (el.gridstackNode) {
2268
- delete el.gridstackNode._initDD; // reset our DD init flag
2269
- }
2270
- delete el.ddElement;
2271
- return this;
2272
- }
2273
- /** @internal called to add drag over to support widgets being added externally */
2274
- _setupAcceptWidget() {
2275
- // check if we need to disable things
2276
- if (this.opts.staticGrid || (!this.opts.acceptWidgets && !this.opts.removable)) {
2277
- dd.droppable(this.el, 'destroy');
2278
- return this;
2279
- }
2280
- // vars shared across all methods
2281
- let cellHeight, cellWidth;
2282
- const onDrag = (event, el, helper) => {
2283
- helper = helper || el;
2284
- const node = helper.gridstackNode;
2285
- if (!node)
2286
- return;
2287
- // if the element is being dragged from outside, scale it down to match the grid's scale
2288
- // and slightly adjust its position relative to the mouse
2289
- if (!node.grid?.el) {
2290
- // this scales the helper down
2291
- helper.style.transform = `scale(${1 / this.dragTransform.xScale},${1 / this.dragTransform.yScale})`;
2292
- // this makes it so that the helper is well positioned relative to the mouse after scaling
2293
- const helperRect = helper.getBoundingClientRect();
2294
- helper.style.left = helperRect.x + (this.dragTransform.xScale - 1) * (event.clientX - helperRect.x) / this.dragTransform.xScale + 'px';
2295
- helper.style.top = helperRect.y + (this.dragTransform.yScale - 1) * (event.clientY - helperRect.y) / this.dragTransform.yScale + 'px';
2296
- helper.style.transformOrigin = `0px 0px`;
2297
- }
2298
- let { top, left } = helper.getBoundingClientRect();
2299
- const rect = this.el.getBoundingClientRect();
2300
- left -= rect.left;
2301
- top -= rect.top;
2302
- const ui = {
2303
- position: {
2304
- top: top * this.dragTransform.xScale,
2305
- left: left * this.dragTransform.yScale
2306
- }
2307
- };
2308
- if (node._temporaryRemoved) {
2309
- node.x = Math.max(0, Math.round(left / cellWidth));
2310
- node.y = Math.max(0, Math.round(top / cellHeight));
2311
- delete node.autoPosition;
2312
- this.engine.nodeBoundFix(node);
2313
- // don't accept *initial* location if doesn't fit #1419 (locked drop region, or can't grow), but maybe try if it will go somewhere
2314
- if (!this.engine.willItFit(node)) {
2315
- node.autoPosition = true; // ignore x,y and try for any slot...
2316
- if (!this.engine.willItFit(node)) {
2317
- dd.off(el, 'drag'); // stop calling us
2318
- return; // full grid or can't grow
2319
- }
2320
- if (node._willFitPos) {
2321
- // use the auto position instead #1687
2322
- Utils.copyPos(node, node._willFitPos);
2323
- delete node._willFitPos;
2324
- }
2325
- }
2326
- // re-use the existing node dragging method
2327
- this._onStartMoving(helper, event, ui, node, cellWidth, cellHeight);
2328
- }
2329
- else {
2330
- // re-use the existing node dragging that does so much of the collision detection
2331
- this._dragOrResize(helper, event, ui, node, cellWidth, cellHeight);
2332
- }
2333
- };
2334
- dd.droppable(this.el, {
2335
- accept: (el) => {
2336
- const node = el.gridstackNode || this._readAttr(el, false);
2337
- // set accept drop to true on ourself (which we ignore) so we don't get "can't drop" icon in HTML5 mode while moving
2338
- if (node?.grid === this)
2339
- return true;
2340
- if (!this.opts.acceptWidgets)
2341
- return false;
2342
- // check for accept method or class matching
2343
- let canAccept = true;
2344
- if (typeof this.opts.acceptWidgets === 'function') {
2345
- canAccept = this.opts.acceptWidgets(el);
2346
- }
2347
- else {
2348
- const selector = (this.opts.acceptWidgets === true ? '.grid-stack-item' : this.opts.acceptWidgets);
2349
- canAccept = el.matches(selector);
2350
- }
2351
- // finally check to make sure we actually have space left #1571 #2633
2352
- if (canAccept && node && this.opts.maxRow) {
2353
- const n = { w: node.w, h: node.h, minW: node.minW, minH: node.minH }; // only width/height matters and autoPosition
2354
- canAccept = this.engine.willItFit(n);
2355
- }
2356
- return canAccept;
2357
- }
2358
- })
2359
- /**
2360
- * entering our grid area
2361
- */
2362
- .on(this.el, 'dropover', (event, el, helper) => {
2363
- // console.log(`over ${this.el.gridstack.opts.id} ${count++}`); // TEST
2364
- let node = helper?.gridstackNode || el.gridstackNode;
2365
- // ignore drop enter on ourself (unless we temporarily removed) which happens on a simple drag of our item
2366
- if (node?.grid === this && !node._temporaryRemoved) {
2367
- // delete node._added; // reset this to track placeholder again in case we were over other grid #1484 (dropout doesn't always clear)
2368
- return false; // prevent parent from receiving msg (which may be a grid as well)
2369
- }
2370
- // If sidebar item, restore the sidebar node size to ensure consistent behavior when dragging between grids
2371
- if (node?._sidebarOrig) {
2372
- node.w = node._sidebarOrig.w;
2373
- node.h = node._sidebarOrig.h;
2374
- }
2375
- // fix #1578 when dragging fast, we may not get a leave on the previous grid so force one now
2376
- if (node?.grid && node.grid !== this && !node._temporaryRemoved) {
2377
- // console.log('dropover without leave'); // TEST
2378
- const otherGrid = node.grid;
2379
- otherGrid._leave(el, helper);
2380
- }
2381
- helper = helper || el;
2382
- // cache cell dimensions (which don't change), position can animate if we removed an item in otherGrid that affects us...
2383
- cellWidth = this.cellWidth();
2384
- cellHeight = this.getCellHeight(true);
2385
- // sidebar items: load any element attributes if we don't have a node on first enter from the sidebar
2386
- if (!node) {
2387
- const attr = helper.getAttribute('data-gs-widget') || helper.getAttribute('gridstacknode'); // TBD: temp support for old V11.0.0 attribute
2388
- if (attr) {
2389
- try {
2390
- node = JSON.parse(attr);
2391
- }
2392
- catch (error) {
2393
- console.error("Gridstack dropover: Bad JSON format: ", attr);
2394
- }
2395
- helper.removeAttribute('data-gs-widget');
2396
- helper.removeAttribute('gridstacknode');
2397
- }
2398
- if (!node)
2399
- node = this._readAttr(helper); // used to pass false for #2354, but now we clone top level node
2400
- // On first grid enter from sidebar, set the initial sidebar item size properties for the node
2401
- node._sidebarOrig = { w: node.w, h: node.h };
2402
- }
2403
- if (!node.grid) { // sidebar item
2404
- if (!node.el)
2405
- node = { ...node }; // clone first time we're coming from sidebar (since 'clone' doesn't copy vars)
2406
- node._isExternal = true;
2407
- helper.gridstackNode = node;
2408
- }
2409
- // calculate the grid size based on element outer size
2410
- const w = node.w || Math.round(helper.offsetWidth / cellWidth) || 1;
2411
- const h = node.h || Math.round(helper.offsetHeight / cellHeight) || 1;
2412
- // if the item came from another grid, make a copy and save the original info in case we go back there
2413
- if (node.grid && node.grid !== this) {
2414
- // copy the node original values (min/max/id/etc...) but override width/height/other flags which are this grid specific
2415
- // console.log('dropover cloning node'); // TEST
2416
- if (!el._gridstackNodeOrig)
2417
- el._gridstackNodeOrig = node; // shouldn't have multiple nested!
2418
- el.gridstackNode = node = { ...node, w, h, grid: this };
2419
- delete node.x;
2420
- delete node.y;
2421
- this.engine.cleanupNode(node)
2422
- .nodeBoundFix(node);
2423
- // restore some internal fields we need after clearing them all
2424
- node._initDD =
2425
- node._isExternal = // DOM needs to be re-parented on a drop
2426
- node._temporaryRemoved = true; // so it can be inserted onDrag below
2427
- }
2428
- else {
2429
- node.w = w;
2430
- node.h = h;
2431
- node._temporaryRemoved = true; // so we can insert it
2432
- }
2433
- // clear any marked for complete removal (Note: don't check _isAboutToRemove as that is cleared above - just do it)
2434
- GridStack._itemRemoving(node.el, false);
2435
- dd.on(el, 'drag', onDrag);
2436
- // make sure this is called at least once when going fast #1578
2437
- onDrag(event, el, helper);
2438
- return false; // prevent parent from receiving msg (which may be a grid as well)
2439
- })
2440
- /**
2441
- * Leaving our grid area...
2442
- */
2443
- .on(this.el, 'dropout', (event, el, helper) => {
2444
- // console.log(`out ${this.el.gridstack.opts.id} ${count++}`); // TEST
2445
- const node = helper?.gridstackNode || el.gridstackNode;
2446
- if (!node)
2447
- return false;
2448
- // fix #1578 when dragging fast, we might get leave after other grid gets enter (which calls us to clean)
2449
- // so skip this one if we're not the active grid really..
2450
- if (!node.grid || node.grid === this) {
2451
- this._leave(el, helper);
2452
- // if we were created as temporary nested grid, go back to before state
2453
- if (this._isTemp) {
2454
- this.removeAsSubGrid(node);
2455
- }
2456
- }
2457
- return false; // prevent parent from receiving msg (which may be grid as well)
2458
- })
2459
- /**
2460
- * end - releasing the mouse
2461
- */
2462
- .on(this.el, 'drop', (event, el, helper) => {
2463
- const node = helper?.gridstackNode || el.gridstackNode;
2464
- // ignore drop on ourself from ourself that didn't come from the outside - dragend will handle the simple move instead
2465
- if (node?.grid === this && !node._isExternal)
2466
- return false;
2467
- const wasAdded = !!this.placeholder.parentElement; // skip items not actually added to us because of constrains, but do cleanup #1419
2468
- const wasSidebar = el !== helper;
2469
- this.placeholder.remove();
2470
- delete this.placeholder.gridstackNode;
2471
- // disable animation when replacing a placeholder (already positioned) with actual content
2472
- if (wasAdded && this.opts.animate) {
2473
- this.setAnimation(false);
2474
- this.setAnimation(true, true); // delay adding back
2475
- }
2476
- // notify previous grid of removal
2477
- // console.log('drop delete _gridstackNodeOrig') // TEST
2478
- const origNode = el._gridstackNodeOrig;
2479
- delete el._gridstackNodeOrig;
2480
- if (wasAdded && origNode?.grid && origNode.grid !== this) {
2481
- const oGrid = origNode.grid;
2482
- oGrid.engine.removeNodeFromLayoutCache(origNode);
2483
- oGrid.engine.removedNodes.push(origNode);
2484
- oGrid._triggerRemoveEvent()._triggerChangeEvent();
2485
- // if it's an empty sub-grid that got auto-created, nuke it
2486
- if (oGrid.parentGridNode && !oGrid.engine.nodes.length && oGrid.opts.subGridDynamic) {
2487
- oGrid.removeAsSubGrid();
2488
- }
2489
- }
2490
- if (!node)
2491
- return false;
2492
- // use existing placeholder node as it's already in our list with drop location
2493
- if (wasAdded) {
2494
- this.engine.cleanupNode(node); // removes all internal _xyz values
2495
- node.grid = this;
2496
- }
2497
- delete node.grid?._isTemp;
2498
- dd.off(el, 'drag');
2499
- // if we made a copy insert that instead of the original (sidebar item)
2500
- if (helper !== el) {
2501
- helper.remove();
2502
- el = helper;
2503
- }
2504
- else {
2505
- el.remove(); // reduce flicker as we change depth here, and size further down
2506
- }
2507
- this._removeDD(el);
2508
- if (!wasAdded)
2509
- return false;
2510
- const subGrid = node.subGrid?.el?.gridstack; // set when actual sub-grid present
2511
- Utils.copyPos(node, this._readAttr(this.placeholder)); // placeholder values as moving VERY fast can throw things off #1578
2512
- Utils.removePositioningStyles(el);
2513
- // give the user a chance to alter the widget that will get inserted if new sidebar item
2514
- if (wasSidebar && (node.content || node.subGridOpts || GridStack.addRemoveCB)) {
2515
- delete node.el;
2516
- el = this.addWidget(node);
2517
- }
2518
- else {
2519
- this._prepareElement(el, true, node);
2520
- this.el.appendChild(el);
2521
- // resizeToContent is skipped in _prepareElement() until node is visible (clientHeight=0) so call it now
2522
- this.resizeToContentCheck(false, node);
2523
- if (subGrid) {
2524
- subGrid.parentGridNode = node;
2525
- }
2526
- this._updateContainerHeight();
2527
- }
2528
- this.engine.addedNodes.push(node);
2529
- this._triggerAddEvent();
2530
- this._triggerChangeEvent();
2531
- this.engine.endUpdate();
2532
- if (this._gsEventHandler['dropped']) {
2533
- this._gsEventHandler['dropped']({ ...event, type: 'dropped' }, origNode && origNode.grid ? origNode : undefined, node);
2534
- }
2535
- return false; // prevent parent from receiving msg (which may be grid as well)
2536
- });
2537
- return this;
2538
- }
2539
- /** @internal mark item for removal */
2540
- static _itemRemoving(el, remove) {
2541
- if (!el)
2542
- return;
2543
- const node = el ? el.gridstackNode : undefined;
2544
- if (!node?.grid || el.classList.contains(node.grid.opts.removableOptions.decline))
2545
- return;
2546
- remove ? node._isAboutToRemove = true : delete node._isAboutToRemove;
2547
- remove ? el.classList.add('grid-stack-item-removing') : el.classList.remove('grid-stack-item-removing');
2548
- }
2549
- /** @internal called to setup a trash drop zone if the user specifies it */
2550
- _setupRemoveDrop() {
2551
- if (typeof this.opts.removable !== 'string')
2552
- return this;
2553
- const trashEl = document.querySelector(this.opts.removable);
2554
- if (!trashEl)
2555
- return this;
2556
- // only register ONE static drop-over/dropout callback for the 'trash', and it will
2557
- // update the passed in item and parent grid because the '.trash' is a shared resource anyway,
2558
- // and Native DD only has 1 event CB (having a list and technically a per grid removableOptions complicates things greatly)
2559
- if (!this.opts.staticGrid && !dd.isDroppable(trashEl)) {
2560
- dd.droppable(trashEl, this.opts.removableOptions)
2561
- .on(trashEl, 'dropover', (event, el) => GridStack._itemRemoving(el, true))
2562
- .on(trashEl, 'dropout', (event, el) => GridStack._itemRemoving(el, false));
2563
- }
2564
- return this;
2565
- }
2566
- /**
2567
- * prepares the element for drag&drop - this is normally called by makeWidget() unless are are delay loading
2568
- * @param el GridItemHTMLElement of the widget
2569
- * @param [force=false]
2570
- * */
2571
- prepareDragDrop(el, force = false) {
2572
- const node = el?.gridstackNode;
2573
- if (!node)
2574
- return;
2575
- const noMove = node.noMove || this.opts.disableDrag;
2576
- const noResize = node.noResize || this.opts.disableResize;
2577
- // check for disabled grid first
2578
- const disable = this.opts.staticGrid || (noMove && noResize);
2579
- if (force || disable) {
2580
- if (node._initDD) {
2581
- this._removeDD(el); // nukes everything instead of just disable, will add some styles back next
2582
- delete node._initDD;
2583
- }
2584
- if (disable)
2585
- el.classList.add('ui-draggable-disabled', 'ui-resizable-disabled'); // add styles one might depend on #1435
2586
- if (!force)
2587
- return this;
2588
- }
2589
- if (!node._initDD) {
2590
- // variables used/cashed between the 3 start/move/end methods, in addition to node passed above
2591
- let cellWidth;
2592
- let cellHeight;
2593
- /** called when item starts moving/resizing */
2594
- const onStartMoving = (event, ui) => {
2595
- // trigger any 'dragstart' / 'resizestart' manually
2596
- this.triggerEvent(event, event.target);
2597
- cellWidth = this.cellWidth();
2598
- cellHeight = this.getCellHeight(true); // force pixels for calculations
2599
- this._onStartMoving(el, event, ui, node, cellWidth, cellHeight);
2600
- };
2601
- /** called when item is being dragged/resized */
2602
- const dragOrResize = (event, ui) => {
2603
- this._dragOrResize(el, event, ui, node, cellWidth, cellHeight);
2604
- };
2605
- /** called when the item stops moving/resizing */
2606
- const onEndMoving = (event) => {
2607
- this.placeholder.remove();
2608
- delete this.placeholder.gridstackNode;
2609
- delete node._moving;
2610
- delete node._resizing;
2611
- delete node._event;
2612
- delete node._lastTried;
2613
- const widthChanged = node.w !== node._orig.w;
2614
- // if the item has moved to another grid, we're done here
2615
- const target = event.target;
2616
- if (!target.gridstackNode || target.gridstackNode.grid !== this)
2617
- return;
2618
- node.el = target;
2619
- if (node._isAboutToRemove) {
2620
- const grid = el.gridstackNode.grid;
2621
- if (grid._gsEventHandler[event.type]) {
2622
- grid._gsEventHandler[event.type](event, target);
2623
- }
2624
- grid.engine.nodes.push(node); // temp add it back so we can proper remove it next
2625
- grid.removeWidget(el, true, true);
2626
- }
2627
- else {
2628
- Utils.removePositioningStyles(target);
2629
- if (node._temporaryRemoved) {
2630
- // use last position we were at (not _orig as we may have pushed others and moved) and add it back
2631
- this._writePosAttr(target, node);
2632
- this.engine.addNode(node);
2633
- }
2634
- else {
2635
- // move to new placeholder location
2636
- this._writePosAttr(target, node);
2637
- }
2638
- this.triggerEvent(event, target);
2639
- }
2640
- // @ts-ignore
2641
- this._extraDragRow = 0; // @ts-ignore
2642
- this._updateContainerHeight(); // @ts-ignore
2643
- this._triggerChangeEvent();
2644
- this.engine.endUpdate();
2645
- if (event.type === 'resizestop') {
2646
- if (Number.isInteger(node.sizeToContent))
2647
- node.sizeToContent = node.h; // new soft limit
2648
- this.resizeToContentCheck(widthChanged, node); // wait for width animation if changed
2649
- }
2650
- };
2651
- dd.draggable(el, {
2652
- start: onStartMoving,
2653
- stop: onEndMoving,
2654
- drag: dragOrResize
2655
- }).resizable(el, {
2656
- start: onStartMoving,
2657
- stop: onEndMoving,
2658
- resize: dragOrResize
2659
- });
2660
- node._initDD = true; // we've set DD support now
2661
- }
2662
- // finally fine tune move vs resize by disabling any part...
2663
- dd.draggable(el, noMove ? 'disable' : 'enable')
2664
- .resizable(el, noResize ? 'disable' : 'enable');
2665
- return this;
2666
- }
2667
- /** @internal handles actual drag/resize start */
2668
- _onStartMoving(el, event, ui, node, cellWidth, cellHeight) {
2669
- this.engine.cleanNodes()
2670
- .beginUpdate(node);
2671
- // @ts-ignore
2672
- this._writePosAttr(this.placeholder, node);
2673
- this.el.appendChild(this.placeholder);
2674
- this.placeholder.gridstackNode = node;
2675
- // console.log('_onStartMoving placeholder') // TEST
2676
- // if the element is inside a grid, it has already been scaled
2677
- // we can use that as a scale reference
2678
- if (node.grid?.el) {
2679
- this.dragTransform = Utils.getValuesFromTransformedElement(el);
2680
- }
2681
- // if the element is being dragged from outside (not from any grid)
2682
- // we use the grid as the transformation reference, since the helper is not subject to transformation
2683
- else if (this.placeholder && this.placeholder.closest('.grid-stack')) {
2684
- const gridEl = this.placeholder.closest('.grid-stack');
2685
- this.dragTransform = Utils.getValuesFromTransformedElement(gridEl);
2686
- }
2687
- // Fallback
2688
- else {
2689
- this.dragTransform = {
2690
- xScale: 1,
2691
- xOffset: 0,
2692
- yScale: 1,
2693
- yOffset: 0,
2694
- };
2695
- }
2696
- node.el = this.placeholder;
2697
- node._lastUiPosition = ui.position;
2698
- node._prevYPix = ui.position.top;
2699
- node._moving = (event.type === 'dragstart'); // 'dropover' are not initially moving so they can go exactly where they enter (will push stuff out of the way)
2700
- node._resizing = (event.type === 'resizestart');
2701
- delete node._lastTried;
2702
- if (event.type === 'dropover' && node._temporaryRemoved) {
2703
- // console.log('engine.addNode x=' + node.x); // TEST
2704
- this.engine.addNode(node); // will add, fix collisions, update attr and clear _temporaryRemoved
2705
- node._moving = true; // AFTER, mark as moving object (wanted fix location before)
2706
- }
2707
- // set the min/max resize info taking into account the column count and position (so we don't resize outside the grid)
2708
- this.engine.cacheRects(cellWidth, cellHeight, this.opts.marginTop, this.opts.marginRight, this.opts.marginBottom, this.opts.marginLeft);
2709
- if (event.type === 'resizestart') {
2710
- const colLeft = this.getColumn() - node.x;
2711
- const rowLeft = (this.opts.maxRow || Number.MAX_SAFE_INTEGER) - node.y;
2712
- dd.resizable(el, 'option', 'minWidth', cellWidth * Math.min(node.minW || 1, colLeft))
2713
- .resizable(el, 'option', 'minHeight', cellHeight * Math.min(node.minH || 1, rowLeft))
2714
- .resizable(el, 'option', 'maxWidth', cellWidth * Math.min(node.maxW || Number.MAX_SAFE_INTEGER, colLeft))
2715
- .resizable(el, 'option', 'maxWidthMoveLeft', cellWidth * Math.min(node.maxW || Number.MAX_SAFE_INTEGER, node.x + node.w))
2716
- .resizable(el, 'option', 'maxHeight', cellHeight * Math.min(node.maxH || Number.MAX_SAFE_INTEGER, rowLeft))
2717
- .resizable(el, 'option', 'maxHeightMoveUp', cellHeight * Math.min(node.maxH || Number.MAX_SAFE_INTEGER, node.y + node.h));
2718
- }
2719
- }
2720
- /** @internal handles actual drag/resize */
2721
- _dragOrResize(el, event, ui, node, cellWidth, cellHeight) {
2722
- const p = { ...node._orig }; // could be undefined (_isExternal) which is ok (drag only set x,y and w,h will default to node value)
2723
- let resizing;
2724
- let mLeft = this.opts.marginLeft, mRight = this.opts.marginRight, mTop = this.opts.marginTop, mBottom = this.opts.marginBottom;
2725
- // if margins (which are used to pass mid point by) are large relative to cell height/width, reduce them down #1855
2726
- const mHeight = Math.round(cellHeight * 0.1), mWidth = Math.round(cellWidth * 0.1);
2727
- mLeft = Math.min(mLeft, mWidth);
2728
- mRight = Math.min(mRight, mWidth);
2729
- mTop = Math.min(mTop, mHeight);
2730
- mBottom = Math.min(mBottom, mHeight);
2731
- if (event.type === 'drag') {
2732
- if (node._temporaryRemoved)
2733
- return; // handled by dropover
2734
- const distance = ui.position.top - node._prevYPix;
2735
- node._prevYPix = ui.position.top;
2736
- if (this.opts.draggable.scroll !== false) {
2737
- Utils.updateScrollPosition(el, ui.position, distance);
2738
- }
2739
- // get new position taking into account the margin in the direction we are moving! (need to pass mid point by margin)
2740
- const left = ui.position.left + (ui.position.left > node._lastUiPosition.left ? -mRight : mLeft);
2741
- const top = ui.position.top + (ui.position.top > node._lastUiPosition.top ? -mBottom : mTop);
2742
- p.x = Math.round(left / cellWidth);
2743
- p.y = Math.round(top / cellHeight);
2744
- // @ts-ignore// if we're at the bottom hitting something else, grow the grid so cursor doesn't leave when trying to place below others
2745
- const prev = this._extraDragRow;
2746
- if (this.engine.collide(node, p)) {
2747
- const row = this.getRow();
2748
- let extra = Math.max(0, (p.y + node.h) - row);
2749
- if (this.opts.maxRow && row + extra > this.opts.maxRow) {
2750
- extra = Math.max(0, this.opts.maxRow - row);
2751
- } // @ts-ignore
2752
- this._extraDragRow = extra; // @ts-ignore
2753
- }
2754
- else
2755
- this._extraDragRow = 0; // @ts-ignore
2756
- if (this._extraDragRow !== prev)
2757
- this._updateContainerHeight();
2758
- if (node.x === p.x && node.y === p.y)
2759
- return; // skip same
2760
- // DON'T skip one we tried as we might have failed because of coverage <50% before
2761
- // if (node._lastTried && node._lastTried.x === x && node._lastTried.y === y) return;
2762
- }
2763
- else if (event.type === 'resize') {
2764
- if (p.x < 0)
2765
- return;
2766
- // Scrolling page if needed
2767
- Utils.updateScrollResize(event, el, cellHeight);
2768
- // get new size
2769
- p.w = Math.round((ui.size.width - mLeft) / cellWidth);
2770
- p.h = Math.round((ui.size.height - mTop) / cellHeight);
2771
- if (node.w === p.w && node.h === p.h)
2772
- return;
2773
- if (node._lastTried && node._lastTried.w === p.w && node._lastTried.h === p.h)
2774
- return; // skip one we tried (but failed)
2775
- // if we size on left/top side this might move us, so get possible new position as well
2776
- const left = ui.position.left + mLeft;
2777
- const top = ui.position.top + mTop;
2778
- p.x = Math.round(left / cellWidth);
2779
- p.y = Math.round(top / cellHeight);
2780
- resizing = true;
2781
- }
2782
- node._event = event;
2783
- node._lastTried = p; // set as last tried (will nuke if we go there)
2784
- const rect = {
2785
- x: ui.position.left + mLeft,
2786
- y: ui.position.top + mTop,
2787
- w: (ui.size ? ui.size.width : node.w * cellWidth) - mLeft - mRight,
2788
- h: (ui.size ? ui.size.height : node.h * cellHeight) - mTop - mBottom
2789
- };
2790
- if (this.engine.moveNodeCheck(node, { ...p, cellWidth, cellHeight, rect, resizing })) {
2791
- node._lastUiPosition = ui.position;
2792
- this.engine.cacheRects(cellWidth, cellHeight, mTop, mRight, mBottom, mLeft);
2793
- delete node._skipDown;
2794
- if (resizing && node.subGrid)
2795
- node.subGrid.onResize();
2796
- this._extraDragRow = 0; // @ts-ignore
2797
- this._updateContainerHeight();
2798
- const target = event.target; // @ts-ignore
2799
- // Do not write sidebar item attributes back to the original sidebar el
2800
- if (!node._sidebarOrig) {
2801
- this._writePosAttr(target, node);
2802
- }
2803
- this.triggerEvent(event, target);
2804
- }
2805
- }
2806
- /** call given event callback on our main top-most grid (if we're nested) */
2807
- triggerEvent(event, target) {
2808
- // eslint-disable-next-line @typescript-eslint/no-this-alias
2809
- let grid = this;
2810
- while (grid.parentGridNode)
2811
- grid = grid.parentGridNode.grid;
2812
- if (grid._gsEventHandler[event.type]) {
2813
- grid._gsEventHandler[event.type](event, target);
2814
- }
2815
- }
2816
- /** @internal called when item leaving our area by either cursor dropout event
2817
- * or shape is outside our boundaries. remove it from us, and mark temporary if this was
2818
- * our item to start with else restore prev node values from prev grid it came from.
2819
- */
2820
- _leave(el, helper) {
2821
- helper = helper || el;
2822
- const node = helper.gridstackNode;
2823
- if (!node)
2824
- return;
2825
- // remove the scale of the helper on leave
2826
- helper.style.transform = helper.style.transformOrigin = null;
2827
- dd.off(el, 'drag'); // no need to track while being outside
2828
- // this gets called when cursor leaves and shape is outside, so only do this once
2829
- if (node._temporaryRemoved)
2830
- return;
2831
- node._temporaryRemoved = true;
2832
- this.engine.removeNode(node); // remove placeholder as well, otherwise it's a sign node is not in our list, which is a bigger issue
2833
- node.el = node._isExternal && helper ? helper : el; // point back to real item being dragged
2834
- const sidebarOrig = node._sidebarOrig;
2835
- if (node._isExternal)
2836
- this.engine.cleanupNode(node);
2837
- // Restore sidebar item initial size info to stay consistent when dragging between multiple grids
2838
- node._sidebarOrig = sidebarOrig;
2839
- if (this.opts.removable === true) { // boolean vs a class string
2840
- // item leaving us and we are supposed to remove on leave (no need to drag onto trash) mark it so
2841
- GridStack._itemRemoving(el, true);
2842
- }
2843
- // finally if item originally came from another grid, but left us, restore things back to prev info
2844
- if (el._gridstackNodeOrig) {
2845
- // console.log('leave delete _gridstackNodeOrig') // TEST
2846
- el.gridstackNode = el._gridstackNodeOrig;
2847
- delete el._gridstackNodeOrig;
2848
- }
2849
- else if (node._isExternal) {
2850
- // item came from outside restore all nodes back to original
2851
- this.engine.restoreInitial();
2852
- }
2853
- }
2854
- // legacy method removed
2855
- commit() { obsolete(this, this.batchUpdate(false), 'commit', 'batchUpdate', '5.2'); return this; }
2856
- }
2857
- /**
2858
- * callback to create the content of widgets so the app can control how to store and restore it
2859
- * By default this lib will do 'el.textContent = w.content' forcing text only support for avoiding potential XSS issues.
2860
- */
2861
- GridStack.renderCB = (el, w) => { if (el && w?.content)
2862
- el.textContent = w.content; };
2863
- /** parent class for sizing content. defaults to '.grid-stack-item-content' */
2864
- GridStack.resizeToContentParent = '.grid-stack-item-content';
2865
- /** scoping so users can call GridStack.Utils.sort() for example */
2866
- GridStack.Utils = Utils;
2867
- /** scoping so users can call new GridStack.Engine(12) for example */
2868
- GridStack.Engine = GridStackEngine;
2869
- /** @internal current version compiled in code */
2870
- GridStack.GDRev = '12.3.3';
2871
- export { GridStack };
2872
- //# sourceMappingURL=gridstack.js.map