ngx-kit 6.3.0 → 7.0.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.
- package/README.md +3 -3
- package/angle-selector/README.md +34 -34
- package/box-shadow/README.md +33 -33
- package/color-picker/README.md +52 -49
- package/data-table/README.md +120 -113
- package/date-picker/README.md +74 -69
- package/dialog/README.md +92 -92
- package/drag-resize/README.md +55 -0
- package/dropzone/README.md +422 -422
- package/fesm2022/ngx-kit-angle-selector.mjs +2 -2
- package/fesm2022/ngx-kit-angle-selector.mjs.map +1 -1
- package/fesm2022/ngx-kit-box-shadow.mjs +2 -2
- package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
- package/fesm2022/ngx-kit-color-picker.mjs +265 -73
- package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-data-table.mjs +2 -2
- package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
- package/fesm2022/ngx-kit-date-picker.mjs +7 -9
- package/fesm2022/ngx-kit-date-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-dialog.mjs +3 -3
- package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
- package/fesm2022/ngx-kit-drag-resize.mjs +1664 -0
- package/fesm2022/ngx-kit-drag-resize.mjs.map +1 -0
- package/fesm2022/ngx-kit-drawer-menu.mjs +389 -285
- package/fesm2022/ngx-kit-drawer-menu.mjs.map +1 -1
- package/fesm2022/ngx-kit-dropzone.mjs +2 -2
- package/fesm2022/ngx-kit-dropzone.mjs.map +1 -1
- package/fesm2022/ngx-kit-form-field.mjs +3 -5
- package/fesm2022/ngx-kit-form-field.mjs.map +1 -1
- package/fesm2022/ngx-kit-gallery.mjs +2 -2
- package/fesm2022/ngx-kit-gallery.mjs.map +1 -1
- package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-grid-layout.mjs +994 -0
- package/fesm2022/ngx-kit-grid-layout.mjs.map +1 -0
- package/fesm2022/ngx-kit-image-editor.mjs.map +1 -1
- package/fesm2022/ngx-kit-menu.mjs +8 -8
- package/fesm2022/ngx-kit-menu.mjs.map +1 -1
- package/fesm2022/ngx-kit-message.mjs.map +1 -1
- package/fesm2022/ngx-kit-notify.mjs +2 -2
- package/fesm2022/ngx-kit-notify.mjs.map +1 -1
- package/fesm2022/ngx-kit-shared.mjs +54 -3
- package/fesm2022/ngx-kit-shared.mjs.map +1 -1
- package/fesm2022/ngx-kit-time-picker.mjs +2 -2
- package/fesm2022/ngx-kit-time-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-virtual-scroll.mjs +392 -0
- package/fesm2022/ngx-kit-virtual-scroll.mjs.map +1 -0
- package/fesm2022/ngx-kit-webflow.mjs +709 -0
- package/fesm2022/ngx-kit-webflow.mjs.map +1 -0
- package/gallery/README.md +71 -71
- package/gradient-picker/README.md +41 -41
- package/grid-layout/README.md +64 -0
- package/image-editor/README.md +12 -12
- package/menu/README.md +85 -85
- package/message/README.md +70 -70
- package/notify/README.md +56 -58
- package/package.json +17 -1
- package/shared/README.md +117 -117
- package/time-picker/README.md +89 -95
- package/types/ngx-kit-color-picker.d.ts +72 -16
- package/types/ngx-kit-drag-resize.d.ts +368 -0
- package/types/ngx-kit-drawer-menu.d.ts +135 -86
- package/types/ngx-kit-grid-layout.d.ts +311 -0
- package/types/ngx-kit-image-editor.d.ts +1 -1
- package/types/ngx-kit-shared.d.ts +12 -1
- package/types/ngx-kit-virtual-scroll.d.ts +179 -0
- package/types/ngx-kit-webflow.d.ts +286 -0
- package/virtual-scroll/README.md +199 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, computed, effect, Injectable, input, output, inject, ElementRef, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from 'ngx-kit/drag-resize';
|
|
4
|
+
import { isRtl, NgxDraggable, NgxResizable } from 'ngx-kit/drag-resize';
|
|
5
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
+
|
|
7
|
+
class GridLayoutOptions {
|
|
8
|
+
cols = 12;
|
|
9
|
+
rowHeight = 50;
|
|
10
|
+
gap = 8;
|
|
11
|
+
padding = 0;
|
|
12
|
+
flow = 'free';
|
|
13
|
+
compact = 'vertical';
|
|
14
|
+
allowOverlap = false;
|
|
15
|
+
pushItems = true;
|
|
16
|
+
swap = false;
|
|
17
|
+
dragThreshold = 3;
|
|
18
|
+
animate = true;
|
|
19
|
+
rtl = 'auto';
|
|
20
|
+
maxRows;
|
|
21
|
+
gridBackgroundConfig = {
|
|
22
|
+
show: 'always',
|
|
23
|
+
borderColor: 'rgba(128,128,128,.16)',
|
|
24
|
+
gapColor: 'transparent',
|
|
25
|
+
rowColor: 'rgba(128,128,128,.07)',
|
|
26
|
+
columnColor: 'rgba(128,128,128,.07)',
|
|
27
|
+
borderWidth: 1,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class GridItemConfig {
|
|
32
|
+
x;
|
|
33
|
+
y;
|
|
34
|
+
w;
|
|
35
|
+
h;
|
|
36
|
+
minW;
|
|
37
|
+
minH;
|
|
38
|
+
maxW;
|
|
39
|
+
maxH;
|
|
40
|
+
/** Item can't be dragged or resized, and never moves during compaction/push. */
|
|
41
|
+
static;
|
|
42
|
+
/** Overrides the layout-level draggable/resizable flags for this item only. */
|
|
43
|
+
isDraggable;
|
|
44
|
+
isResizable;
|
|
45
|
+
constructor(x = 0, y = 0, w = 1, h = 1, s) {
|
|
46
|
+
this.x = x;
|
|
47
|
+
this.y = y;
|
|
48
|
+
this.w = w;
|
|
49
|
+
this.h = h;
|
|
50
|
+
this.static = s == true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const normalizeGridItem = (value) => ({
|
|
54
|
+
x: Math.max(0, Math.floor(value?.x ?? 0)),
|
|
55
|
+
y: Math.max(0, Math.floor(value?.y ?? 0)),
|
|
56
|
+
w: Math.max(1, Math.floor(value?.w ?? 1)),
|
|
57
|
+
h: Math.max(1, Math.floor(value?.h ?? 1)),
|
|
58
|
+
minW: value?.minW,
|
|
59
|
+
minH: value?.minH,
|
|
60
|
+
maxW: value?.maxW,
|
|
61
|
+
maxH: value?.maxH,
|
|
62
|
+
static: value?.static ?? false,
|
|
63
|
+
isDraggable: value?.isDraggable,
|
|
64
|
+
isResizable: value?.isResizable,
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Value equality for two (already-normalized) configs.
|
|
68
|
+
*
|
|
69
|
+
* This is what breaks the register -> settle -> new-object -> re-register
|
|
70
|
+
* feedback loop: signals compare by *reference*, so anything that recomputes
|
|
71
|
+
* a fresh config object on every change-detection pass (a `computed()` layout,
|
|
72
|
+
* an inline object literal in a template, etc.) will always look "changed" to
|
|
73
|
+
* an `effect()` even when nothing meaningful did. Callers must compare by
|
|
74
|
+
* value before writing back to a signal, otherwise the write itself produces
|
|
75
|
+
* a new reference that trips the same effect again — forever.
|
|
76
|
+
*/
|
|
77
|
+
function gridItemConfigsEqual(a, b) {
|
|
78
|
+
return (a.x === b.x &&
|
|
79
|
+
a.y === b.y &&
|
|
80
|
+
a.w === b.w &&
|
|
81
|
+
a.h === b.h &&
|
|
82
|
+
(a.minW ?? null) === (b.minW ?? null) &&
|
|
83
|
+
(a.minH ?? null) === (b.minH ?? null) &&
|
|
84
|
+
(a.maxW ?? null) === (b.maxW ?? null) &&
|
|
85
|
+
(a.maxH ?? null) === (b.maxH ?? null) &&
|
|
86
|
+
!!a.static === !!b.static &&
|
|
87
|
+
(a.isDraggable ?? null) === (b.isDraggable ?? null) &&
|
|
88
|
+
(a.isResizable ?? null) === (b.isResizable ?? null));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Axis-aligned bounding-box overlap test in grid-cell space. */
|
|
92
|
+
function collides(a, b) {
|
|
93
|
+
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
94
|
+
}
|
|
95
|
+
/** First item (other than `self`) whose cells overlap `config`, or null. */
|
|
96
|
+
function getFirstCollision(items, selfId, config) {
|
|
97
|
+
for (const item of items) {
|
|
98
|
+
if (item.id === selfId)
|
|
99
|
+
continue;
|
|
100
|
+
if (collides(item.config, config))
|
|
101
|
+
return item;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/** All items whose cells overlap `config`, excluding `selfId`. */
|
|
106
|
+
function getAllCollisions(items, selfId, config) {
|
|
107
|
+
return items.filter((item) => item.id !== selfId && collides(item.config, config));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isStatic(node) {
|
|
111
|
+
return !!node.config.static;
|
|
112
|
+
}
|
|
113
|
+
function clampToCols(config, cols) {
|
|
114
|
+
config.w = Math.min(Math.max(1, config.w), cols);
|
|
115
|
+
config.x = Math.max(0, Math.min(config.x, cols - config.w));
|
|
116
|
+
config.y = Math.max(0, config.y);
|
|
117
|
+
}
|
|
118
|
+
function clone(items) {
|
|
119
|
+
return items.map((i) => ({ ...i, config: { ...i.config } }));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Moves `id` to grid cell (x, y) and, unless overlap is explicitly allowed,
|
|
123
|
+
* cascades any resulting collisions by pushing the colliding items out of the
|
|
124
|
+
* way (downward for vertical-flow grids, rightward for horizontal-flow ones).
|
|
125
|
+
* Static items are treated as fixed obstacles: they block, but never move.
|
|
126
|
+
* Pure function — returns a new array, never mutates its input.
|
|
127
|
+
*/
|
|
128
|
+
function moveItem(items, id, x, y, opts, depth = 0) {
|
|
129
|
+
let arr = clone(items);
|
|
130
|
+
const item = arr.find((i) => i.id === id);
|
|
131
|
+
if (!item || isStatic(item))
|
|
132
|
+
return arr;
|
|
133
|
+
item.config.x = x;
|
|
134
|
+
item.config.y = y;
|
|
135
|
+
clampToCols(item.config, opts.cols);
|
|
136
|
+
if (opts.allowOverlap || depth > arr.length + 24)
|
|
137
|
+
return arr;
|
|
138
|
+
const compareFn = compareForCompaction(opts.compact);
|
|
139
|
+
let guard = 0;
|
|
140
|
+
while (guard++ < arr.length + 24) {
|
|
141
|
+
const mover = arr.find((i) => i.id === id);
|
|
142
|
+
const staticBlocker = arr.find((i) => i.id !== id && isStatic(i) && collides(mover.config, i.config));
|
|
143
|
+
if (staticBlocker) {
|
|
144
|
+
if (opts.compact === 'horizontal') {
|
|
145
|
+
mover.config.x = staticBlocker.config.x + staticBlocker.config.w;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
mover.config.y = staticBlocker.config.y + staticBlocker.config.h;
|
|
149
|
+
}
|
|
150
|
+
clampToCols(mover.config, opts.cols);
|
|
151
|
+
continue; // the new position may collide with something else — re-check from the top.
|
|
152
|
+
}
|
|
153
|
+
const blockers = arr
|
|
154
|
+
.filter((i) => i.id !== id && !isStatic(i) && collides(mover.config, i.config))
|
|
155
|
+
.sort(compareFn);
|
|
156
|
+
if (blockers.length === 0)
|
|
157
|
+
break;
|
|
158
|
+
arr = pushAway(arr, mover, blockers[0], opts, depth + 1);
|
|
159
|
+
}
|
|
160
|
+
return arr;
|
|
161
|
+
}
|
|
162
|
+
function pushAway(items, mover, blocked, opts, depth) {
|
|
163
|
+
const targetX = opts.compact === 'horizontal' ? mover.config.x + mover.config.w : blocked.config.x;
|
|
164
|
+
const targetY = opts.compact === 'horizontal' ? blocked.config.y : mover.config.y + mover.config.h;
|
|
165
|
+
return moveItem(items, blocked.id, targetX, targetY, opts, depth);
|
|
166
|
+
}
|
|
167
|
+
function compareForCompaction(mode) {
|
|
168
|
+
return (a, b) => mode === 'horizontal'
|
|
169
|
+
? a.config.x - b.config.x || a.config.y - b.config.y
|
|
170
|
+
: a.config.y - b.config.y || a.config.x - b.config.x;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* When the actively-moved item directly overlaps exactly one same-size item,
|
|
174
|
+
* exchanging their positions reads much better than a cascading push. The
|
|
175
|
+
* caller (service) decides when to offer this vs. falling back to `moveItem`.
|
|
176
|
+
*/
|
|
177
|
+
function trySwap(items, id, previous, next) {
|
|
178
|
+
if (items.some((i) => i.id !== id && isStatic(i) && collides(i.config, next)))
|
|
179
|
+
return null;
|
|
180
|
+
const collisions = items.filter((i) => i.id !== id && !isStatic(i) && collides(i.config, next));
|
|
181
|
+
if (collisions.length !== 1)
|
|
182
|
+
return null;
|
|
183
|
+
const [target] = collisions;
|
|
184
|
+
if (target.config.w !== next.w || target.config.h !== next.h)
|
|
185
|
+
return null;
|
|
186
|
+
const arr = clone(items);
|
|
187
|
+
const mover = arr.find((i) => i.id === id);
|
|
188
|
+
const other = arr.find((i) => i.id === target.id);
|
|
189
|
+
mover.config.x = next.x;
|
|
190
|
+
mover.config.y = next.y;
|
|
191
|
+
other.config.x = previous.x;
|
|
192
|
+
other.config.y = previous.y;
|
|
193
|
+
return arr;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Removes gaps by pulling every non-static item as far toward the start of the
|
|
197
|
+
* grid as it can go without overlapping another item, in reading order.
|
|
198
|
+
*/
|
|
199
|
+
function compact(items, mode) {
|
|
200
|
+
const arr = clone(items);
|
|
201
|
+
if (mode === 'none')
|
|
202
|
+
return arr;
|
|
203
|
+
const canX = mode === 'horizontal' || mode === 'both';
|
|
204
|
+
const canY = mode === 'vertical' || mode === 'both';
|
|
205
|
+
const sorted = [...arr].sort((a, b) => a.config.y - b.config.y || a.config.x - b.config.x);
|
|
206
|
+
for (const item of sorted) {
|
|
207
|
+
if (isStatic(item))
|
|
208
|
+
continue;
|
|
209
|
+
if (canY) {
|
|
210
|
+
let y = item.config.y;
|
|
211
|
+
while (y > 0 && !getFirstCollision(arr, item.id, { ...item.config, y: y - 1 }))
|
|
212
|
+
y--;
|
|
213
|
+
item.config.y = y;
|
|
214
|
+
}
|
|
215
|
+
if (canX) {
|
|
216
|
+
let x = item.config.x;
|
|
217
|
+
while (x > 0 && !getFirstCollision(arr, item.id, { ...item.config, x: x - 1 }))
|
|
218
|
+
x--;
|
|
219
|
+
item.config.x = x;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return arr;
|
|
223
|
+
}
|
|
224
|
+
/** Highest occupied row (0-based, exclusive) across all items — drives the grid's auto-height. */
|
|
225
|
+
function maxOccupiedRow(items) {
|
|
226
|
+
return items.reduce((max, i) => Math.max(max, i.config.y + i.config.h), 0);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Finds a cell for `config` that doesn't collide with any existing item.
|
|
230
|
+
* Tries the requested (x, y) first; if that's already occupied, scans
|
|
231
|
+
* forward in reading order (row by row, top to bottom, left to right — the
|
|
232
|
+
* caller is responsible for any RTL flipping when it turns this back into
|
|
233
|
+
* pixels) for the first gap large enough to fit it.
|
|
234
|
+
*
|
|
235
|
+
* This is what a newly-registered item (or one whose config changes to a
|
|
236
|
+
* position that's no longer free) should go through instead of silently
|
|
237
|
+
* overlapping whatever's already there: unlike a drag/resize, registering an
|
|
238
|
+
* item isn't a deliberate "make room for me" gesture, so relocating the new
|
|
239
|
+
* item to open space reads far better than shoving already-placed items
|
|
240
|
+
* around to accommodate it.
|
|
241
|
+
*/
|
|
242
|
+
function findFreeSpot(items, config, cols, excludeId) {
|
|
243
|
+
const others = excludeId ? items.filter((i) => i.id !== excludeId) : items;
|
|
244
|
+
const fits = (x, y) => !others.some((i) => collides(i.config, { ...config, x, y }));
|
|
245
|
+
const startX = Math.max(0, Math.min(config.x, cols - config.w));
|
|
246
|
+
const startY = Math.max(0, config.y);
|
|
247
|
+
if (fits(startX, startY))
|
|
248
|
+
return { x: startX, y: startY };
|
|
249
|
+
// The row just past the current bottom is always fully empty, so this loop
|
|
250
|
+
// is guaranteed to find a fit no later than `maxY`.
|
|
251
|
+
const maxY = maxOccupiedRow(others) + 1;
|
|
252
|
+
for (let y = 0; y <= maxY; y++) {
|
|
253
|
+
for (let x = 0; x <= cols - config.w; x++) {
|
|
254
|
+
if (fits(x, y))
|
|
255
|
+
return { x, y };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return { x: startX, y: maxY + 1 };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function computeMetrics(containerWidth, options) {
|
|
262
|
+
const cols = Math.max(1, Math.floor(options.cols));
|
|
263
|
+
const gap = options.gap ?? 0;
|
|
264
|
+
const padding = options.padding ?? 0;
|
|
265
|
+
const contentWidth = Math.max(0, containerWidth - padding * 2);
|
|
266
|
+
const colWidth = Math.max(1, (contentWidth - gap * (cols - 1)) / cols);
|
|
267
|
+
const rowHeight = options.rowHeight === 'fit' ? Math.max(1, colWidth) : Math.max(1, options.rowHeight);
|
|
268
|
+
return { contentWidth, colWidth, rowHeight, gap, padding };
|
|
269
|
+
}
|
|
270
|
+
/** Physical CSS `left` (px, relative to the grid surface's content box) for a logical column. */
|
|
271
|
+
function colToLeft(colIndex, spanCols, m, rtl) {
|
|
272
|
+
if (!rtl)
|
|
273
|
+
return m.padding + colIndex * (m.colWidth + m.gap);
|
|
274
|
+
const widthPx = spanCols * m.colWidth + (spanCols - 1) * m.gap;
|
|
275
|
+
const rightEdge = m.padding + colIndex * (m.colWidth + m.gap);
|
|
276
|
+
return m.contentWidth + m.padding - rightEdge - widthPx;
|
|
277
|
+
}
|
|
278
|
+
function rowToTop(rowIndex, m) {
|
|
279
|
+
return m.padding + rowIndex * (m.rowHeight + m.gap);
|
|
280
|
+
}
|
|
281
|
+
function spanToWidthPx(spanCols, m) {
|
|
282
|
+
return spanCols * m.colWidth + Math.max(0, spanCols - 1) * m.gap;
|
|
283
|
+
}
|
|
284
|
+
function spanToHeightPx(spanRows, m) {
|
|
285
|
+
return spanRows * m.rowHeight + Math.max(0, spanRows - 1) * m.gap;
|
|
286
|
+
}
|
|
287
|
+
/** Physical left px (as above) -> nearest logical column index. Inverse of `colToLeft`. */
|
|
288
|
+
function leftToCol(leftPx, spanCols, m, rtl) {
|
|
289
|
+
if (!rtl)
|
|
290
|
+
return Math.round((leftPx - m.padding) / (m.colWidth + m.gap));
|
|
291
|
+
const widthPx = spanCols * m.colWidth + (spanCols - 1) * m.gap;
|
|
292
|
+
const rightEdge = m.contentWidth + m.padding - leftPx - widthPx;
|
|
293
|
+
return Math.round((rightEdge - m.padding) / (m.colWidth + m.gap));
|
|
294
|
+
}
|
|
295
|
+
function topToRow(topPx, m) {
|
|
296
|
+
return Math.round((topPx - m.padding) / (m.rowHeight + m.gap));
|
|
297
|
+
}
|
|
298
|
+
/** Places an item's pixel box (left/top/width/height) inside the grid surface, RTL-aware. */
|
|
299
|
+
function placeItem(config, m, rtl) {
|
|
300
|
+
return {
|
|
301
|
+
left: colToLeft(config.x, config.w, m, rtl),
|
|
302
|
+
top: rowToTop(config.y, m),
|
|
303
|
+
width: spanToWidthPx(config.w, m),
|
|
304
|
+
height: spanToHeightPx(config.h, m),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Structural + value equality for two item-state arrays (order-independent). */
|
|
309
|
+
function itemsEqual(a, b) {
|
|
310
|
+
if (a.length !== b.length)
|
|
311
|
+
return false;
|
|
312
|
+
return a.every((itemA) => {
|
|
313
|
+
const itemB = b.find((x) => x.id === itemA.id);
|
|
314
|
+
return (!!itemB && itemA.element === itemB.element && gridItemConfigsEqual(itemA.config, itemB.config));
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/** Value equality for two options objects — see `setOptions` for why this matters. */
|
|
318
|
+
function optionsEqual(a, b) {
|
|
319
|
+
const bg1 = a.gridBackgroundConfig ?? {};
|
|
320
|
+
const bg2 = b.gridBackgroundConfig ?? {};
|
|
321
|
+
return (a.cols === b.cols &&
|
|
322
|
+
a.rowHeight === b.rowHeight &&
|
|
323
|
+
a.gap === b.gap &&
|
|
324
|
+
a.padding === b.padding &&
|
|
325
|
+
a.flow === b.flow &&
|
|
326
|
+
a.compact === b.compact &&
|
|
327
|
+
a.allowOverlap === b.allowOverlap &&
|
|
328
|
+
a.pushItems === b.pushItems &&
|
|
329
|
+
a.swap === b.swap &&
|
|
330
|
+
a.dragThreshold === b.dragThreshold &&
|
|
331
|
+
a.animate === b.animate &&
|
|
332
|
+
a.rtl === b.rtl &&
|
|
333
|
+
(a.maxRows ?? null) === (b.maxRows ?? null) &&
|
|
334
|
+
bg1.show === bg2.show &&
|
|
335
|
+
bg1.borderColor === bg2.borderColor &&
|
|
336
|
+
bg1.gapColor === bg2.gapColor &&
|
|
337
|
+
bg1.rowColor === bg2.rowColor &&
|
|
338
|
+
bg1.columnColor === bg2.columnColor &&
|
|
339
|
+
bg1.borderWidth === bg2.borderWidth);
|
|
340
|
+
}
|
|
341
|
+
class GridLayoutService {
|
|
342
|
+
options = signal(new GridLayoutOptions(), /* @ts-ignore */
|
|
343
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
344
|
+
items = signal([], /* @ts-ignore */
|
|
345
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
346
|
+
editMode = signal(true, /* @ts-ignore */
|
|
347
|
+
...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
|
|
348
|
+
activeId = signal(null, /* @ts-ignore */
|
|
349
|
+
...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
350
|
+
isInteracting = computed(() => this.activeId() !== null, /* @ts-ignore */
|
|
351
|
+
...(ngDevMode ? [{ debugName: "isInteracting" }] : /* istanbul ignore next */ []));
|
|
352
|
+
height = computed(() => this.calculateHeight(this.items(), this.options()), /* @ts-ignore */
|
|
353
|
+
...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
354
|
+
layout = computed(() => this.items().map((x) => ({ id: x.id, ...x.config })), /* @ts-ignore */
|
|
355
|
+
...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
356
|
+
columns = computed(() => Math.max(1, Math.floor(this.options().cols)), /* @ts-ignore */
|
|
357
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
358
|
+
rtl = computed(() => {
|
|
359
|
+
this.rtlTick();
|
|
360
|
+
return this.resolveRtl();
|
|
361
|
+
}, /* @ts-ignore */
|
|
362
|
+
...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
|
|
363
|
+
rtlTick = signal(0, /* @ts-ignore */
|
|
364
|
+
...(ngDevMode ? [{ debugName: "rtlTick" }] : /* istanbul ignore next */ []));
|
|
365
|
+
layoutListener;
|
|
366
|
+
startConfig = null;
|
|
367
|
+
startItems = null;
|
|
368
|
+
previewItems = signal(null, /* @ts-ignore */
|
|
369
|
+
...(ngDevMode ? [{ debugName: "previewItems" }] : /* istanbul ignore next */ []));
|
|
370
|
+
_element;
|
|
371
|
+
placeholder;
|
|
372
|
+
constructor() {
|
|
373
|
+
effect(() => {
|
|
374
|
+
// Re-run whenever options or items change so CSS positions always reflect state.
|
|
375
|
+
this.options();
|
|
376
|
+
this.items();
|
|
377
|
+
this.previewItems();
|
|
378
|
+
this.rtlTick();
|
|
379
|
+
if (this._element)
|
|
380
|
+
this.applyCss();
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
connect(onLayout) {
|
|
384
|
+
this.layoutListener = onLayout;
|
|
385
|
+
}
|
|
386
|
+
setOptions(value) {
|
|
387
|
+
const next = Object.assign(new GridLayoutOptions(), this.options(), value);
|
|
388
|
+
if (optionsEqual(this.options(), next))
|
|
389
|
+
return;
|
|
390
|
+
this.options.set(next);
|
|
391
|
+
}
|
|
392
|
+
setEditMode(value) {
|
|
393
|
+
this.editMode.set(value);
|
|
394
|
+
}
|
|
395
|
+
setItems(items) {
|
|
396
|
+
this.items.set(items.map((x) => ({ ...x, config: normalizeGridItem(x.config) })));
|
|
397
|
+
this.settle();
|
|
398
|
+
this.emit();
|
|
399
|
+
}
|
|
400
|
+
registerItem(item) {
|
|
401
|
+
const existing = this.items().find((x) => x.id === item.id);
|
|
402
|
+
if (!existing) {
|
|
403
|
+
const normalized = normalizeGridItem(item.config);
|
|
404
|
+
const placed = this.options().allowOverlap
|
|
405
|
+
? normalized
|
|
406
|
+
: { ...normalized, ...findFreeSpot(this.items(), normalized, this.columns()) };
|
|
407
|
+
this.items.update((xs) => [...xs, { ...item, config: placed }]);
|
|
408
|
+
this.settle();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (existing.element === item.element)
|
|
412
|
+
return;
|
|
413
|
+
this.items.update((xs) => xs.map((x) => (x.id === item.id ? { ...x, element: item.element } : x)));
|
|
414
|
+
}
|
|
415
|
+
updateItemConfig(id, config) {
|
|
416
|
+
const existing = this.items().find((x) => x.id === id);
|
|
417
|
+
if (!existing)
|
|
418
|
+
return;
|
|
419
|
+
const normalized = normalizeGridItem(config);
|
|
420
|
+
const placed = this.options().allowOverlap
|
|
421
|
+
? normalized
|
|
422
|
+
: { ...normalized, ...findFreeSpot(this.items(), normalized, this.columns(), id) };
|
|
423
|
+
if (gridItemConfigsEqual(existing.config, placed))
|
|
424
|
+
return;
|
|
425
|
+
this.items.update((xs) => xs.map((x) => (x.id === id ? { ...x, config: placed } : x)));
|
|
426
|
+
this.settle();
|
|
427
|
+
}
|
|
428
|
+
unregisterItem(id) {
|
|
429
|
+
this.items.update((xs) => xs.filter((x) => x.id !== id));
|
|
430
|
+
this.settle();
|
|
431
|
+
this.emit();
|
|
432
|
+
}
|
|
433
|
+
/** Called once the grid surface element exists (e.g. from `ngAfterViewInit`). */
|
|
434
|
+
attachElement(el) {
|
|
435
|
+
this._element = el;
|
|
436
|
+
this.placeholder =
|
|
437
|
+
el.querySelector('.ngx-grid-layout__placeholder') ?? undefined;
|
|
438
|
+
if (this.placeholder) {
|
|
439
|
+
this.placeholder.style.position = 'absolute';
|
|
440
|
+
this.placeholder.style.display = 'none';
|
|
441
|
+
this.placeholder.style.pointerEvents = 'none';
|
|
442
|
+
this.placeholder.setAttribute('aria-hidden', 'true');
|
|
443
|
+
}
|
|
444
|
+
// `rtl` reads `this._element` directly (it isn't a signal), so bump a tick
|
|
445
|
+
// signal to force the `rtl` computed to re-evaluate now that the element,
|
|
446
|
+
// and therefore its computed `direction`, is known.
|
|
447
|
+
this.rtlTick.update((n) => n + 1);
|
|
448
|
+
this.applyCss();
|
|
449
|
+
}
|
|
450
|
+
/** Recomputes pixel positions after the surface resizes. Call from a `ResizeObserver`. */
|
|
451
|
+
refreshMetrics() {
|
|
452
|
+
this.applyCss();
|
|
453
|
+
}
|
|
454
|
+
isItemDraggable(item) {
|
|
455
|
+
return this.editMode() && !item.config.static && (item.config.isDraggable ?? true);
|
|
456
|
+
}
|
|
457
|
+
isItemResizable(item) {
|
|
458
|
+
return this.editMode() && !item.config.static && (item.config.isResizable ?? true);
|
|
459
|
+
}
|
|
460
|
+
// ---- Pointer-driven interaction (drag / resize) ----------------------------------
|
|
461
|
+
begin(id) {
|
|
462
|
+
const target = this.items().find((x) => x.id === id);
|
|
463
|
+
if (!target)
|
|
464
|
+
return;
|
|
465
|
+
this.startItems = this.items().map((x) => ({ ...x, config: { ...x.config } }));
|
|
466
|
+
this.startConfig = { ...target.config };
|
|
467
|
+
this.previewItems.set(null);
|
|
468
|
+
this.activeId.set(id);
|
|
469
|
+
this.applyCss();
|
|
470
|
+
}
|
|
471
|
+
move(id) {
|
|
472
|
+
const target = this.items().find((it) => it.id === id);
|
|
473
|
+
if (!target?.element)
|
|
474
|
+
return;
|
|
475
|
+
const next = this.screenToGrid(target);
|
|
476
|
+
this.preview(id, next);
|
|
477
|
+
}
|
|
478
|
+
resize(id, out) {
|
|
479
|
+
const target = this.items().find((x) => x.id === id);
|
|
480
|
+
if (!target?.element)
|
|
481
|
+
return;
|
|
482
|
+
const next = this.screenResizeToGrid(target, out);
|
|
483
|
+
this.preview(id, next);
|
|
484
|
+
}
|
|
485
|
+
end(id) {
|
|
486
|
+
if (this.activeId() !== id)
|
|
487
|
+
return;
|
|
488
|
+
const preview = this.previewItems();
|
|
489
|
+
if (preview) {
|
|
490
|
+
this.items.set(preview.map((x) => ({ ...x, config: { ...x.config } })));
|
|
491
|
+
}
|
|
492
|
+
const target = this.items().find((x) => x.id === id);
|
|
493
|
+
if (target?.element)
|
|
494
|
+
target.element.style.transform = '';
|
|
495
|
+
this.previewItems.set(null);
|
|
496
|
+
this.activeId.set(null);
|
|
497
|
+
this.startConfig = null;
|
|
498
|
+
this.startItems = null;
|
|
499
|
+
// Final normalization/compaction happens only after the preview is committed.
|
|
500
|
+
this.settle(id);
|
|
501
|
+
this.applyCss();
|
|
502
|
+
this.emit();
|
|
503
|
+
}
|
|
504
|
+
// ---- Keyboard-driven interaction (a11y) -------------------------------------------
|
|
505
|
+
/** Moves an item by whole grid cells — used by arrow-key handling in the item component. */
|
|
506
|
+
moveByCells(id, dCols, dRows) {
|
|
507
|
+
const target = this.items().find((x) => x.id === id);
|
|
508
|
+
if (!target)
|
|
509
|
+
return;
|
|
510
|
+
this.begin(id);
|
|
511
|
+
this.preview(id, { ...target.config, x: target.config.x + dCols, y: target.config.y + dRows });
|
|
512
|
+
this.end(id);
|
|
513
|
+
}
|
|
514
|
+
/** Resizes an item by whole grid cells — used by shift+arrow handling in the item component. */
|
|
515
|
+
resizeByCells(id, dCols, dRows) {
|
|
516
|
+
const target = this.items().find((x) => x.id === id);
|
|
517
|
+
if (!target)
|
|
518
|
+
return;
|
|
519
|
+
this.begin(id);
|
|
520
|
+
const w = this.clampSize(target.config.w + dCols, target.config.minW, target.config.maxW, this.columns());
|
|
521
|
+
const h = this.clampSize(target.config.h + dRows, target.config.minH, target.config.maxH, Infinity);
|
|
522
|
+
this.preview(id, { ...target.config, w, h });
|
|
523
|
+
this.end(id);
|
|
524
|
+
}
|
|
525
|
+
// ---- Internals ----------------------------------------------------------------------
|
|
526
|
+
preview(id, config) {
|
|
527
|
+
const base = this.startItems;
|
|
528
|
+
if (!base)
|
|
529
|
+
return;
|
|
530
|
+
const clamped = this.clamp(config);
|
|
531
|
+
const opt = this.options();
|
|
532
|
+
// The placeholder is the moving item during preview. The real DOM element
|
|
533
|
+
// remains under ngxDraggable's control and is never repositioned by the grid.
|
|
534
|
+
let candidate;
|
|
535
|
+
if (!opt.allowOverlap && opt.swap && this.startConfig) {
|
|
536
|
+
const swapped = trySwap(base, id, this.startConfig, clamped);
|
|
537
|
+
candidate =
|
|
538
|
+
swapped ??
|
|
539
|
+
base.map((x) => x.id === id ? { ...x, config: { ...clamped } } : { ...x, config: { ...x.config } });
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
candidate = base.map((x) => x.id === id ? { ...x, config: { ...clamped } } : { ...x, config: { ...x.config } });
|
|
543
|
+
}
|
|
544
|
+
if (!opt.allowOverlap && opt.pushItems) {
|
|
545
|
+
candidate = moveItem(candidate, id, clamped.x, clamped.y, {
|
|
546
|
+
cols: this.columns(),
|
|
547
|
+
compact: opt.compact === 'none' ? 'vertical' : opt.compact,
|
|
548
|
+
allowOverlap: false,
|
|
549
|
+
maxRows: opt.maxRows,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (!opt.allowOverlap && opt.compact !== 'none') {
|
|
553
|
+
candidate = compact(candidate, opt.compact);
|
|
554
|
+
}
|
|
555
|
+
this.previewItems.set(candidate);
|
|
556
|
+
this.applyCss();
|
|
557
|
+
}
|
|
558
|
+
screenToGrid(item) {
|
|
559
|
+
const containerRect = this._element.getBoundingClientRect();
|
|
560
|
+
const r = item.element.getBoundingClientRect();
|
|
561
|
+
const m = this.metrics();
|
|
562
|
+
const rtl = this.rtl();
|
|
563
|
+
const leftPx = r.left - containerRect.left;
|
|
564
|
+
const topPx = r.top - containerRect.top;
|
|
565
|
+
const nx = leftToCol(leftPx, item.config.w, m, rtl);
|
|
566
|
+
const ny = topToRow(topPx, m);
|
|
567
|
+
return this.clamp({ ...item.config, x: nx, y: ny });
|
|
568
|
+
}
|
|
569
|
+
screenResizeToGrid(item, out) {
|
|
570
|
+
const m = this.metrics();
|
|
571
|
+
const gap = m.gap;
|
|
572
|
+
let w = Math.max(1, Math.round((out.width + gap) / (m.colWidth + gap)));
|
|
573
|
+
let h = Math.max(1, Math.round((out.height + gap) / (m.rowHeight + gap)));
|
|
574
|
+
let x = item.config.x;
|
|
575
|
+
let y = item.config.y;
|
|
576
|
+
// moveLeft/moveTop are physical (viewport) deltas from the resizable directive;
|
|
577
|
+
// moving the item's *start* edge left/up in physical space means, in RTL, that
|
|
578
|
+
// the logical column count grows from the *end* instead — handled by re-deriving
|
|
579
|
+
// x from the resulting physical box rather than from moveLeft directly.
|
|
580
|
+
if (this.rtl()) {
|
|
581
|
+
// 'w' handle grows width to the right physically but that's the grid's logical end;
|
|
582
|
+
// recompute x from the (possibly shifted) physical left edge captured via the element.
|
|
583
|
+
const r = item.element.getBoundingClientRect();
|
|
584
|
+
const containerRect = this._element.getBoundingClientRect();
|
|
585
|
+
const leftPx = r.left + out.moveLeft - containerRect.left;
|
|
586
|
+
x = leftToCol(leftPx, w, m, true);
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
if (out.moveLeft)
|
|
590
|
+
x += Math.round(out.moveLeft / (m.colWidth + gap));
|
|
591
|
+
}
|
|
592
|
+
if (out.moveTop)
|
|
593
|
+
y += Math.round(out.moveTop / (m.rowHeight + gap));
|
|
594
|
+
return this.clamp({ ...item.config, x, y, w, h });
|
|
595
|
+
}
|
|
596
|
+
clamp(c) {
|
|
597
|
+
const cols = this.columns();
|
|
598
|
+
const next = { ...c };
|
|
599
|
+
next.w = this.clampSize(next.w, next.minW, next.maxW, cols);
|
|
600
|
+
next.h = this.clampSize(next.h, next.minH, next.maxH, this.options().maxRows ?? Infinity);
|
|
601
|
+
next.x = Math.max(0, Math.min(next.x, cols - next.w));
|
|
602
|
+
next.y = Math.max(0, next.y);
|
|
603
|
+
if (this.options().maxRows)
|
|
604
|
+
next.y = Math.min(next.y, Math.max(0, this.options().maxRows - next.h));
|
|
605
|
+
return next;
|
|
606
|
+
}
|
|
607
|
+
clampSize(size, min, max, hardMax) {
|
|
608
|
+
let v = Math.max(min ?? 1, size);
|
|
609
|
+
if (max)
|
|
610
|
+
v = Math.min(v, max);
|
|
611
|
+
return Math.min(v, hardMax);
|
|
612
|
+
}
|
|
613
|
+
/** Applies pushes on drag-end and, when configured, removes gaps via compaction. */
|
|
614
|
+
settle(activeId) {
|
|
615
|
+
const opt = this.options();
|
|
616
|
+
const before = this.items();
|
|
617
|
+
let arr = before.map((x) => ({ ...x, config: { ...x.config } }));
|
|
618
|
+
if (!opt.allowOverlap) {
|
|
619
|
+
if (activeId && opt.pushItems) {
|
|
620
|
+
arr = moveItem(arr, activeId, arr.find((x) => x.id === activeId).config.x, arr.find((x) => x.id === activeId).config.y, {
|
|
621
|
+
cols: this.columns(),
|
|
622
|
+
compact: opt.compact === 'none' ? 'vertical' : opt.compact,
|
|
623
|
+
allowOverlap: false,
|
|
624
|
+
maxRows: opt.maxRows,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
if (opt.compact !== 'none')
|
|
628
|
+
arr = compact(arr, opt.compact);
|
|
629
|
+
}
|
|
630
|
+
// Compaction/push are deterministic pure functions: if they didn't change
|
|
631
|
+
// anything, keep the previous array reference instead of writing an
|
|
632
|
+
// equal-but-new one — that would otherwise ripple into every computed
|
|
633
|
+
// derived from `items` (layout, height, per-item `interactive`, ...) for
|
|
634
|
+
// no reason, and risks re-arming the same feedback loop described above.
|
|
635
|
+
if (itemsEqual(before, arr))
|
|
636
|
+
return;
|
|
637
|
+
this.items.set(arr);
|
|
638
|
+
this.applyCss();
|
|
639
|
+
}
|
|
640
|
+
applyCss() {
|
|
641
|
+
if (!this._element)
|
|
642
|
+
return;
|
|
643
|
+
const source = this.previewItems() ?? this.items();
|
|
644
|
+
const activeId = this.activeId();
|
|
645
|
+
const m = this.metrics();
|
|
646
|
+
const rtl = this.rtl();
|
|
647
|
+
for (const item of source) {
|
|
648
|
+
if (!item.element)
|
|
649
|
+
continue;
|
|
650
|
+
// While dragging/resizing, the real active element is moved by
|
|
651
|
+
// ngxDraggable/ngxResizable. Never fight those directives by writing
|
|
652
|
+
// left/top/width/height here.
|
|
653
|
+
if (item.id === activeId)
|
|
654
|
+
continue;
|
|
655
|
+
const box = placeItem(item.config, m, rtl);
|
|
656
|
+
const style = item.element.style;
|
|
657
|
+
style.position = 'absolute';
|
|
658
|
+
style.left = `${box.left}px`;
|
|
659
|
+
style.top = `${box.top}px`;
|
|
660
|
+
style.width = `${box.width}px`;
|
|
661
|
+
style.height = `${box.height}px`;
|
|
662
|
+
item.element.dataset['gridX'] = String(item.config.x);
|
|
663
|
+
item.element.dataset['gridY'] = String(item.config.y);
|
|
664
|
+
item.element.dataset['gridW'] = String(item.config.w);
|
|
665
|
+
item.element.dataset['gridH'] = String(item.config.h);
|
|
666
|
+
}
|
|
667
|
+
const placeholderItem = activeId ? source.find((x) => x.id === activeId) : undefined;
|
|
668
|
+
if (this.placeholder) {
|
|
669
|
+
if (placeholderItem && activeId) {
|
|
670
|
+
const box = placeItem(placeholderItem.config, m, rtl);
|
|
671
|
+
const style = this.placeholder.style;
|
|
672
|
+
style.display = 'block';
|
|
673
|
+
style.left = `${box.left}px`;
|
|
674
|
+
style.top = `${box.top}px`;
|
|
675
|
+
style.width = `${box.width}px`;
|
|
676
|
+
style.height = `${box.height}px`;
|
|
677
|
+
this.placeholder.dataset['gridX'] = String(placeholderItem.config.x);
|
|
678
|
+
this.placeholder.dataset['gridY'] = String(placeholderItem.config.y);
|
|
679
|
+
this.placeholder.dataset['gridW'] = String(placeholderItem.config.w);
|
|
680
|
+
this.placeholder.dataset['gridH'] = String(placeholderItem.config.h);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
this.placeholder.style.display = 'none';
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
metrics() {
|
|
688
|
+
const rect = this._element?.getBoundingClientRect() ?? { width: 0 };
|
|
689
|
+
return computeMetrics(rect.width, this.options());
|
|
690
|
+
}
|
|
691
|
+
resolveRtl() {
|
|
692
|
+
const opt = this.options().rtl;
|
|
693
|
+
if (opt === true || opt === false)
|
|
694
|
+
return opt;
|
|
695
|
+
return this._element ? isRtl(this._element) : false;
|
|
696
|
+
}
|
|
697
|
+
calculateHeight(items, o) {
|
|
698
|
+
const preview = this.previewItems();
|
|
699
|
+
const source = preview ?? items;
|
|
700
|
+
const rows = Math.max(1, maxOccupiedRow(source));
|
|
701
|
+
const m = this._element
|
|
702
|
+
? this.metrics()
|
|
703
|
+
: {
|
|
704
|
+
rowHeight: o.rowHeight === 'fit' ? 100 : o.rowHeight,
|
|
705
|
+
gap: o.gap ?? 0,
|
|
706
|
+
padding: o.padding ?? 0,
|
|
707
|
+
};
|
|
708
|
+
return m.padding * 2 + rows * m.rowHeight + Math.max(0, rows - 1) * m.gap;
|
|
709
|
+
}
|
|
710
|
+
emit() {
|
|
711
|
+
this.layoutListener?.(this.layout());
|
|
712
|
+
}
|
|
713
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: GridLayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
714
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: GridLayoutService });
|
|
715
|
+
}
|
|
716
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: GridLayoutService, decorators: [{
|
|
717
|
+
type: Injectable
|
|
718
|
+
}], ctorParameters: () => [] });
|
|
719
|
+
|
|
720
|
+
class NgxGridLayoutComponent {
|
|
721
|
+
options = input(new GridLayoutOptions(), /* @ts-ignore */
|
|
722
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
723
|
+
editMode = input(true, /* @ts-ignore */
|
|
724
|
+
...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
|
|
725
|
+
layoutChange = output();
|
|
726
|
+
/** Explicit direction override. Leave unset to inherit the page/host direction automatically. */
|
|
727
|
+
dir = input(undefined, /* @ts-ignore */
|
|
728
|
+
...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
729
|
+
dirOverride = this.dir;
|
|
730
|
+
service = inject(GridLayoutService);
|
|
731
|
+
el = inject((ElementRef));
|
|
732
|
+
surface;
|
|
733
|
+
resizeObserver;
|
|
734
|
+
constructor() {
|
|
735
|
+
effect(() => {
|
|
736
|
+
const opts = { ...this.options() };
|
|
737
|
+
if (this.dir())
|
|
738
|
+
opts.rtl = this.dir() === 'rtl';
|
|
739
|
+
this.service.setOptions(opts);
|
|
740
|
+
this.service.setEditMode(this.editMode());
|
|
741
|
+
});
|
|
742
|
+
this.service.connect((layout) => this.layoutChange.emit(layout));
|
|
743
|
+
}
|
|
744
|
+
ngAfterViewInit() {
|
|
745
|
+
this.surface = this.el.nativeElement.querySelector('.ngx-grid-layout__surface');
|
|
746
|
+
this.service.attachElement(this.surface);
|
|
747
|
+
this.setCss();
|
|
748
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
749
|
+
this.service.refreshMetrics();
|
|
750
|
+
this.setCss();
|
|
751
|
+
});
|
|
752
|
+
this.resizeObserver.observe(this.surface);
|
|
753
|
+
}
|
|
754
|
+
ngOnDestroy() {
|
|
755
|
+
this.resizeObserver?.disconnect();
|
|
756
|
+
}
|
|
757
|
+
setItems(items) {
|
|
758
|
+
this.service.setItems(items);
|
|
759
|
+
}
|
|
760
|
+
update(options) {
|
|
761
|
+
this.service.setOptions(options);
|
|
762
|
+
this.setCss();
|
|
763
|
+
}
|
|
764
|
+
setCss() {
|
|
765
|
+
const el = this.el.nativeElement, o = this.service.options();
|
|
766
|
+
el.style.setProperty('--grid-cols', String(o.cols));
|
|
767
|
+
el.style.setProperty('--grid-gap', `${o.gap ?? 0}px`);
|
|
768
|
+
el.style.setProperty('--grid-row', `${o.rowHeight === 'fit' ? 100 : o.rowHeight}px`);
|
|
769
|
+
const b = o.gridBackgroundConfig;
|
|
770
|
+
el.style.setProperty('--grid-gap-color', `${b.gapColor}`);
|
|
771
|
+
el.style.setProperty('--grid-border-width', `${b?.borderWidth ?? 0}px`);
|
|
772
|
+
el.style.setProperty('--grid-border-color', b?.borderColor ?? 'transparent');
|
|
773
|
+
el.style.setProperty('--grid-row-color', b?.rowColor ?? 'transparent');
|
|
774
|
+
el.style.setProperty('--grid-col-color', b?.columnColor ?? 'transparent');
|
|
775
|
+
el.style.setProperty('--grid-bg-opacity', b?.show === 'never'
|
|
776
|
+
? '0'
|
|
777
|
+
: b?.show === 'whenDragging'
|
|
778
|
+
? this.service.isInteracting()
|
|
779
|
+
? '1'
|
|
780
|
+
: '0'
|
|
781
|
+
: '1');
|
|
782
|
+
}
|
|
783
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
784
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: NgxGridLayoutComponent, isStandalone: true, selector: "ngx-grid-layout", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layoutChange: "layoutChange" }, host: { properties: { "class.ngx-grid-layout--dragging": "service.isInteracting()", "class.ngx-grid-layout--view": "!editMode()", "style.height.px": "service.height()", "attr.dir": "dirOverride() ?? null" } }, ngImport: i0, template: `
|
|
785
|
+
<div class="ngx-grid-layout__surface" [class.edit-mode]="editMode()">
|
|
786
|
+
<div class="ngx-grid-layout__placeholder"></div>
|
|
787
|
+
<ng-content />
|
|
788
|
+
</div>
|
|
789
|
+
`, isInline: true, styles: [":host{display:block;position:relative;contain:layout style;min-height:1px}.ngx-grid-layout__surface{position:relative;width:100%;height:100%;min-height:inherit}.ngx-grid-layout__placeholder{display:none;position:absolute;box-sizing:border-box;pointer-events:none;z-index:0;border:2px dashed currentColor;border-radius:inherit;opacity:.45}.ngx-grid-layout__surface.edit-mode:before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:var(--grid-bg-opacity);border:var(--grid-border-width) solid var(--grid-border-color);box-sizing:border-box;transition:opacity .15s ease;background-image:repeating-linear-gradient(var(--grid-border-color) 0 var(--grid-border-width),var(--grid-row-color) var(--grid-border-width) calc(var(--grid-row) - var(--grid-border-width)),var(--grid-border-color) calc(var(--grid-row) - var(--grid-border-width)) calc(var(--grid-row)),var(--grid-gap-color) calc(var(--grid-row)) calc(var(--grid-row) + var(--grid-gap))),repeating-linear-gradient(90deg,var(--grid-border-color) 0 var(--grid-border-width),var(--grid-col-color) var(--grid-border-width) calc(100% - (var(--grid-border-width) + var(--grid-gap))),var(--grid-border-color) calc(100% - (var(--grid-border-width) + var(--grid-gap))) calc(100% - var(--grid-gap)),var(--grid-gap-color) calc(100% - var(--grid-gap)) 100%);background-size:calc((100% + var(--grid-gap)) / var(--grid-cols)) calc(var(--grid-row) + var(--grid-gap));background-position:0 0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
790
|
+
}
|
|
791
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridLayoutComponent, decorators: [{
|
|
792
|
+
type: Component,
|
|
793
|
+
args: [{ selector: 'ngx-grid-layout', standalone: true, template: `
|
|
794
|
+
<div class="ngx-grid-layout__surface" [class.edit-mode]="editMode()">
|
|
795
|
+
<div class="ngx-grid-layout__placeholder"></div>
|
|
796
|
+
<ng-content />
|
|
797
|
+
</div>
|
|
798
|
+
`, host: {
|
|
799
|
+
'[class.ngx-grid-layout--dragging]': 'service.isInteracting()',
|
|
800
|
+
'[class.ngx-grid-layout--view]': '!editMode()',
|
|
801
|
+
'[style.height.px]': 'service.height()',
|
|
802
|
+
'[attr.dir]': 'dirOverride() ?? null',
|
|
803
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;contain:layout style;min-height:1px}.ngx-grid-layout__surface{position:relative;width:100%;height:100%;min-height:inherit}.ngx-grid-layout__placeholder{display:none;position:absolute;box-sizing:border-box;pointer-events:none;z-index:0;border:2px dashed currentColor;border-radius:inherit;opacity:.45}.ngx-grid-layout__surface.edit-mode:before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:var(--grid-bg-opacity);border:var(--grid-border-width) solid var(--grid-border-color);box-sizing:border-box;transition:opacity .15s ease;background-image:repeating-linear-gradient(var(--grid-border-color) 0 var(--grid-border-width),var(--grid-row-color) var(--grid-border-width) calc(var(--grid-row) - var(--grid-border-width)),var(--grid-border-color) calc(var(--grid-row) - var(--grid-border-width)) calc(var(--grid-row)),var(--grid-gap-color) calc(var(--grid-row)) calc(var(--grid-row) + var(--grid-gap))),repeating-linear-gradient(90deg,var(--grid-border-color) 0 var(--grid-border-width),var(--grid-col-color) var(--grid-border-width) calc(100% - (var(--grid-border-width) + var(--grid-gap))),var(--grid-border-color) calc(100% - (var(--grid-border-width) + var(--grid-gap))) calc(100% - var(--grid-gap)),var(--grid-gap-color) calc(100% - var(--grid-gap)) 100%);background-size:calc((100% + var(--grid-gap)) / var(--grid-cols)) calc(var(--grid-row) + var(--grid-gap));background-position:0 0}\n"] }]
|
|
804
|
+
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], editMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editMode", required: false }] }], layoutChange: [{ type: i0.Output, args: ["layoutChange"] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }] } });
|
|
805
|
+
|
|
806
|
+
class NgxGridItemComponent {
|
|
807
|
+
/** Bound as `[id]` in templates (matches the demo app's existing usage). */
|
|
808
|
+
itemId = input(undefined, { ...(ngDevMode ? { debugName: "itemId" } : /* istanbul ignore next */ {}), alias: 'id' });
|
|
809
|
+
config = input({ x: 0, y: 0, w: 1, h: 1 }, /* @ts-ignore */
|
|
810
|
+
...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
|
|
811
|
+
layoutChange = output();
|
|
812
|
+
dragging = signal(false, /* @ts-ignore */
|
|
813
|
+
...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
814
|
+
resizing = signal(false, /* @ts-ignore */
|
|
815
|
+
...(ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []));
|
|
816
|
+
active = computed(() => this.dragging() || this.resizing(), /* @ts-ignore */
|
|
817
|
+
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
818
|
+
transitionStyle = computed(() => !this.active() && this.service.options().animate
|
|
819
|
+
? 'left .18s ease, top .18s ease, width .18s ease, height .18s ease'
|
|
820
|
+
: 'none', /* @ts-ignore */
|
|
821
|
+
...(ngDevMode ? [{ debugName: "transitionStyle" }] : /* istanbul ignore next */ []));
|
|
822
|
+
interactive = computed(() => {
|
|
823
|
+
const id = this.itemId();
|
|
824
|
+
const item = id ? this.service.items().find((x) => x.id === id) : undefined;
|
|
825
|
+
return !!item && (this.service.isItemDraggable(item) || this.service.isItemResizable(item));
|
|
826
|
+
}, /* @ts-ignore */
|
|
827
|
+
...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
828
|
+
service = inject(GridLayoutService);
|
|
829
|
+
el = inject((ElementRef));
|
|
830
|
+
drag = inject(NgxDraggable);
|
|
831
|
+
resize = inject(NgxResizable);
|
|
832
|
+
resizable = computed(() => {
|
|
833
|
+
return !this.resize.disabled && this.service.editMode();
|
|
834
|
+
}, /* @ts-ignore */
|
|
835
|
+
...(ngDevMode ? [{ debugName: "resizable" }] : /* istanbul ignore next */ []));
|
|
836
|
+
constructor() {
|
|
837
|
+
// `takeUntilDestroyed()` releases these subscriptions on destroy — the
|
|
838
|
+
// event for every grid item ever created (real damage in layouts where
|
|
839
|
+
// items are added/removed dynamically, e.g. via *ngFor + trackBy).
|
|
840
|
+
this.drag.dragStart.pipe(takeUntilDestroyed()).subscribe(() => this.onDragStart());
|
|
841
|
+
this.drag.dragMove.pipe(takeUntilDestroyed()).subscribe(() => this.onDragMove());
|
|
842
|
+
this.drag.dragEnd.pipe(takeUntilDestroyed()).subscribe(() => this.onDragEnd());
|
|
843
|
+
this.resize.resizeStart.pipe(takeUntilDestroyed()).subscribe(() => this.onResizeStart());
|
|
844
|
+
this.resize.resizeMove.pipe(takeUntilDestroyed()).subscribe((v) => this.onResizeMove(v));
|
|
845
|
+
this.resize.resizeEnd.pipe(takeUntilDestroyed()).subscribe(() => this.onResizeEnd());
|
|
846
|
+
// Keep the hosted directives in sync with per-item state: static items and
|
|
847
|
+
// items explicitly marked non-draggable/non-resizable stop reacting entirely.
|
|
848
|
+
effect(() => {
|
|
849
|
+
const id = this.itemId();
|
|
850
|
+
const item = id ? this.service.items().find((x) => x.id === id) : undefined;
|
|
851
|
+
this.drag.disabled = !item || !this.service.isItemDraggable(item);
|
|
852
|
+
this.resize.disabled = !item || !this.service.isItemResizable(item);
|
|
853
|
+
if (item)
|
|
854
|
+
this.applySizeConstraints(item.config);
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
ngOnInit() {
|
|
858
|
+
const id = this.itemId();
|
|
859
|
+
if (!id)
|
|
860
|
+
return;
|
|
861
|
+
this.service.registerItem({
|
|
862
|
+
id,
|
|
863
|
+
config: this.config(),
|
|
864
|
+
element: this.el.nativeElement,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
ngOnDestroy() {
|
|
868
|
+
const id = this.itemId();
|
|
869
|
+
if (id)
|
|
870
|
+
this.service.unregisterItem(id);
|
|
871
|
+
}
|
|
872
|
+
onDragStart = () => {
|
|
873
|
+
this.dragging.set(true);
|
|
874
|
+
const id = this.itemId();
|
|
875
|
+
if (id)
|
|
876
|
+
this.service.begin(id);
|
|
877
|
+
};
|
|
878
|
+
onDragMove = () => {
|
|
879
|
+
const id = this.itemId();
|
|
880
|
+
if (id)
|
|
881
|
+
this.service.move(id);
|
|
882
|
+
};
|
|
883
|
+
onDragEnd = () => {
|
|
884
|
+
this.dragging.set(false);
|
|
885
|
+
this.finishInteraction();
|
|
886
|
+
};
|
|
887
|
+
onResizeStart = () => {
|
|
888
|
+
this.resizing.set(true);
|
|
889
|
+
const id = this.itemId();
|
|
890
|
+
if (id)
|
|
891
|
+
this.service.begin(id);
|
|
892
|
+
};
|
|
893
|
+
onResizeMove = (v) => {
|
|
894
|
+
const id = this.itemId();
|
|
895
|
+
if (id)
|
|
896
|
+
this.service.resize(id, v);
|
|
897
|
+
};
|
|
898
|
+
onResizeEnd = () => {
|
|
899
|
+
this.resizing.set(false);
|
|
900
|
+
this.finishInteraction();
|
|
901
|
+
};
|
|
902
|
+
/** Arrow keys move the item by one cell; Shift+arrow resizes it by one cell. */
|
|
903
|
+
onKeyDown(e) {
|
|
904
|
+
const id = this.itemId();
|
|
905
|
+
if (!id || !this.interactive())
|
|
906
|
+
return;
|
|
907
|
+
const item = this.service.items().find((x) => x.id === id);
|
|
908
|
+
if (!item)
|
|
909
|
+
return;
|
|
910
|
+
const deltas = {
|
|
911
|
+
ArrowLeft: [-1, 0],
|
|
912
|
+
ArrowRight: [1, 0],
|
|
913
|
+
ArrowUp: [0, -1],
|
|
914
|
+
ArrowDown: [0, 1],
|
|
915
|
+
};
|
|
916
|
+
const delta = deltas[e.key];
|
|
917
|
+
if (!delta)
|
|
918
|
+
return;
|
|
919
|
+
e.preventDefault();
|
|
920
|
+
if (e.shiftKey && this.service.isItemResizable(item)) {
|
|
921
|
+
this.service.resizeByCells(id, delta[0], delta[1]);
|
|
922
|
+
}
|
|
923
|
+
else if (this.service.isItemDraggable(item)) {
|
|
924
|
+
this.service.moveByCells(id, delta[0], delta[1]);
|
|
925
|
+
}
|
|
926
|
+
this.layoutChange.emit(this.service.items().find((x) => x.id === id)?.config ?? this.config());
|
|
927
|
+
}
|
|
928
|
+
finishInteraction() {
|
|
929
|
+
const id = this.itemId();
|
|
930
|
+
if (!id)
|
|
931
|
+
return;
|
|
932
|
+
this.service.end(id);
|
|
933
|
+
this.layoutChange.emit(this.service.items().find((x) => x.id === id)?.config ?? this.config());
|
|
934
|
+
}
|
|
935
|
+
/** Converts the item's min/max cell counts (config) into px for the resize directive. */
|
|
936
|
+
applySizeConstraints(config) {
|
|
937
|
+
const cellPx = this.el.nativeElement.getBoundingClientRect();
|
|
938
|
+
const cols = Math.max(1, config.w);
|
|
939
|
+
const rows = Math.max(1, config.h);
|
|
940
|
+
const colPx = cellPx.width / cols || 1;
|
|
941
|
+
const rowPx = cellPx.height / rows || 1;
|
|
942
|
+
this.resize.minWidth = (config.minW ?? 1) * colPx;
|
|
943
|
+
this.resize.minHeight = (config.minH ?? 1) * rowPx;
|
|
944
|
+
this.resize.maxWidth = config.maxW ? config.maxW * colPx : Infinity;
|
|
945
|
+
this.resize.maxHeight = config.maxH ? config.maxH * rowPx : Infinity;
|
|
946
|
+
}
|
|
947
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
948
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: NgxGridItemComponent, isStandalone: true, selector: "ngx-grid-item", inputs: { itemId: { classPropertyName: "itemId", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layoutChange: "layoutChange" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "style.position": "\"absolute\"", "style.display": "\"block\"", "style.boxSizing": "\"border-box\"", "style.transition": "transitionStyle()", "class.ngx-grid-item--dragging": "dragging()", "class.ngx-grid-item--resizing": "resizing()", "class.ngx-grid-item--static": "config().static", "class.with-resize-handler": "resizable()", "attr.tabindex": "interactive() ? 0 : null", "attr.aria-grabbed": "dragging()", "attr.data-grid-id": "itemId()" } }, hostDirectives: [{ directive: i1.NgxDraggable, inputs: ["dragHandle", "dragHandle", "lockAxis", "lockAxis"] }, { directive: i1.NgxResizable }], ngImport: i0, template: `
|
|
949
|
+
<ng-content />
|
|
950
|
+
`, isInline: true, styles: [":host{display:block}:host.with-resize-handler:after{position:absolute;pointer-events:none;outline:1px #2196f3 solid;outline-offset:-5px;background:linear-gradient(#2196f3,#2196f3) 0 0/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 100% 0/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 0 100%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 100% 100%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 50% 0/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 50% 100%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 0 50%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 100% 50%/10px 10px no-repeat;background-repeat:no-repeat;transition:none;inset:-2.5px;opacity:0;animation:showResizeHandle .3s forwards}:host:hover:after{opacity:1;content:\" \"}@keyframes showResizeHandle{0%{opacity:0}to{opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
951
|
+
}
|
|
952
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridItemComponent, decorators: [{
|
|
953
|
+
type: Component,
|
|
954
|
+
args: [{ selector: 'ngx-grid-item', standalone: true, hostDirectives: [
|
|
955
|
+
{ directive: NgxDraggable, inputs: ['dragHandle', 'lockAxis'], outputs: [] },
|
|
956
|
+
NgxResizable,
|
|
957
|
+
], template: `
|
|
958
|
+
<ng-content />
|
|
959
|
+
`, host: {
|
|
960
|
+
'[style.position]': '"absolute"',
|
|
961
|
+
'[style.display]': '"block"',
|
|
962
|
+
'[style.boxSizing]': '"border-box"',
|
|
963
|
+
'[style.transition]': 'transitionStyle()',
|
|
964
|
+
'[class.ngx-grid-item--dragging]': 'dragging()',
|
|
965
|
+
'[class.ngx-grid-item--resizing]': 'resizing()',
|
|
966
|
+
'[class.ngx-grid-item--static]': 'config().static',
|
|
967
|
+
'[class.with-resize-handler]': 'resizable()',
|
|
968
|
+
'[attr.tabindex]': 'interactive() ? 0 : null',
|
|
969
|
+
'[attr.aria-grabbed]': 'dragging()',
|
|
970
|
+
'[attr.data-grid-id]': 'itemId()',
|
|
971
|
+
'(keydown)': 'onKeyDown($event)',
|
|
972
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}:host.with-resize-handler:after{position:absolute;pointer-events:none;outline:1px #2196f3 solid;outline-offset:-5px;background:linear-gradient(#2196f3,#2196f3) 0 0/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 100% 0/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 0 100%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 100% 100%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 50% 0/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 50% 100%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 0 50%/10px 10px no-repeat,linear-gradient(#2196f3,#2196f3) 100% 50%/10px 10px no-repeat;background-repeat:no-repeat;transition:none;inset:-2.5px;opacity:0;animation:showResizeHandle .3s forwards}:host:hover:after{opacity:1;content:\" \"}@keyframes showResizeHandle{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
973
|
+
}], ctorParameters: () => [], propDecorators: { itemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], layoutChange: [{ type: i0.Output, args: ["layoutChange"] }] } });
|
|
974
|
+
|
|
975
|
+
class NgxGridLayoutModule {
|
|
976
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridLayoutModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
977
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.4", ngImport: i0, type: NgxGridLayoutModule, imports: [NgxGridLayoutComponent, NgxGridItemComponent], exports: [NgxGridLayoutComponent, NgxGridItemComponent] });
|
|
978
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridLayoutModule, providers: [GridLayoutService] });
|
|
979
|
+
}
|
|
980
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxGridLayoutModule, decorators: [{
|
|
981
|
+
type: NgModule,
|
|
982
|
+
args: [{
|
|
983
|
+
imports: [NgxGridLayoutComponent, NgxGridItemComponent],
|
|
984
|
+
exports: [NgxGridLayoutComponent, NgxGridItemComponent],
|
|
985
|
+
providers: [GridLayoutService],
|
|
986
|
+
}]
|
|
987
|
+
}] });
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Generated bundle index. Do not edit.
|
|
991
|
+
*/
|
|
992
|
+
|
|
993
|
+
export { GridItemConfig, GridLayoutOptions, GridLayoutService, NgxGridItemComponent, NgxGridLayoutComponent, NgxGridLayoutModule, colToLeft, collides, compact, computeMetrics, findFreeSpot, getAllCollisions, getFirstCollision, gridItemConfigsEqual, leftToCol, maxOccupiedRow, moveItem, normalizeGridItem, placeItem, rowToTop, spanToHeightPx, spanToWidthPx, topToRow, trySwap };
|
|
994
|
+
//# sourceMappingURL=ngx-kit-grid-layout.mjs.map
|