@appulsauce/svelte-grid 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,566 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ import {
5
+ coordinate2size,
6
+ calcPosition,
7
+ snapOnMove,
8
+ snapOnResize,
9
+ type SnapGridParams
10
+ } from './utils/item';
11
+ import { hasCollisions, getCollisions, getAvailablePosition } from './utils/grid';
12
+
13
+ import type { LayoutItem, LayoutChangeDetail, Size, ItemSize, GridItemProps } from './types';
14
+ import { getGridContext } from './Grid.svelte';
15
+
16
+ let {
17
+ id = crypto.randomUUID(),
18
+ x = $bindable(),
19
+ y = $bindable(),
20
+ w = $bindable(1),
21
+ h = $bindable(1),
22
+ min = { w: 1, h: 1 },
23
+ max,
24
+ movable = true,
25
+ resizable = true,
26
+ class: classes,
27
+ activeClass,
28
+ previewClass,
29
+ resizerClass,
30
+ style = '',
31
+ onchange,
32
+ onpreviewchange,
33
+ children,
34
+ moveHandle,
35
+ resizeHandle
36
+ }: GridItemProps = $props();
37
+
38
+ // Get grid context - this returns a reactive reference wrapper
39
+ const gridParamsRef = getGridContext();
40
+
41
+ // Access current within reactive contexts ($effect, $derived, template)
42
+ const gridParams = $derived(gridParamsRef.current);
43
+
44
+ let active = $state(false);
45
+ let left = $state(0);
46
+ let top = $state(0);
47
+ let width = $state(0);
48
+ let height = $state(0);
49
+
50
+ /**
51
+ * Item object that is used in `gridParams.items`.
52
+ */
53
+ let item = $state<LayoutItem>({
54
+ id,
55
+ x,
56
+ y,
57
+ w,
58
+ h,
59
+ min,
60
+ max,
61
+ movable,
62
+ resizable,
63
+ invalidate
64
+ });
65
+
66
+ // Sync props to item
67
+ $effect(() => {
68
+ item.x = x;
69
+ item.y = y;
70
+ item.w = w;
71
+ item.h = h;
72
+ item.min = min;
73
+ item.max = max;
74
+ item.movable = movable;
75
+ item.resizable = resizable;
76
+ });
77
+
78
+ /**
79
+ * Updates svelte-components props behind that item.
80
+ */
81
+ function invalidate() {
82
+ x = item.x;
83
+ y = item.y;
84
+ w = item.w;
85
+ h = item.h;
86
+ onchange?.({ item });
87
+ gridParams.onchange({ item });
88
+ gridParams.updateGrid();
89
+ }
90
+
91
+ onMount(() => {
92
+ gridParams.registerItem(item);
93
+ return () => {
94
+ gridParams.unregisterItem(item);
95
+ };
96
+ });
97
+
98
+ // Reposition item on grid change
99
+ $effect(() => {
100
+ if (!active && gridParams.itemSize) {
101
+ const newPosition = calcPosition(item, {
102
+ itemSize: gridParams.itemSize,
103
+ gap: gridParams.gap
104
+ });
105
+ left = newPosition.left;
106
+ top = newPosition.top;
107
+ width = newPosition.width;
108
+ height = newPosition.height;
109
+ }
110
+ });
111
+
112
+ let previewItem = $state<LayoutItem>({ ...item });
113
+
114
+ $effect(() => {
115
+ if (!active && item) {
116
+ previewItem = { ...item };
117
+ }
118
+ });
119
+
120
+ $effect(() => {
121
+ onpreviewchange?.({ item: previewItem });
122
+ });
123
+
124
+ function applyPreview() {
125
+ item.x = previewItem.x;
126
+ item.y = previewItem.y;
127
+ item.w = previewItem.w;
128
+ item.h = previewItem.h;
129
+ item.invalidate();
130
+ }
131
+
132
+ // INTERACTION LOGIC
133
+
134
+ let itemRef: HTMLElement;
135
+
136
+ const initialPointerPosition = { left: 0, top: 0 };
137
+
138
+ function initInteraction(event: PointerEvent) {
139
+ active = true;
140
+ initialPointerPosition.left = event.pageX;
141
+ initialPointerPosition.top = event.pageY;
142
+ itemRef.setPointerCapture(event.pointerId);
143
+ }
144
+
145
+ function endInteraction(event: PointerEvent) {
146
+ applyPreview();
147
+ active = false;
148
+ initialPointerPosition.left = 0;
149
+ initialPointerPosition.top = 0;
150
+ itemRef.releasePointerCapture(event.pointerId);
151
+ gridParams.updateGrid();
152
+ }
153
+
154
+ // MOVE ITEM LOGIC
155
+
156
+ let initialPosition = { left: 0, top: 0 };
157
+
158
+ const _movable = $derived(!gridParams.readOnly && movable);
159
+
160
+ let pointerShift = { left: 0, top: 0 };
161
+
162
+ function moveStart(event: PointerEvent) {
163
+ if (event.button !== 0) return;
164
+ event.stopPropagation();
165
+ initInteraction(event);
166
+ initialPosition = { left, top };
167
+ pointerShift = { left: event.pageX - left, top: event.pageY - top };
168
+ window.addEventListener('pointermove', move);
169
+ window.addEventListener('pointerup', moveEnd);
170
+ }
171
+
172
+ function move(event: PointerEvent) {
173
+ if (!gridParams.itemSize) {
174
+ throw new Error('Grid is not mounted yet');
175
+ }
176
+ let _left = event.pageX - initialPointerPosition.left + initialPosition.left;
177
+ let _top = event.pageY - initialPointerPosition.top + initialPosition.top;
178
+
179
+ if (gridParams.bounds && gridParams.boundsTo) {
180
+ const parentRect = gridParams.boundsTo.getBoundingClientRect();
181
+ if (_left < parentRect.left) {
182
+ _left = parentRect.left;
183
+ }
184
+ if (_top < parentRect.top) {
185
+ _top = parentRect.top;
186
+ }
187
+ if (_left + width > parentRect.right) {
188
+ _left = parentRect.right - width;
189
+ }
190
+ if (_top + height > parentRect.bottom) {
191
+ _top = parentRect.bottom - height;
192
+ }
193
+ }
194
+
195
+ left = _left;
196
+ top = _top;
197
+
198
+ // Snap and handle collisions
199
+ const snapParams = gridParams as SnapGridParams;
200
+ const { x: newX, y: newY } = snapOnMove(left, top, previewItem, snapParams);
201
+
202
+ if (gridParams.collision !== 'none') {
203
+ movePreviewWithCollisions(newX, newY);
204
+ } else {
205
+ if (!hasCollisions({ ...previewItem, x: newX, y: newY }, Object.values(gridParams.items))) {
206
+ previewItem = { ...previewItem, x: newX, y: newY };
207
+ }
208
+ }
209
+ }
210
+
211
+ function updateCollItemPositionWithPush(collItem: LayoutItem, items: LayoutItem[]) {
212
+ const newPosition = getAvailablePosition(
213
+ collItem,
214
+ items,
215
+ gridParams.maxCols,
216
+ gridParams.maxRows
217
+ );
218
+ if (newPosition) {
219
+ const { x, y } = newPosition;
220
+ collItem.x = x;
221
+ collItem.y = y;
222
+ collItem.invalidate();
223
+ }
224
+ gridParams.updateGrid();
225
+ }
226
+
227
+ function handleCollisionsForPreviewItemWithPush(newAttributes: {
228
+ x?: number;
229
+ y?: number;
230
+ w?: number;
231
+ h?: number;
232
+ }) {
233
+ const gridItems = Object.values(gridParams.items);
234
+ const itemsExceptPreview = gridItems.filter((item) => item.id != previewItem.id);
235
+ const collItems = getCollisions({ ...previewItem, ...newAttributes }, itemsExceptPreview);
236
+
237
+ collItems.forEach((collItem: LayoutItem) => {
238
+ const itemsExceptCollItem = gridItems.filter((item) => item.id != collItem.id);
239
+ const items = [
240
+ ...itemsExceptCollItem.filter((item) => item.id != previewItem.id),
241
+ { ...previewItem, ...newAttributes }
242
+ ];
243
+ updateCollItemPositionWithPush(collItem, items);
244
+ });
245
+
246
+ previewItem = { ...previewItem, ...newAttributes };
247
+ gridParams.updateGrid();
248
+ applyPreview();
249
+ }
250
+
251
+ function movePreviewWithCollisionsWithPush(x: number, y: number) {
252
+ handleCollisionsForPreviewItemWithPush({ x, y });
253
+ }
254
+
255
+ function movePreviewWithCollisionsWithCompress(newX: number, newY: number) {
256
+ const gridItems = Object.values(gridParams.items);
257
+ let computedY = newY;
258
+ const itemsExceptPreview = gridItems.filter((i) => i.id != previewItem.id);
259
+ while (computedY >= 0) {
260
+ const collItems = getCollisions({ ...previewItem, x: newX, y: computedY }, gridItems);
261
+ if (collItems.length > 0) {
262
+ const sortedItems = collItems.sort((a, b) => b.y - a.y);
263
+ let moved = false;
264
+ sortedItems.forEach((sortItem) => {
265
+ if (newY + previewItem.h / 2 >= sortItem.y + sortItem.h / 2) {
266
+ moved = true;
267
+ computedY = sortItem.y + sortItem.h;
268
+ sortedItems.forEach((itm) => {
269
+ if (
270
+ !hasCollisions({ ...itm, y: itm.y - previewItem.h }, itemsExceptPreview) &&
271
+ itm.y - previewItem.h >= 0
272
+ ) {
273
+ itm.y -= previewItem.h;
274
+ itm.invalidate();
275
+ }
276
+ });
277
+ return false;
278
+ }
279
+ });
280
+ if (!moved) {
281
+ computedY = previewItem.y;
282
+ }
283
+ break;
284
+ }
285
+ computedY--;
286
+ }
287
+ if (computedY < 0 || newY === 0) {
288
+ computedY = 0;
289
+ }
290
+ const positionChanged = newX != previewItem.x || computedY != previewItem.y;
291
+ previewItem = { ...previewItem, x: newX, y: computedY };
292
+ if (positionChanged) {
293
+ compressItems();
294
+ applyPreview();
295
+ }
296
+ }
297
+
298
+ function movePreviewWithCollisions(x: number, y: number) {
299
+ if (gridParams.collision === 'compress') {
300
+ movePreviewWithCollisionsWithCompress(x, y);
301
+ } else {
302
+ movePreviewWithCollisionsWithPush(x, y);
303
+ }
304
+ }
305
+
306
+ function moveEnd(event: PointerEvent) {
307
+ if (event.button !== 0) return;
308
+ endInteraction(event);
309
+ pointerShift = { left: 0, top: 0 };
310
+ window.removeEventListener('pointermove', move);
311
+ window.removeEventListener('pointerup', moveEnd);
312
+ }
313
+
314
+ // RESIZE ITEM LOGIC
315
+
316
+ let initialSize = { width: 0, height: 0 };
317
+
318
+ const minSize = $derived.by(() => {
319
+ if (gridParams.itemSize) {
320
+ return {
321
+ width: coordinate2size(min.w, gridParams.itemSize.width, gridParams.gap),
322
+ height: coordinate2size(min.h, gridParams.itemSize.height, gridParams.gap)
323
+ };
324
+ }
325
+ return undefined;
326
+ });
327
+
328
+ const maxSize = $derived.by(() => {
329
+ if (gridParams.itemSize && max) {
330
+ return {
331
+ width: coordinate2size(max.w, gridParams.itemSize.width, gridParams.gap),
332
+ height: coordinate2size(max.h, gridParams.itemSize.height, gridParams.gap)
333
+ };
334
+ }
335
+ return undefined;
336
+ });
337
+
338
+ const _resizable = $derived(!gridParams.readOnly && item.resizable);
339
+
340
+ function resizeStart(event: PointerEvent) {
341
+ if (event.button !== 0) return;
342
+ event.stopPropagation();
343
+ initInteraction(event);
344
+ initialSize = { width, height };
345
+ window.addEventListener('pointermove', resize);
346
+ window.addEventListener('pointerup', resizeEnd);
347
+ }
348
+
349
+ function resize(event: PointerEvent) {
350
+ if (!gridParams.itemSize) {
351
+ throw new Error('Grid is not mounted yet');
352
+ }
353
+
354
+ width = event.pageX + initialSize.width - initialPointerPosition.left;
355
+ height = event.pageY + initialSize.height - initialPointerPosition.top;
356
+
357
+ if (gridParams.bounds && gridParams.boundsTo) {
358
+ const parentRect = gridParams.boundsTo.getBoundingClientRect();
359
+ if (width + left > parentRect.width) {
360
+ width = parentRect.width - left;
361
+ }
362
+ if (height + top > parentRect.height) {
363
+ height = parentRect.height - top;
364
+ }
365
+ }
366
+
367
+ if (minSize) {
368
+ width = Math.max(width, minSize.width);
369
+ height = Math.max(height, minSize.height);
370
+ }
371
+ if (maxSize) {
372
+ width = Math.min(width, maxSize.width);
373
+ height = Math.min(height, maxSize.height);
374
+ }
375
+
376
+ const snapParams = gridParams as SnapGridParams;
377
+ const { w: newW, h: newH } = snapOnResize(width, height, previewItem, snapParams);
378
+ if (gridParams.collision !== 'none') {
379
+ resizePreviewWithCollisions(newW, newH);
380
+ } else {
381
+ if (!hasCollisions({ ...previewItem, w: newW, h: newH }, Object.values(gridParams.items))) {
382
+ previewItem = { ...previewItem, w: newW, h: newH };
383
+ }
384
+ }
385
+ }
386
+
387
+ function resizePreviewWithCollisionsWithPush(newW: number, newH: number) {
388
+ handleCollisionsForPreviewItemWithPush({ w: newW, h: newH });
389
+ }
390
+
391
+ function resizePreviewWithCollisionsWithCompress(newW: number, newH: number) {
392
+ const sizeChanged = newW != previewItem.w || newH != previewItem.h;
393
+ if (sizeChanged) {
394
+ const hGap = newH - previewItem.h;
395
+ previewItem = { ...previewItem, w: newW, h: newH };
396
+ applyPreview();
397
+ const collItems = getCollisions(
398
+ { ...previewItem, w: newW, h: 9999 },
399
+ Object.values(gridParams.items)
400
+ );
401
+ collItems.forEach((i) => {
402
+ i.y += hGap;
403
+ i.invalidate();
404
+ gridParams.updateGrid();
405
+ });
406
+ compressItems();
407
+ }
408
+ }
409
+
410
+ function resizePreviewWithCollisions(w: number, h: number) {
411
+ if (gridParams.collision === 'compress') {
412
+ resizePreviewWithCollisionsWithCompress(w, h);
413
+ } else {
414
+ resizePreviewWithCollisionsWithPush(w, h);
415
+ }
416
+ }
417
+
418
+ function resizeEnd(event: PointerEvent) {
419
+ if (event.button !== 0) return;
420
+ endInteraction(event);
421
+ window.removeEventListener('pointermove', resize);
422
+ window.removeEventListener('pointerup', resizeEnd);
423
+ }
424
+
425
+ function compressItems() {
426
+ const gridItems = Object.values(gridParams.items);
427
+ const sortedItems = [...gridItems].sort((a, b) => a.y - b.y);
428
+ sortedItems.reduce(
429
+ (accItem, currentItem) => {
430
+ if (currentItem.id === previewItem.id) {
431
+ // if previewItem do nothing
432
+ } else if (previewItem.y < currentItem.y + currentItem.h) {
433
+ // compress items above previewItem
434
+ const maxY =
435
+ currentItem.y >= previewItem.y
436
+ ? currentItem.y + previewItem.h + 1
437
+ : previewItem.y + currentItem.h + 1;
438
+ let newY = maxY;
439
+ while (newY >= 0) {
440
+ if (hasCollisions({ ...currentItem, y: newY }, accItem)) {
441
+ break;
442
+ }
443
+ newY--;
444
+ }
445
+ currentItem.y = newY + 1;
446
+ currentItem.invalidate();
447
+ gridParams.updateGrid();
448
+ accItem.push(currentItem);
449
+ } else {
450
+ // compress items below previewItem
451
+ let newY = currentItem.y;
452
+ while (newY >= 0) {
453
+ if (hasCollisions({ ...currentItem, y: newY }, accItem)) {
454
+ break;
455
+ }
456
+ newY--;
457
+ }
458
+ if (newY < currentItem.y && newY > 0) {
459
+ currentItem.y = newY + 1;
460
+ }
461
+ currentItem.invalidate();
462
+ gridParams.updateGrid();
463
+ accItem.push(currentItem);
464
+ }
465
+ return accItem;
466
+ },
467
+ [previewItem as LayoutItem]
468
+ );
469
+ }
470
+
471
+ const hasMoveHandle = $derived(!!moveHandle);
472
+ </script>
473
+
474
+ <div
475
+ role="group"
476
+ class={`${classes ?? ''} ${active ? (activeClass ?? '') : ''}`}
477
+ class:item-default={!classes}
478
+ class:active-default={!activeClass && active}
479
+ class:non-active-default={!active}
480
+ onpointerdown={_movable && !hasMoveHandle ? moveStart : undefined}
481
+ style={`position: absolute; left:${left}px; top:${top}px; width: ${width}px; height: ${height}px;
482
+ ${_movable && !hasMoveHandle ? 'cursor: move;' : ''} touch-action: none; user-select: none;
483
+ ${style}`}
484
+ bind:this={itemRef}
485
+ >
486
+ {#if _movable && moveHandle}
487
+ {@render moveHandle({ moveStart })}
488
+ {/if}
489
+
490
+ {#if children}
491
+ {@render children({ id, active, w, h })}
492
+ {/if}
493
+
494
+ {#if _resizable}
495
+ {#if resizeHandle}
496
+ {@render resizeHandle({ resizeStart })}
497
+ {:else}
498
+ <div
499
+ role="button"
500
+ tabindex="0"
501
+ class={resizerClass ?? ''}
502
+ class:resizer-default={!resizerClass}
503
+ onpointerdown={resizeStart}
504
+ ></div>
505
+ {/if}
506
+ {/if}
507
+ </div>
508
+
509
+ {#if active && gridParams.itemSize}
510
+ {@const preview = calcPosition(previewItem, {
511
+ itemSize: gridParams.itemSize,
512
+ gap: gridParams.gap
513
+ })}
514
+ <div
515
+ class={previewClass ?? ''}
516
+ class:item-preview-default={!previewClass}
517
+ style={`position: absolute; left:${preview.left}px; top:${preview.top}px;
518
+ width: ${preview.width}px; height: ${preview.height}px; z-index: -10;`}
519
+ ></div>
520
+ {/if}
521
+
522
+ <style>
523
+ .item-default {
524
+ transition:
525
+ width 0.2s,
526
+ height 0.2s;
527
+ transition:
528
+ transform 0.2s,
529
+ opacity 0.2s;
530
+ }
531
+ .active-default {
532
+ opacity: 0.7;
533
+ }
534
+ .item-preview-default {
535
+ background-color: rgb(192, 127, 127);
536
+ transition: all 0.2s;
537
+ }
538
+ .non-active-default {
539
+ transition:
540
+ left 0.2s,
541
+ top 0.2s;
542
+ transition-timing-function: ease-in-out;
543
+ }
544
+ .resizer-default {
545
+ -webkit-user-select: none;
546
+ -moz-user-select: none;
547
+ user-select: none;
548
+ touch-action: none;
549
+ position: absolute;
550
+ width: 20px;
551
+ height: 20px;
552
+ right: 0;
553
+ bottom: 0;
554
+ cursor: se-resize;
555
+ }
556
+ .resizer-default::after {
557
+ content: '';
558
+ position: absolute;
559
+ right: 3px;
560
+ bottom: 3px;
561
+ width: 5px;
562
+ height: 5px;
563
+ border-right: 2px solid rgba(0, 0, 0, 0.4);
564
+ border-bottom: 2px solid rgba(0, 0, 0, 0.4);
565
+ }
566
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { GridItemProps } from './types';
2
+ declare const GridItem: import("svelte").Component<GridItemProps, {}, "x" | "y" | "w" | "h">;
3
+ type GridItem = ReturnType<typeof GridItem>;
4
+ export default GridItem;
@@ -0,0 +1,5 @@
1
+ import Grid from './Grid.svelte';
2
+ import type { LayoutItem, LayoutChangeDetail, GridController, GridItemProps, Collision } from './types';
3
+ export { Grid, type LayoutItem, type LayoutChangeDetail, type GridController, type GridItemProps, type Collision };
4
+ export { default as GridItem } from './GridItem.svelte';
5
+ export default Grid;
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import Grid from './Grid.svelte';
2
+ export { Grid };
3
+ export { default as GridItem } from './GridItem.svelte';
4
+ export default Grid;
@@ -0,0 +1,112 @@
1
+ import type { RequireAtLeastOne } from './utils/types';
2
+ import type { Snippet } from 'svelte';
3
+ export type LayoutItem = Size & Position & {
4
+ id: string;
5
+ min?: Size;
6
+ max?: Size;
7
+ movable: boolean;
8
+ resizable: boolean;
9
+ invalidate: () => void;
10
+ };
11
+ /**
12
+ * Item position in grid units
13
+ */
14
+ export type Size = {
15
+ w: number;
16
+ h: number;
17
+ };
18
+ /**
19
+ * Item position in grid units
20
+ */
21
+ export type Position = {
22
+ x: number;
23
+ y: number;
24
+ };
25
+ /**
26
+ * Item position in pixels
27
+ */
28
+ export type ItemPosition = {
29
+ left: number;
30
+ top: number;
31
+ };
32
+ /**
33
+ * Item size in pixels
34
+ */
35
+ export type ItemSize = {
36
+ width: number;
37
+ height: number;
38
+ };
39
+ export type ItemChangeEvent = {
40
+ id: number;
41
+ x: number;
42
+ y: number;
43
+ w: number;
44
+ h: number;
45
+ };
46
+ export type BreakpointKey = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs';
47
+ export type Breakpoints = Record<BreakpointKey, number>;
48
+ export type GridSize = number | RequireAtLeastOne<Breakpoints>;
49
+ export type GridDimensions = {
50
+ cols: number;
51
+ rows: number;
52
+ };
53
+ export type GridParams = {
54
+ cols: number;
55
+ rows: number;
56
+ itemSize?: ItemSize;
57
+ gap: number;
58
+ maxCols: number;
59
+ maxRows: number;
60
+ bounds: boolean;
61
+ boundsTo?: HTMLElement;
62
+ items: Record<string, LayoutItem>;
63
+ readOnly: boolean;
64
+ debug: boolean;
65
+ collision: Collision;
66
+ registerItem: (item: LayoutItem) => void;
67
+ unregisterItem: (item: LayoutItem) => void;
68
+ updateGrid: () => void;
69
+ onchange: (detail: LayoutChangeDetail) => void;
70
+ };
71
+ export type LayoutChangeDetail = {
72
+ item: LayoutItem;
73
+ };
74
+ export type Collision = 'none' | 'push' | 'compress';
75
+ export type GridController = {
76
+ gridParams: GridParams;
77
+ getFirstAvailablePosition: (w: number, h: number) => Position | null;
78
+ compress: () => void;
79
+ };
80
+ export type MoveHandleSnippetProps = {
81
+ moveStart: (event: PointerEvent) => void;
82
+ };
83
+ export type ResizeHandleSnippetProps = {
84
+ resizeStart: (event: PointerEvent) => void;
85
+ };
86
+ export type GridItemDefaultSnippetProps = {
87
+ id: string;
88
+ active: boolean;
89
+ w: number;
90
+ h: number;
91
+ };
92
+ export type GridItemProps = {
93
+ id?: string;
94
+ x: number;
95
+ y: number;
96
+ w?: number;
97
+ h?: number;
98
+ min?: Size;
99
+ max?: Size;
100
+ movable?: boolean;
101
+ resizable?: boolean;
102
+ class?: string;
103
+ activeClass?: string;
104
+ previewClass?: string;
105
+ resizerClass?: string;
106
+ style?: string;
107
+ onchange?: (detail: LayoutChangeDetail) => void;
108
+ onpreviewchange?: (detail: LayoutChangeDetail) => void;
109
+ children?: Snippet<[GridItemDefaultSnippetProps]>;
110
+ moveHandle?: Snippet<[MoveHandleSnippetProps]>;
111
+ resizeHandle?: Snippet<[ResizeHandleSnippetProps]>;
112
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { Collision, GridSize, ItemSize } from '../types';
2
+ export type GridOptions = {
3
+ cols: GridSize;
4
+ rows: GridSize;
5
+ itemSize: Partial<ItemSize>;
6
+ collision?: Collision;
7
+ };
8
+ export declare function assertGridOptions(options: GridOptions): void;