barsa-user-workspace 0.0.0-watch
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 +24 -0
- package/esm2022/barsa-user-workspace.mjs +5 -0
- package/esm2022/lib/barsa-user-workspace.module.mjs +82 -0
- package/esm2022/lib/coercion/boolean-property.mjs +5 -0
- package/esm2022/lib/coercion/number-property.mjs +14 -0
- package/esm2022/lib/directives/drag-handle.mjs +29 -0
- package/esm2022/lib/directives/placeholder.mjs +31 -0
- package/esm2022/lib/directives/resize-handle.mjs +29 -0
- package/esm2022/lib/grid/grid.component.mjs +609 -0
- package/esm2022/lib/grid-item/grid-item.component.mjs +196 -0
- package/esm2022/lib/grid.definitions.mjs +3 -0
- package/esm2022/lib/grid.service.mjs +49 -0
- package/esm2022/lib/layout-container/layout-container.component.mjs +213 -0
- package/esm2022/lib/layout-grid-mapper.pipe.mjs +29 -0
- package/esm2022/lib/nav-container/nav-container.component.mjs +27 -0
- package/esm2022/lib/report-grid-layout/report-grid-layout.component.mjs +15 -0
- package/esm2022/lib/utils/client-rect.mjs +57 -0
- package/esm2022/lib/utils/grid.utils.mjs +225 -0
- package/esm2022/lib/utils/operators.mjs +17 -0
- package/esm2022/lib/utils/passive-listeners.mjs +29 -0
- package/esm2022/lib/utils/pointer.utils.mjs +110 -0
- package/esm2022/lib/utils/react-grid-layout.utils.mjs +493 -0
- package/esm2022/lib/utils/scroll.mjs +233 -0
- package/esm2022/lib/utils/transition-duration.mjs +34 -0
- package/esm2022/lib/utils.mjs +14 -0
- package/esm2022/public-api.mjs +15 -0
- package/esm2022/types.mjs +2 -0
- package/fesm2022/barsa-user-workspace.mjs +2469 -0
- package/fesm2022/barsa-user-workspace.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/barsa-user-workspace.module.d.ts +34 -0
- package/lib/coercion/boolean-property.d.ts +7 -0
- package/lib/coercion/number-property.d.ts +9 -0
- package/lib/directives/drag-handle.d.ts +15 -0
- package/lib/directives/placeholder.d.ts +17 -0
- package/lib/directives/resize-handle.d.ts +15 -0
- package/lib/grid/grid.component.d.ts +147 -0
- package/lib/grid-item/grid-item.component.d.ts +83 -0
- package/lib/grid.definitions.d.ts +61 -0
- package/lib/grid.service.d.ts +15 -0
- package/lib/layout-container/layout-container.component.d.ts +79 -0
- package/lib/layout-grid-mapper.pipe.d.ts +9 -0
- package/lib/nav-container/nav-container.component.d.ts +10 -0
- package/lib/report-grid-layout/report-grid-layout.component.d.ts +7 -0
- package/lib/utils/client-rect.d.ts +36 -0
- package/lib/utils/grid.utils.d.ts +45 -0
- package/lib/utils/operators.d.ts +6 -0
- package/lib/utils/passive-listeners.d.ts +12 -0
- package/lib/utils/pointer.utils.d.ts +29 -0
- package/lib/utils/react-grid-layout.utils.d.ts +177 -0
- package/lib/utils/scroll.d.ts +28 -0
- package/lib/utils/transition-duration.d.ts +6 -0
- package/lib/utils.d.ts +6 -0
- package/package.json +25 -0
- package/public-api.d.ts +12 -0
- package/types.d.ts +3 -0
|
@@ -0,0 +1,2469 @@
|
|
|
1
|
+
import { iif, fromEvent, merge, Observable, Subject, BehaviorSubject, NEVER, interval, animationFrameScheduler, combineLatest, of, debounceTime, filter as filter$1 } from 'rxjs';
|
|
2
|
+
import { filter, switchMap, startWith, exhaustMap, takeUntil, take, map, tap, distinctUntilChanged } from 'rxjs/operators';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { InjectionToken, Directive, Input, Injectable, ElementRef, Component, ChangeDetectionStrategy, Inject, ContentChildren, ViewChild, ContentChild, EventEmitter, ViewEncapsulation, Output, inject, Pipe, NgModule } from '@angular/core';
|
|
5
|
+
import { Router, RouterModule } from '@angular/router';
|
|
6
|
+
import * as i1$2 from 'barsa-novin-ray-core';
|
|
7
|
+
import { BaseComponent, ReportViewBaseComponent, BaseModule, BarsaNovinRayCoreModule } from 'barsa-novin-ray-core';
|
|
8
|
+
import * as i1 from '@fundamental-ngx/platform/icon-tab-bar';
|
|
9
|
+
import { coerceNumberProperty as coerceNumberProperty$1 } from '@angular/cdk/coercion';
|
|
10
|
+
import * as i1$1 from '@angular/common';
|
|
11
|
+
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
12
|
+
import { FormsModule } from '@angular/forms';
|
|
13
|
+
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
14
|
+
import { CdkTableModule } from '@angular/cdk/table';
|
|
15
|
+
import { FundamentalNgxCoreModule } from '@fundamental-ngx/core';
|
|
16
|
+
import { FundamentalNgxPlatformModule } from '@fundamental-ngx/platform';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* IMPORTANT:
|
|
20
|
+
* This utils are taken from the project: https://github.com/STRML/react-grid-layout.
|
|
21
|
+
* The code should be as less modified as possible for easy maintenance.
|
|
22
|
+
*/
|
|
23
|
+
const DEBUG = false;
|
|
24
|
+
/**
|
|
25
|
+
* Return the bottom coordinate of the layout.
|
|
26
|
+
*
|
|
27
|
+
* @param {Array} layout Layout array.
|
|
28
|
+
* @return {Number} Bottom coordinate.
|
|
29
|
+
*/
|
|
30
|
+
function bottom(layout) {
|
|
31
|
+
let max = 0, bottomY;
|
|
32
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
33
|
+
bottomY = layout[i].y + layout[i].h;
|
|
34
|
+
if (bottomY > max) {
|
|
35
|
+
max = bottomY;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return max;
|
|
39
|
+
}
|
|
40
|
+
function cloneLayout(layout) {
|
|
41
|
+
const newLayout = Array(layout.length);
|
|
42
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
43
|
+
newLayout[i] = cloneLayoutItem(layout[i]);
|
|
44
|
+
}
|
|
45
|
+
return newLayout;
|
|
46
|
+
}
|
|
47
|
+
// Fast path to cloning, since this is monomorphic
|
|
48
|
+
/** NOTE: This code has been modified from the original source */
|
|
49
|
+
function cloneLayoutItem(layoutItem) {
|
|
50
|
+
const clonedLayoutItem = {
|
|
51
|
+
w: layoutItem.w,
|
|
52
|
+
h: layoutItem.h,
|
|
53
|
+
x: layoutItem.x,
|
|
54
|
+
y: layoutItem.y,
|
|
55
|
+
id: layoutItem.id,
|
|
56
|
+
moved: !!layoutItem.moved,
|
|
57
|
+
static: !!layoutItem.static,
|
|
58
|
+
};
|
|
59
|
+
if (layoutItem.minW !== undefined) {
|
|
60
|
+
clonedLayoutItem.minW = layoutItem.minW;
|
|
61
|
+
}
|
|
62
|
+
if (layoutItem.maxW !== undefined) {
|
|
63
|
+
clonedLayoutItem.maxW = layoutItem.maxW;
|
|
64
|
+
}
|
|
65
|
+
if (layoutItem.minH !== undefined) {
|
|
66
|
+
clonedLayoutItem.minH = layoutItem.minH;
|
|
67
|
+
}
|
|
68
|
+
if (layoutItem.maxH !== undefined) {
|
|
69
|
+
clonedLayoutItem.maxH = layoutItem.maxH;
|
|
70
|
+
}
|
|
71
|
+
// These can be null
|
|
72
|
+
if (layoutItem.isDraggable !== undefined) {
|
|
73
|
+
clonedLayoutItem.isDraggable = layoutItem.isDraggable;
|
|
74
|
+
}
|
|
75
|
+
if (layoutItem.isResizable !== undefined) {
|
|
76
|
+
clonedLayoutItem.isResizable = layoutItem.isResizable;
|
|
77
|
+
}
|
|
78
|
+
return clonedLayoutItem;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Given two layoutitems, check if they collide.
|
|
82
|
+
*/
|
|
83
|
+
function collides(l1, l2) {
|
|
84
|
+
if (l1.id === l2.id) {
|
|
85
|
+
return false;
|
|
86
|
+
} // same element
|
|
87
|
+
if (l1.x + l1.w <= l2.x) {
|
|
88
|
+
return false;
|
|
89
|
+
} // l1 is left of l2
|
|
90
|
+
if (l1.x >= l2.x + l2.w) {
|
|
91
|
+
return false;
|
|
92
|
+
} // l1 is right of l2
|
|
93
|
+
if (l1.y + l1.h <= l2.y) {
|
|
94
|
+
return false;
|
|
95
|
+
} // l1 is above l2
|
|
96
|
+
if (l1.y >= l2.y + l2.h) {
|
|
97
|
+
return false;
|
|
98
|
+
} // l1 is below l2
|
|
99
|
+
return true; // boxes overlap
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Given a layout, compact it. This involves going down each y coordinate and removing gaps
|
|
103
|
+
* between items.
|
|
104
|
+
*
|
|
105
|
+
* @param {Array} layout Layout.
|
|
106
|
+
* @param {Boolean} verticalCompact Whether or not to compact the layout
|
|
107
|
+
* vertically.
|
|
108
|
+
* @return {Array} Compacted Layout.
|
|
109
|
+
*/
|
|
110
|
+
function compact(layout, compactType, cols) {
|
|
111
|
+
// Statics go in the compareWith array right away so items flow around them.
|
|
112
|
+
const compareWith = getStatics(layout);
|
|
113
|
+
// We go through the items by row and column.
|
|
114
|
+
const sorted = sortLayoutItems(layout, compactType);
|
|
115
|
+
// Holding for new items.
|
|
116
|
+
const out = Array(layout.length);
|
|
117
|
+
for (let i = 0, len = sorted.length; i < len; i++) {
|
|
118
|
+
let l = cloneLayoutItem(sorted[i]);
|
|
119
|
+
// Don't move static elements
|
|
120
|
+
if (!l.static) {
|
|
121
|
+
l = compactItem(compareWith, l, compactType, cols, sorted);
|
|
122
|
+
// Add to comparison array. We only collide with items before this one.
|
|
123
|
+
// Statics are already in this array.
|
|
124
|
+
compareWith.push(l);
|
|
125
|
+
}
|
|
126
|
+
// Add to output array to make sure they still come out in the right order.
|
|
127
|
+
out[layout.indexOf(sorted[i])] = l;
|
|
128
|
+
// Clear moved flag, if it exists.
|
|
129
|
+
l.moved = false;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
const heightWidth = { x: 'w', y: 'h' };
|
|
134
|
+
/**
|
|
135
|
+
* Before moving item down, it will check if the movement will cause collisions and move those items down before.
|
|
136
|
+
*/
|
|
137
|
+
function resolveCompactionCollision(layout, item, moveToCoord, axis) {
|
|
138
|
+
const sizeProp = heightWidth[axis];
|
|
139
|
+
item[axis] += 1;
|
|
140
|
+
const itemIndex = layout
|
|
141
|
+
.map(layoutItem => {
|
|
142
|
+
return layoutItem.id;
|
|
143
|
+
})
|
|
144
|
+
.indexOf(item.id);
|
|
145
|
+
// Go through each item we collide with.
|
|
146
|
+
for (let i = itemIndex + 1; i < layout.length; i++) {
|
|
147
|
+
const otherItem = layout[i];
|
|
148
|
+
// Ignore static items
|
|
149
|
+
if (otherItem.static) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// Optimization: we can break early if we know we're past this el
|
|
153
|
+
// We can do this b/c it's a sorted layout
|
|
154
|
+
if (otherItem.y > item.y + item.h) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (collides(item, otherItem)) {
|
|
158
|
+
resolveCompactionCollision(layout, otherItem, moveToCoord + item[sizeProp], axis);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
item[axis] = moveToCoord;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Compact an item in the layout.
|
|
165
|
+
*/
|
|
166
|
+
function compactItem(compareWith, l, compactType, cols, fullLayout) {
|
|
167
|
+
const compactV = compactType === 'vertical';
|
|
168
|
+
const compactH = compactType === 'horizontal';
|
|
169
|
+
if (compactV) {
|
|
170
|
+
// Bottom 'y' possible is the bottom of the layout.
|
|
171
|
+
// This allows you to do nice stuff like specify {y: Infinity}
|
|
172
|
+
// This is here because the layout must be sorted in order to get the correct bottom `y`.
|
|
173
|
+
l.y = Math.min(bottom(compareWith), l.y);
|
|
174
|
+
// Move the element up as far as it can go without colliding.
|
|
175
|
+
while (l.y > 0 && !getFirstCollision(compareWith, l)) {
|
|
176
|
+
l.y--;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (compactH) {
|
|
180
|
+
// Move the element left as far as it can go without colliding.
|
|
181
|
+
while (l.x > 0 && !getFirstCollision(compareWith, l)) {
|
|
182
|
+
l.x--;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Move it down, and keep moving it down if it's colliding.
|
|
186
|
+
let collides;
|
|
187
|
+
while ((collides = getFirstCollision(compareWith, l))) {
|
|
188
|
+
if (compactH) {
|
|
189
|
+
resolveCompactionCollision(fullLayout, l, collides.x + collides.w, 'x');
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
resolveCompactionCollision(fullLayout, l, collides.y + collides.h, 'y');
|
|
193
|
+
}
|
|
194
|
+
// Since we can't grow without bounds horizontally, if we've overflown, let's move it down and try again.
|
|
195
|
+
if (compactH && l.x + l.w > cols) {
|
|
196
|
+
l.x = cols - l.w;
|
|
197
|
+
l.y++;
|
|
198
|
+
// ALso move element as left as much as we can (buw-custom-change)
|
|
199
|
+
while (l.x > 0 && !getFirstCollision(compareWith, l)) {
|
|
200
|
+
l.x--;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Ensure that there are no negative positions
|
|
205
|
+
l.y = Math.max(l.y, 0);
|
|
206
|
+
l.x = Math.max(l.x, 0);
|
|
207
|
+
return l;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Given a layout, make sure all elements fit within its bounds.
|
|
211
|
+
*
|
|
212
|
+
* @param {Array} layout Layout array.
|
|
213
|
+
* @param {Number} bounds Number of columns.
|
|
214
|
+
*/
|
|
215
|
+
function correctBounds(layout, bounds) {
|
|
216
|
+
const collidesWith = getStatics(layout);
|
|
217
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
218
|
+
const l = layout[i];
|
|
219
|
+
// Overflows right
|
|
220
|
+
if (l.x + l.w > bounds.cols) {
|
|
221
|
+
l.x = bounds.cols - l.w;
|
|
222
|
+
}
|
|
223
|
+
// Overflows left
|
|
224
|
+
if (l.x < 0) {
|
|
225
|
+
l.x = 0;
|
|
226
|
+
l.w = bounds.cols;
|
|
227
|
+
}
|
|
228
|
+
if (!l.static) {
|
|
229
|
+
collidesWith.push(l);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// If this is static and collides with other statics, we must move it down.
|
|
233
|
+
// We have to do something nicer than just letting them overlap.
|
|
234
|
+
while (getFirstCollision(collidesWith, l)) {
|
|
235
|
+
l.y++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return layout;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get a layout item by ID. Used so we can override later on if necessary.
|
|
243
|
+
*
|
|
244
|
+
* @param {Array} layout Layout array.
|
|
245
|
+
* @param {String} id ID
|
|
246
|
+
* @return {LayoutItem} Item at ID.
|
|
247
|
+
*/
|
|
248
|
+
function getLayoutItem(layout, id) {
|
|
249
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
250
|
+
if (layout[i].id === id) {
|
|
251
|
+
return layout[i];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Returns the first item this layout collides with.
|
|
258
|
+
* It doesn't appear to matter which order we approach this from, although
|
|
259
|
+
* perhaps that is the wrong thing to do.
|
|
260
|
+
*
|
|
261
|
+
* @param {Object} layoutItem Layout item.
|
|
262
|
+
* @return {Object|undefined} A colliding layout item, or undefined.
|
|
263
|
+
*/
|
|
264
|
+
function getFirstCollision(layout, layoutItem) {
|
|
265
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
266
|
+
if (collides(layout[i], layoutItem)) {
|
|
267
|
+
return layout[i];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
function getAllCollisions(layout, layoutItem) {
|
|
273
|
+
return layout.filter(l => collides(l, layoutItem));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get all static elements.
|
|
277
|
+
* @param {Array} layout Array of layout objects.
|
|
278
|
+
* @return {Array} Array of static layout items..
|
|
279
|
+
*/
|
|
280
|
+
function getStatics(layout) {
|
|
281
|
+
return layout.filter(l => l.static);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Move an element. Responsible for doing cascading movements of other elements.
|
|
285
|
+
*
|
|
286
|
+
* @param {Array} layout Full layout to modify.
|
|
287
|
+
* @param {LayoutItem} l element to move.
|
|
288
|
+
* @param {Number} [x] X position in grid units.
|
|
289
|
+
* @param {Number} [y] Y position in grid units.
|
|
290
|
+
*/
|
|
291
|
+
function moveElement(layout, l, x, y, isUserAction, preventCollision, compactType, cols) {
|
|
292
|
+
// If this is static and not explicitly enabled as draggable,
|
|
293
|
+
// no move is possible, so we can short-circuit this immediately.
|
|
294
|
+
if (l.static && l.isDraggable !== true) {
|
|
295
|
+
return layout;
|
|
296
|
+
}
|
|
297
|
+
// Short-circuit if nothing to do.
|
|
298
|
+
if (l.y === y && l.x === x) {
|
|
299
|
+
return layout;
|
|
300
|
+
}
|
|
301
|
+
log(`Moving element ${l.id} to [${String(x)},${String(y)}] from [${l.x},${l.y}]`);
|
|
302
|
+
const oldX = l.x;
|
|
303
|
+
const oldY = l.y;
|
|
304
|
+
// This is quite a bit faster than extending the object
|
|
305
|
+
if (typeof x === 'number') {
|
|
306
|
+
l.x = x;
|
|
307
|
+
}
|
|
308
|
+
if (typeof y === 'number') {
|
|
309
|
+
l.y = y;
|
|
310
|
+
}
|
|
311
|
+
l.moved = true;
|
|
312
|
+
// If this collides with anything, move it.
|
|
313
|
+
// When doing this comparison, we have to sort the items we compare with
|
|
314
|
+
// to ensure, in the case of multiple collisions, that we're getting the
|
|
315
|
+
// nearest collision.
|
|
316
|
+
let sorted = sortLayoutItems(layout, compactType);
|
|
317
|
+
const movingUp = compactType === 'vertical' && typeof y === 'number'
|
|
318
|
+
? oldY >= y
|
|
319
|
+
: compactType === 'horizontal' && typeof x === 'number'
|
|
320
|
+
? oldX >= x
|
|
321
|
+
: false;
|
|
322
|
+
if (movingUp) {
|
|
323
|
+
sorted = sorted.reverse();
|
|
324
|
+
}
|
|
325
|
+
const collisions = getAllCollisions(sorted, l);
|
|
326
|
+
// There was a collision; abort
|
|
327
|
+
if (preventCollision && collisions.length) {
|
|
328
|
+
log(`Collision prevented on ${l.id}, reverting.`);
|
|
329
|
+
l.x = oldX;
|
|
330
|
+
l.y = oldY;
|
|
331
|
+
l.moved = false;
|
|
332
|
+
return layout;
|
|
333
|
+
}
|
|
334
|
+
// Move each item that collides away from this element.
|
|
335
|
+
for (let i = 0, len = collisions.length; i < len; i++) {
|
|
336
|
+
const collision = collisions[i];
|
|
337
|
+
log(`Resolving collision between ${l.id} at [${l.x},${l.y}] and ${collision.id} at [${collision.x},${collision.y}]`);
|
|
338
|
+
// Short circuit so we can't infinite loop
|
|
339
|
+
if (collision.moved) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
// Don't move static items - we have to move *this* element away
|
|
343
|
+
if (collision.static) {
|
|
344
|
+
layout = moveElementAwayFromCollision(layout, collision, l, isUserAction, compactType, cols);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
layout = moveElementAwayFromCollision(layout, l, collision, isUserAction, compactType, cols);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return layout;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* This is where the magic needs to happen - given a collision, move an element away from the collision.
|
|
354
|
+
* We attempt to move it up if there's room, otherwise it goes below.
|
|
355
|
+
*
|
|
356
|
+
* @param {Array} layout Full layout to modify.
|
|
357
|
+
* @param {LayoutItem} collidesWith Layout item we're colliding with.
|
|
358
|
+
* @param {LayoutItem} itemToMove Layout item we're moving.
|
|
359
|
+
*/
|
|
360
|
+
function moveElementAwayFromCollision(layout, collidesWith, itemToMove, isUserAction, compactType, cols) {
|
|
361
|
+
const compactH = compactType === 'horizontal';
|
|
362
|
+
// Compact vertically if not set to horizontal
|
|
363
|
+
const compactV = compactType !== 'horizontal';
|
|
364
|
+
const preventCollision = collidesWith.static; // we're already colliding (not for static items)
|
|
365
|
+
// If there is enough space above the collision to put this element, move it there.
|
|
366
|
+
// We only do this on the main collision as this can get funky in cascades and cause
|
|
367
|
+
// unwanted swapping behavior.
|
|
368
|
+
if (isUserAction) {
|
|
369
|
+
// Reset isUserAction flag because we're not in the main collision anymore.
|
|
370
|
+
isUserAction = false;
|
|
371
|
+
// Make a mock item so we don't modify the item here, only modify in moveElement.
|
|
372
|
+
const fakeItem = {
|
|
373
|
+
x: compactH
|
|
374
|
+
? Math.max(collidesWith.x - itemToMove.w, 0)
|
|
375
|
+
: itemToMove.x,
|
|
376
|
+
y: compactV
|
|
377
|
+
? Math.max(collidesWith.y - itemToMove.h, 0)
|
|
378
|
+
: itemToMove.y,
|
|
379
|
+
w: itemToMove.w,
|
|
380
|
+
h: itemToMove.h,
|
|
381
|
+
id: '-1',
|
|
382
|
+
};
|
|
383
|
+
// No collision? If so, we can go up there; otherwise, we'll end up moving down as normal
|
|
384
|
+
if (!getFirstCollision(layout, fakeItem)) {
|
|
385
|
+
log(`Doing reverse collision on ${itemToMove.id} up to [${fakeItem.x},${fakeItem.y}].`);
|
|
386
|
+
return moveElement(layout, itemToMove, compactH ? fakeItem.x : undefined, compactV ? fakeItem.y : undefined, isUserAction, preventCollision, compactType, cols);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return moveElement(layout, itemToMove, compactH ? itemToMove.x + 1 : undefined, compactV ? itemToMove.y + 1 : undefined, isUserAction, preventCollision, compactType, cols);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Helper to convert a number to a percentage string.
|
|
393
|
+
*
|
|
394
|
+
* @param {Number} num Any number
|
|
395
|
+
* @return {String} That number as a percentage.
|
|
396
|
+
*/
|
|
397
|
+
function perc(num) {
|
|
398
|
+
return num * 100 + '%';
|
|
399
|
+
}
|
|
400
|
+
function setTransform({ top, left, width, height }) {
|
|
401
|
+
// Replace unitless items with px
|
|
402
|
+
const translate = `translate(${left}px,${top}px)`;
|
|
403
|
+
return {
|
|
404
|
+
transform: translate,
|
|
405
|
+
WebkitTransform: translate,
|
|
406
|
+
MozTransform: translate,
|
|
407
|
+
msTransform: translate,
|
|
408
|
+
OTransform: translate,
|
|
409
|
+
width: `${width}px`,
|
|
410
|
+
height: `${height}px`,
|
|
411
|
+
position: 'absolute',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function setTopLeft({ top, left, width, height }) {
|
|
415
|
+
return {
|
|
416
|
+
top: `${top}px`,
|
|
417
|
+
left: `${left}px`,
|
|
418
|
+
width: `${width}px`,
|
|
419
|
+
height: `${height}px`,
|
|
420
|
+
position: 'absolute',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get layout items sorted from top left to right and down.
|
|
425
|
+
*
|
|
426
|
+
* @return {Array} Array of layout objects.
|
|
427
|
+
* @return {Array} Layout, sorted static items first.
|
|
428
|
+
*/
|
|
429
|
+
function sortLayoutItems(layout, compactType) {
|
|
430
|
+
if (compactType === 'horizontal') {
|
|
431
|
+
return sortLayoutItemsByColRow(layout);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
return sortLayoutItemsByRowCol(layout);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function sortLayoutItemsByRowCol(layout) {
|
|
438
|
+
return [].concat(layout).sort(function (a, b) {
|
|
439
|
+
if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
|
|
440
|
+
return 1;
|
|
441
|
+
}
|
|
442
|
+
else if (a.y === b.y && a.x === b.x) {
|
|
443
|
+
// Without this, we can get different sort results in IE vs. Chrome/FF
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
return -1;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function sortLayoutItemsByColRow(layout) {
|
|
450
|
+
return [].concat(layout).sort(function (a, b) {
|
|
451
|
+
if (a.x > b.x || (a.x === b.x && a.y > b.y)) {
|
|
452
|
+
return 1;
|
|
453
|
+
}
|
|
454
|
+
return -1;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Validate a layout. Throws errors.
|
|
459
|
+
*
|
|
460
|
+
* @param {Array} layout Array of layout items.
|
|
461
|
+
* @param {String} [contextName] Context name for errors.
|
|
462
|
+
* @throw {Error} Validation error.
|
|
463
|
+
*/
|
|
464
|
+
function validateLayout(layout, contextName = 'Layout') {
|
|
465
|
+
const subProps = ['x', 'y', 'w', 'h'];
|
|
466
|
+
if (!Array.isArray(layout)) {
|
|
467
|
+
throw new Error(contextName + ' must be an array!');
|
|
468
|
+
}
|
|
469
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
470
|
+
const item = layout[i];
|
|
471
|
+
for (let j = 0; j < subProps.length; j++) {
|
|
472
|
+
if (typeof item[subProps[j]] !== 'number') {
|
|
473
|
+
throw new Error('ReactGridLayout: ' +
|
|
474
|
+
contextName +
|
|
475
|
+
'[' +
|
|
476
|
+
i +
|
|
477
|
+
'].' +
|
|
478
|
+
subProps[j] +
|
|
479
|
+
' must be a number!');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (item.id && typeof item.id !== 'string') {
|
|
483
|
+
throw new Error('ReactGridLayout: ' +
|
|
484
|
+
contextName +
|
|
485
|
+
'[' +
|
|
486
|
+
i +
|
|
487
|
+
'].i must be a string!');
|
|
488
|
+
}
|
|
489
|
+
if (item.static !== undefined && typeof item.static !== 'boolean') {
|
|
490
|
+
throw new Error('ReactGridLayout: ' +
|
|
491
|
+
contextName +
|
|
492
|
+
'[' +
|
|
493
|
+
i +
|
|
494
|
+
'].static must be a boolean!');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Flow can't really figure this out, so we just use Object
|
|
499
|
+
function autoBindHandlers(el, fns) {
|
|
500
|
+
fns.forEach(key => (el[key] = el[key].bind(el)));
|
|
501
|
+
}
|
|
502
|
+
function log(...args) {
|
|
503
|
+
if (!DEBUG) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// eslint-disable-next-line no-console
|
|
507
|
+
console.log(...args);
|
|
508
|
+
}
|
|
509
|
+
const noop = () => { };
|
|
510
|
+
|
|
511
|
+
/** Cached result of whether the user's browser supports passive event listeners. */
|
|
512
|
+
let supportsPassiveEvents;
|
|
513
|
+
/**
|
|
514
|
+
* Checks whether the user's browser supports passive event listeners.
|
|
515
|
+
* See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
|
516
|
+
*/
|
|
517
|
+
function ktdSupportsPassiveEventListeners() {
|
|
518
|
+
if (supportsPassiveEvents == null && typeof window !== 'undefined') {
|
|
519
|
+
try {
|
|
520
|
+
window.addEventListener('test', null, Object.defineProperty({}, 'passive', {
|
|
521
|
+
get: () => supportsPassiveEvents = true
|
|
522
|
+
}));
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
supportsPassiveEvents = supportsPassiveEvents || false;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return supportsPassiveEvents;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Normalizes an `AddEventListener` object to something that can be passed
|
|
532
|
+
* to `addEventListener` on any browser, no matter whether it supports the
|
|
533
|
+
* `options` parameter.
|
|
534
|
+
* @param options Object to be normalized.
|
|
535
|
+
*/
|
|
536
|
+
function ktdNormalizePassiveListenerOptions(options) {
|
|
537
|
+
return ktdSupportsPassiveEventListeners() ? options : !!options.capture;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Options that can be used to bind a passive event listener. */
|
|
541
|
+
const passiveEventListenerOptions = ktdNormalizePassiveListenerOptions({ passive: true });
|
|
542
|
+
/** Options that can be used to bind an active event listener. */
|
|
543
|
+
const activeEventListenerOptions = ktdNormalizePassiveListenerOptions({ passive: false });
|
|
544
|
+
let isMobile = null;
|
|
545
|
+
function ktdIsMobileOrTablet() {
|
|
546
|
+
if (isMobile != null) {
|
|
547
|
+
return isMobile;
|
|
548
|
+
}
|
|
549
|
+
// Generic match pattern to identify mobile or tablet devices
|
|
550
|
+
const isMobileDevice = /Android|webOS|BlackBerry|Windows Phone|iPad|iPhone|iPod/i.test(navigator.userAgent);
|
|
551
|
+
// Since IOS 13 is not safe to just check for the generic solution. See: https://stackoverflow.com/questions/58019463/how-to-detect-device-name-in-safari-on-ios-13-while-it-doesnt-show-the-correct
|
|
552
|
+
const isIOSMobileDevice = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
553
|
+
isMobile = isMobileDevice || isIOSMobileDevice;
|
|
554
|
+
return isMobile;
|
|
555
|
+
}
|
|
556
|
+
function ktdIsMouseEvent(event) {
|
|
557
|
+
return event.clientX != null;
|
|
558
|
+
}
|
|
559
|
+
function ktdIsTouchEvent(event) {
|
|
560
|
+
return event.touches != null && event.touches.length != null;
|
|
561
|
+
}
|
|
562
|
+
function ktdPointerClientX(event) {
|
|
563
|
+
return ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX;
|
|
564
|
+
}
|
|
565
|
+
function ktdPointerClientY(event) {
|
|
566
|
+
return ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY;
|
|
567
|
+
}
|
|
568
|
+
function ktdPointerClient(event) {
|
|
569
|
+
return {
|
|
570
|
+
clientX: ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX,
|
|
571
|
+
clientY: ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function ktdIsMouseEventOrMousePointerEvent(event) {
|
|
575
|
+
return event.type === 'mousedown'
|
|
576
|
+
|| (event.type === 'pointerdown' && event.pointerType === 'mouse');
|
|
577
|
+
}
|
|
578
|
+
/** Returns true if browser supports pointer events */
|
|
579
|
+
function ktdSupportsPointerEvents() {
|
|
580
|
+
return !!window.PointerEvent;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Emits when a mousedown or touchstart emits. Avoids conflicts between both events.
|
|
584
|
+
* @param element, html element where to listen the events.
|
|
585
|
+
* @param touchNumber number of the touch to track the event, default to the first one.
|
|
586
|
+
*/
|
|
587
|
+
function ktdMouseOrTouchDown(element, touchNumber = 1) {
|
|
588
|
+
return iif(() => ktdIsMobileOrTablet(), fromEvent(element, 'touchstart', passiveEventListenerOptions).pipe(filter((touchEvent) => touchEvent.touches.length === touchNumber)), fromEvent(element, 'mousedown', activeEventListenerOptions).pipe(filter((mouseEvent) => {
|
|
589
|
+
/**
|
|
590
|
+
* 0 : Left mouse button
|
|
591
|
+
* 1 : Wheel button or middle button (if present)
|
|
592
|
+
* 2 : Right mouse button
|
|
593
|
+
*/
|
|
594
|
+
return mouseEvent.button === 0; // Mouse down to be only fired if is left click
|
|
595
|
+
})));
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Emits when a 'mousemove' or a 'touchmove' event gets fired.
|
|
599
|
+
* @param element, html element where to listen the events.
|
|
600
|
+
* @param touchNumber number of the touch to track the event, default to the first one.
|
|
601
|
+
*/
|
|
602
|
+
function ktdMouseOrTouchMove(element, touchNumber = 1) {
|
|
603
|
+
return iif(() => ktdIsMobileOrTablet(), fromEvent(element, 'touchmove', activeEventListenerOptions).pipe(filter((touchEvent) => touchEvent.touches.length === touchNumber)), fromEvent(element, 'mousemove', activeEventListenerOptions));
|
|
604
|
+
}
|
|
605
|
+
function ktdTouchEnd(element, touchNumber = 1) {
|
|
606
|
+
return merge(fromEvent(element, 'touchend').pipe(filter((touchEvent) => touchEvent.touches.length === touchNumber - 1)), fromEvent(element, 'touchcancel').pipe(filter((touchEvent) => touchEvent.touches.length === touchNumber - 1)));
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Emits when a there is a 'mouseup' or the touch ends.
|
|
610
|
+
* @param element, html element where to listen the events.
|
|
611
|
+
* @param touchNumber number of the touch to track the event, default to the first one.
|
|
612
|
+
*/
|
|
613
|
+
function ktdMouserOrTouchEnd(element, touchNumber = 1) {
|
|
614
|
+
return iif(() => ktdIsMobileOrTablet(), ktdTouchEnd(element, touchNumber), fromEvent(element, 'mouseup'));
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Emits when a 'pointerdown' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported.
|
|
618
|
+
* @param element, html element where to listen the events.
|
|
619
|
+
*/
|
|
620
|
+
function ktdPointerDown(element) {
|
|
621
|
+
if (!ktdSupportsPointerEvents()) {
|
|
622
|
+
return ktdMouseOrTouchDown(element);
|
|
623
|
+
}
|
|
624
|
+
return fromEvent(element, 'pointerdown', activeEventListenerOptions).pipe(filter((pointerEvent) => pointerEvent.isPrimary));
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Emits when a 'pointermove' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported.
|
|
628
|
+
* @param element, html element where to listen the events.
|
|
629
|
+
*/
|
|
630
|
+
function ktdPointerMove(element) {
|
|
631
|
+
if (!ktdSupportsPointerEvents()) {
|
|
632
|
+
return ktdMouseOrTouchMove(element);
|
|
633
|
+
}
|
|
634
|
+
return fromEvent(element, 'pointermove', activeEventListenerOptions).pipe(filter((pointerEvent) => pointerEvent.isPrimary));
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Emits when a 'pointerup' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported.
|
|
638
|
+
* @param element, html element where to listen the events.
|
|
639
|
+
*/
|
|
640
|
+
function ktdPointerUp(element) {
|
|
641
|
+
if (!ktdSupportsPointerEvents()) {
|
|
642
|
+
return ktdMouserOrTouchEnd(element);
|
|
643
|
+
}
|
|
644
|
+
return fromEvent(element, 'pointerup').pipe(filter(pointerEvent => pointerEvent.isPrimary));
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** Tracks items by id. This function is mean to be used in conjunction with the ngFor that renders the 'buw-grid-items' */
|
|
648
|
+
function ktdTrackById(index, item) {
|
|
649
|
+
return item.id;
|
|
650
|
+
}
|
|
651
|
+
/** Given a layout, the gridHeight and the gap return the resulting rowHeight */
|
|
652
|
+
function ktdGetGridItemRowHeight(layout, gridHeight, gap) {
|
|
653
|
+
const numberOfRows = layout.reduce((acc, cur) => Math.max(acc, Math.max(cur.y + cur.h, 0)), 0);
|
|
654
|
+
const gapTotalHeight = (numberOfRows - 1) * gap;
|
|
655
|
+
const gridHeightMinusGap = gridHeight - gapTotalHeight;
|
|
656
|
+
return gridHeightMinusGap / numberOfRows;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Call react-grid-layout utils 'compact()' function and return the compacted layout.
|
|
660
|
+
* @param layout to be compacted.
|
|
661
|
+
* @param compactType, type of compaction.
|
|
662
|
+
* @param cols, number of columns of the grid.
|
|
663
|
+
*/
|
|
664
|
+
function ktdGridCompact(layout, compactType, cols) {
|
|
665
|
+
return (compact(layout, compactType, cols)
|
|
666
|
+
// Prune react-grid-layout compact extra properties.
|
|
667
|
+
.map((item) => ({
|
|
668
|
+
id: item.id,
|
|
669
|
+
x: item.x,
|
|
670
|
+
y: item.y,
|
|
671
|
+
w: item.w,
|
|
672
|
+
h: item.h,
|
|
673
|
+
minW: item.minW,
|
|
674
|
+
minH: item.minH,
|
|
675
|
+
maxW: item.maxW,
|
|
676
|
+
maxH: item.maxH
|
|
677
|
+
})));
|
|
678
|
+
}
|
|
679
|
+
function screenXToGridX(screenXPos, cols, width, gap) {
|
|
680
|
+
const widthMinusGaps = width - gap * (cols - 1);
|
|
681
|
+
const itemWidth = widthMinusGaps / cols;
|
|
682
|
+
const widthMinusOneItem = width - itemWidth;
|
|
683
|
+
const colWidthWithGap = widthMinusOneItem / (cols - 1);
|
|
684
|
+
return Math.round(screenXPos / colWidthWithGap);
|
|
685
|
+
}
|
|
686
|
+
function screenYToGridY(screenYPos, rowHeight, height, gap) {
|
|
687
|
+
return Math.round(screenYPos / (rowHeight + gap));
|
|
688
|
+
}
|
|
689
|
+
function screenWidthToGridWidth(gridScreenWidth, cols, width, gap) {
|
|
690
|
+
const widthMinusGaps = width - gap * (cols - 1);
|
|
691
|
+
const itemWidth = widthMinusGaps / cols;
|
|
692
|
+
const gridScreenWidthMinusFirst = gridScreenWidth - itemWidth;
|
|
693
|
+
return Math.round(gridScreenWidthMinusFirst / (itemWidth + gap)) + 1;
|
|
694
|
+
}
|
|
695
|
+
function screenHeightToGridHeight(gridScreenHeight, rowHeight, height, gap) {
|
|
696
|
+
const gridScreenHeightMinusFirst = gridScreenHeight - rowHeight;
|
|
697
|
+
return Math.round(gridScreenHeightMinusFirst / (rowHeight + gap)) + 1;
|
|
698
|
+
}
|
|
699
|
+
/** Returns a Dictionary where the key is the id and the value is the change applied to that item. If no changes Dictionary is empty. */
|
|
700
|
+
function ktdGetGridLayoutDiff(gridLayoutA, gridLayoutB) {
|
|
701
|
+
const diff = {};
|
|
702
|
+
gridLayoutA.forEach((itemA) => {
|
|
703
|
+
const itemB = gridLayoutB.find((_itemB) => _itemB.id === itemA.id);
|
|
704
|
+
if (itemB != null) {
|
|
705
|
+
const posChanged = itemA.x !== itemB.x || itemA.y !== itemB.y;
|
|
706
|
+
const sizeChanged = itemA.w !== itemB.w || itemA.h !== itemB.h;
|
|
707
|
+
const change = posChanged && sizeChanged ? 'moveresize' : posChanged ? 'move' : sizeChanged ? 'resize' : null;
|
|
708
|
+
if (change) {
|
|
709
|
+
diff[itemB.id] = { change };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
return diff;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position
|
|
717
|
+
* @param gridItem grid item that is been dragged
|
|
718
|
+
* @param config current grid configuration
|
|
719
|
+
* @param compactionType type of compaction that will be performed
|
|
720
|
+
* @param draggingData contains all the information about the drag
|
|
721
|
+
*/
|
|
722
|
+
function ktdGridItemDragging(gridItem, config, compactionType, draggingData) {
|
|
723
|
+
const { pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElemClientRect, scrollDifference } = draggingData;
|
|
724
|
+
const gridItemId = gridItem.id;
|
|
725
|
+
const draggingElemPrevItem = config.layout.find((item) => item.id === gridItemId);
|
|
726
|
+
const clientStartX = ktdPointerClientX(pointerDownEvent);
|
|
727
|
+
const clientStartY = ktdPointerClientY(pointerDownEvent);
|
|
728
|
+
const clientX = ktdPointerClientX(pointerDragEvent);
|
|
729
|
+
const clientY = ktdPointerClientY(pointerDragEvent);
|
|
730
|
+
const offsetX = clientStartX - dragElemClientRect.left;
|
|
731
|
+
const offsetY = clientStartY - dragElemClientRect.top;
|
|
732
|
+
// Grid element positions taking into account the possible scroll total difference from the beginning.
|
|
733
|
+
const gridElementLeftPosition = gridElemClientRect.left + scrollDifference.left;
|
|
734
|
+
const gridElementTopPosition = gridElemClientRect.top + scrollDifference.top;
|
|
735
|
+
// Calculate position relative to the grid element.
|
|
736
|
+
const gridRelXPos = clientX - gridElementLeftPosition - offsetX;
|
|
737
|
+
const gridRelYPos = clientY - gridElementTopPosition - offsetY;
|
|
738
|
+
const rowHeightInPixels = config.rowHeight === 'fit'
|
|
739
|
+
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
|
|
740
|
+
: config.rowHeight;
|
|
741
|
+
// Get layout item position
|
|
742
|
+
const layoutItem = {
|
|
743
|
+
...draggingElemPrevItem,
|
|
744
|
+
x: screenXToGridX(gridRelXPos, config.cols, gridElemClientRect.width, config.gap),
|
|
745
|
+
y: screenYToGridY(gridRelYPos, rowHeightInPixels, gridElemClientRect.height, config.gap)
|
|
746
|
+
};
|
|
747
|
+
// Correct the values if they overflow, since 'moveElement' function doesn't do it
|
|
748
|
+
layoutItem.x = Math.max(0, layoutItem.x);
|
|
749
|
+
layoutItem.y = Math.max(0, layoutItem.y);
|
|
750
|
+
if (layoutItem.x + layoutItem.w > config.cols) {
|
|
751
|
+
layoutItem.x = Math.max(0, config.cols - layoutItem.w);
|
|
752
|
+
}
|
|
753
|
+
// Parse to LayoutItem array data in order to use 'react.grid-layout' utils
|
|
754
|
+
const layoutItems = config.layout;
|
|
755
|
+
const draggedLayoutItem = layoutItems.find((item) => item.id === gridItemId);
|
|
756
|
+
let newLayoutItems = moveElement(layoutItems, draggedLayoutItem, layoutItem.x, layoutItem.y, true, config.preventCollision, compactionType, config.cols);
|
|
757
|
+
newLayoutItems = compact(newLayoutItems, compactionType, config.cols);
|
|
758
|
+
return {
|
|
759
|
+
layout: newLayoutItems,
|
|
760
|
+
draggedItemPos: {
|
|
761
|
+
top: gridRelYPos,
|
|
762
|
+
left: gridRelXPos,
|
|
763
|
+
width: dragElemClientRect.width,
|
|
764
|
+
height: dragElemClientRect.height
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position
|
|
770
|
+
* @param gridItem grid item that is been dragged
|
|
771
|
+
* @param config current grid configuration
|
|
772
|
+
* @param compactionType type of compaction that will be performed
|
|
773
|
+
* @param draggingData contains all the information about the drag
|
|
774
|
+
*/
|
|
775
|
+
function ktdGridItemResizing(gridItem, config, compactionType, draggingData) {
|
|
776
|
+
const { pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElemClientRect, scrollDifference } = draggingData;
|
|
777
|
+
const gridItemId = gridItem.id;
|
|
778
|
+
const clientStartX = ktdPointerClientX(pointerDownEvent);
|
|
779
|
+
const clientStartY = ktdPointerClientY(pointerDownEvent);
|
|
780
|
+
const clientX = ktdPointerClientX(pointerDragEvent);
|
|
781
|
+
const clientY = ktdPointerClientY(pointerDragEvent);
|
|
782
|
+
// Get the difference between the mouseDown and the position 'right' of the resize element.
|
|
783
|
+
const resizeElemOffsetX = dragElemClientRect.width - (clientStartX - dragElemClientRect.left);
|
|
784
|
+
const resizeElemOffsetY = dragElemClientRect.height - (clientStartY - dragElemClientRect.top);
|
|
785
|
+
const draggingElemPrevItem = config.layout.find((item) => item.id === gridItemId);
|
|
786
|
+
const width = clientX + resizeElemOffsetX - (dragElemClientRect.left + scrollDifference.left);
|
|
787
|
+
const height = clientY + resizeElemOffsetY - (dragElemClientRect.top + scrollDifference.top);
|
|
788
|
+
const rowHeightInPixels = config.rowHeight === 'fit'
|
|
789
|
+
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
|
|
790
|
+
: config.rowHeight;
|
|
791
|
+
// Get layout item grid position
|
|
792
|
+
const layoutItem = {
|
|
793
|
+
...draggingElemPrevItem,
|
|
794
|
+
w: screenWidthToGridWidth(width, config.cols, gridElemClientRect.width, config.gap),
|
|
795
|
+
h: screenHeightToGridHeight(height, rowHeightInPixels, gridElemClientRect.height, config.gap)
|
|
796
|
+
};
|
|
797
|
+
layoutItem.w = limitNumberWithinRange(layoutItem.w, gridItem.minW ?? layoutItem.minW, gridItem.maxW ?? layoutItem.maxW);
|
|
798
|
+
layoutItem.h = limitNumberWithinRange(layoutItem.h, gridItem.minH ?? layoutItem.minH, gridItem.maxH ?? layoutItem.maxH);
|
|
799
|
+
if (layoutItem.x + layoutItem.w > config.cols) {
|
|
800
|
+
layoutItem.w = Math.max(1, config.cols - layoutItem.x);
|
|
801
|
+
}
|
|
802
|
+
if (config.preventCollision) {
|
|
803
|
+
const maxW = layoutItem.w;
|
|
804
|
+
const maxH = layoutItem.h;
|
|
805
|
+
let colliding = hasCollision(config.layout, layoutItem);
|
|
806
|
+
let shrunkDimension;
|
|
807
|
+
while (colliding) {
|
|
808
|
+
shrunkDimension = getDimensionToShrink(layoutItem, shrunkDimension);
|
|
809
|
+
layoutItem[shrunkDimension]--;
|
|
810
|
+
colliding = hasCollision(config.layout, layoutItem);
|
|
811
|
+
}
|
|
812
|
+
if (shrunkDimension === 'w') {
|
|
813
|
+
layoutItem.h = maxH;
|
|
814
|
+
colliding = hasCollision(config.layout, layoutItem);
|
|
815
|
+
while (colliding) {
|
|
816
|
+
layoutItem.h--;
|
|
817
|
+
colliding = hasCollision(config.layout, layoutItem);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (shrunkDimension === 'h') {
|
|
821
|
+
layoutItem.w = maxW;
|
|
822
|
+
colliding = hasCollision(config.layout, layoutItem);
|
|
823
|
+
while (colliding) {
|
|
824
|
+
layoutItem.w--;
|
|
825
|
+
colliding = hasCollision(config.layout, layoutItem);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const newLayoutItems = config.layout.map((item) => (item.id === gridItemId ? layoutItem : item));
|
|
830
|
+
return {
|
|
831
|
+
layout: compact(newLayoutItems, compactionType, config.cols),
|
|
832
|
+
draggedItemPos: {
|
|
833
|
+
top: dragElemClientRect.top - gridElemClientRect.top,
|
|
834
|
+
left: dragElemClientRect.left - gridElemClientRect.left,
|
|
835
|
+
width,
|
|
836
|
+
height
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
function hasCollision(layout, layoutItem) {
|
|
841
|
+
return !!getFirstCollision(layout, layoutItem);
|
|
842
|
+
}
|
|
843
|
+
function getDimensionToShrink(layoutItem, lastShrunk) {
|
|
844
|
+
if (layoutItem.h <= 1) {
|
|
845
|
+
return 'w';
|
|
846
|
+
}
|
|
847
|
+
if (layoutItem.w <= 1) {
|
|
848
|
+
return 'h';
|
|
849
|
+
}
|
|
850
|
+
return lastShrunk === 'w' ? 'h' : 'w';
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Given the current number and min/max values, returns the number within the range
|
|
854
|
+
* @param number can be any numeric value
|
|
855
|
+
* @param min minimum value of range
|
|
856
|
+
* @param max maximum value of range
|
|
857
|
+
*/
|
|
858
|
+
function limitNumberWithinRange(num, min = 1, max = Infinity) {
|
|
859
|
+
return Math.min(Math.max(num, min < 1 ? 1 : min), max);
|
|
860
|
+
}
|
|
861
|
+
/** Returns true if both item1 and item2 KtdGridLayoutItems are equivalent. */
|
|
862
|
+
function ktdGridItemLayoutItemAreEqual(item1, item2) {
|
|
863
|
+
return (item1.id === item2.id &&
|
|
864
|
+
item1.x === item2.x &&
|
|
865
|
+
item1.y === item2.y &&
|
|
866
|
+
item1.w === item2.w &&
|
|
867
|
+
item1.h === item2.h);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Injection token that can be used to reference instances of `KtdGridDragHandle`. It serves as
|
|
872
|
+
* alternative token to the actual `KtdGridDragHandle` class which could cause unnecessary
|
|
873
|
+
* retention of the class and its directive metadata.
|
|
874
|
+
*/
|
|
875
|
+
const KTD_GRID_DRAG_HANDLE = new InjectionToken('KtdGridDragHandle');
|
|
876
|
+
/** Handle that can be used to drag a KtdGridItem instance. */
|
|
877
|
+
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
|
878
|
+
class KtdGridDragHandle {
|
|
879
|
+
constructor(element) {
|
|
880
|
+
this.element = element;
|
|
881
|
+
}
|
|
882
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridDragHandle, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
883
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: KtdGridDragHandle, selector: "[buwGridDragHandle]", host: { classAttribute: "buw-grid-drag-handle" }, providers: [{ provide: KTD_GRID_DRAG_HANDLE, useExisting: KtdGridDragHandle }], ngImport: i0 }); }
|
|
884
|
+
}
|
|
885
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridDragHandle, decorators: [{
|
|
886
|
+
type: Directive,
|
|
887
|
+
args: [{
|
|
888
|
+
selector: '[buwGridDragHandle]',
|
|
889
|
+
// eslint-disable-next-line @angular-eslint/no-host-metadata-property
|
|
890
|
+
host: {
|
|
891
|
+
class: 'buw-grid-drag-handle'
|
|
892
|
+
},
|
|
893
|
+
providers: [{ provide: KTD_GRID_DRAG_HANDLE, useExisting: KtdGridDragHandle }]
|
|
894
|
+
}]
|
|
895
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }] });
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Injection token that can be used to reference instances of `KtdGridResizeHandle`. It serves as
|
|
899
|
+
* alternative token to the actual `KtdGridResizeHandle` class which could cause unnecessary
|
|
900
|
+
* retention of the class and its directive metadata.
|
|
901
|
+
*/
|
|
902
|
+
const KTD_GRID_RESIZE_HANDLE = new InjectionToken('KtdGridResizeHandle');
|
|
903
|
+
/** Handle that can be used to drag a KtdGridItem instance. */
|
|
904
|
+
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
|
905
|
+
class KtdGridResizeHandle {
|
|
906
|
+
constructor(element) {
|
|
907
|
+
this.element = element;
|
|
908
|
+
}
|
|
909
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridResizeHandle, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
910
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: KtdGridResizeHandle, selector: "[buwGridResizeHandle]", host: { classAttribute: "buw-grid-resize-handle" }, providers: [{ provide: KTD_GRID_RESIZE_HANDLE, useExisting: KtdGridResizeHandle }], ngImport: i0 }); }
|
|
911
|
+
}
|
|
912
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridResizeHandle, decorators: [{
|
|
913
|
+
type: Directive,
|
|
914
|
+
args: [{
|
|
915
|
+
selector: '[buwGridResizeHandle]',
|
|
916
|
+
// eslint-disable-next-line @angular-eslint/no-host-metadata-property
|
|
917
|
+
host: {
|
|
918
|
+
class: 'buw-grid-resize-handle'
|
|
919
|
+
},
|
|
920
|
+
providers: [{ provide: KTD_GRID_RESIZE_HANDLE, useExisting: KtdGridResizeHandle }]
|
|
921
|
+
}]
|
|
922
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }] });
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Injection token that can be used to reference instances of `KtdGridItemPlaceholder`. It serves as
|
|
926
|
+
* alternative token to the actual `KtdGridItemPlaceholder` class which could cause unnecessary
|
|
927
|
+
* retention of the class and its directive metadata.
|
|
928
|
+
*/
|
|
929
|
+
const KTD_GRID_ITEM_PLACEHOLDER = new InjectionToken('KtdGridItemPlaceholder');
|
|
930
|
+
/** Directive that can be used to create a custom placeholder for a KtdGridItem instance. */
|
|
931
|
+
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
|
932
|
+
class KtdGridItemPlaceholder {
|
|
933
|
+
constructor(templateRef) {
|
|
934
|
+
this.templateRef = templateRef;
|
|
935
|
+
}
|
|
936
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridItemPlaceholder, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
937
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: KtdGridItemPlaceholder, selector: "ng-template[buwGridItemPlaceholder]", inputs: { data: "data" }, host: { classAttribute: "buw-grid-item-placeholder-content" }, providers: [{ provide: KTD_GRID_ITEM_PLACEHOLDER, useExisting: KtdGridItemPlaceholder }], ngImport: i0 }); }
|
|
938
|
+
}
|
|
939
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridItemPlaceholder, decorators: [{
|
|
940
|
+
type: Directive,
|
|
941
|
+
args: [{
|
|
942
|
+
selector: 'ng-template[buwGridItemPlaceholder]',
|
|
943
|
+
// eslint-disable-next-line @angular-eslint/no-host-metadata-property
|
|
944
|
+
host: {
|
|
945
|
+
class: 'buw-grid-item-placeholder-content'
|
|
946
|
+
},
|
|
947
|
+
providers: [{ provide: KTD_GRID_ITEM_PLACEHOLDER, useExisting: KtdGridItemPlaceholder }]
|
|
948
|
+
}]
|
|
949
|
+
}], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { data: [{
|
|
950
|
+
type: Input
|
|
951
|
+
}] } });
|
|
952
|
+
|
|
953
|
+
/** Coerces a data-bound value (typically a string) to a boolean. */
|
|
954
|
+
function coerceBooleanProperty(value) {
|
|
955
|
+
return value != null && `${value}` !== 'false';
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function coerceNumberProperty(value, fallbackValue = 0) {
|
|
959
|
+
return _isNumberValue(value) ? Number(value) : fallbackValue;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Whether the provided value is considered a number.
|
|
963
|
+
* @docs-private
|
|
964
|
+
*/
|
|
965
|
+
function _isNumberValue(value) {
|
|
966
|
+
// parseFloat(value) handles most of the cases we're interested in (it treats null, empty string,
|
|
967
|
+
// and other non-number values as NaN, where Number just uses 0) but it considers the string
|
|
968
|
+
// '123hello' to be a valid number. Therefore we also check if Number(value) is NaN.
|
|
969
|
+
return !isNaN(parseFloat(value)) && !isNaN(Number(value));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const GRID_ITEM_GET_RENDER_DATA_TOKEN = new InjectionToken('GRID_ITEM_GET_RENDER_DATA_TOKEN');
|
|
973
|
+
|
|
974
|
+
/** Runs source observable outside the zone */
|
|
975
|
+
function ktdOutsideZone(zone) {
|
|
976
|
+
return (source) => {
|
|
977
|
+
return new Observable(observer => {
|
|
978
|
+
return zone.runOutsideAngular(() => source.subscribe(observer));
|
|
979
|
+
});
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
/** Rxjs operator that makes source observable to no emit any data */
|
|
983
|
+
function ktdNoEmit() {
|
|
984
|
+
return (source$) => {
|
|
985
|
+
return source$.pipe(filter(() => false));
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Event options that can be used to bind an active, capturing event. */
|
|
990
|
+
const activeCapturingEventOptions = ktdNormalizePassiveListenerOptions({
|
|
991
|
+
passive: false,
|
|
992
|
+
capture: true
|
|
993
|
+
});
|
|
994
|
+
class KtdGridService {
|
|
995
|
+
constructor(ngZone) {
|
|
996
|
+
this.ngZone = ngZone;
|
|
997
|
+
this.touchMoveSubject = new Subject();
|
|
998
|
+
this.touchMove$ = this.touchMoveSubject.asObservable();
|
|
999
|
+
this.registerTouchMoveSubscription();
|
|
1000
|
+
}
|
|
1001
|
+
ngOnDestroy() {
|
|
1002
|
+
this.touchMoveSubscription.unsubscribe();
|
|
1003
|
+
}
|
|
1004
|
+
mouseOrTouchMove$(element) {
|
|
1005
|
+
if (!ktdSupportsPointerEvents()) {
|
|
1006
|
+
return iif(() => ktdIsMobileOrTablet(), this.touchMove$, fromEvent(element, 'mousemove', activeCapturingEventOptions)
|
|
1007
|
+
// TODO: Fix rxjs typings, boolean should be a good param too.
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
return fromEvent(element, 'pointermove', activeCapturingEventOptions);
|
|
1011
|
+
}
|
|
1012
|
+
registerTouchMoveSubscription() {
|
|
1013
|
+
// The `touchmove` event gets bound once, ahead of time, because WebKit
|
|
1014
|
+
// won't preventDefault on a dynamically-added `touchmove` listener.
|
|
1015
|
+
// See https://bugs.webkit.org/show_bug.cgi?id=184250.
|
|
1016
|
+
this.touchMoveSubscription = this.ngZone.runOutsideAngular(() =>
|
|
1017
|
+
// The event handler has to be explicitly active,
|
|
1018
|
+
// because newer browsers make it passive by default.
|
|
1019
|
+
fromEvent(document, 'touchmove', activeCapturingEventOptions)
|
|
1020
|
+
// TODO: Fix rxjs typings, boolean should be a good param too.
|
|
1021
|
+
.pipe(filter((touchEvent) => touchEvent.touches.length === 1))
|
|
1022
|
+
.subscribe((touchEvent) => this.touchMoveSubject.next(touchEvent)));
|
|
1023
|
+
}
|
|
1024
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1025
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridService, providedIn: 'root' }); }
|
|
1026
|
+
}
|
|
1027
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridService, decorators: [{
|
|
1028
|
+
type: Injectable,
|
|
1029
|
+
args: [{ providedIn: 'root' }]
|
|
1030
|
+
}], ctorParameters: () => [{ type: i0.NgZone }] });
|
|
1031
|
+
|
|
1032
|
+
class KtdGridItemComponent {
|
|
1033
|
+
/** Id of the grid item. This property is strictly compulsory. */
|
|
1034
|
+
get id() {
|
|
1035
|
+
return this._id;
|
|
1036
|
+
}
|
|
1037
|
+
set id(val) {
|
|
1038
|
+
this._id = val;
|
|
1039
|
+
}
|
|
1040
|
+
/** Minimum amount of pixels that the user should move before it starts the drag sequence. */
|
|
1041
|
+
get dragStartThreshold() {
|
|
1042
|
+
return this._dragStartThreshold;
|
|
1043
|
+
}
|
|
1044
|
+
set dragStartThreshold(val) {
|
|
1045
|
+
this._dragStartThreshold = coerceNumberProperty(val);
|
|
1046
|
+
}
|
|
1047
|
+
/** Whether the item is draggable or not. Defaults to true. Does not affect manual dragging using the startDragManually method. */
|
|
1048
|
+
get draggable() {
|
|
1049
|
+
return this._draggable;
|
|
1050
|
+
}
|
|
1051
|
+
set draggable(val) {
|
|
1052
|
+
this._draggable = coerceBooleanProperty(val);
|
|
1053
|
+
this._draggable$.next(this._draggable);
|
|
1054
|
+
}
|
|
1055
|
+
/** Whether the item is resizable or not. Defaults to true. */
|
|
1056
|
+
get resizable() {
|
|
1057
|
+
return this._resizable;
|
|
1058
|
+
}
|
|
1059
|
+
set resizable(val) {
|
|
1060
|
+
this._resizable = coerceBooleanProperty(val);
|
|
1061
|
+
this._resizable$.next(this._resizable);
|
|
1062
|
+
}
|
|
1063
|
+
constructor(elementRef, gridService, renderer, ngZone, getItemRenderData) {
|
|
1064
|
+
this.elementRef = elementRef;
|
|
1065
|
+
this.gridService = gridService;
|
|
1066
|
+
this.renderer = renderer;
|
|
1067
|
+
this.ngZone = ngZone;
|
|
1068
|
+
this.getItemRenderData = getItemRenderData;
|
|
1069
|
+
/** CSS transition style. Note that for more performance is preferable only make transition on transform property. */
|
|
1070
|
+
this.transition = 'transform 500ms ease, width 500ms ease, height 500ms ease';
|
|
1071
|
+
this._dragStartThreshold = 0;
|
|
1072
|
+
this._draggable = true;
|
|
1073
|
+
this._draggable$ = new BehaviorSubject(this._draggable);
|
|
1074
|
+
this._manualDragEvents$ = new Subject();
|
|
1075
|
+
this._resizable = true;
|
|
1076
|
+
this._resizable$ = new BehaviorSubject(this._resizable);
|
|
1077
|
+
this.dragStartSubject = new Subject();
|
|
1078
|
+
this.resizeStartSubject = new Subject();
|
|
1079
|
+
this.subscriptions = [];
|
|
1080
|
+
this.dragStart$ = this.dragStartSubject.asObservable();
|
|
1081
|
+
this.resizeStart$ = this.resizeStartSubject.asObservable();
|
|
1082
|
+
}
|
|
1083
|
+
ngOnInit() {
|
|
1084
|
+
const gridItemRenderData = this.getItemRenderData(this.id);
|
|
1085
|
+
this.setStyles(gridItemRenderData);
|
|
1086
|
+
}
|
|
1087
|
+
ngAfterContentInit() {
|
|
1088
|
+
this.subscriptions.push(this._dragStart$().subscribe(this.dragStartSubject), this._resizeStart$().subscribe(this.resizeStartSubject));
|
|
1089
|
+
}
|
|
1090
|
+
ngOnDestroy() {
|
|
1091
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* To manually start dragging, route the desired pointer events to this method.
|
|
1095
|
+
* Dragging initiated by this method will work regardless of the value of the draggable Input.
|
|
1096
|
+
* It is the caller's responsibility to call this method with only the events that are desired to cause a drag.
|
|
1097
|
+
* For example, if you only want left clicks to cause a drag, it is your responsibility to filter out other mouse button events.
|
|
1098
|
+
* @param startEvent The pointer event that should initiate the drag.
|
|
1099
|
+
*/
|
|
1100
|
+
startDragManually(startEvent) {
|
|
1101
|
+
this._manualDragEvents$.next(startEvent);
|
|
1102
|
+
}
|
|
1103
|
+
setStyles({ top, left, width, height }) {
|
|
1104
|
+
// transform is 6x times faster than top/left
|
|
1105
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'transform', `translateX(${left}) translateY(${top})`);
|
|
1106
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'display', `block`);
|
|
1107
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'transition', this.transition);
|
|
1108
|
+
if (width != null) {
|
|
1109
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'width', width);
|
|
1110
|
+
}
|
|
1111
|
+
if (height != null) {
|
|
1112
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'height', height);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
_dragStart$() {
|
|
1116
|
+
return merge(this._manualDragEvents$, this._draggable$.pipe(switchMap((draggable) => {
|
|
1117
|
+
if (!draggable) {
|
|
1118
|
+
return NEVER;
|
|
1119
|
+
}
|
|
1120
|
+
return this._dragHandles.changes.pipe(startWith(this._dragHandles), switchMap((dragHandles) => iif(() => dragHandles.length > 0, merge(...dragHandles
|
|
1121
|
+
.toArray()
|
|
1122
|
+
.map((dragHandle) => ktdPointerDown(dragHandle.element.nativeElement))), ktdPointerDown(this.elementRef.nativeElement))));
|
|
1123
|
+
}))).pipe(exhaustMap((startEvent) => {
|
|
1124
|
+
// If the event started from an element with the native HTML drag&drop, it'll interfere
|
|
1125
|
+
// with our own dragging (e.g. `img` tags do it by default). Prevent the default action
|
|
1126
|
+
// to stop it from happening. Note that preventing on `dragstart` also seems to work, but
|
|
1127
|
+
// it's flaky and it fails if the user drags it away quickly. Also note that we only want
|
|
1128
|
+
// to do this for `mousedown` and `pointerdown` since doing the same for `touchstart` will
|
|
1129
|
+
// stop any `click` events from firing on touch devices.
|
|
1130
|
+
if (ktdIsMouseEventOrMousePointerEvent(startEvent)) {
|
|
1131
|
+
startEvent.preventDefault();
|
|
1132
|
+
}
|
|
1133
|
+
const startPointer = ktdPointerClient(startEvent);
|
|
1134
|
+
return this.gridService.mouseOrTouchMove$(document).pipe(takeUntil(ktdPointerUp(document)), ktdOutsideZone(this.ngZone), filter((moveEvent) => {
|
|
1135
|
+
moveEvent.preventDefault();
|
|
1136
|
+
const movePointer = ktdPointerClient(moveEvent);
|
|
1137
|
+
const distanceX = Math.abs(startPointer.clientX - movePointer.clientX);
|
|
1138
|
+
const distanceY = Math.abs(startPointer.clientY - movePointer.clientY);
|
|
1139
|
+
// When this conditions returns true mean that we are over threshold.
|
|
1140
|
+
return distanceX + distanceY >= this.dragStartThreshold;
|
|
1141
|
+
}), take(1),
|
|
1142
|
+
// Return the original start event
|
|
1143
|
+
map(() => startEvent));
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1146
|
+
_resizeStart$() {
|
|
1147
|
+
return this._resizable$.pipe(switchMap((resizable) => {
|
|
1148
|
+
if (!resizable) {
|
|
1149
|
+
// Side effect to hide the resizeElem if resize is disabled.
|
|
1150
|
+
this.renderer.setStyle(this.resizeElem.nativeElement, 'display', 'none');
|
|
1151
|
+
return NEVER;
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
return this._resizeHandles.changes.pipe(startWith(this._resizeHandles), switchMap((resizeHandles) => {
|
|
1155
|
+
if (resizeHandles.length > 0) {
|
|
1156
|
+
// Side effect to hide the resizeElem if there are resize handles.
|
|
1157
|
+
this.renderer.setStyle(this.resizeElem.nativeElement, 'display', 'none');
|
|
1158
|
+
return merge(...resizeHandles
|
|
1159
|
+
.toArray()
|
|
1160
|
+
.map((resizeHandle) => ktdPointerDown(resizeHandle.element.nativeElement)));
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
this.renderer.setStyle(this.resizeElem.nativeElement, 'display', 'block');
|
|
1164
|
+
return ktdPointerDown(this.resizeElem.nativeElement);
|
|
1165
|
+
}
|
|
1166
|
+
}), tap((startEvent) => {
|
|
1167
|
+
if (ktdIsMouseEventOrMousePointerEvent(startEvent)) {
|
|
1168
|
+
startEvent.preventDefault();
|
|
1169
|
+
}
|
|
1170
|
+
}));
|
|
1171
|
+
}
|
|
1172
|
+
}));
|
|
1173
|
+
}
|
|
1174
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridItemComponent, deps: [{ token: i0.ElementRef }, { token: KtdGridService }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: GRID_ITEM_GET_RENDER_DATA_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1175
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: KtdGridItemComponent, selector: "buw-grid-item", inputs: { minW: "minW", minH: "minH", maxW: "maxW", maxH: "maxH", transition: "transition", id: "id", dragStartThreshold: "dragStartThreshold", draggable: "draggable", resizable: "resizable" }, queries: [{ propertyName: "placeholder", first: true, predicate: KTD_GRID_ITEM_PLACEHOLDER, descendants: true }, { propertyName: "_dragHandles", predicate: KTD_GRID_DRAG_HANDLE, descendants: true }, { propertyName: "_resizeHandles", predicate: KTD_GRID_RESIZE_HANDLE, descendants: true }], viewQueries: [{ propertyName: "resizeElem", first: true, predicate: ["resizeElem"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<ng-content></ng-content>\n<div #resizeElem class=\"grid-item-resize-icon\"></div>", styles: [":host{display:none;position:absolute;z-index:1;overflow:hidden;touch-action:none;border:1px dashed #000}:host div{position:absolute;-webkit-user-select:none;user-select:none;z-index:10}:host div.grid-item-resize-icon{cursor:se-resize;width:20px;height:20px;bottom:0;right:0;color:inherit}:host div.grid-item-resize-icon:after{content:\"\";position:absolute;right:3px;bottom:3px;width:5px;height:5px;border-right:2px solid;border-bottom:2px solid}.display-none{display:none!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1176
|
+
}
|
|
1177
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridItemComponent, decorators: [{
|
|
1178
|
+
type: Component,
|
|
1179
|
+
args: [{ selector: 'buw-grid-item', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\n<div #resizeElem class=\"grid-item-resize-icon\"></div>", styles: [":host{display:none;position:absolute;z-index:1;overflow:hidden;touch-action:none;border:1px dashed #000}:host div{position:absolute;-webkit-user-select:none;user-select:none;z-index:10}:host div.grid-item-resize-icon{cursor:se-resize;width:20px;height:20px;bottom:0;right:0;color:inherit}:host div.grid-item-resize-icon:after{content:\"\";position:absolute;right:3px;bottom:3px;width:5px;height:5px;border-right:2px solid;border-bottom:2px solid}.display-none{display:none!important}\n"] }]
|
|
1180
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: KtdGridService }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: undefined, decorators: [{
|
|
1181
|
+
type: Inject,
|
|
1182
|
+
args: [GRID_ITEM_GET_RENDER_DATA_TOKEN]
|
|
1183
|
+
}] }], propDecorators: { _dragHandles: [{
|
|
1184
|
+
type: ContentChildren,
|
|
1185
|
+
args: [KTD_GRID_DRAG_HANDLE, { descendants: true }]
|
|
1186
|
+
}], _resizeHandles: [{
|
|
1187
|
+
type: ContentChildren,
|
|
1188
|
+
args: [KTD_GRID_RESIZE_HANDLE, { descendants: true }]
|
|
1189
|
+
}], resizeElem: [{
|
|
1190
|
+
type: ViewChild,
|
|
1191
|
+
args: ['resizeElem', { static: true, read: ElementRef }]
|
|
1192
|
+
}], placeholder: [{
|
|
1193
|
+
type: ContentChild,
|
|
1194
|
+
args: [KTD_GRID_ITEM_PLACEHOLDER]
|
|
1195
|
+
}], minW: [{
|
|
1196
|
+
type: Input
|
|
1197
|
+
}], minH: [{
|
|
1198
|
+
type: Input
|
|
1199
|
+
}], maxW: [{
|
|
1200
|
+
type: Input
|
|
1201
|
+
}], maxH: [{
|
|
1202
|
+
type: Input
|
|
1203
|
+
}], transition: [{
|
|
1204
|
+
type: Input
|
|
1205
|
+
}], id: [{
|
|
1206
|
+
type: Input
|
|
1207
|
+
}], dragStartThreshold: [{
|
|
1208
|
+
type: Input
|
|
1209
|
+
}], draggable: [{
|
|
1210
|
+
type: Input
|
|
1211
|
+
}], resizable: [{
|
|
1212
|
+
type: Input
|
|
1213
|
+
}] } });
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* Client rect utilities.
|
|
1217
|
+
* This file is taken from Angular Material repository.
|
|
1218
|
+
*/
|
|
1219
|
+
/** Gets a mutable version of an element's bounding `ClientRect`. */
|
|
1220
|
+
function getMutableClientRect(element) {
|
|
1221
|
+
const clientRect = element.getBoundingClientRect();
|
|
1222
|
+
// We need to clone the `clientRect` here, because all the values on it are readonly
|
|
1223
|
+
// and we need to be able to update them. Also we can't use a spread here, because
|
|
1224
|
+
// the values on a `ClientRect` aren't own properties. See:
|
|
1225
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
|
|
1226
|
+
return {
|
|
1227
|
+
top: clientRect.top,
|
|
1228
|
+
right: clientRect.right,
|
|
1229
|
+
bottom: clientRect.bottom,
|
|
1230
|
+
left: clientRect.left,
|
|
1231
|
+
width: clientRect.width,
|
|
1232
|
+
height: clientRect.height
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Checks whether some coordinates are within a `ClientRect`.
|
|
1237
|
+
* @param clientRect ClientRect that is being checked.
|
|
1238
|
+
* @param x Coordinates along the X axis.
|
|
1239
|
+
* @param y Coordinates along the Y axis.
|
|
1240
|
+
*/
|
|
1241
|
+
function isInsideClientRect(clientRect, x, y) {
|
|
1242
|
+
const { top, bottom, left, right } = clientRect;
|
|
1243
|
+
return y >= top && y <= bottom && x >= left && x <= right;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Updates the top/left positions of a `ClientRect`, as well as their bottom/right counterparts.
|
|
1247
|
+
* @param clientRect `ClientRect` that should be updated.
|
|
1248
|
+
* @param top Amount to add to the `top` position.
|
|
1249
|
+
* @param left Amount to add to the `left` position.
|
|
1250
|
+
*/
|
|
1251
|
+
function adjustClientRect(clientRect, top, left) {
|
|
1252
|
+
clientRect.top += top;
|
|
1253
|
+
clientRect.bottom = clientRect.top + clientRect.height;
|
|
1254
|
+
clientRect.left += left;
|
|
1255
|
+
clientRect.right = clientRect.left + clientRect.width;
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Checks whether the pointer coordinates are close to a ClientRect.
|
|
1259
|
+
* @param rect ClientRect to check against.
|
|
1260
|
+
* @param threshold Threshold around the ClientRect.
|
|
1261
|
+
* @param pointerX Coordinates along the X axis.
|
|
1262
|
+
* @param pointerY Coordinates along the Y axis.
|
|
1263
|
+
*/
|
|
1264
|
+
function isPointerNearClientRect(rect, threshold, pointerX, pointerY) {
|
|
1265
|
+
const { top, right, bottom, left, width, height } = rect;
|
|
1266
|
+
const xThreshold = width * threshold;
|
|
1267
|
+
const yThreshold = height * threshold;
|
|
1268
|
+
return pointerY > top - yThreshold && pointerY < bottom + yThreshold &&
|
|
1269
|
+
pointerX > left - xThreshold && pointerX < right + xThreshold;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* Proximity, as a ratio to width/height at which to start auto-scrolling.
|
|
1274
|
+
* The value comes from trying it out manually until it feels right.
|
|
1275
|
+
*/
|
|
1276
|
+
const SCROLL_PROXIMITY_THRESHOLD = 0.05;
|
|
1277
|
+
/**
|
|
1278
|
+
* Increments the vertical scroll position of a node.
|
|
1279
|
+
* @param node Node whose scroll position should change.
|
|
1280
|
+
* @param amount Amount of pixels that the `node` should be scrolled.
|
|
1281
|
+
*/
|
|
1282
|
+
function incrementVerticalScroll(node, amount) {
|
|
1283
|
+
if (node === window) {
|
|
1284
|
+
node.scrollBy(0, amount);
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
// Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
|
|
1288
|
+
node.scrollTop += amount;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Increments the horizontal scroll position of a node.
|
|
1293
|
+
* @param node Node whose scroll position should change.
|
|
1294
|
+
* @param amount Amount of pixels that the `node` should be scrolled.
|
|
1295
|
+
*/
|
|
1296
|
+
function incrementHorizontalScroll(node, amount) {
|
|
1297
|
+
if (node === window) {
|
|
1298
|
+
node.scrollBy(amount, 0);
|
|
1299
|
+
}
|
|
1300
|
+
else {
|
|
1301
|
+
// Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
|
|
1302
|
+
node.scrollLeft += amount;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Gets whether the vertical auto-scroll direction of a node.
|
|
1307
|
+
* @param clientRect Dimensions of the node.
|
|
1308
|
+
* @param pointerY Position of the user's pointer along the y axis.
|
|
1309
|
+
*/
|
|
1310
|
+
function getVerticalScrollDirection(clientRect, pointerY) {
|
|
1311
|
+
const { top, bottom, height } = clientRect;
|
|
1312
|
+
const yThreshold = height * SCROLL_PROXIMITY_THRESHOLD;
|
|
1313
|
+
if (pointerY >= top - yThreshold && pointerY <= top + yThreshold) {
|
|
1314
|
+
return 1 /* AutoScrollVerticalDirection.UP */;
|
|
1315
|
+
}
|
|
1316
|
+
else if (pointerY >= bottom - yThreshold && pointerY <= bottom + yThreshold) {
|
|
1317
|
+
return 2 /* AutoScrollVerticalDirection.DOWN */;
|
|
1318
|
+
}
|
|
1319
|
+
return 0 /* AutoScrollVerticalDirection.NONE */;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Gets whether the horizontal auto-scroll direction of a node.
|
|
1323
|
+
* @param clientRect Dimensions of the node.
|
|
1324
|
+
* @param pointerX Position of the user's pointer along the x axis.
|
|
1325
|
+
*/
|
|
1326
|
+
function getHorizontalScrollDirection(clientRect, pointerX) {
|
|
1327
|
+
const { left, right, width } = clientRect;
|
|
1328
|
+
const xThreshold = width * SCROLL_PROXIMITY_THRESHOLD;
|
|
1329
|
+
if (pointerX >= left - xThreshold && pointerX <= left + xThreshold) {
|
|
1330
|
+
return 1 /* AutoScrollHorizontalDirection.LEFT */;
|
|
1331
|
+
}
|
|
1332
|
+
else if (pointerX >= right - xThreshold && pointerX <= right + xThreshold) {
|
|
1333
|
+
return 2 /* AutoScrollHorizontalDirection.RIGHT */;
|
|
1334
|
+
}
|
|
1335
|
+
return 0 /* AutoScrollHorizontalDirection.NONE */;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Returns an observable that schedules a loop and apply scroll on the scrollNode into the specified direction/s.
|
|
1339
|
+
* This observable doesn't emit, it just performs the 'scroll' side effect.
|
|
1340
|
+
* @param scrollNode, node where the scroll would be applied.
|
|
1341
|
+
* @param verticalScrollDirection, vertical direction of the scroll.
|
|
1342
|
+
* @param horizontalScrollDirection, horizontal direction of the scroll.
|
|
1343
|
+
* @param scrollStep, scroll step in CSS pixels that would be applied in every loop.
|
|
1344
|
+
*/
|
|
1345
|
+
function scrollToDirectionInterval$(scrollNode, verticalScrollDirection, horizontalScrollDirection, scrollStep = 2) {
|
|
1346
|
+
return interval(0, animationFrameScheduler)
|
|
1347
|
+
.pipe(tap(() => {
|
|
1348
|
+
if (verticalScrollDirection === 1 /* AutoScrollVerticalDirection.UP */) {
|
|
1349
|
+
incrementVerticalScroll(scrollNode, -scrollStep);
|
|
1350
|
+
}
|
|
1351
|
+
else if (verticalScrollDirection === 2 /* AutoScrollVerticalDirection.DOWN */) {
|
|
1352
|
+
incrementVerticalScroll(scrollNode, scrollStep);
|
|
1353
|
+
}
|
|
1354
|
+
if (horizontalScrollDirection === 1 /* AutoScrollHorizontalDirection.LEFT */) {
|
|
1355
|
+
incrementHorizontalScroll(scrollNode, -scrollStep);
|
|
1356
|
+
}
|
|
1357
|
+
else if (horizontalScrollDirection === 2 /* AutoScrollHorizontalDirection.RIGHT */) {
|
|
1358
|
+
incrementHorizontalScroll(scrollNode, scrollStep);
|
|
1359
|
+
}
|
|
1360
|
+
}), ktdNoEmit());
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Given a source$ observable with pointer location, scroll the scrollNode if the pointer is near to it.
|
|
1364
|
+
* This observable doesn't emit, it just performs a 'scroll' side effect.
|
|
1365
|
+
* @param scrollableParent, parent node in which the scroll would be performed.
|
|
1366
|
+
* @param options, configuration options.
|
|
1367
|
+
*/
|
|
1368
|
+
function ktdScrollIfNearElementClientRect$(scrollableParent, options) {
|
|
1369
|
+
let scrollNode;
|
|
1370
|
+
let scrollableParentClientRect;
|
|
1371
|
+
let scrollableParentScrollWidth;
|
|
1372
|
+
if (scrollableParent === document) {
|
|
1373
|
+
scrollNode = document.defaultView;
|
|
1374
|
+
const { width, height } = getViewportSize();
|
|
1375
|
+
scrollableParentClientRect = { width, height, top: 0, right: width, bottom: height, left: 0 };
|
|
1376
|
+
scrollableParentScrollWidth = getDocumentScrollWidth();
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
scrollNode = scrollableParent;
|
|
1380
|
+
scrollableParentClientRect = getMutableClientRect(scrollableParent);
|
|
1381
|
+
scrollableParentScrollWidth = scrollableParent.scrollWidth;
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* IMPORTANT: By design, only let scroll horizontal if the scrollable parent has explicitly an scroll horizontal.
|
|
1385
|
+
* This layout solution is not designed in mind to have any scroll horizontal, but exceptionally we allow it in this
|
|
1386
|
+
* specific use case.
|
|
1387
|
+
*/
|
|
1388
|
+
options = options || {};
|
|
1389
|
+
if (options.disableHorizontal == null && scrollableParentScrollWidth <= scrollableParentClientRect.width) {
|
|
1390
|
+
options.disableHorizontal = true;
|
|
1391
|
+
}
|
|
1392
|
+
return (source$) => source$.pipe(map(({ pointerX, pointerY }) => {
|
|
1393
|
+
let verticalScrollDirection = getVerticalScrollDirection(scrollableParentClientRect, pointerY);
|
|
1394
|
+
let horizontalScrollDirection = getHorizontalScrollDirection(scrollableParentClientRect, pointerX);
|
|
1395
|
+
// Check if scroll directions are disabled.
|
|
1396
|
+
if (options?.disableVertical) {
|
|
1397
|
+
verticalScrollDirection = 0 /* AutoScrollVerticalDirection.NONE */;
|
|
1398
|
+
}
|
|
1399
|
+
if (options?.disableHorizontal) {
|
|
1400
|
+
horizontalScrollDirection = 0 /* AutoScrollHorizontalDirection.NONE */;
|
|
1401
|
+
}
|
|
1402
|
+
return { verticalScrollDirection, horizontalScrollDirection };
|
|
1403
|
+
}), distinctUntilChanged((prev, actual) => {
|
|
1404
|
+
return prev.verticalScrollDirection === actual.verticalScrollDirection
|
|
1405
|
+
&& prev.horizontalScrollDirection === actual.horizontalScrollDirection;
|
|
1406
|
+
}), switchMap(({ verticalScrollDirection, horizontalScrollDirection }) => {
|
|
1407
|
+
if (verticalScrollDirection || horizontalScrollDirection) {
|
|
1408
|
+
return scrollToDirectionInterval$(scrollNode, verticalScrollDirection, horizontalScrollDirection, options?.scrollStep);
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
return NEVER;
|
|
1412
|
+
}
|
|
1413
|
+
}));
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Emits on EVERY scroll event and returns the accumulated scroll offset relative to the initial scroll position.
|
|
1417
|
+
* @param scrollableParent, node in which scroll events would be listened.
|
|
1418
|
+
*/
|
|
1419
|
+
function ktdGetScrollTotalRelativeDifference$(scrollableParent) {
|
|
1420
|
+
let scrollInitialPosition;
|
|
1421
|
+
// Calculate initial scroll position
|
|
1422
|
+
if (scrollableParent === document) {
|
|
1423
|
+
scrollInitialPosition = getViewportScrollPosition();
|
|
1424
|
+
}
|
|
1425
|
+
else {
|
|
1426
|
+
scrollInitialPosition = {
|
|
1427
|
+
top: scrollableParent.scrollTop,
|
|
1428
|
+
left: scrollableParent.scrollLeft
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
return fromEvent(scrollableParent, 'scroll', ktdNormalizePassiveListenerOptions({ capture: true })).pipe(map(() => {
|
|
1432
|
+
let newTop;
|
|
1433
|
+
let newLeft;
|
|
1434
|
+
if (scrollableParent === document) {
|
|
1435
|
+
const viewportScrollPosition = getViewportScrollPosition();
|
|
1436
|
+
newTop = viewportScrollPosition.top;
|
|
1437
|
+
newLeft = viewportScrollPosition.left;
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
newTop = scrollableParent.scrollTop;
|
|
1441
|
+
newLeft = scrollableParent.scrollLeft;
|
|
1442
|
+
}
|
|
1443
|
+
const topDifference = scrollInitialPosition.top - newTop;
|
|
1444
|
+
const leftDifference = scrollInitialPosition.left - newLeft;
|
|
1445
|
+
return { top: topDifference, left: leftDifference };
|
|
1446
|
+
}));
|
|
1447
|
+
}
|
|
1448
|
+
/** Returns the viewport's width and height. */
|
|
1449
|
+
function getViewportSize() {
|
|
1450
|
+
const _window = document.defaultView || window;
|
|
1451
|
+
return {
|
|
1452
|
+
width: _window.innerWidth,
|
|
1453
|
+
height: _window.innerHeight
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
/** Gets a ClientRect for the viewport's bounds. */
|
|
1457
|
+
function getViewportRect() {
|
|
1458
|
+
// Use the document element's bounding rect rather than the window scroll properties
|
|
1459
|
+
// (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll
|
|
1460
|
+
// properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different
|
|
1461
|
+
// conceptual viewports. Under most circumstances these viewports are equivalent, but they
|
|
1462
|
+
// can disagree when the page is pinch-zoomed (on devices that support touch).
|
|
1463
|
+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4
|
|
1464
|
+
// We use the documentElement instead of the body because, by default (without a css reset)
|
|
1465
|
+
// browsers typically give the document body an 8px margin, which is not included in
|
|
1466
|
+
// getBoundingClientRect().
|
|
1467
|
+
const scrollPosition = getViewportScrollPosition();
|
|
1468
|
+
const { width, height } = getViewportSize();
|
|
1469
|
+
return {
|
|
1470
|
+
top: scrollPosition.top,
|
|
1471
|
+
left: scrollPosition.left,
|
|
1472
|
+
bottom: scrollPosition.top + height,
|
|
1473
|
+
right: scrollPosition.left + width,
|
|
1474
|
+
height,
|
|
1475
|
+
width,
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
/** Gets the (top, left) scroll position of the viewport. */
|
|
1479
|
+
function getViewportScrollPosition() {
|
|
1480
|
+
// The top-left-corner of the viewport is determined by the scroll position of the document
|
|
1481
|
+
// body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about
|
|
1482
|
+
// whether `document.body` or `document.documentElement` is the scrolled element, so reading
|
|
1483
|
+
// `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding rect of
|
|
1484
|
+
// `document.documentElement` works consistently, where the `top` and `left` values will
|
|
1485
|
+
// equal negative the scroll position.
|
|
1486
|
+
const windowRef = document.defaultView || window;
|
|
1487
|
+
const documentElement = document.documentElement;
|
|
1488
|
+
const documentRect = documentElement.getBoundingClientRect();
|
|
1489
|
+
const top = -documentRect.top || document.body.scrollTop || windowRef.scrollY ||
|
|
1490
|
+
documentElement.scrollTop || 0;
|
|
1491
|
+
const left = -documentRect.left || document.body.scrollLeft || windowRef.scrollX ||
|
|
1492
|
+
documentElement.scrollLeft || 0;
|
|
1493
|
+
return { top, left };
|
|
1494
|
+
}
|
|
1495
|
+
/** Returns the document scroll width */
|
|
1496
|
+
function getDocumentScrollWidth() {
|
|
1497
|
+
return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* Transition duration utilities.
|
|
1502
|
+
* This file is taken from Angular Material repository.
|
|
1503
|
+
*/
|
|
1504
|
+
/* eslint-disable @katoid/prefix-exported-code */
|
|
1505
|
+
/** Parses a CSS time value to milliseconds. */
|
|
1506
|
+
function parseCssTimeUnitsToMs(value) {
|
|
1507
|
+
// Some browsers will return it in seconds, whereas others will return milliseconds.
|
|
1508
|
+
const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000;
|
|
1509
|
+
return parseFloat(value) * multiplier;
|
|
1510
|
+
}
|
|
1511
|
+
/** Gets the transform transition duration, including the delay, of an element in milliseconds. */
|
|
1512
|
+
function getTransformTransitionDurationInMs(element) {
|
|
1513
|
+
const computedStyle = getComputedStyle(element);
|
|
1514
|
+
const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property');
|
|
1515
|
+
const property = transitionedProperties.find(prop => prop === 'transform' || prop === 'all');
|
|
1516
|
+
// If there's no transition for `all` or `transform`, we shouldn't do anything.
|
|
1517
|
+
if (!property) {
|
|
1518
|
+
return 0;
|
|
1519
|
+
}
|
|
1520
|
+
// Get the index of the property that we're interested in and match
|
|
1521
|
+
// it up to the same index in `transition-delay` and `transition-duration`.
|
|
1522
|
+
const propertyIndex = transitionedProperties.indexOf(property);
|
|
1523
|
+
const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration');
|
|
1524
|
+
const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay');
|
|
1525
|
+
return parseCssTimeUnitsToMs(rawDurations[propertyIndex]) +
|
|
1526
|
+
parseCssTimeUnitsToMs(rawDelays[propertyIndex]);
|
|
1527
|
+
}
|
|
1528
|
+
/** Parses out multiple values from a computed style into an array. */
|
|
1529
|
+
function parseCssPropertyValue(computedStyle, name) {
|
|
1530
|
+
const value = computedStyle.getPropertyValue(name);
|
|
1531
|
+
return value.split(',').map(part => part.trim());
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function getDragResizeEventData(gridItem, layout) {
|
|
1535
|
+
return {
|
|
1536
|
+
layout,
|
|
1537
|
+
layoutItem: layout.find((item) => item.id === gridItem.id),
|
|
1538
|
+
gridItemRef: gridItem
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function getColumnWidth(config, width) {
|
|
1542
|
+
const { cols, gap } = config;
|
|
1543
|
+
const widthExcludingGap = width - Math.max(gap * (cols - 1), 0);
|
|
1544
|
+
return widthExcludingGap / cols;
|
|
1545
|
+
}
|
|
1546
|
+
function getRowHeightInPixels(config, height) {
|
|
1547
|
+
const { rowHeight, layout, gap } = config;
|
|
1548
|
+
return rowHeight === 'fit' ? ktdGetGridItemRowHeight(layout, height, gap) : rowHeight;
|
|
1549
|
+
}
|
|
1550
|
+
function layoutToRenderItems(config, width, height) {
|
|
1551
|
+
const { layout, gap } = config;
|
|
1552
|
+
const rowHeightInPixels = getRowHeightInPixels(config, height);
|
|
1553
|
+
const itemWidthPerColumn = getColumnWidth(config, width);
|
|
1554
|
+
const renderItems = {};
|
|
1555
|
+
for (const item of layout) {
|
|
1556
|
+
renderItems[item.id] = {
|
|
1557
|
+
id: item.id,
|
|
1558
|
+
top: item.y * rowHeightInPixels + gap * item.y,
|
|
1559
|
+
left: item.x * itemWidthPerColumn + gap * item.x,
|
|
1560
|
+
width: item.w * itemWidthPerColumn + gap * Math.max(item.w - 1, 0),
|
|
1561
|
+
height: item.h * rowHeightInPixels + gap * Math.max(item.h - 1, 0)
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
return renderItems;
|
|
1565
|
+
}
|
|
1566
|
+
function getGridHeight(layout, rowHeight, gap) {
|
|
1567
|
+
return layout.reduce((acc, cur) => Math.max(acc, (cur.y + cur.h) * rowHeight + Math.max(cur.y + cur.h - 1, 0) * gap), 0);
|
|
1568
|
+
}
|
|
1569
|
+
// eslint-disable-next-line
|
|
1570
|
+
function parseRenderItemToPixels(renderItem) {
|
|
1571
|
+
return {
|
|
1572
|
+
id: renderItem.id,
|
|
1573
|
+
top: `${renderItem.top}px`,
|
|
1574
|
+
left: `${renderItem.left}px`,
|
|
1575
|
+
width: `${renderItem.width}px`,
|
|
1576
|
+
height: `${renderItem.height}px`
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
// eslint-disable-next-line
|
|
1580
|
+
function __gridItemGetRenderDataFactoryFunc(gridCmp) {
|
|
1581
|
+
return function (id) {
|
|
1582
|
+
return parseRenderItemToPixels(gridCmp.getItemRenderData(id));
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
function ktdGridItemGetRenderDataFactoryFunc(gridCmp) {
|
|
1586
|
+
// Workaround explained: https://github.com/ng-packagr/ng-packagr/issues/696#issuecomment-387114613
|
|
1587
|
+
const resultFunc = __gridItemGetRenderDataFactoryFunc(gridCmp);
|
|
1588
|
+
return resultFunc;
|
|
1589
|
+
}
|
|
1590
|
+
const defaultBackgroundConfig = {
|
|
1591
|
+
borderColor: '#ffa72678',
|
|
1592
|
+
gapColor: 'transparent',
|
|
1593
|
+
rowColor: 'transparent',
|
|
1594
|
+
columnColor: 'transparent',
|
|
1595
|
+
borderWidth: 1
|
|
1596
|
+
};
|
|
1597
|
+
class KtdGridComponent {
|
|
1598
|
+
/** Whether or not to update the internal layout when some dependent property change. */
|
|
1599
|
+
get compactOnPropsChange() {
|
|
1600
|
+
return this._compactOnPropsChange;
|
|
1601
|
+
}
|
|
1602
|
+
set compactOnPropsChange(value) {
|
|
1603
|
+
this._compactOnPropsChange = coerceBooleanProperty(value);
|
|
1604
|
+
}
|
|
1605
|
+
/** If true, grid items won't change position when being dragged over. Handy when using no compaction */
|
|
1606
|
+
get preventCollision() {
|
|
1607
|
+
return this._preventCollision;
|
|
1608
|
+
}
|
|
1609
|
+
set preventCollision(value) {
|
|
1610
|
+
this._preventCollision = coerceBooleanProperty(value);
|
|
1611
|
+
}
|
|
1612
|
+
/** Number of CSS pixels that would be scrolled on each 'tick' when auto scroll is performed. */
|
|
1613
|
+
get scrollSpeed() {
|
|
1614
|
+
return this._scrollSpeed;
|
|
1615
|
+
}
|
|
1616
|
+
set scrollSpeed(value) {
|
|
1617
|
+
this._scrollSpeed = coerceNumberProperty(value, 2);
|
|
1618
|
+
}
|
|
1619
|
+
/** Type of compaction that will be applied to the layout (vertical, horizontal or free). Defaults to 'vertical' */
|
|
1620
|
+
get compactType() {
|
|
1621
|
+
return this._compactType;
|
|
1622
|
+
}
|
|
1623
|
+
set compactType(val) {
|
|
1624
|
+
this._compactType = val;
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Row height as number or as 'fit'.
|
|
1628
|
+
* If rowHeight is a number value, it means that each row would have those css pixels in height.
|
|
1629
|
+
* if rowHeight is 'fit', it means that rows will fit in the height available. If 'fit' value is set, a 'height' should be also provided.
|
|
1630
|
+
*/
|
|
1631
|
+
get rowHeight() {
|
|
1632
|
+
return this._rowHeight;
|
|
1633
|
+
}
|
|
1634
|
+
set rowHeight(val) {
|
|
1635
|
+
this._rowHeight = val === 'fit' ? val : Math.max(1, Math.round(coerceNumberProperty(val)));
|
|
1636
|
+
}
|
|
1637
|
+
/** Number of columns */
|
|
1638
|
+
get cols() {
|
|
1639
|
+
return this._cols;
|
|
1640
|
+
}
|
|
1641
|
+
set cols(val) {
|
|
1642
|
+
this._cols = Math.max(1, Math.round(coerceNumberProperty(val)));
|
|
1643
|
+
}
|
|
1644
|
+
/** Layout of the grid. Array of all the grid items with its 'id' and position on the grid. */
|
|
1645
|
+
get layout() {
|
|
1646
|
+
return this._layout;
|
|
1647
|
+
}
|
|
1648
|
+
set layout(layout) {
|
|
1649
|
+
/**
|
|
1650
|
+
* Enhancement:
|
|
1651
|
+
* Only set layout if it's reference has changed and use a boolean to track whenever recalculate the layout on ngOnChanges.
|
|
1652
|
+
*
|
|
1653
|
+
* Why:
|
|
1654
|
+
* The normal use of this lib is having the variable layout in the outer component or in a store, assigning it whenever it changes and
|
|
1655
|
+
* binded in the component with it's input [layout]. In this scenario, we would always calculate one unnecessary change on the layout when
|
|
1656
|
+
* it is re-binded on the input.
|
|
1657
|
+
*/
|
|
1658
|
+
this._layout = layout;
|
|
1659
|
+
}
|
|
1660
|
+
/** Grid gap in css pixels */
|
|
1661
|
+
get gap() {
|
|
1662
|
+
return this._gap;
|
|
1663
|
+
}
|
|
1664
|
+
set gap(val) {
|
|
1665
|
+
this._gap = Math.max(coerceNumberProperty(val), 0);
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* If height is a number, fixes the height of the grid to it, recommended when rowHeight = 'fit' is used.
|
|
1669
|
+
* If height is null, height will be automatically set according to its inner grid items.
|
|
1670
|
+
* Defaults to null.
|
|
1671
|
+
* */
|
|
1672
|
+
get height() {
|
|
1673
|
+
return this._height;
|
|
1674
|
+
}
|
|
1675
|
+
set height(val) {
|
|
1676
|
+
this._height = typeof val === 'number' ? Math.max(val, 0) : null;
|
|
1677
|
+
}
|
|
1678
|
+
get backgroundConfig() {
|
|
1679
|
+
return this._backgroundConfig;
|
|
1680
|
+
}
|
|
1681
|
+
set backgroundConfig(val) {
|
|
1682
|
+
this._backgroundConfig = val;
|
|
1683
|
+
// If there is background configuration, add main grid background class. Grid background class comes with opacity 0.
|
|
1684
|
+
// It is done this way for adding opacity animation and to don't add any styles when grid background is null.
|
|
1685
|
+
const classList = this.elementRef.nativeElement.classList;
|
|
1686
|
+
this._backgroundConfig !== null
|
|
1687
|
+
? classList.add('buw-grid-background')
|
|
1688
|
+
: classList.remove('buw-grid-background');
|
|
1689
|
+
// Set background visibility
|
|
1690
|
+
this.setGridBackgroundVisible(this._backgroundConfig?.show === 'always');
|
|
1691
|
+
}
|
|
1692
|
+
get config() {
|
|
1693
|
+
return {
|
|
1694
|
+
cols: this.cols,
|
|
1695
|
+
rowHeight: this.rowHeight,
|
|
1696
|
+
height: this.height,
|
|
1697
|
+
layout: this.layout,
|
|
1698
|
+
preventCollision: this.preventCollision,
|
|
1699
|
+
gap: this.gap
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
constructor(gridService, elementRef, viewContainerRef, renderer, ngZone) {
|
|
1703
|
+
this.gridService = gridService;
|
|
1704
|
+
this.elementRef = elementRef;
|
|
1705
|
+
this.viewContainerRef = viewContainerRef;
|
|
1706
|
+
this.renderer = renderer;
|
|
1707
|
+
this.ngZone = ngZone;
|
|
1708
|
+
/** Emits when layout change */
|
|
1709
|
+
this.layoutUpdated = new EventEmitter();
|
|
1710
|
+
/** Emits when drag starts */
|
|
1711
|
+
this.dragStarted = new EventEmitter();
|
|
1712
|
+
/** Emits when resize starts */
|
|
1713
|
+
this.resizeStarted = new EventEmitter();
|
|
1714
|
+
/** Emits when drag ends */
|
|
1715
|
+
this.dragEnded = new EventEmitter();
|
|
1716
|
+
/** Emits when resize ends */
|
|
1717
|
+
this.resizeEnded = new EventEmitter();
|
|
1718
|
+
/** Emits when a grid item is being resized and its bounds have changed */
|
|
1719
|
+
this.gridItemResize = new EventEmitter();
|
|
1720
|
+
/**
|
|
1721
|
+
* Parent element that contains the scroll. If an string is provided it would search that element by id on the dom.
|
|
1722
|
+
* If no data provided or null autoscroll is not performed.
|
|
1723
|
+
*/
|
|
1724
|
+
this.scrollableParent = null;
|
|
1725
|
+
this._compactOnPropsChange = true;
|
|
1726
|
+
this._preventCollision = false;
|
|
1727
|
+
this._scrollSpeed = 2;
|
|
1728
|
+
this._compactType = 'vertical';
|
|
1729
|
+
this._rowHeight = 100;
|
|
1730
|
+
this._cols = 6;
|
|
1731
|
+
this._gap = 0;
|
|
1732
|
+
this._height = null;
|
|
1733
|
+
this._backgroundConfig = null;
|
|
1734
|
+
this.subscriptions = [];
|
|
1735
|
+
}
|
|
1736
|
+
ngOnChanges(changes) {
|
|
1737
|
+
if (this.rowHeight === 'fit' && this.height == null) {
|
|
1738
|
+
console.warn(`KtdGridComponent: The @Input() height should not be null when using rowHeight 'fit'`);
|
|
1739
|
+
}
|
|
1740
|
+
let needsCompactLayout = false;
|
|
1741
|
+
let needsRecalculateRenderData = false;
|
|
1742
|
+
// TODO: Does fist change need to be compacted by default?
|
|
1743
|
+
// Compact layout whenever some dependent prop changes.
|
|
1744
|
+
if (changes.compactType || changes.cols || changes.layout) {
|
|
1745
|
+
needsCompactLayout = true;
|
|
1746
|
+
}
|
|
1747
|
+
// Check if wee need to recalculate rendering data.
|
|
1748
|
+
if (needsCompactLayout || changes.rowHeight || changes.height || changes.gap || changes.backgroundConfig) {
|
|
1749
|
+
needsRecalculateRenderData = true;
|
|
1750
|
+
}
|
|
1751
|
+
// Only compact layout if lib user has provided it. Lib users that want to save/store always the same layout as it is represented (compacted)
|
|
1752
|
+
// can use KtdCompactGrid utility and pre-compact the layout. This is the recommended behaviour for always having a the same layout on this component
|
|
1753
|
+
// and the ones that uses it.
|
|
1754
|
+
if (needsCompactLayout && this.compactOnPropsChange) {
|
|
1755
|
+
this.compactLayout();
|
|
1756
|
+
}
|
|
1757
|
+
if (needsRecalculateRenderData) {
|
|
1758
|
+
this.calculateRenderData();
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
ngAfterContentInit() {
|
|
1762
|
+
this.initSubscriptions();
|
|
1763
|
+
}
|
|
1764
|
+
ngAfterContentChecked() {
|
|
1765
|
+
this.render();
|
|
1766
|
+
}
|
|
1767
|
+
resize() {
|
|
1768
|
+
this.calculateRenderData();
|
|
1769
|
+
this.render();
|
|
1770
|
+
}
|
|
1771
|
+
ngOnDestroy() {
|
|
1772
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
1773
|
+
}
|
|
1774
|
+
compactLayout() {
|
|
1775
|
+
this.layout = compact(this.layout, this.compactType, this.cols);
|
|
1776
|
+
}
|
|
1777
|
+
getItemsRenderData() {
|
|
1778
|
+
return { ...this._gridItemsRenderData };
|
|
1779
|
+
}
|
|
1780
|
+
getItemRenderData(itemId) {
|
|
1781
|
+
return this._gridItemsRenderData[itemId];
|
|
1782
|
+
}
|
|
1783
|
+
calculateRenderData() {
|
|
1784
|
+
const clientRect = this.elementRef.nativeElement.getBoundingClientRect();
|
|
1785
|
+
this.gridCurrentHeight =
|
|
1786
|
+
this.height ??
|
|
1787
|
+
(this.rowHeight === 'fit' ? clientRect.height : getGridHeight(this.layout, this.rowHeight, this.gap));
|
|
1788
|
+
this._gridItemsRenderData = layoutToRenderItems(this.config, clientRect.width, this.gridCurrentHeight);
|
|
1789
|
+
// Set Background CSS variables
|
|
1790
|
+
this.setBackgroundCssVariables(getRowHeightInPixels(this.config, this.gridCurrentHeight));
|
|
1791
|
+
}
|
|
1792
|
+
render() {
|
|
1793
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${this.gridCurrentHeight}px`);
|
|
1794
|
+
this.updateGridItemsStyles();
|
|
1795
|
+
}
|
|
1796
|
+
setBackgroundCssVariables(rowHeight) {
|
|
1797
|
+
const style = this.elementRef.nativeElement.style;
|
|
1798
|
+
if (this._backgroundConfig) {
|
|
1799
|
+
// structure
|
|
1800
|
+
style.setProperty('--gap', this.gap + 'px');
|
|
1801
|
+
style.setProperty('--row-height', rowHeight + 'px');
|
|
1802
|
+
style.setProperty('--columns', `${this.cols}`);
|
|
1803
|
+
style.setProperty('--border-width', (this._backgroundConfig.borderWidth ?? defaultBackgroundConfig.borderWidth) + 'px');
|
|
1804
|
+
// colors
|
|
1805
|
+
style.setProperty('--border-color', this._backgroundConfig.borderColor ?? defaultBackgroundConfig.borderColor);
|
|
1806
|
+
style.setProperty('--gap-color', this._backgroundConfig.gapColor ?? defaultBackgroundConfig.gapColor);
|
|
1807
|
+
style.setProperty('--row-color', this._backgroundConfig.rowColor ?? defaultBackgroundConfig.rowColor);
|
|
1808
|
+
style.setProperty('--column-color', this._backgroundConfig.columnColor ?? defaultBackgroundConfig.columnColor);
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
style.removeProperty('--gap');
|
|
1812
|
+
style.removeProperty('--row-height');
|
|
1813
|
+
style.removeProperty('--columns');
|
|
1814
|
+
style.removeProperty('--border-width');
|
|
1815
|
+
style.removeProperty('--border-color');
|
|
1816
|
+
style.removeProperty('--gap-color');
|
|
1817
|
+
style.removeProperty('--row-color');
|
|
1818
|
+
style.removeProperty('--column-color');
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
updateGridItemsStyles() {
|
|
1822
|
+
this._gridItems.forEach((item) => {
|
|
1823
|
+
const gridItemRenderData = this._gridItemsRenderData[item.id];
|
|
1824
|
+
if (gridItemRenderData == null) {
|
|
1825
|
+
console.error(`Couldn't find the specified grid item for the id: ${item.id}`);
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
item.setStyles(parseRenderItemToPixels(gridItemRenderData));
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
setGridBackgroundVisible(visible) {
|
|
1833
|
+
const classList = this.elementRef.nativeElement.classList;
|
|
1834
|
+
visible ? classList.add('buw-grid-background-visible') : classList.remove('buw-grid-background-visible');
|
|
1835
|
+
}
|
|
1836
|
+
initSubscriptions() {
|
|
1837
|
+
this.subscriptions = [
|
|
1838
|
+
this._gridItems.changes
|
|
1839
|
+
.pipe(startWith(this._gridItems), switchMap((gridItems) => merge(...gridItems.map((gridItem) => gridItem.dragStart$.pipe(map((event) => ({ event, gridItem, type: 'drag' })))), ...gridItems.map((gridItem) => gridItem.resizeStart$.pipe(map((event) => ({
|
|
1840
|
+
event,
|
|
1841
|
+
gridItem,
|
|
1842
|
+
type: 'resize'
|
|
1843
|
+
}))))).pipe(exhaustMap(({ event, gridItem, type }) => {
|
|
1844
|
+
// Emit drag or resize start events. Ensure that is start event is inside the zone.
|
|
1845
|
+
this.ngZone.run(() => (type === 'drag' ? this.dragStarted : this.resizeStarted).emit(getDragResizeEventData(gridItem, this.layout)));
|
|
1846
|
+
this.setGridBackgroundVisible(this._backgroundConfig?.show === 'whenDragging' ||
|
|
1847
|
+
this._backgroundConfig?.show === 'always');
|
|
1848
|
+
// Perform drag sequence
|
|
1849
|
+
return this.performDragSequence$(gridItem, event, type).pipe(map((layout) => ({ layout, gridItem, type })));
|
|
1850
|
+
}))))
|
|
1851
|
+
.subscribe(({ layout, gridItem, type }) => {
|
|
1852
|
+
this.layout = layout;
|
|
1853
|
+
// Calculate new rendering data given the new layout.
|
|
1854
|
+
this.calculateRenderData();
|
|
1855
|
+
// Emit drag or resize end events.
|
|
1856
|
+
(type === 'drag' ? this.dragEnded : this.resizeEnded).emit(getDragResizeEventData(gridItem, layout));
|
|
1857
|
+
// Notify that the layout has been updated.
|
|
1858
|
+
this.layoutUpdated.emit(layout);
|
|
1859
|
+
this.setGridBackgroundVisible(this._backgroundConfig?.show === 'always');
|
|
1860
|
+
})
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Perform a general grid drag action, from start to end. A general grid drag action basically includes creating the placeholder element and adding
|
|
1865
|
+
* some class animations. calcNewStateFunc needs to be provided in order to calculate the new state of the layout.
|
|
1866
|
+
* @param gridItem that is been dragged
|
|
1867
|
+
* @param pointerDownEvent event (mousedown or touchdown) where the user initiated the drag
|
|
1868
|
+
* @param calcNewStateFunc function that return the new layout state and the drag element position
|
|
1869
|
+
*/
|
|
1870
|
+
performDragSequence$(gridItem, pointerDownEvent, type) {
|
|
1871
|
+
return new Observable((observer) => {
|
|
1872
|
+
// Retrieve grid (parent) and gridItem (draggedElem) client rects.
|
|
1873
|
+
const gridElemClientRect = getMutableClientRect(this.elementRef.nativeElement);
|
|
1874
|
+
const dragElemClientRect = getMutableClientRect(gridItem.elementRef.nativeElement);
|
|
1875
|
+
const scrollableParent = typeof this.scrollableParent === 'string'
|
|
1876
|
+
? document.getElementById(this.scrollableParent)
|
|
1877
|
+
: this.scrollableParent;
|
|
1878
|
+
this.renderer.addClass(gridItem.elementRef.nativeElement, 'no-transitions');
|
|
1879
|
+
this.renderer.addClass(gridItem.elementRef.nativeElement, 'buw-grid-item-dragging');
|
|
1880
|
+
const placeholderClientRect = {
|
|
1881
|
+
...dragElemClientRect,
|
|
1882
|
+
left: dragElemClientRect.left - gridElemClientRect.left,
|
|
1883
|
+
top: dragElemClientRect.top - gridElemClientRect.top
|
|
1884
|
+
};
|
|
1885
|
+
this.createPlaceholderElement(placeholderClientRect, gridItem.placeholder);
|
|
1886
|
+
let newLayout;
|
|
1887
|
+
// TODO (enhancement): consider move this 'side effect' observable inside the main drag loop.
|
|
1888
|
+
// - Pros are that we would not repeat subscriptions and takeUntil would shut down observables at the same time.
|
|
1889
|
+
// - Cons are that moving this functionality as a side effect inside the main drag loop would be confusing.
|
|
1890
|
+
const scrollSubscription = this.ngZone.runOutsideAngular(() => (!scrollableParent
|
|
1891
|
+
? NEVER
|
|
1892
|
+
: this.gridService.mouseOrTouchMove$(document).pipe(map((event) => ({
|
|
1893
|
+
pointerX: ktdPointerClientX(event),
|
|
1894
|
+
pointerY: ktdPointerClientY(event)
|
|
1895
|
+
})), ktdScrollIfNearElementClientRect$(scrollableParent, { scrollStep: this.scrollSpeed })))
|
|
1896
|
+
.pipe(takeUntil(ktdPointerUp(document)))
|
|
1897
|
+
.subscribe());
|
|
1898
|
+
/**
|
|
1899
|
+
* Main subscription, it listens for 'pointer move' and 'scroll' events and recalculates the layout on each emission
|
|
1900
|
+
*/
|
|
1901
|
+
const subscription = this.ngZone.runOutsideAngular(() => merge(combineLatest([
|
|
1902
|
+
this.gridService.mouseOrTouchMove$(document),
|
|
1903
|
+
...(!scrollableParent
|
|
1904
|
+
? [of({ top: 0, left: 0 })]
|
|
1905
|
+
: [
|
|
1906
|
+
ktdGetScrollTotalRelativeDifference$(scrollableParent).pipe(startWith({ top: 0, left: 0 }) // Force first emission to allow CombineLatest to emit even no scroll event has occurred
|
|
1907
|
+
)
|
|
1908
|
+
])
|
|
1909
|
+
]))
|
|
1910
|
+
.pipe(takeUntil(ktdPointerUp(document)))
|
|
1911
|
+
.subscribe(([pointerDragEvent, scrollDifference]) => {
|
|
1912
|
+
pointerDragEvent.preventDefault();
|
|
1913
|
+
/**
|
|
1914
|
+
* Set the new layout to be the layout in which the calcNewStateFunc would be executed.
|
|
1915
|
+
* NOTE: using the mutated layout is the way to go by 'react-grid-layout' utils. If we don't use the previous layout,
|
|
1916
|
+
* some utilities from 'react-grid-layout' would not work as expected.
|
|
1917
|
+
*/
|
|
1918
|
+
const currentLayout = newLayout || this.layout;
|
|
1919
|
+
// Get the correct newStateFunc depending on if we are dragging or resizing
|
|
1920
|
+
const calcNewStateFunc = type === 'drag' ? ktdGridItemDragging : ktdGridItemResizing;
|
|
1921
|
+
const { layout, draggedItemPos } = calcNewStateFunc(gridItem, {
|
|
1922
|
+
layout: currentLayout,
|
|
1923
|
+
rowHeight: this.rowHeight,
|
|
1924
|
+
height: this.height,
|
|
1925
|
+
cols: this.cols,
|
|
1926
|
+
preventCollision: this.preventCollision,
|
|
1927
|
+
gap: this.gap
|
|
1928
|
+
}, this.compactType, {
|
|
1929
|
+
pointerDownEvent,
|
|
1930
|
+
pointerDragEvent,
|
|
1931
|
+
gridElemClientRect,
|
|
1932
|
+
dragElemClientRect,
|
|
1933
|
+
scrollDifference
|
|
1934
|
+
});
|
|
1935
|
+
newLayout = layout;
|
|
1936
|
+
this.gridCurrentHeight =
|
|
1937
|
+
this.height ??
|
|
1938
|
+
(this.rowHeight === 'fit'
|
|
1939
|
+
? gridElemClientRect.height
|
|
1940
|
+
: getGridHeight(newLayout, this.rowHeight, this.gap));
|
|
1941
|
+
this._gridItemsRenderData = layoutToRenderItems({
|
|
1942
|
+
cols: this.cols,
|
|
1943
|
+
rowHeight: this.rowHeight,
|
|
1944
|
+
height: this.height,
|
|
1945
|
+
layout: newLayout,
|
|
1946
|
+
preventCollision: this.preventCollision,
|
|
1947
|
+
gap: this.gap
|
|
1948
|
+
}, gridElemClientRect.width, gridElemClientRect.height);
|
|
1949
|
+
const newGridItemRenderData = { ...this._gridItemsRenderData[gridItem.id] };
|
|
1950
|
+
const placeholderStyles = parseRenderItemToPixels(newGridItemRenderData);
|
|
1951
|
+
// Put the real final position to the placeholder element
|
|
1952
|
+
this.placeholder.style.width = placeholderStyles.width;
|
|
1953
|
+
this.placeholder.style.height = placeholderStyles.height;
|
|
1954
|
+
this.placeholder.style.transform = `translateX(${placeholderStyles.left}) translateY(${placeholderStyles.top})`;
|
|
1955
|
+
// modify the position of the dragged item to be the once we want (for example the mouse position or whatever)
|
|
1956
|
+
this._gridItemsRenderData[gridItem.id] = {
|
|
1957
|
+
...draggedItemPos,
|
|
1958
|
+
id: this._gridItemsRenderData[gridItem.id].id
|
|
1959
|
+
};
|
|
1960
|
+
this.setBackgroundCssVariables(this.rowHeight === 'fit'
|
|
1961
|
+
? ktdGetGridItemRowHeight(newLayout, gridElemClientRect.height, this.gap)
|
|
1962
|
+
: this.rowHeight);
|
|
1963
|
+
this.render();
|
|
1964
|
+
// If we are performing a resize, and bounds have changed, emit event.
|
|
1965
|
+
// NOTE: Only emit on resize for now. Use case for normal drag is not justified for now. Emitting on resize is,
|
|
1966
|
+
// since we may want to re-render the grid item or the placeholder in order to fit the new bounds.
|
|
1967
|
+
if (type === 'resize') {
|
|
1968
|
+
const prevGridItem = currentLayout.find((item) => item.id === gridItem.id);
|
|
1969
|
+
const newGridItem = newLayout.find((item) => item.id === gridItem.id);
|
|
1970
|
+
// Check if item resized has changed, if so, emit resize change event
|
|
1971
|
+
if (!ktdGridItemLayoutItemAreEqual(prevGridItem, newGridItem)) {
|
|
1972
|
+
this.gridItemResize.emit({
|
|
1973
|
+
width: newGridItemRenderData.width,
|
|
1974
|
+
height: newGridItemRenderData.height,
|
|
1975
|
+
gridItemRef: getDragResizeEventData(gridItem, newLayout).gridItemRef
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}, (error) => observer.error(error), () => {
|
|
1980
|
+
this.ngZone.run(() => {
|
|
1981
|
+
// Remove drag classes
|
|
1982
|
+
this.renderer.removeClass(gridItem.elementRef.nativeElement, 'no-transitions');
|
|
1983
|
+
this.renderer.removeClass(gridItem.elementRef.nativeElement, 'buw-grid-item-dragging');
|
|
1984
|
+
this.addGridItemAnimatingClass(gridItem).subscribe();
|
|
1985
|
+
// Consider destroying the placeholder after the animation has finished.
|
|
1986
|
+
this.destroyPlaceholder();
|
|
1987
|
+
if (newLayout) {
|
|
1988
|
+
// TODO: newLayout should already be pruned. If not, it should have type Layout, not KtdGridLayout as it is now.
|
|
1989
|
+
// Prune react-grid-layout compact extra properties.
|
|
1990
|
+
observer.next(newLayout.map((item) => ({
|
|
1991
|
+
id: item.id,
|
|
1992
|
+
x: item.x,
|
|
1993
|
+
y: item.y,
|
|
1994
|
+
w: item.w,
|
|
1995
|
+
h: item.h,
|
|
1996
|
+
minW: item.minW,
|
|
1997
|
+
minH: item.minH,
|
|
1998
|
+
maxW: item.maxW,
|
|
1999
|
+
maxH: item.maxH
|
|
2000
|
+
})));
|
|
2001
|
+
}
|
|
2002
|
+
else {
|
|
2003
|
+
// TODO: Need we really to emit if there is no layout change but drag started and ended?
|
|
2004
|
+
observer.next(this.layout);
|
|
2005
|
+
}
|
|
2006
|
+
observer.complete();
|
|
2007
|
+
});
|
|
2008
|
+
}));
|
|
2009
|
+
return () => {
|
|
2010
|
+
scrollSubscription.unsubscribe();
|
|
2011
|
+
subscription.unsubscribe();
|
|
2012
|
+
};
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* It adds the `buw-grid-item-animating` class and removes it when the animated transition is complete.
|
|
2017
|
+
* This function is meant to be executed when the drag has ended.
|
|
2018
|
+
* @param gridItem that has been dragged
|
|
2019
|
+
*/
|
|
2020
|
+
addGridItemAnimatingClass(gridItem) {
|
|
2021
|
+
return new Observable((observer) => {
|
|
2022
|
+
const duration = getTransformTransitionDurationInMs(gridItem.elementRef.nativeElement);
|
|
2023
|
+
if (duration === 0) {
|
|
2024
|
+
observer.next();
|
|
2025
|
+
observer.complete();
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
this.renderer.addClass(gridItem.elementRef.nativeElement, 'buw-grid-item-animating');
|
|
2029
|
+
const handler = ((event) => {
|
|
2030
|
+
if (!event ||
|
|
2031
|
+
(event.target === gridItem.elementRef.nativeElement && event.propertyName === 'transform')) {
|
|
2032
|
+
this.renderer.removeClass(gridItem.elementRef.nativeElement, 'buw-grid-item-animating');
|
|
2033
|
+
removeEventListener();
|
|
2034
|
+
clearTimeout(timeout);
|
|
2035
|
+
observer.next();
|
|
2036
|
+
observer.complete();
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
// If a transition is short enough, the browser might not fire the `transitionend` event.
|
|
2040
|
+
// Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
|
|
2041
|
+
// fire if the transition hasn't completed when it was supposed to.
|
|
2042
|
+
const timeout = setTimeout(handler, duration * 1.5);
|
|
2043
|
+
const removeEventListener = this.renderer.listen(gridItem.elementRef.nativeElement, 'transitionend', handler);
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
/** Creates placeholder element */
|
|
2047
|
+
createPlaceholderElement(clientRect, gridItemPlaceholder) {
|
|
2048
|
+
this.placeholder = this.renderer.createElement('div');
|
|
2049
|
+
this.placeholder.style.width = `${clientRect.width}px`;
|
|
2050
|
+
this.placeholder.style.height = `${clientRect.height}px`;
|
|
2051
|
+
this.placeholder.style.transform = `translateX(${clientRect.left}px) translateY(${clientRect.top}px)`;
|
|
2052
|
+
this.placeholder.classList.add('buw-grid-item-placeholder');
|
|
2053
|
+
this.renderer.appendChild(this.elementRef.nativeElement, this.placeholder);
|
|
2054
|
+
// Create and append custom placeholder if provided.
|
|
2055
|
+
// Important: Append it after creating & appending the container placeholder. This way we ensure parent bounds are set when creating the embeddedView.
|
|
2056
|
+
if (gridItemPlaceholder) {
|
|
2057
|
+
this.placeholderRef = this.viewContainerRef.createEmbeddedView(gridItemPlaceholder.templateRef, gridItemPlaceholder.data);
|
|
2058
|
+
this.placeholderRef.rootNodes.forEach((node) => this.placeholder.appendChild(node));
|
|
2059
|
+
this.placeholderRef.detectChanges();
|
|
2060
|
+
}
|
|
2061
|
+
else {
|
|
2062
|
+
this.placeholder.classList.add('buw-grid-item-placeholder-default');
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/** Destroys the placeholder element and its ViewRef. */
|
|
2066
|
+
destroyPlaceholder() {
|
|
2067
|
+
this.placeholder?.remove();
|
|
2068
|
+
this.placeholderRef?.destroy();
|
|
2069
|
+
this.placeholder = this.placeholderRef = null;
|
|
2070
|
+
}
|
|
2071
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridComponent, deps: [{ token: KtdGridService }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2072
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: KtdGridComponent, selector: "buw-grid", inputs: { scrollableParent: "scrollableParent", compactOnPropsChange: "compactOnPropsChange", preventCollision: "preventCollision", scrollSpeed: "scrollSpeed", compactType: "compactType", rowHeight: "rowHeight", cols: "cols", layout: "layout", gap: "gap", height: "height", backgroundConfig: "backgroundConfig" }, outputs: { layoutUpdated: "layoutUpdated", dragStarted: "dragStarted", resizeStarted: "resizeStarted", dragEnded: "dragEnded", resizeEnded: "resizeEnded", gridItemResize: "gridItemResize" }, providers: [
|
|
2073
|
+
{
|
|
2074
|
+
provide: GRID_ITEM_GET_RENDER_DATA_TOKEN,
|
|
2075
|
+
useFactory: ktdGridItemGetRenderDataFactoryFunc,
|
|
2076
|
+
deps: [KtdGridComponent]
|
|
2077
|
+
}
|
|
2078
|
+
], queries: [{ propertyName: "_gridItems", predicate: KtdGridItemComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-content></ng-content>", styles: ["buw-grid{display:block;position:relative;width:100%}buw-grid.buw-grid-background:before{content:\"\";border:none;position:absolute;inset:0;z-index:0;transition:opacity .2s;opacity:0;background-image:repeating-linear-gradient(var(--border-color) 0 var(--border-width),var(--row-color) var(--border-width) calc(var(--row-height) - var(--border-width)),var(--border-color) calc(var(--row-height) - var(--border-width)) calc(var(--row-height)),var(--gap-color) calc(var(--row-height)) calc(var(--row-height) + var(--gap))),repeating-linear-gradient(90deg,var(--border-color) 0 var(--border-width),var(--column-color) var(--border-width) calc(100% - (var(--border-width) + var(--gap))),var(--border-color) calc(100% - (var(--border-width) + var(--gap))) calc(100% - var(--gap)),var(--gap-color) calc(100% - var(--gap)) 100%);background-size:calc((100% + var(--gap)) / var(--columns)) calc(var(--row-height) + var(--gap));background-position:0 0}buw-grid.buw-grid-background.buw-grid-background-visible:before{opacity:1}buw-grid buw-grid-item.buw-grid-item-dragging,buw-grid buw-grid-item.buw-grid-item-animating{z-index:1000}buw-grid buw-grid-item.no-transitions{transition:none!important}buw-grid .buw-grid-item-placeholder{position:absolute;z-index:0;transition-property:transform;transition:all .15s ease}buw-grid .buw-grid-item-placeholder-default{background-color:#8b0000;opacity:.6}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
2079
|
+
}
|
|
2080
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: KtdGridComponent, decorators: [{
|
|
2081
|
+
type: Component,
|
|
2082
|
+
args: [{ selector: 'buw-grid', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
2083
|
+
{
|
|
2084
|
+
provide: GRID_ITEM_GET_RENDER_DATA_TOKEN,
|
|
2085
|
+
useFactory: ktdGridItemGetRenderDataFactoryFunc,
|
|
2086
|
+
deps: [KtdGridComponent]
|
|
2087
|
+
}
|
|
2088
|
+
], template: "<ng-content></ng-content>", styles: ["buw-grid{display:block;position:relative;width:100%}buw-grid.buw-grid-background:before{content:\"\";border:none;position:absolute;inset:0;z-index:0;transition:opacity .2s;opacity:0;background-image:repeating-linear-gradient(var(--border-color) 0 var(--border-width),var(--row-color) var(--border-width) calc(var(--row-height) - var(--border-width)),var(--border-color) calc(var(--row-height) - var(--border-width)) calc(var(--row-height)),var(--gap-color) calc(var(--row-height)) calc(var(--row-height) + var(--gap))),repeating-linear-gradient(90deg,var(--border-color) 0 var(--border-width),var(--column-color) var(--border-width) calc(100% - (var(--border-width) + var(--gap))),var(--border-color) calc(100% - (var(--border-width) + var(--gap))) calc(100% - var(--gap)),var(--gap-color) calc(100% - var(--gap)) 100%);background-size:calc((100% + var(--gap)) / var(--columns)) calc(var(--row-height) + var(--gap));background-position:0 0}buw-grid.buw-grid-background.buw-grid-background-visible:before{opacity:1}buw-grid buw-grid-item.buw-grid-item-dragging,buw-grid buw-grid-item.buw-grid-item-animating{z-index:1000}buw-grid buw-grid-item.no-transitions{transition:none!important}buw-grid .buw-grid-item-placeholder{position:absolute;z-index:0;transition-property:transform;transition:all .15s ease}buw-grid .buw-grid-item-placeholder-default{background-color:#8b0000;opacity:.6}\n"] }]
|
|
2089
|
+
}], ctorParameters: () => [{ type: KtdGridService }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: i0.Renderer2 }, { type: i0.NgZone }], propDecorators: { _gridItems: [{
|
|
2090
|
+
type: ContentChildren,
|
|
2091
|
+
args: [KtdGridItemComponent, { descendants: true }]
|
|
2092
|
+
}], layoutUpdated: [{
|
|
2093
|
+
type: Output
|
|
2094
|
+
}], dragStarted: [{
|
|
2095
|
+
type: Output
|
|
2096
|
+
}], resizeStarted: [{
|
|
2097
|
+
type: Output
|
|
2098
|
+
}], dragEnded: [{
|
|
2099
|
+
type: Output
|
|
2100
|
+
}], resizeEnded: [{
|
|
2101
|
+
type: Output
|
|
2102
|
+
}], gridItemResize: [{
|
|
2103
|
+
type: Output
|
|
2104
|
+
}], scrollableParent: [{
|
|
2105
|
+
type: Input
|
|
2106
|
+
}], compactOnPropsChange: [{
|
|
2107
|
+
type: Input
|
|
2108
|
+
}], preventCollision: [{
|
|
2109
|
+
type: Input
|
|
2110
|
+
}], scrollSpeed: [{
|
|
2111
|
+
type: Input
|
|
2112
|
+
}], compactType: [{
|
|
2113
|
+
type: Input
|
|
2114
|
+
}], rowHeight: [{
|
|
2115
|
+
type: Input
|
|
2116
|
+
}], cols: [{
|
|
2117
|
+
type: Input
|
|
2118
|
+
}], layout: [{
|
|
2119
|
+
type: Input
|
|
2120
|
+
}], gap: [{
|
|
2121
|
+
type: Input
|
|
2122
|
+
}], height: [{
|
|
2123
|
+
type: Input
|
|
2124
|
+
}], backgroundConfig: [{
|
|
2125
|
+
type: Input
|
|
2126
|
+
}] } });
|
|
2127
|
+
|
|
2128
|
+
class NavContainerComponent extends BaseComponent {
|
|
2129
|
+
constructor() {
|
|
2130
|
+
super(...arguments);
|
|
2131
|
+
this._router = inject(Router);
|
|
2132
|
+
}
|
|
2133
|
+
onTabSelect(e) {
|
|
2134
|
+
console.log(e);
|
|
2135
|
+
if (e.label === 'صفحه اصلی') {
|
|
2136
|
+
this._router.navigate(['/home']);
|
|
2137
|
+
}
|
|
2138
|
+
if (e.label === 'برنامه ها') {
|
|
2139
|
+
this._router.navigate(['/home/launchpad']);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NavContainerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2143
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: NavContainerComponent, selector: "buw-nav-container", providers: [], usesInheritance: true, ngImport: i0, template: "<div class=\"fd-shellbar fd-shellbar--xl\">\r\n <fdp-icon-tab-bar layoutMode=\"row\" maxContentHeight=\"250px\" (iconTabSelected)=\"onTabSelect($event)\">\r\n <fdp-icon-tab-bar-tab label=\"\u0635\u0641\u062D\u0647 \u0627\u0635\u0644\u06CC\" active> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632 \u06A9\u0627\u0631 \u0645\u0646\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u062A\u06CC\u06A9\u062A\u06CC\u0646\u06AF\">\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06311\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06312\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06313\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06314\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u0646\u0627\u0628\u0639 \u0627\u0646\u0633\u0627\u0646\u06CC\">\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06311\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06312\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06313\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06314\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u0631\u0647\u0627\">\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06311\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06312\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06313\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06314\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0628\u0631\u0646\u0627\u0645\u0647 \u0647\u0627\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0627\u0628\u0632\u0627\u0631\u0647\u0627\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar>\r\n</div>", styles: [":host{display:block}:host ::ng-deep>div ul{background-color:transparent}:host ::ng-deep>div ul .fd-icon-tab-bar__tag{color:#fff}:host ::ng-deep>div ul fd-icon,:host ::ng-deep>div ul .fd-icon-tab-bar__arrow{color:#fff!important}:host ::ng-deep>div ul .fd-icon-tab-bar__tab[aria-selected=true]:after{background-color:#fff}\n"], dependencies: [{ kind: "component", type: i1.IconTabBarComponent, selector: "fdp-icon-tab-bar", inputs: ["stackContent", "iconTabType", "tabsConfig", "densityMode", "iconTabFont", "enableTabReordering", "showTotalTab", "multiClick", "layoutMode", "iconTabBackground", "iconTabSize", "colorAssociations", "maxContentHeight"], outputs: ["tabsConfigChange", "densityModeChange", "iconTabSelected", "iconTabReordered", "closeTab"] }, { kind: "component", type: i1.IconTabBarTabComponent, selector: "fdp-icon-tab-bar-tab", inputs: ["label", "color", "icon", "iconFont", "counter", "active", "badge", "closable", "id"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2144
|
+
}
|
|
2145
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NavContainerComponent, decorators: [{
|
|
2146
|
+
type: Component,
|
|
2147
|
+
args: [{ selector: 'buw-nav-container', providers: [], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"fd-shellbar fd-shellbar--xl\">\r\n <fdp-icon-tab-bar layoutMode=\"row\" maxContentHeight=\"250px\" (iconTabSelected)=\"onTabSelect($event)\">\r\n <fdp-icon-tab-bar-tab label=\"\u0635\u0641\u062D\u0647 \u0627\u0635\u0644\u06CC\" active> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632 \u06A9\u0627\u0631 \u0645\u0646\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u062A\u06CC\u06A9\u062A\u06CC\u0646\u06AF\">\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06311\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06312\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06313\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06314\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u0646\u0627\u0628\u0639 \u0627\u0646\u0633\u0627\u0646\u06CC\">\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06311\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06312\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06313\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06314\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u0631\u0647\u0627\">\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06311\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06312\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06313\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0645\u06CC\u0632\u06A9\u0627\u06314\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0628\u0631\u0646\u0627\u0645\u0647 \u0647\u0627\"> </fdp-icon-tab-bar-tab>\r\n <fdp-icon-tab-bar-tab label=\"\u0627\u0628\u0632\u0627\u0631\u0647\u0627\"> </fdp-icon-tab-bar-tab>\r\n </fdp-icon-tab-bar>\r\n</div>", styles: [":host{display:block}:host ::ng-deep>div ul{background-color:transparent}:host ::ng-deep>div ul .fd-icon-tab-bar__tag{color:#fff}:host ::ng-deep>div ul fd-icon,:host ::ng-deep>div ul .fd-icon-tab-bar__arrow{color:#fff!important}:host ::ng-deep>div ul .fd-icon-tab-bar__tab[aria-selected=true]:after{background-color:#fff}\n"] }]
|
|
2148
|
+
}] });
|
|
2149
|
+
|
|
2150
|
+
/**
|
|
2151
|
+
* Removes and item from an array. Returns a new array instance (it doesn't mutate the source array).
|
|
2152
|
+
* @param array source array to be returned without the element to remove
|
|
2153
|
+
* @param condition function that will return true for the item that we want to remove
|
|
2154
|
+
*/
|
|
2155
|
+
function ktdArrayRemoveItem(array, condition) {
|
|
2156
|
+
const arrayCopy = [...array];
|
|
2157
|
+
const index = array.findIndex((item) => condition(item));
|
|
2158
|
+
if (index > -1) {
|
|
2159
|
+
arrayCopy.splice(index, 1);
|
|
2160
|
+
}
|
|
2161
|
+
return arrayCopy;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
class LayoutContainerComponent extends BaseComponent {
|
|
2165
|
+
constructor(ngZone, elementRef, document) {
|
|
2166
|
+
// this.ngZone.onUnstable.subscribe(() => console.log('UnStable'));
|
|
2167
|
+
super();
|
|
2168
|
+
this.ngZone = ngZone;
|
|
2169
|
+
this.elementRef = elementRef;
|
|
2170
|
+
this.document = document;
|
|
2171
|
+
// @Input() layout: KtdGridLayout = [{ id: '0', x: 5, y: 0, w: 2, h: 3 }];
|
|
2172
|
+
this.layout = [];
|
|
2173
|
+
this.moDataList = [];
|
|
2174
|
+
this.trackById = ktdTrackById;
|
|
2175
|
+
this.cols = 12;
|
|
2176
|
+
this.rowHeight = 50;
|
|
2177
|
+
this.rowHeightFit = false;
|
|
2178
|
+
this.gridHeight = null;
|
|
2179
|
+
this.compactType = 'vertical';
|
|
2180
|
+
this.transitions = [
|
|
2181
|
+
{ name: 'ease', value: 'transform 500ms ease, width 500ms ease, height 500ms ease' },
|
|
2182
|
+
{ name: 'ease-out', value: 'transform 500ms ease-out, width 500ms ease-out, height 500ms ease-out' },
|
|
2183
|
+
{ name: 'linear', value: 'transform 500ms linear, width 500ms linear, height 500ms linear' },
|
|
2184
|
+
{
|
|
2185
|
+
name: 'overflowing',
|
|
2186
|
+
value: 'transform 500ms cubic-bezier(.28,.49,.79,1.35), width 500ms cubic-bezier(.28,.49,.79,1.35), height 500ms cubic-bezier(.28,.49,.79,1.35)'
|
|
2187
|
+
},
|
|
2188
|
+
{ name: 'fast', value: 'transform 200ms ease, width 200ms linear, height 200ms linear' },
|
|
2189
|
+
{ name: 'slow-motion', value: 'transform 1000ms linear, width 1000ms linear, height 1000ms linear' },
|
|
2190
|
+
{ name: 'transform-only', value: 'transform 500ms ease' }
|
|
2191
|
+
];
|
|
2192
|
+
this.currentTransition = this.transitions[0].value;
|
|
2193
|
+
this.placeholders = ['None', 'Default', 'Custom 1', 'Custom 2', 'Custom 3'];
|
|
2194
|
+
this.currentPlaceholder = 'Default';
|
|
2195
|
+
this.dragStartThreshold = 0;
|
|
2196
|
+
this.gap = 10;
|
|
2197
|
+
this.autoScroll = true;
|
|
2198
|
+
this.disableDrag = false;
|
|
2199
|
+
this.disableResize = false;
|
|
2200
|
+
this.disableRemove = false;
|
|
2201
|
+
this.autoResize = true;
|
|
2202
|
+
this.preventCollision = false;
|
|
2203
|
+
this.isDragging = false;
|
|
2204
|
+
this.isResizing = false;
|
|
2205
|
+
this.showBackground = false;
|
|
2206
|
+
this.gridBackgroundVisibilityOptions = ['never', 'always', 'whenDragging'];
|
|
2207
|
+
this.gridBackgroundConfig = {
|
|
2208
|
+
show: 'always',
|
|
2209
|
+
borderColor: 'rgba(255, 128, 0, 0.25)',
|
|
2210
|
+
gapColor: 'transparent',
|
|
2211
|
+
borderWidth: 1,
|
|
2212
|
+
rowColor: 'rgba(128, 128, 128, 0.10)',
|
|
2213
|
+
columnColor: 'rgba(128, 128, 128, 0.10)'
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
ngOnInit() {
|
|
2217
|
+
this.resizeSubscription = merge(fromEvent(window, 'resize'), fromEvent(window, 'orientationchange'))
|
|
2218
|
+
.pipe(debounceTime(50), filter$1(() => this.autoResize))
|
|
2219
|
+
.subscribe(() => {
|
|
2220
|
+
this.grid.resize();
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
ngOnDestroy() {
|
|
2224
|
+
this.resizeSubscription.unsubscribe();
|
|
2225
|
+
}
|
|
2226
|
+
ngOnChanges(changes) {
|
|
2227
|
+
super.ngOnChanges(changes);
|
|
2228
|
+
const { layout } = changes;
|
|
2229
|
+
if (layout && !layout.firstChange) {
|
|
2230
|
+
this.grid.resize();
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
onDragStarted(event) {
|
|
2234
|
+
this.isDragging = true;
|
|
2235
|
+
}
|
|
2236
|
+
onResizeStarted(event) {
|
|
2237
|
+
this.isResizing = true;
|
|
2238
|
+
}
|
|
2239
|
+
onDragEnded(event) {
|
|
2240
|
+
this.isDragging = false;
|
|
2241
|
+
}
|
|
2242
|
+
onResizeEnded(event) {
|
|
2243
|
+
this.isResizing = false;
|
|
2244
|
+
}
|
|
2245
|
+
onLayoutUpdated(layout) {
|
|
2246
|
+
console.log('on layout updated', layout);
|
|
2247
|
+
this.layout = layout;
|
|
2248
|
+
}
|
|
2249
|
+
onAutoScrollChange(checked) {
|
|
2250
|
+
this.autoScroll = checked;
|
|
2251
|
+
}
|
|
2252
|
+
onDisableDragChange(checked) {
|
|
2253
|
+
this.disableDrag = checked;
|
|
2254
|
+
}
|
|
2255
|
+
onDisableResizeChange(checked) {
|
|
2256
|
+
this.disableResize = checked;
|
|
2257
|
+
}
|
|
2258
|
+
onShowBackgroundChange(checked) {
|
|
2259
|
+
this.showBackground = checked;
|
|
2260
|
+
}
|
|
2261
|
+
onDisableRemoveChange(checked) {
|
|
2262
|
+
this.disableRemove = checked;
|
|
2263
|
+
}
|
|
2264
|
+
onAutoResizeChange(checked) {
|
|
2265
|
+
this.autoResize = checked;
|
|
2266
|
+
}
|
|
2267
|
+
onPreventCollisionChange(checked) {
|
|
2268
|
+
this.preventCollision = checked;
|
|
2269
|
+
}
|
|
2270
|
+
onColsChange(event) {
|
|
2271
|
+
this.cols = coerceNumberProperty$1(event.target.value);
|
|
2272
|
+
}
|
|
2273
|
+
onRowHeightChange(event) {
|
|
2274
|
+
this.rowHeight = coerceNumberProperty$1(event.target.value);
|
|
2275
|
+
}
|
|
2276
|
+
onGridHeightChange(event) {
|
|
2277
|
+
this.gridHeight = coerceNumberProperty$1(event.target.value);
|
|
2278
|
+
}
|
|
2279
|
+
onDragStartThresholdChange(event) {
|
|
2280
|
+
this.dragStartThreshold = coerceNumberProperty$1(event.target.value);
|
|
2281
|
+
}
|
|
2282
|
+
onGapChange(event) {
|
|
2283
|
+
this.gap = coerceNumberProperty$1(event.target.value);
|
|
2284
|
+
}
|
|
2285
|
+
generateLayout() {
|
|
2286
|
+
const layout = [];
|
|
2287
|
+
for (let i = 0; i < this.cols; i++) {
|
|
2288
|
+
const y = Math.ceil(Math.random() * 4) + 1;
|
|
2289
|
+
layout.push({
|
|
2290
|
+
x: Math.round(Math.random() * Math.floor(this.cols / 2 - 1)) * 2,
|
|
2291
|
+
y: Math.floor(i / 6) * y,
|
|
2292
|
+
w: 2,
|
|
2293
|
+
h: y,
|
|
2294
|
+
id: i.toString()
|
|
2295
|
+
// static: Math.random() < 0.05
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
this.layout = ktdGridCompact(layout, this.compactType, this.cols);
|
|
2299
|
+
console.log('generateLayout', this.layout);
|
|
2300
|
+
}
|
|
2301
|
+
/** Adds a grid item to the layout */
|
|
2302
|
+
addItemToLayout() {
|
|
2303
|
+
const maxId = this.layout.reduce((acc, cur) => Math.max(acc, parseInt(cur.id, 10)), -1);
|
|
2304
|
+
const nextId = maxId + 1;
|
|
2305
|
+
const newLayoutItem = {
|
|
2306
|
+
id: nextId.toString(),
|
|
2307
|
+
x: -1,
|
|
2308
|
+
y: -1,
|
|
2309
|
+
w: 2,
|
|
2310
|
+
h: 2
|
|
2311
|
+
};
|
|
2312
|
+
// Important: Don't mutate the array, create new instance. This way notifies the Grid component that the layout has changed.
|
|
2313
|
+
this.layout = [newLayoutItem, ...this.layout];
|
|
2314
|
+
this.layout = ktdGridCompact(this.layout, this.compactType, this.cols);
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Fired when a mousedown happens on the remove grid item button.
|
|
2318
|
+
* Stops the event from propagating an causing the drag to start.
|
|
2319
|
+
* We don't want to drag when mousedown is fired on remove icon button.
|
|
2320
|
+
*/
|
|
2321
|
+
stopEventPropagation(event) {
|
|
2322
|
+
event.preventDefault();
|
|
2323
|
+
event.stopPropagation();
|
|
2324
|
+
}
|
|
2325
|
+
/** Removes the item from the layout */
|
|
2326
|
+
removeItem(id) {
|
|
2327
|
+
// Important: Don't mutate the array. Let Angular know that the layout has changed creating a new reference.
|
|
2328
|
+
this.layout = ktdArrayRemoveItem(this.layout, (item) => item.id === id);
|
|
2329
|
+
}
|
|
2330
|
+
updateGridBgBorderWidth(borderWidth) {
|
|
2331
|
+
this.gridBackgroundConfig = {
|
|
2332
|
+
...this.gridBackgroundConfig,
|
|
2333
|
+
borderWidth: Number(borderWidth)
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
updateGridBgColor(color, property) {
|
|
2337
|
+
this.gridBackgroundConfig = {
|
|
2338
|
+
...this.gridBackgroundConfig,
|
|
2339
|
+
[property]: color
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
getCurrentBackgroundVisibility() {
|
|
2343
|
+
return this.gridBackgroundConfig?.show ?? 'never';
|
|
2344
|
+
}
|
|
2345
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LayoutContainerComponent, deps: [{ token: i0.NgZone }, { token: i0.ElementRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2346
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: LayoutContainerComponent, selector: "buw-layout-container", inputs: { layout: "layout", moDataList: "moDataList" }, providers: [], viewQueries: [{ propertyName: "grid", first: true, predicate: KtdGridComponent, descendants: true, static: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div style=\"position: relative\" dir=\"ltr\" fillEmptySpace [setMinHeight]=\"true\">\r\n <buw-grid\r\n [cols]=\"cols\"\r\n [backgroundConfig]=\"gridBackgroundConfig\"\r\n [height]=\"rowHeightFit && gridHeight ? gridHeight : null\"\r\n [rowHeight]=\"rowHeightFit ? 'fit' : rowHeight\"\r\n [layout]=\"layout\"\r\n [compactType]=\"compactType\"\r\n [preventCollision]=\"preventCollision\"\r\n [scrollableParent]=\"autoScroll ? document : null\"\r\n [gap]=\"gap\"\r\n [scrollSpeed]=\"4\"\r\n (dragStarted)=\"onDragStarted($event)\"\r\n (resizeStarted)=\"onResizeStarted($event)\"\r\n (dragEnded)=\"onDragEnded($event)\"\r\n (resizeEnded)=\"onResizeEnded($event)\"\r\n (layoutUpdated)=\"onLayoutUpdated($event)\"\r\n >\r\n <buw-grid-item\r\n *ngFor=\"let item of layout; trackBy: trackById; let i = index\"\r\n [id]=\"item.id\"\r\n [transition]=\"currentTransition\"\r\n [dragStartThreshold]=\"dragStartThreshold\"\r\n [draggable]=\"!disableDrag\"\r\n [resizable]=\"!disableResize\"\r\n >\r\n <div class=\"grid-item-content\">\r\n @if(moDataList?.length && moDataList[i]?.EjrayOlgo ){\r\n <bnrc-dynamic-item-component [component]=\"moDataList[i].EjrayOlgo\"> </bnrc-dynamic-item-component>\r\n }\r\n \r\n </div>\r\n <div\r\n class=\"grid-item-remove-handle\"\r\n *ngIf=\"!disableRemove\"\r\n (mousedown)=\"stopEventPropagation($event)\"\r\n (click)=\"removeItem(item.id)\"\r\n ></div>\r\n <ng-template *ngIf=\"currentPlaceholder !== 'Default'\" buwGridItemPlaceholder>\r\n <div\r\n *ngIf=\"currentPlaceholder === 'Custom 1'\"\r\n class=\"grid-item-content custom-placeholder custom-placeholder-1\"\r\n >\r\n {{ item.id }}\r\n </div>\r\n <div\r\n *ngIf=\"currentPlaceholder === 'Custom 2'\"\r\n class=\"grid-item-content custom-placeholder custom-placeholder-2\"\r\n >\r\n {{ item.id }}\r\n </div>\r\n <div\r\n *ngIf=\"currentPlaceholder === 'Custom 3'\"\r\n class=\"grid-item-content custom-placeholder custom-placeholder-3\"\r\n >\r\n {{ item.id }}\r\n </div>\r\n </ng-template>\r\n </buw-grid-item>\r\n </buw-grid>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .grid-item-content{box-sizing:border-box;background:#ccc;border:1px solid;width:100%;height:100%;-webkit-user-select:none;user-select:none;display:flex;align-items:center;justify-content:center}:host ::ng-deep .grid-item-remove-handle{position:absolute;cursor:pointer;display:flex;justify-content:center;width:20px;height:20px;top:0;right:0}:host ::ng-deep .grid-item-remove-handle:after{content:\"x\";color:#121212;font-size:16px;font-weight:300;font-family:Arial,sans-serif}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1$2.DynamicItemComponent, selector: "bnrc-dynamic-item-component", inputs: ["mo", "allColumns", "moDataList", "columns", "column", "index", "last", "hideOpenIcon", "deviceName", "deviceSize", "rtl", "editMode", "setting", "parameters", "contextMenuItems", "canView", "showRowNumber", "rowNumber", "formSetting", "conditionalFormats", "disableOverflowContextMenu", "navigationArrow", "isCheckList", "fields", "isChecked", "layout94$", "inlineEditMode", "isNewInlineMo", "allowInlineEdit", "typeDefId", "rowIndicator", "rowIndicatorColor", "UlvMainCtrlr"] }, { kind: "directive", type: i1$2.FillEmptySpaceDirective, selector: "[fillEmptySpace]", inputs: ["containerDom", "decrement", "disable", "height", "dontUseTopBound", "setMinHeight"], exportAs: ["fillEmptySpace"] }, { kind: "component", type: KtdGridComponent, selector: "buw-grid", inputs: ["scrollableParent", "compactOnPropsChange", "preventCollision", "scrollSpeed", "compactType", "rowHeight", "cols", "layout", "gap", "height", "backgroundConfig"], outputs: ["layoutUpdated", "dragStarted", "resizeStarted", "dragEnded", "resizeEnded", "gridItemResize"] }, { kind: "component", type: KtdGridItemComponent, selector: "buw-grid-item", inputs: ["minW", "minH", "maxW", "maxH", "transition", "id", "dragStartThreshold", "draggable", "resizable"] }, { kind: "directive", type: KtdGridItemPlaceholder, selector: "ng-template[buwGridItemPlaceholder]", inputs: ["data"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2347
|
+
}
|
|
2348
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LayoutContainerComponent, decorators: [{
|
|
2349
|
+
type: Component,
|
|
2350
|
+
args: [{ selector: 'buw-layout-container', providers: [], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div style=\"position: relative\" dir=\"ltr\" fillEmptySpace [setMinHeight]=\"true\">\r\n <buw-grid\r\n [cols]=\"cols\"\r\n [backgroundConfig]=\"gridBackgroundConfig\"\r\n [height]=\"rowHeightFit && gridHeight ? gridHeight : null\"\r\n [rowHeight]=\"rowHeightFit ? 'fit' : rowHeight\"\r\n [layout]=\"layout\"\r\n [compactType]=\"compactType\"\r\n [preventCollision]=\"preventCollision\"\r\n [scrollableParent]=\"autoScroll ? document : null\"\r\n [gap]=\"gap\"\r\n [scrollSpeed]=\"4\"\r\n (dragStarted)=\"onDragStarted($event)\"\r\n (resizeStarted)=\"onResizeStarted($event)\"\r\n (dragEnded)=\"onDragEnded($event)\"\r\n (resizeEnded)=\"onResizeEnded($event)\"\r\n (layoutUpdated)=\"onLayoutUpdated($event)\"\r\n >\r\n <buw-grid-item\r\n *ngFor=\"let item of layout; trackBy: trackById; let i = index\"\r\n [id]=\"item.id\"\r\n [transition]=\"currentTransition\"\r\n [dragStartThreshold]=\"dragStartThreshold\"\r\n [draggable]=\"!disableDrag\"\r\n [resizable]=\"!disableResize\"\r\n >\r\n <div class=\"grid-item-content\">\r\n @if(moDataList?.length && moDataList[i]?.EjrayOlgo ){\r\n <bnrc-dynamic-item-component [component]=\"moDataList[i].EjrayOlgo\"> </bnrc-dynamic-item-component>\r\n }\r\n \r\n </div>\r\n <div\r\n class=\"grid-item-remove-handle\"\r\n *ngIf=\"!disableRemove\"\r\n (mousedown)=\"stopEventPropagation($event)\"\r\n (click)=\"removeItem(item.id)\"\r\n ></div>\r\n <ng-template *ngIf=\"currentPlaceholder !== 'Default'\" buwGridItemPlaceholder>\r\n <div\r\n *ngIf=\"currentPlaceholder === 'Custom 1'\"\r\n class=\"grid-item-content custom-placeholder custom-placeholder-1\"\r\n >\r\n {{ item.id }}\r\n </div>\r\n <div\r\n *ngIf=\"currentPlaceholder === 'Custom 2'\"\r\n class=\"grid-item-content custom-placeholder custom-placeholder-2\"\r\n >\r\n {{ item.id }}\r\n </div>\r\n <div\r\n *ngIf=\"currentPlaceholder === 'Custom 3'\"\r\n class=\"grid-item-content custom-placeholder custom-placeholder-3\"\r\n >\r\n {{ item.id }}\r\n </div>\r\n </ng-template>\r\n </buw-grid-item>\r\n </buw-grid>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .grid-item-content{box-sizing:border-box;background:#ccc;border:1px solid;width:100%;height:100%;-webkit-user-select:none;user-select:none;display:flex;align-items:center;justify-content:center}:host ::ng-deep .grid-item-remove-handle{position:absolute;cursor:pointer;display:flex;justify-content:center;width:20px;height:20px;top:0;right:0}:host ::ng-deep .grid-item-remove-handle:after{content:\"x\";color:#121212;font-size:16px;font-weight:300;font-family:Arial,sans-serif}\n"] }]
|
|
2351
|
+
}], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.ElementRef }, { type: Document, decorators: [{
|
|
2352
|
+
type: Inject,
|
|
2353
|
+
args: [DOCUMENT]
|
|
2354
|
+
}] }], propDecorators: { grid: [{
|
|
2355
|
+
type: ViewChild,
|
|
2356
|
+
args: [KtdGridComponent, { static: true }]
|
|
2357
|
+
}], layout: [{
|
|
2358
|
+
type: Input
|
|
2359
|
+
}], moDataList: [{
|
|
2360
|
+
type: Input
|
|
2361
|
+
}] } });
|
|
2362
|
+
|
|
2363
|
+
class LayoutGridMapperPipe {
|
|
2364
|
+
transform(molist) {
|
|
2365
|
+
const x = molist.map((c) => ({
|
|
2366
|
+
id: c.Id,
|
|
2367
|
+
x: +c.x,
|
|
2368
|
+
y: +c.y,
|
|
2369
|
+
w: +c.w,
|
|
2370
|
+
h: +c.h,
|
|
2371
|
+
minW: c.minW,
|
|
2372
|
+
minH: c.minH,
|
|
2373
|
+
maxW: c.maxW,
|
|
2374
|
+
maxH: c.maxH,
|
|
2375
|
+
ejrayOlgo: c.EjrayOlgo
|
|
2376
|
+
}));
|
|
2377
|
+
return x;
|
|
2378
|
+
}
|
|
2379
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LayoutGridMapperPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2380
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: LayoutGridMapperPipe, name: "layoutGridMapper" }); }
|
|
2381
|
+
}
|
|
2382
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LayoutGridMapperPipe, decorators: [{
|
|
2383
|
+
type: Pipe,
|
|
2384
|
+
args: [{
|
|
2385
|
+
name: 'layoutGridMapper',
|
|
2386
|
+
standalone: false
|
|
2387
|
+
}]
|
|
2388
|
+
}] });
|
|
2389
|
+
|
|
2390
|
+
class ReportGridLayoutComponent extends ReportViewBaseComponent {
|
|
2391
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ReportGridLayoutComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2392
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ReportGridLayoutComponent, selector: "buw-report-grid-layout", providers: [], usesInheritance: true, ngImport: i0, template: "<div style=\"position: relative\" dir=\"ltr\" fillEmptySpace [setMinHeight]=\"true\">\r\n <buw-layout-container [moDataList]=\"moDataList\" [layout]=\"moDataList | layoutGridMapper\"> </buw-layout-container>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .grid-item-content{box-sizing:border-box;background:#ccc;border:1px solid;width:100%;height:100%;-webkit-user-select:none;user-select:none;display:flex;align-items:center;justify-content:center}:host ::ng-deep .grid-item-remove-handle{position:absolute;cursor:pointer;display:flex;justify-content:center;width:20px;height:20px;top:0;right:0}:host ::ng-deep .grid-item-remove-handle:after{content:\"x\";color:#121212;font-size:16px;font-weight:300;font-family:Arial,sans-serif}\n"], dependencies: [{ kind: "directive", type: i1$2.FillEmptySpaceDirective, selector: "[fillEmptySpace]", inputs: ["containerDom", "decrement", "disable", "height", "dontUseTopBound", "setMinHeight"], exportAs: ["fillEmptySpace"] }, { kind: "component", type: LayoutContainerComponent, selector: "buw-layout-container", inputs: ["layout", "moDataList"] }, { kind: "pipe", type: LayoutGridMapperPipe, name: "layoutGridMapper" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2393
|
+
}
|
|
2394
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ReportGridLayoutComponent, decorators: [{
|
|
2395
|
+
type: Component,
|
|
2396
|
+
args: [{ selector: 'buw-report-grid-layout', providers: [], changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div style=\"position: relative\" dir=\"ltr\" fillEmptySpace [setMinHeight]=\"true\">\r\n <buw-layout-container [moDataList]=\"moDataList\" [layout]=\"moDataList | layoutGridMapper\"> </buw-layout-container>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .grid-item-content{box-sizing:border-box;background:#ccc;border:1px solid;width:100%;height:100%;-webkit-user-select:none;user-select:none;display:flex;align-items:center;justify-content:center}:host ::ng-deep .grid-item-remove-handle{position:absolute;cursor:pointer;display:flex;justify-content:center;width:20px;height:20px;top:0;right:0}:host ::ng-deep .grid-item-remove-handle:after{content:\"x\";color:#121212;font-size:16px;font-weight:300;font-family:Arial,sans-serif}\n"] }]
|
|
2397
|
+
}] });
|
|
2398
|
+
|
|
2399
|
+
const components = [
|
|
2400
|
+
KtdGridComponent,
|
|
2401
|
+
KtdGridItemComponent,
|
|
2402
|
+
NavContainerComponent,
|
|
2403
|
+
LayoutContainerComponent,
|
|
2404
|
+
ReportGridLayoutComponent
|
|
2405
|
+
];
|
|
2406
|
+
const pipes = [LayoutGridMapperPipe];
|
|
2407
|
+
const directives = [KtdGridDragHandle, KtdGridResizeHandle, KtdGridItemPlaceholder];
|
|
2408
|
+
class BarsaUserWorkspaceModule extends BaseModule {
|
|
2409
|
+
constructor(dcm, componentFactoryResolver) {
|
|
2410
|
+
super(dcm, componentFactoryResolver, 'BarsaUserWorkspaceModule');
|
|
2411
|
+
this.dcm = dcm;
|
|
2412
|
+
this.componentFactoryResolver = componentFactoryResolver;
|
|
2413
|
+
this.dynamicComponents = [...components];
|
|
2414
|
+
}
|
|
2415
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BarsaUserWorkspaceModule, deps: [{ token: i1$2.DynamicComponentService }, { token: i0.ComponentFactoryResolver }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2416
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: BarsaUserWorkspaceModule, declarations: [KtdGridComponent,
|
|
2417
|
+
KtdGridItemComponent,
|
|
2418
|
+
NavContainerComponent,
|
|
2419
|
+
LayoutContainerComponent,
|
|
2420
|
+
ReportGridLayoutComponent, KtdGridDragHandle, KtdGridResizeHandle, KtdGridItemPlaceholder, LayoutGridMapperPipe], imports: [CommonModule,
|
|
2421
|
+
FormsModule,
|
|
2422
|
+
RouterModule,
|
|
2423
|
+
DragDropModule,
|
|
2424
|
+
CdkTableModule,
|
|
2425
|
+
FundamentalNgxCoreModule,
|
|
2426
|
+
FundamentalNgxPlatformModule,
|
|
2427
|
+
BarsaNovinRayCoreModule], exports: [KtdGridComponent,
|
|
2428
|
+
KtdGridItemComponent,
|
|
2429
|
+
NavContainerComponent,
|
|
2430
|
+
LayoutContainerComponent,
|
|
2431
|
+
ReportGridLayoutComponent, KtdGridDragHandle, KtdGridResizeHandle, KtdGridItemPlaceholder] }); }
|
|
2432
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BarsaUserWorkspaceModule, providers: [KtdGridService, ...pipes], imports: [CommonModule,
|
|
2433
|
+
FormsModule,
|
|
2434
|
+
RouterModule,
|
|
2435
|
+
DragDropModule,
|
|
2436
|
+
CdkTableModule,
|
|
2437
|
+
FundamentalNgxCoreModule,
|
|
2438
|
+
FundamentalNgxPlatformModule,
|
|
2439
|
+
BarsaNovinRayCoreModule] }); }
|
|
2440
|
+
}
|
|
2441
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BarsaUserWorkspaceModule, decorators: [{
|
|
2442
|
+
type: NgModule,
|
|
2443
|
+
args: [{
|
|
2444
|
+
imports: [
|
|
2445
|
+
CommonModule,
|
|
2446
|
+
FormsModule,
|
|
2447
|
+
RouterModule,
|
|
2448
|
+
DragDropModule,
|
|
2449
|
+
CdkTableModule,
|
|
2450
|
+
FundamentalNgxCoreModule,
|
|
2451
|
+
FundamentalNgxPlatformModule,
|
|
2452
|
+
BarsaNovinRayCoreModule
|
|
2453
|
+
],
|
|
2454
|
+
declarations: [...components, ...directives, ...pipes],
|
|
2455
|
+
providers: [KtdGridService, ...pipes],
|
|
2456
|
+
exports: [...components, ...directives]
|
|
2457
|
+
}]
|
|
2458
|
+
}], ctorParameters: () => [{ type: i1$2.DynamicComponentService }, { type: i0.ComponentFactoryResolver }] });
|
|
2459
|
+
|
|
2460
|
+
/*
|
|
2461
|
+
* Public API Surface of grid
|
|
2462
|
+
*/
|
|
2463
|
+
|
|
2464
|
+
/**
|
|
2465
|
+
* Generated bundle index. Do not edit.
|
|
2466
|
+
*/
|
|
2467
|
+
|
|
2468
|
+
export { BarsaUserWorkspaceModule, GRID_ITEM_GET_RENDER_DATA_TOKEN, KTD_GRID_DRAG_HANDLE, KTD_GRID_ITEM_PLACEHOLDER, KTD_GRID_RESIZE_HANDLE, KtdGridComponent, KtdGridDragHandle, KtdGridItemComponent, KtdGridItemPlaceholder, KtdGridResizeHandle, LayoutContainerComponent, NavContainerComponent, ReportGridLayoutComponent, __gridItemGetRenderDataFactoryFunc, ktdGridCompact, ktdGridItemGetRenderDataFactoryFunc, ktdTrackById, parseRenderItemToPixels };
|
|
2469
|
+
//# sourceMappingURL=barsa-user-workspace.mjs.map
|