ngx-kit 9.0.1 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/drag-resize/README.md +42 -5
- package/fesm2022/ngx-kit-drag-resize.mjs +868 -201
- package/fesm2022/ngx-kit-drag-resize.mjs.map +1 -1
- package/fesm2022/ngx-kit-image-viewer.mjs +2 -23
- package/fesm2022/ngx-kit-image-viewer.mjs.map +1 -1
- package/fesm2022/ngx-kit-media-control.mjs +2 -2
- package/fesm2022/ngx-kit-media-control.mjs.map +1 -1
- package/fesm2022/ngx-kit-notify.mjs +2 -2
- package/fesm2022/ngx-kit-side-nav.mjs +2 -0
- package/fesm2022/ngx-kit-side-nav.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-kit-drag-resize.d.ts +99 -9
- package/types/ngx-kit-image-viewer.d.ts +0 -21
- package/types/ngx-kit-side-nav.d.ts +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { signal, EventEmitter, Injectable, InjectionToken, inject, ApplicationRef, Directive, model, input, output, Renderer2, effect, ContentChild, Input, HostListener, Output, ElementRef } from '@angular/core';
|
|
3
|
-
import { DOCUMENT } from '@angular/common';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Boundary clamping utilities.
|
|
@@ -70,17 +69,117 @@ function clampResizeWithinBoundary(boundaryRect, startRect, width, height, left,
|
|
|
70
69
|
return { width: clampedWidth, height: clampedHeight, left: clampedLeft, top: clampedTop };
|
|
71
70
|
}
|
|
72
71
|
/** Current page zoom level (1 = 100%). Falls back to devicePixelRatio pre visualViewport. */
|
|
73
|
-
function getPageZoom() {
|
|
74
|
-
|
|
72
|
+
function getPageZoom(element) {
|
|
73
|
+
const win = element?.ownerDocument.defaultView ?? (typeof window !== 'undefined' ? window : null);
|
|
74
|
+
return win?.visualViewport?.scale ?? win?.devicePixelRatio ?? 1;
|
|
75
75
|
}
|
|
76
76
|
/** Adjusts a raw pointer-derived value for the current page zoom level. */
|
|
77
|
-
function adjustForZoom(value) {
|
|
78
|
-
const zoom = getPageZoom();
|
|
77
|
+
function adjustForZoom(value, element) {
|
|
78
|
+
const zoom = getPageZoom(element);
|
|
79
79
|
return zoom ? value / zoom : value;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Base (LTR) physical direction for every logical direction. */
|
|
83
|
+
const LTR_BASE = {
|
|
84
|
+
start: 'w',
|
|
85
|
+
end: 'e',
|
|
86
|
+
'n-start': 'nw',
|
|
87
|
+
'n-end': 'ne',
|
|
88
|
+
's-start': 'sw',
|
|
89
|
+
's-end': 'se',
|
|
90
|
+
};
|
|
91
|
+
/** Mirrors the horizontal component of a physical direction. 'n' / 's' pass through unchanged. */
|
|
92
|
+
const MIRROR = {
|
|
93
|
+
n: 'n',
|
|
94
|
+
s: 's',
|
|
95
|
+
e: 'w',
|
|
96
|
+
w: 'e',
|
|
97
|
+
ne: 'nw',
|
|
98
|
+
nw: 'ne',
|
|
99
|
+
se: 'sw',
|
|
100
|
+
sw: 'se',
|
|
101
|
+
};
|
|
102
|
+
function isLogicalDirection(dir) {
|
|
103
|
+
return Object.prototype.hasOwnProperty.call(LTR_BASE, dir);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* True when `element` sits inside a right-to-left context.
|
|
107
|
+
*
|
|
108
|
+
* Reads the *computed* `direction` style instead of `element.dir`, because
|
|
109
|
+
* `direction` is inherited and is almost never set on the resizable element
|
|
110
|
+
* itself - it's usually set on `<html>`, `<body>`, or some ancestor wrapper.
|
|
111
|
+
*/
|
|
112
|
+
function isRtl(element) {
|
|
113
|
+
const win = element.ownerDocument.defaultView;
|
|
114
|
+
return win ? win.getComputedStyle(element).direction === 'rtl' : false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolves a logical direction ('start' | 'end' | 'n-start' | 'n-end' | 's-start' | 's-end')
|
|
118
|
+
* to a physical, viewport-space direction ('n' | 'e' | 's' | 'w' | 'ne' | 'nw' | 'se' | 'sw')
|
|
119
|
+
* based on `rtl`.
|
|
120
|
+
*
|
|
121
|
+
* Physical directions pass through untouched, so it's always safe to call this
|
|
122
|
+
* even when `dir` might already be physical (e.g. resolving a direction that was
|
|
123
|
+
* already resolved once before).
|
|
124
|
+
*/
|
|
125
|
+
function resolveLogicalDirection(dir, rtl) {
|
|
126
|
+
if (!isLogicalDirection(dir)) {
|
|
127
|
+
return dir;
|
|
128
|
+
}
|
|
129
|
+
const base = LTR_BASE[dir];
|
|
130
|
+
return rtl ? MIRROR[base] : base;
|
|
131
|
+
}
|
|
132
|
+
|
|
82
133
|
/** Above any app chrome (modals, sticky headers) but below the browser's own UI. */
|
|
83
134
|
const PREVIEW_Z_INDEX = '2147483000';
|
|
135
|
+
/**
|
|
136
|
+
* The clone lives in <body>, so rules that depend on its ORIGINAL ancestors (`.palette .tile`,
|
|
137
|
+
* `.board { font: ... }`, `[dir=rtl] .x`) no longer match it. These are the properties that make
|
|
138
|
+
* the difference between "looks like the block I grabbed" and "looks like an unstyled <div>":
|
|
139
|
+
* the inherited text properties plus the root box's own visual properties. Only the ROOT is
|
|
140
|
+
* handled (one `getComputedStyle` at drag start); use `*ngxDragPreview` for a bespoke preview.
|
|
141
|
+
*/
|
|
142
|
+
const PREVIEW_STYLE_PROPS = [
|
|
143
|
+
// inherited
|
|
144
|
+
'direction',
|
|
145
|
+
'color',
|
|
146
|
+
'font-family',
|
|
147
|
+
'font-size',
|
|
148
|
+
'font-weight',
|
|
149
|
+
'font-style',
|
|
150
|
+
'line-height',
|
|
151
|
+
'letter-spacing',
|
|
152
|
+
'text-align',
|
|
153
|
+
'text-transform',
|
|
154
|
+
'white-space',
|
|
155
|
+
// the root box
|
|
156
|
+
'background-color',
|
|
157
|
+
'background-image',
|
|
158
|
+
'background-size',
|
|
159
|
+
'background-position',
|
|
160
|
+
'background-repeat',
|
|
161
|
+
'border-top',
|
|
162
|
+
'border-right',
|
|
163
|
+
'border-bottom',
|
|
164
|
+
'border-left',
|
|
165
|
+
'border-radius',
|
|
166
|
+
'box-shadow',
|
|
167
|
+
'padding-top',
|
|
168
|
+
'padding-right',
|
|
169
|
+
'padding-bottom',
|
|
170
|
+
'padding-left',
|
|
171
|
+
'opacity',
|
|
172
|
+
];
|
|
173
|
+
function copyVisualStyles(source, target) {
|
|
174
|
+
const cs = source.ownerDocument.defaultView?.getComputedStyle(source);
|
|
175
|
+
if (!cs)
|
|
176
|
+
return;
|
|
177
|
+
for (const prop of PREVIEW_STYLE_PROPS) {
|
|
178
|
+
const value = cs.getPropertyValue(prop);
|
|
179
|
+
if (value)
|
|
180
|
+
target.style.setProperty(prop, value);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
84
183
|
class DragPreviewRef {
|
|
85
184
|
tpl;
|
|
86
185
|
/** Set by `ngxDragPreview` so the embedded view can join Angular's change-detection tree. */
|
|
@@ -112,6 +211,7 @@ class DragPreviewRef {
|
|
|
112
211
|
// Avoid duplicated ids in the document.
|
|
113
212
|
this.element.removeAttribute('id');
|
|
114
213
|
this.element.querySelectorAll('[id]').forEach((n) => n.removeAttribute('id'));
|
|
214
|
+
copyVisualStyles(dragEl, this.element);
|
|
115
215
|
this.element.classList.add('ngx-drag-preview');
|
|
116
216
|
this.element.setAttribute('aria-hidden', 'true');
|
|
117
217
|
this.element.setAttribute('inert', '');
|
|
@@ -135,9 +235,10 @@ class DragPreviewRef {
|
|
|
135
235
|
willChange: 'transform',
|
|
136
236
|
zIndex: PREVIEW_Z_INDEX,
|
|
137
237
|
});
|
|
238
|
+
const win = doc.defaultView;
|
|
138
239
|
if (isRtl(doc.body)) {
|
|
139
240
|
Object.assign(this.element.style, {
|
|
140
|
-
right: `${
|
|
241
|
+
right: `${(win?.innerWidth ?? rect.right) - rect.right}px`,
|
|
141
242
|
left: 'auto',
|
|
142
243
|
});
|
|
143
244
|
}
|
|
@@ -147,7 +248,7 @@ class DragPreviewRef {
|
|
|
147
248
|
right: 'auto',
|
|
148
249
|
});
|
|
149
250
|
}
|
|
150
|
-
doc.body.appendChild(this.element);
|
|
251
|
+
(doc.body ?? doc.documentElement).appendChild(this.element);
|
|
151
252
|
return this.element;
|
|
152
253
|
}
|
|
153
254
|
detach() {
|
|
@@ -161,6 +262,174 @@ class DragPreviewRef {
|
|
|
161
262
|
}
|
|
162
263
|
}
|
|
163
264
|
|
|
265
|
+
function viewportDeltaToLocal(element, delta, includeElementTransform = true) {
|
|
266
|
+
const inverse = invertLinear(getLocalToViewportMatrix(element, includeElementTransform));
|
|
267
|
+
return applyLinear(inverse, delta);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Inverse of a 2D linear transform, or `null` when it is not invertible (zero-scale ancestor).
|
|
271
|
+
* Exposed so hot paths (every pointer move) can compute the matrix ONCE per drag instead of
|
|
272
|
+
* walking the ancestor chain with `getComputedStyle` on every event.
|
|
273
|
+
*/
|
|
274
|
+
function invertLinear(matrix) {
|
|
275
|
+
const determinant = matrix.a * matrix.d - matrix.b * matrix.c;
|
|
276
|
+
if (!Number.isFinite(determinant) || Math.abs(determinant) < 1e-8)
|
|
277
|
+
return null;
|
|
278
|
+
return {
|
|
279
|
+
a: matrix.d / determinant,
|
|
280
|
+
b: -matrix.b / determinant,
|
|
281
|
+
c: -matrix.c / determinant,
|
|
282
|
+
d: matrix.a / determinant,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** Applies a linear transform to a vector. A `null` matrix (see `invertLinear`) yields `{0,0}`. */
|
|
286
|
+
function applyLinear(matrix, delta) {
|
|
287
|
+
if (!matrix)
|
|
288
|
+
return { x: 0, y: 0 };
|
|
289
|
+
return {
|
|
290
|
+
x: matrix.a * delta.x + matrix.c * delta.y,
|
|
291
|
+
y: matrix.b * delta.x + matrix.d * delta.y,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Converts a SIZE measured in the viewport pixels of `fromDocument` into the viewport pixels of
|
|
296
|
+
* `toDocument` (they differ when the iframe element is scaled, e.g. a zoomed-out device preview).
|
|
297
|
+
* Returns `null` when the documents are unrelated / cross-origin.
|
|
298
|
+
*/
|
|
299
|
+
function convertSizeBetweenDocuments(size, fromDocument, toDocument) {
|
|
300
|
+
if (fromDocument === toDocument)
|
|
301
|
+
return { ...size };
|
|
302
|
+
const a = convertPointBetweenDocuments({ x: 0, y: 0 }, fromDocument, toDocument);
|
|
303
|
+
const b = convertPointBetweenDocuments({ x: size.x, y: size.y }, fromDocument, toDocument);
|
|
304
|
+
if (!a || !b)
|
|
305
|
+
return null;
|
|
306
|
+
return { x: b.x - a.x, y: b.y - a.y };
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Axis scale (always positive) between `element`'s local CSS pixels and viewport pixels,
|
|
310
|
+
* i.e. how many screen pixels one CSS pixel inside `element` occupies. Rotation is ignored.
|
|
311
|
+
*/
|
|
312
|
+
function getLocalScale(element) {
|
|
313
|
+
const m = getLocalToViewportMatrix(element);
|
|
314
|
+
return { x: Math.hypot(m.a, m.b) || 1, y: Math.hypot(m.c, m.d) || 1 };
|
|
315
|
+
}
|
|
316
|
+
function localDeltaToViewport(element, delta) {
|
|
317
|
+
const matrix = getLocalToViewportMatrix(element);
|
|
318
|
+
return {
|
|
319
|
+
x: matrix.a * delta.x + matrix.c * delta.y,
|
|
320
|
+
y: matrix.b * delta.x + matrix.d * delta.y,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
/** Returns the composed 2D linear transform from element-local to viewport space. */
|
|
324
|
+
function getLocalToViewportMatrix(element, includeElementTransform = true) {
|
|
325
|
+
const win = element.ownerDocument.defaultView;
|
|
326
|
+
if (!win || typeof win.DOMMatrix !== 'function') {
|
|
327
|
+
return { a: 1, b: 0, c: 0, d: 1 };
|
|
328
|
+
}
|
|
329
|
+
let result = { a: 1, b: 0, c: 0, d: 1 };
|
|
330
|
+
let current = includeElementTransform ? element : element.parentElement;
|
|
331
|
+
while (current) {
|
|
332
|
+
const style = win.getComputedStyle(current);
|
|
333
|
+
const transform = style.transform;
|
|
334
|
+
if (transform && transform !== 'none') {
|
|
335
|
+
try {
|
|
336
|
+
const matrix = new win.DOMMatrix(transform);
|
|
337
|
+
result = multiplyLinear({ a: matrix.a, b: matrix.b, c: matrix.c, d: matrix.d }, result);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Ignore unsupported/malformed transforms and continue with remaining ancestors.
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// CSS `zoom` is not represented consistently in computed `transform` across browsers.
|
|
344
|
+
const zoom = Number.parseFloat(style.getPropertyValue('zoom'));
|
|
345
|
+
if (Number.isFinite(zoom) && zoom > 0 && zoom !== 1) {
|
|
346
|
+
result = multiplyLinear({ a: zoom, b: 0, c: 0, d: zoom }, result);
|
|
347
|
+
}
|
|
348
|
+
current = current.parentElement;
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function multiplyLinear(left, right) {
|
|
353
|
+
return {
|
|
354
|
+
a: left.a * right.a + left.c * right.b,
|
|
355
|
+
b: left.b * right.a + left.d * right.b,
|
|
356
|
+
c: left.a * right.c + left.c * right.d,
|
|
357
|
+
d: left.b * right.c + left.d * right.d,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Converts a client-space point from one same-origin Document viewport to another.
|
|
362
|
+
* The conversion walks both frameElement chains through their common top-level document.
|
|
363
|
+
* Cross-origin frame boundaries cannot be inspected and return null.
|
|
364
|
+
*/
|
|
365
|
+
function convertPointBetweenDocuments(point, fromDocument, toDocument) {
|
|
366
|
+
if (fromDocument === toDocument)
|
|
367
|
+
return { ...point };
|
|
368
|
+
const toTop = (doc, value) => {
|
|
369
|
+
let current = doc;
|
|
370
|
+
let result = { ...value };
|
|
371
|
+
const visited = new Set();
|
|
372
|
+
while (current.defaultView?.frameElement) {
|
|
373
|
+
if (visited.has(current))
|
|
374
|
+
return null;
|
|
375
|
+
visited.add(current);
|
|
376
|
+
let frame;
|
|
377
|
+
try {
|
|
378
|
+
frame = current.defaultView.frameElement;
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const iframe = frame;
|
|
384
|
+
const rect = iframe.getBoundingClientRect();
|
|
385
|
+
const scaleX = iframe.offsetWidth ? rect.width / iframe.offsetWidth : 1;
|
|
386
|
+
const scaleY = iframe.offsetHeight ? rect.height / iframe.offsetHeight : 1;
|
|
387
|
+
result = {
|
|
388
|
+
x: rect.left + iframe.clientLeft * scaleX + result.x * scaleX,
|
|
389
|
+
y: rect.top + iframe.clientTop * scaleY + result.y * scaleY,
|
|
390
|
+
};
|
|
391
|
+
current = iframe.ownerDocument;
|
|
392
|
+
}
|
|
393
|
+
return { document: current, point: result };
|
|
394
|
+
};
|
|
395
|
+
const from = toTop(fromDocument, point);
|
|
396
|
+
if (!from)
|
|
397
|
+
return null;
|
|
398
|
+
const targetFrames = [];
|
|
399
|
+
let current = toDocument;
|
|
400
|
+
const visited = new Set();
|
|
401
|
+
while (current && current !== from.document) {
|
|
402
|
+
if (visited.has(current))
|
|
403
|
+
return null;
|
|
404
|
+
visited.add(current);
|
|
405
|
+
let frame;
|
|
406
|
+
try {
|
|
407
|
+
frame = current.defaultView?.frameElement ?? null;
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
if (!frame)
|
|
413
|
+
return null;
|
|
414
|
+
targetFrames.push(frame);
|
|
415
|
+
current = frame.ownerDocument;
|
|
416
|
+
}
|
|
417
|
+
if (current !== from.document)
|
|
418
|
+
return null;
|
|
419
|
+
let result = from.point;
|
|
420
|
+
for (let i = targetFrames.length - 1; i >= 0; i--) {
|
|
421
|
+
const iframe = targetFrames[i];
|
|
422
|
+
const rect = iframe.getBoundingClientRect();
|
|
423
|
+
const scaleX = iframe.offsetWidth ? rect.width / iframe.offsetWidth : 1;
|
|
424
|
+
const scaleY = iframe.offsetHeight ? rect.height / iframe.offsetHeight : 1;
|
|
425
|
+
result = {
|
|
426
|
+
x: (result.x - rect.left) / scaleX - iframe.clientLeft,
|
|
427
|
+
y: (result.y - rect.top) / scaleY - iframe.clientTop,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
|
|
164
433
|
let zIndexCounter = 1000;
|
|
165
434
|
class DragRef {
|
|
166
435
|
data;
|
|
@@ -197,6 +466,8 @@ class DragRef {
|
|
|
197
466
|
sourceDisplay = '';
|
|
198
467
|
sourcePointerEvents = '';
|
|
199
468
|
sourceTransition = '';
|
|
469
|
+
sourceWillChange = '';
|
|
470
|
+
sourceTransform = '';
|
|
200
471
|
/** True while a drop-list drag (body preview + placeholder) is in progress. Free drag: always false. */
|
|
201
472
|
listDrag = false;
|
|
202
473
|
cachedLists = [];
|
|
@@ -211,8 +482,18 @@ class DragRef {
|
|
|
211
482
|
lastEffective = { x: 0, y: 0 };
|
|
212
483
|
scrollListener;
|
|
213
484
|
resizeListener;
|
|
485
|
+
/**
|
|
486
|
+
* Every document that hosts a candidate list (the source document, plus any same-origin iframe
|
|
487
|
+
* such as a page-builder canvas) needs its own scroll/resize listeners: `scroll` does not cross
|
|
488
|
+
* the iframe boundary, so scrolling the canvas never reaches a listener on the parent page.
|
|
489
|
+
*/
|
|
490
|
+
documentListeners = new Map();
|
|
491
|
+
/** viewport → local matrix of the dragged element, computed once per free drag. */
|
|
492
|
+
freeDragInverse;
|
|
214
493
|
init() {
|
|
215
|
-
this.previousTransform =
|
|
494
|
+
this.previousTransform =
|
|
495
|
+
this.el.ownerDocument.defaultView?.getComputedStyle(this.el).transform ?? '';
|
|
496
|
+
this.sourceTransform = this.el.style.transform;
|
|
216
497
|
}
|
|
217
498
|
withDropList(list) {
|
|
218
499
|
if (this.dropList === list)
|
|
@@ -237,18 +518,22 @@ class DragRef {
|
|
|
237
518
|
this.moveDx = 0;
|
|
238
519
|
this.moveDy = 0;
|
|
239
520
|
this.scrollCompensation = { x: 0, y: 0 };
|
|
521
|
+
this.freeDragInverse = undefined;
|
|
240
522
|
// Only draggables that are direct children of their drop list take part in sorting.
|
|
241
523
|
// Everything else is a plain free drag.
|
|
242
524
|
const list = this.dropList && this.dropList.el === this.el.parentElement ? this.dropList : null;
|
|
243
525
|
this.originDropList = list;
|
|
244
526
|
this.originIndex = list?.getItemIndex(this) ?? -1;
|
|
245
|
-
|
|
527
|
+
const win = this.el.ownerDocument.defaultView;
|
|
528
|
+
this.previousTransform = win?.getComputedStyle(this.el).transform ?? '';
|
|
529
|
+
this.sourceTransform = this.el.style.transform;
|
|
246
530
|
this.isDragging.set(true);
|
|
247
531
|
this.previousZIndex = this.el.style.zIndex;
|
|
248
532
|
this.sourceVisibility = this.el.style.visibility;
|
|
249
533
|
this.sourceDisplay = this.el.style.display;
|
|
250
534
|
this.sourcePointerEvents = this.el.style.pointerEvents;
|
|
251
535
|
this.sourceTransition = this.el.style.transition;
|
|
536
|
+
this.sourceWillChange = this.el.style.willChange;
|
|
252
537
|
this.el.classList.add('ngx-draggable--dragging');
|
|
253
538
|
this.el.style.willChange = 'transform';
|
|
254
539
|
this.el.style.transition = 'none';
|
|
@@ -334,7 +619,8 @@ class DragRef {
|
|
|
334
619
|
this.el.style.visibility = this.sourceVisibility;
|
|
335
620
|
this.el.style.display = this.sourceDisplay;
|
|
336
621
|
this.el.style.pointerEvents = this.sourcePointerEvents;
|
|
337
|
-
this.el.style.willChange =
|
|
622
|
+
this.el.style.willChange = this.sourceWillChange;
|
|
623
|
+
this.el.style.transition = this.sourceTransition;
|
|
338
624
|
this.el.style.zIndex = this.previousZIndex;
|
|
339
625
|
}
|
|
340
626
|
cancelDrag() {
|
|
@@ -350,7 +636,7 @@ class DragRef {
|
|
|
350
636
|
return;
|
|
351
637
|
}
|
|
352
638
|
// ---- free drag: unchanged ----
|
|
353
|
-
this.el.style.transform = this.
|
|
639
|
+
this.el.style.transform = this.sourceTransform;
|
|
354
640
|
this.el.style.visibility = this.sourceVisibility;
|
|
355
641
|
this.el.style.pointerEvents = this.sourcePointerEvents;
|
|
356
642
|
this.scrollCompensation = { x: 0, y: 0 };
|
|
@@ -358,10 +644,24 @@ class DragRef {
|
|
|
358
644
|
}
|
|
359
645
|
/** Abort without emitting anything (the directive was destroyed mid-drag). Idempotent. */
|
|
360
646
|
dispose() {
|
|
361
|
-
if (!this.
|
|
647
|
+
if (!this.isDragging())
|
|
362
648
|
return;
|
|
363
649
|
this.isDragging.set(false);
|
|
364
|
-
this.
|
|
650
|
+
this.position.set({ x: 0, y: 0 });
|
|
651
|
+
if (this.listDrag) {
|
|
652
|
+
this.finishListDrag(false);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
this.el.style.transform = this.sourceTransform;
|
|
656
|
+
this.el.style.visibility = this.sourceVisibility;
|
|
657
|
+
this.el.style.display = this.sourceDisplay;
|
|
658
|
+
this.el.style.pointerEvents = this.sourcePointerEvents;
|
|
659
|
+
this.el.style.transition = this.sourceTransition;
|
|
660
|
+
this.el.style.willChange = this.sourceWillChange;
|
|
661
|
+
this.el.style.zIndex = this.previousZIndex;
|
|
662
|
+
this.el.classList.remove('ngx-draggable--dragging');
|
|
663
|
+
this.scrollCompensation = { x: 0, y: 0 };
|
|
664
|
+
this.position.set({ x: 0, y: 0 });
|
|
365
665
|
}
|
|
366
666
|
/** True while a drop-list drag is running. */
|
|
367
667
|
get isListDrag() {
|
|
@@ -410,14 +710,21 @@ class DragRef {
|
|
|
410
710
|
: '';
|
|
411
711
|
const tx = this.moveDx + this.scrollCompensation.x;
|
|
412
712
|
const ty = this.moveDy + this.scrollCompensation.y;
|
|
413
|
-
this.
|
|
713
|
+
if (this.freeDragInverse === undefined) {
|
|
714
|
+
// Includes the element's own (pre-drag) transform: the translate is appended after it.
|
|
715
|
+
this.freeDragInverse = invertLinear(getLocalToViewportMatrix(this.el, true));
|
|
716
|
+
}
|
|
717
|
+
const local = applyLinear(this.freeDragInverse, { x: tx, y: ty });
|
|
718
|
+
this.el.style.transform = `${base}translate3d(${local.x}px, ${local.y}px, 0)`;
|
|
414
719
|
}
|
|
415
720
|
// -------------------------------------------------------------------------------------
|
|
416
721
|
// drop-list drag
|
|
417
722
|
// -------------------------------------------------------------------------------------
|
|
418
723
|
beginListDrag(origin, pointer) {
|
|
419
724
|
const doc = this.el.ownerDocument;
|
|
420
|
-
const win = doc.defaultView
|
|
725
|
+
const win = doc.defaultView;
|
|
726
|
+
if (!win)
|
|
727
|
+
return;
|
|
421
728
|
this.dropListsSnapshot = null; // force refreshDropCandidates() to do a full pass below
|
|
422
729
|
this.refreshDropCandidates(origin);
|
|
423
730
|
this.lastEffective = { ...pointer };
|
|
@@ -436,9 +743,40 @@ class DragRef {
|
|
|
436
743
|
l._refresh();
|
|
437
744
|
this.updateDropTarget(this.lastEffective);
|
|
438
745
|
};
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
746
|
+
this.syncDocumentListeners();
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Keeps scroll/resize listeners attached to every document that currently hosts a candidate
|
|
750
|
+
* list. Capture: `scroll` does not bubble, and any ancestor (or the page) may be scrolling.
|
|
751
|
+
*/
|
|
752
|
+
syncDocumentListeners() {
|
|
753
|
+
if (!this.scrollListener || !this.resizeListener)
|
|
754
|
+
return;
|
|
755
|
+
const scroll = this.scrollListener;
|
|
756
|
+
const resize = this.resizeListener;
|
|
757
|
+
const wanted = new Set([this.el.ownerDocument]);
|
|
758
|
+
for (const list of this.cachedLists) {
|
|
759
|
+
const doc = list.el?.ownerDocument;
|
|
760
|
+
if (doc)
|
|
761
|
+
wanted.add(doc);
|
|
762
|
+
}
|
|
763
|
+
for (const [doc, remove] of this.documentListeners) {
|
|
764
|
+
if (!wanted.has(doc)) {
|
|
765
|
+
remove();
|
|
766
|
+
this.documentListeners.delete(doc);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
for (const doc of wanted) {
|
|
770
|
+
const win = doc.defaultView;
|
|
771
|
+
if (!win || this.documentListeners.has(doc))
|
|
772
|
+
continue;
|
|
773
|
+
doc.addEventListener('scroll', scroll, { capture: true, passive: true });
|
|
774
|
+
win.addEventListener('resize', resize, { passive: true });
|
|
775
|
+
this.documentListeners.set(doc, () => {
|
|
776
|
+
doc.removeEventListener('scroll', scroll, { capture: true });
|
|
777
|
+
win.removeEventListener('resize', resize);
|
|
778
|
+
});
|
|
779
|
+
}
|
|
442
780
|
}
|
|
443
781
|
/**
|
|
444
782
|
* Recomputes which registered lists are valid targets for this drag — connected to the
|
|
@@ -467,6 +805,7 @@ class DragRef {
|
|
|
467
805
|
}
|
|
468
806
|
this.dropCandidates = next;
|
|
469
807
|
this.cachedLists = next.map((c) => c.list);
|
|
808
|
+
this.syncDocumentListeners();
|
|
470
809
|
}
|
|
471
810
|
/**
|
|
472
811
|
* Decide which list is under the pointer, switch if needed, then re-sort in the active one.
|
|
@@ -484,9 +823,11 @@ class DragRef {
|
|
|
484
823
|
if (!origin)
|
|
485
824
|
return;
|
|
486
825
|
this.refreshDropCandidates(origin);
|
|
487
|
-
const
|
|
826
|
+
const resolved = this.resolveDropList(pointer);
|
|
827
|
+
const next = resolved?.list ?? null;
|
|
828
|
+
const targetPointer = resolved?.pointer ?? pointer;
|
|
488
829
|
if (next === this.activeDropList) {
|
|
489
|
-
next?.sortItem(this,
|
|
830
|
+
next?.sortItem(this, targetPointer);
|
|
490
831
|
return;
|
|
491
832
|
}
|
|
492
833
|
const prev = this.activeDropList;
|
|
@@ -501,7 +842,7 @@ class DragRef {
|
|
|
501
842
|
origin.exit(this); // truly outside the origin's territory now: reset it (idempotent)
|
|
502
843
|
}
|
|
503
844
|
this.activeDropList = next;
|
|
504
|
-
next?.enter(this,
|
|
845
|
+
next?.enter(this, targetPointer.x, targetPointer.y); // coordinates belong to the target document
|
|
505
846
|
}
|
|
506
847
|
/**
|
|
507
848
|
* Innermost connected list under the pointer, from the precomputed candidates: deeper always
|
|
@@ -513,15 +854,22 @@ class DragRef {
|
|
|
513
854
|
let best = null;
|
|
514
855
|
let bestDepth = -1;
|
|
515
856
|
for (const { list, depth } of this.dropCandidates) {
|
|
516
|
-
|
|
857
|
+
const targetDocument = list.el?.ownerDocument;
|
|
858
|
+
if (!targetDocument)
|
|
859
|
+
continue;
|
|
860
|
+
// Pointer coordinates originate in the draggable's document. A same-origin iframe has
|
|
861
|
+
// its own viewport coordinate space, so convert before testing its DropList geometry.
|
|
862
|
+
const targetPoint = convertPointBetweenDocuments(pointer, this.el.ownerDocument, targetDocument);
|
|
863
|
+
if (!targetPoint || !list._containsPoint(targetPoint.x, targetPoint.y))
|
|
517
864
|
continue;
|
|
518
865
|
if (!best || depth > bestDepth) {
|
|
519
|
-
best = list;
|
|
866
|
+
best = { list, pointer: targetPoint };
|
|
520
867
|
bestDepth = depth;
|
|
521
868
|
}
|
|
522
|
-
else if (depth === bestDepth && best !== this.activeDropList) {
|
|
523
|
-
if (list === this.activeDropList || list._area() < best._area())
|
|
524
|
-
best = list;
|
|
869
|
+
else if (depth === bestDepth && best.list !== this.activeDropList) {
|
|
870
|
+
if (list === this.activeDropList || list._area() < best.list._area()) {
|
|
871
|
+
best = { list, pointer: targetPoint };
|
|
872
|
+
}
|
|
525
873
|
}
|
|
526
874
|
}
|
|
527
875
|
return best;
|
|
@@ -532,11 +880,9 @@ class DragRef {
|
|
|
532
880
|
* regardless of how the drag ended. Only then is the drop event emitted.
|
|
533
881
|
*/
|
|
534
882
|
finishListDrag(commit) {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (this.resizeListener)
|
|
539
|
-
(doc.defaultView ?? window).removeEventListener('resize', this.resizeListener);
|
|
883
|
+
for (const remove of this.documentListeners.values())
|
|
884
|
+
remove();
|
|
885
|
+
this.documentListeners.clear();
|
|
540
886
|
this.scrollListener = this.resizeListener = undefined;
|
|
541
887
|
const target = commit ? this.activeDropList : null;
|
|
542
888
|
const event = target?._finish(this, true) ?? null;
|
|
@@ -556,7 +902,7 @@ class DragRef {
|
|
|
556
902
|
this.el.style.display = this.sourceDisplay;
|
|
557
903
|
this.el.style.pointerEvents = this.sourcePointerEvents;
|
|
558
904
|
this.el.style.transition = this.sourceTransition;
|
|
559
|
-
this.el.style.willChange =
|
|
905
|
+
this.el.style.willChange = this.sourceWillChange;
|
|
560
906
|
this.el.style.zIndex = this.previousZIndex;
|
|
561
907
|
if (event && target)
|
|
562
908
|
target.onDrop.emit(event);
|
|
@@ -564,6 +910,7 @@ class DragRef {
|
|
|
564
910
|
updatePreview() {
|
|
565
911
|
if (!this.preview || !this.preview.element)
|
|
566
912
|
return;
|
|
913
|
+
// Preview is fixed in the source document's viewport coordinate space.
|
|
567
914
|
this.preview.element.style.transform = `translate3d(${this.moveDx}px, ${this.moveDy}px, 0)`;
|
|
568
915
|
}
|
|
569
916
|
}
|
|
@@ -586,7 +933,7 @@ class ElementHelper {
|
|
|
586
933
|
return false;
|
|
587
934
|
}
|
|
588
935
|
static findParentBySelector(elm, selector) {
|
|
589
|
-
var all =
|
|
936
|
+
var all = elm.ownerDocument.querySelectorAll(selector);
|
|
590
937
|
var cur = elm.parentNode;
|
|
591
938
|
while (cur && !ElementHelper.collectionHas(all, cur)) {
|
|
592
939
|
//keep going up until you find a match
|
|
@@ -615,15 +962,17 @@ function getFirstLevelDraggables(container) {
|
|
|
615
962
|
return firstLevel;
|
|
616
963
|
}
|
|
617
964
|
function isElementNode(node) {
|
|
618
|
-
return node?.nodeType === Node.ELEMENT_NODE;
|
|
965
|
+
return node?.nodeType === Node.ELEMENT_NODE; // 1
|
|
619
966
|
}
|
|
620
967
|
function isDocumentNode(node) {
|
|
621
|
-
return node?.nodeType === Node.DOCUMENT_NODE;
|
|
968
|
+
return node?.nodeType === Node.DOCUMENT_NODE; // 9
|
|
622
969
|
}
|
|
623
970
|
function isShadowRootNode(node) {
|
|
624
971
|
return node?.nodeType === Node.DOCUMENT_FRAGMENT_NODE && 'host' in node;
|
|
625
972
|
}
|
|
626
973
|
function getOwnerWindow(node) {
|
|
974
|
+
if (isDocumentNode(node))
|
|
975
|
+
return node.defaultView;
|
|
627
976
|
return node?.ownerDocument?.defaultView ?? null;
|
|
628
977
|
}
|
|
629
978
|
function isWindow(value) {
|
|
@@ -950,7 +1299,7 @@ class ScrollFrame {
|
|
|
950
1299
|
* @param start first element to inspect (walks up through its ancestors)
|
|
951
1300
|
* @param includeStart whether `start` itself counts (true for a list whose own scroll moves its items)
|
|
952
1301
|
*/
|
|
953
|
-
constructor(start, includeStart = true, win =
|
|
1302
|
+
constructor(start, includeStart = true, win = start?.ownerDocument.defaultView ?? null) {
|
|
954
1303
|
this.win = win;
|
|
955
1304
|
let node = includeStart ? start : (start?.parentElement ?? null);
|
|
956
1305
|
const root = start?.ownerDocument.documentElement;
|
|
@@ -960,7 +1309,8 @@ class ScrollFrame {
|
|
|
960
1309
|
this.targets.push(node);
|
|
961
1310
|
node = node.parentElement;
|
|
962
1311
|
}
|
|
963
|
-
|
|
1312
|
+
if (win)
|
|
1313
|
+
this.targets.push(win);
|
|
964
1314
|
this.origin = this.read();
|
|
965
1315
|
}
|
|
966
1316
|
/** Call when any scroll event fires. */
|
|
@@ -980,23 +1330,26 @@ class ScrollFrame {
|
|
|
980
1330
|
let x = 0;
|
|
981
1331
|
let y = 0;
|
|
982
1332
|
for (const t of this.targets) {
|
|
983
|
-
if (t === this.win) {
|
|
1333
|
+
if (this.win && t === this.win) {
|
|
984
1334
|
x += this.win.scrollX;
|
|
985
1335
|
y += this.win.scrollY;
|
|
986
1336
|
}
|
|
987
1337
|
else {
|
|
988
|
-
|
|
989
|
-
|
|
1338
|
+
const el = t;
|
|
1339
|
+
// Element scroll offsets are local CSS pixels; geometry snapshots are viewport pixels.
|
|
1340
|
+
const delta = localDeltaToViewport(el, { x: el.scrollLeft, y: el.scrollTop });
|
|
1341
|
+
x += delta.x;
|
|
1342
|
+
y += delta.y;
|
|
990
1343
|
}
|
|
991
1344
|
}
|
|
992
1345
|
return { x, y };
|
|
993
1346
|
}
|
|
994
1347
|
}
|
|
995
1348
|
function scrolls(el) {
|
|
996
|
-
const s = getComputedStyle
|
|
1349
|
+
const s = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
997
1350
|
// `hidden` containers still scroll programmatically; `clip` never does.
|
|
998
1351
|
const scrollish = (v) => v === 'auto' || v === 'scroll' || v === 'overlay' || v === 'hidden';
|
|
999
|
-
return scrollish(s.overflowX) || scrollish(s.overflowY);
|
|
1352
|
+
return !!s && (scrollish(s.overflowX) || scrollish(s.overflowY));
|
|
1000
1353
|
}
|
|
1001
1354
|
|
|
1002
1355
|
/**
|
|
@@ -1007,7 +1360,7 @@ function scrolls(el) {
|
|
|
1007
1360
|
*/
|
|
1008
1361
|
function makeSpacer(container) {
|
|
1009
1362
|
for (const n of Array.from(container.childNodes)) {
|
|
1010
|
-
if (n.nodeType ===
|
|
1363
|
+
if (n.nodeType === 3 && n.nodeValue && /^\s+$/.test(n.nodeValue)) {
|
|
1011
1364
|
return container.ownerDocument.createTextNode(n.nodeValue);
|
|
1012
1365
|
}
|
|
1013
1366
|
}
|
|
@@ -1058,6 +1411,13 @@ class SortSession {
|
|
|
1058
1411
|
/** undefined = not created yet, null = this layout has no whitespace separators. */
|
|
1059
1412
|
spacer;
|
|
1060
1413
|
onOffsetChange;
|
|
1414
|
+
/**
|
|
1415
|
+
* viewport → local-CSS-pixel matrix of the container's coordinate space. Every slot is a direct
|
|
1416
|
+
* child of the container, so ONE matrix serves all of them. It is computed lazily and kept for
|
|
1417
|
+
* the whole session: walking the ancestor chain with `getComputedStyle` on every displacement
|
|
1418
|
+
* would put style reads on the hot path (see the "zero style reads per move" test).
|
|
1419
|
+
*/
|
|
1420
|
+
inverse;
|
|
1061
1421
|
constructor(init) {
|
|
1062
1422
|
this.spacer = init.spacer;
|
|
1063
1423
|
this.onOffsetChange = init.onOffsetChange;
|
|
@@ -1130,6 +1490,7 @@ class SortSession {
|
|
|
1130
1490
|
return;
|
|
1131
1491
|
const target = this.targetIdx;
|
|
1132
1492
|
this.clearDisplacement();
|
|
1493
|
+
this.inverse = undefined; // the viewport may have changed the ancestors' transforms
|
|
1133
1494
|
this.snapshot();
|
|
1134
1495
|
this.frame = new ScrollFrame(this.container, true);
|
|
1135
1496
|
if (this.mode === 'transform') {
|
|
@@ -1215,6 +1576,7 @@ class SortSession {
|
|
|
1215
1576
|
const horizontal = this.model.axis === 'x';
|
|
1216
1577
|
this.entries.forEach((e, i) => {
|
|
1217
1578
|
const o = offsets[i];
|
|
1579
|
+
// `o` is derived from DOMRect measurements (viewport CSS pixels).
|
|
1218
1580
|
this.setOffset(e, horizontal ? { x: o, y: 0 } : { x: 0, y: o });
|
|
1219
1581
|
});
|
|
1220
1582
|
}
|
|
@@ -1228,10 +1590,17 @@ class SortSession {
|
|
|
1228
1590
|
}
|
|
1229
1591
|
else {
|
|
1230
1592
|
this.applied.set(e.el, off);
|
|
1231
|
-
|
|
1593
|
+
const local = this.toLocal(off);
|
|
1594
|
+
e.el.style.transform = `translate3d(${local.x}px, ${local.y}px, 0)${e.base}`;
|
|
1232
1595
|
}
|
|
1233
1596
|
this.onOffsetChange?.(e.el, off);
|
|
1234
1597
|
}
|
|
1598
|
+
toLocal(delta) {
|
|
1599
|
+
if (this.inverse === undefined) {
|
|
1600
|
+
this.inverse = invertLinear(getLocalToViewportMatrix(this.container, true));
|
|
1601
|
+
}
|
|
1602
|
+
return applyLinear(this.inverse, delta);
|
|
1603
|
+
}
|
|
1235
1604
|
// ---------------------------------------------------------------------------------------
|
|
1236
1605
|
// flow strategy (single placeholder move + FLIP transforms)
|
|
1237
1606
|
// ---------------------------------------------------------------------------------------
|
|
@@ -1253,7 +1622,8 @@ class SortSession {
|
|
|
1253
1622
|
const dx = first[i].left - last[i].left;
|
|
1254
1623
|
const dy = first[i].top - last[i].top;
|
|
1255
1624
|
if (this.animationMs > 0 && (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5)) {
|
|
1256
|
-
|
|
1625
|
+
const local = this.toLocal({ x: dx, y: dy });
|
|
1626
|
+
e.el.style.transform = `translate3d(${local.x}px, ${local.y}px, 0)${e.base}`;
|
|
1257
1627
|
moved.push(e);
|
|
1258
1628
|
}
|
|
1259
1629
|
});
|
|
@@ -1298,63 +1668,10 @@ class SortSession {
|
|
|
1298
1668
|
}
|
|
1299
1669
|
}
|
|
1300
1670
|
function initialTransform(el) {
|
|
1301
|
-
const t = getComputedStyle
|
|
1671
|
+
const t = el.ownerDocument.defaultView?.getComputedStyle(el).transform;
|
|
1302
1672
|
return t && t !== 'none' ? ` ${t}` : '';
|
|
1303
1673
|
}
|
|
1304
1674
|
|
|
1305
|
-
/** Base (LTR) physical direction for every logical direction. */
|
|
1306
|
-
const LTR_BASE = {
|
|
1307
|
-
start: 'w',
|
|
1308
|
-
end: 'e',
|
|
1309
|
-
'n-start': 'nw',
|
|
1310
|
-
'n-end': 'ne',
|
|
1311
|
-
's-start': 'sw',
|
|
1312
|
-
's-end': 'se',
|
|
1313
|
-
};
|
|
1314
|
-
/** Mirrors the horizontal component of a physical direction. 'n' / 's' pass through unchanged. */
|
|
1315
|
-
const MIRROR = {
|
|
1316
|
-
n: 'n',
|
|
1317
|
-
s: 's',
|
|
1318
|
-
e: 'w',
|
|
1319
|
-
w: 'e',
|
|
1320
|
-
ne: 'nw',
|
|
1321
|
-
nw: 'ne',
|
|
1322
|
-
se: 'sw',
|
|
1323
|
-
sw: 'se',
|
|
1324
|
-
};
|
|
1325
|
-
function isLogicalDirection(dir) {
|
|
1326
|
-
return Object.prototype.hasOwnProperty.call(LTR_BASE, dir);
|
|
1327
|
-
}
|
|
1328
|
-
/**
|
|
1329
|
-
* True when `element` sits inside a right-to-left context.
|
|
1330
|
-
*
|
|
1331
|
-
* Reads the *computed* `direction` style instead of `element.dir`, because
|
|
1332
|
-
* `direction` is inherited and is almost never set on the resizable element
|
|
1333
|
-
* itself - it's usually set on `<html>`, `<body>`, or some ancestor wrapper.
|
|
1334
|
-
*/
|
|
1335
|
-
function isRtl(element) {
|
|
1336
|
-
if (typeof window === 'undefined') {
|
|
1337
|
-
return false;
|
|
1338
|
-
}
|
|
1339
|
-
return window.getComputedStyle(element).direction === 'rtl';
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Resolves a logical direction ('start' | 'end' | 'n-start' | 'n-end' | 's-start' | 's-end')
|
|
1343
|
-
* to a physical, viewport-space direction ('n' | 'e' | 's' | 'w' | 'ne' | 'nw' | 'se' | 'sw')
|
|
1344
|
-
* based on `rtl`.
|
|
1345
|
-
*
|
|
1346
|
-
* Physical directions pass through untouched, so it's always safe to call this
|
|
1347
|
-
* even when `dir` might already be physical (e.g. resolving a direction that was
|
|
1348
|
-
* already resolved once before).
|
|
1349
|
-
*/
|
|
1350
|
-
function resolveLogicalDirection(dir, rtl) {
|
|
1351
|
-
if (!isLogicalDirection(dir)) {
|
|
1352
|
-
return dir;
|
|
1353
|
-
}
|
|
1354
|
-
const base = LTR_BASE[dir];
|
|
1355
|
-
return rtl ? MIRROR[base] : base;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
1675
|
/**
|
|
1359
1676
|
* A drop list.
|
|
1360
1677
|
*
|
|
@@ -1375,6 +1692,14 @@ class DropListRef {
|
|
|
1375
1692
|
dropListGroup;
|
|
1376
1693
|
/** Duration (ms) of the sibling slide animation. `0` disables animation. */
|
|
1377
1694
|
sortAnimationDuration = 200;
|
|
1695
|
+
/**
|
|
1696
|
+
* How the auto-generated placeholder is sized.
|
|
1697
|
+
* - `'item'` (default): exactly the dragged item's footprint.
|
|
1698
|
+
* - `'stretch'`: only the MAIN axis follows the item; the cross axis fills the list. This is
|
|
1699
|
+
* what a page-builder canvas wants when a small palette tile is dragged into a full-width
|
|
1700
|
+
* column of blocks (the tile's own width means nothing there).
|
|
1701
|
+
*/
|
|
1702
|
+
placeholderFit = 'item';
|
|
1378
1703
|
_draggables = new Set();
|
|
1379
1704
|
onDrop = new EventEmitter();
|
|
1380
1705
|
/**
|
|
@@ -1836,14 +2161,35 @@ class DropListRef {
|
|
|
1836
2161
|
if (ph.custom)
|
|
1837
2162
|
return;
|
|
1838
2163
|
placeholder.style.display = drag.originalDisplay;
|
|
2164
|
+
// `rect` is in the VIEWPORT pixels of the dragged item's document. CSS sizes are local to the
|
|
2165
|
+
// list: convert across an (optionally scaled) iframe boundary first, then through the
|
|
2166
|
+
// list's own scaled/zoomed ancestors. Without this a placeholder inside a `scale(.5)` canvas
|
|
2167
|
+
// would be drawn at half the size it reserves.
|
|
2168
|
+
const inTarget = convertSizeBetweenDocuments({ x: rect.width, y: rect.height }, drag.el.ownerDocument, this.el.ownerDocument) ?? { x: rect.width, y: rect.height };
|
|
2169
|
+
const scale = getLocalScale(this.el);
|
|
2170
|
+
const width = Math.abs(inTarget.x) / scale.x;
|
|
2171
|
+
const height = Math.abs(inTarget.y) / scale.y;
|
|
1839
2172
|
// Occupies exactly the dragged item's footprint, so the gap reserved in the list — and the
|
|
1840
2173
|
// sibling-displacement math derived from it — matches the item's real size.
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2174
|
+
const axis = this.placeholderFit === 'stretch' ? fallbackAxis(this.el) : null;
|
|
2175
|
+
if (axis === 'y') {
|
|
2176
|
+
// vertical stack: the width is whatever the list gives its children
|
|
2177
|
+
set('width', 'auto');
|
|
2178
|
+
set('max-width', 'none');
|
|
2179
|
+
if (placeholder.style.display === 'inline' || placeholder.style.display === 'inline-block') {
|
|
2180
|
+
placeholder.style.display = 'block';
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
else {
|
|
2184
|
+
set('width', `${width}px`);
|
|
2185
|
+
}
|
|
2186
|
+
if (axis === 'x') {
|
|
2187
|
+
set('height', 'auto');
|
|
2188
|
+
set('max-height', 'none');
|
|
2189
|
+
}
|
|
2190
|
+
else {
|
|
2191
|
+
set('height', `${height}px`);
|
|
2192
|
+
}
|
|
1847
2193
|
set('visibility', 'visible');
|
|
1848
2194
|
set('opacity', '0.16');
|
|
1849
2195
|
set('pointer-events', 'none');
|
|
@@ -1853,13 +2199,14 @@ class DropListRef {
|
|
|
1853
2199
|
}
|
|
1854
2200
|
/** Orientation to assume while a list has fewer than two items to measure. */
|
|
1855
2201
|
function fallbackAxis(container) {
|
|
1856
|
-
const
|
|
1857
|
-
|
|
2202
|
+
const win = container.ownerDocument.defaultView;
|
|
2203
|
+
const s = win?.getComputedStyle(container);
|
|
2204
|
+
if (s?.display.includes('flex'))
|
|
1858
2205
|
return s.flexDirection.startsWith('column') ? 'y' : 'x';
|
|
1859
|
-
if (s
|
|
2206
|
+
if (s?.display.includes('grid'))
|
|
1860
2207
|
return 'y';
|
|
1861
2208
|
const child = container.firstElementChild;
|
|
1862
|
-
if (child && getComputedStyle(child).display.startsWith('inline'))
|
|
2209
|
+
if (child && win?.getComputedStyle(child).display.startsWith('inline'))
|
|
1863
2210
|
return 'x';
|
|
1864
2211
|
return 'y';
|
|
1865
2212
|
}
|
|
@@ -1870,8 +2217,28 @@ function measureHit(el) {
|
|
|
1870
2217
|
return null;
|
|
1871
2218
|
const clips = [];
|
|
1872
2219
|
const root = el.ownerDocument.documentElement;
|
|
2220
|
+
// The visible window of the document itself. Matters for a list inside an <iframe>: a tall
|
|
2221
|
+
// canvas extends far below the iframe's viewport, and a pointer that is already outside the
|
|
2222
|
+
// frame (toolbar, footer) must not count as "over the list".
|
|
2223
|
+
const ownerWin = el.ownerDocument.defaultView;
|
|
2224
|
+
if (ownerWin?.frameElement) {
|
|
2225
|
+
// In quirks mode (an iframe written without <!doctype>) `documentElement.clientHeight` is the
|
|
2226
|
+
// height of the CONTENT, and the viewport size lives on <body> instead.
|
|
2227
|
+
const sizer = el.ownerDocument.compatMode === 'CSS1Compat' ? root : (el.ownerDocument.body ?? root);
|
|
2228
|
+
clips.push({
|
|
2229
|
+
box: {
|
|
2230
|
+
left: 0,
|
|
2231
|
+
top: 0,
|
|
2232
|
+
right: sizer.clientWidth || ownerWin.innerWidth,
|
|
2233
|
+
bottom: sizer.clientHeight || ownerWin.innerHeight,
|
|
2234
|
+
},
|
|
2235
|
+
frame: new ScrollFrame(null, false, null), // the window clip never moves with scrolling
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
1873
2238
|
for (let node = el.parentElement; node && node !== root; node = node.parentElement) {
|
|
1874
|
-
const s = getComputedStyle(node);
|
|
2239
|
+
const s = node.ownerDocument.defaultView?.getComputedStyle(node);
|
|
2240
|
+
if (!s)
|
|
2241
|
+
continue;
|
|
1875
2242
|
const cx = s.overflowX !== 'visible';
|
|
1876
2243
|
const cy = s.overflowY !== 'visible';
|
|
1877
2244
|
if (!cx && !cy)
|
|
@@ -1957,6 +2324,8 @@ class DragDropService {
|
|
|
1957
2324
|
continue;
|
|
1958
2325
|
}
|
|
1959
2326
|
if (isDocumentNode(node)) {
|
|
2327
|
+
// Same-origin iframe: continue the logical DOM ancestry through its frame element.
|
|
2328
|
+
// This lets a DropList inside an iframe inherit an outer NgxDropListGroup.
|
|
1960
2329
|
const frame = node.defaultView?.frameElement;
|
|
1961
2330
|
if (frame) {
|
|
1962
2331
|
node = frame;
|
|
@@ -1985,15 +2354,13 @@ class DragDropService {
|
|
|
1985
2354
|
node = node.host;
|
|
1986
2355
|
continue;
|
|
1987
2356
|
}
|
|
1988
|
-
// iframe
|
|
1989
|
-
// 9 === DOCUMENT_NODE
|
|
2357
|
+
// Cross a same-origin iframe boundary so groups can connect lists in both documents.
|
|
1990
2358
|
if (node.nodeType === 9) {
|
|
1991
2359
|
const frame = node.defaultView?.frameElement;
|
|
1992
2360
|
if (frame) {
|
|
1993
2361
|
node = frame;
|
|
1994
2362
|
continue;
|
|
1995
2363
|
}
|
|
1996
|
-
// root document
|
|
1997
2364
|
break;
|
|
1998
2365
|
}
|
|
1999
2366
|
node = node.parentNode;
|
|
@@ -2030,12 +2397,16 @@ class DragDropService {
|
|
|
2030
2397
|
if (!source)
|
|
2031
2398
|
return null;
|
|
2032
2399
|
let best = null;
|
|
2400
|
+
const sourceDocument = dragEl?.ownerDocument ?? source.el?.ownerDocument;
|
|
2401
|
+
if (!sourceDocument)
|
|
2402
|
+
return null;
|
|
2033
2403
|
for (const list of this.dropLists()) {
|
|
2034
2404
|
if (!list.el || !source.isConnectedTo(list))
|
|
2035
2405
|
continue;
|
|
2036
2406
|
if (dragEl && dragEl !== list.el && dragEl.contains(list.el))
|
|
2037
2407
|
continue;
|
|
2038
|
-
|
|
2408
|
+
const targetPoint = convertPointBetweenDocuments(point, sourceDocument, list.el.ownerDocument);
|
|
2409
|
+
if (!targetPoint || !list._containsPoint(targetPoint.x, targetPoint.y))
|
|
2039
2410
|
continue;
|
|
2040
2411
|
if (!best)
|
|
2041
2412
|
best = list;
|
|
@@ -2065,7 +2436,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
2065
2436
|
*/
|
|
2066
2437
|
class AutoScroller {
|
|
2067
2438
|
raf = 0;
|
|
2068
|
-
container
|
|
2439
|
+
container;
|
|
2440
|
+
win;
|
|
2441
|
+
/** Document the pointer coordinates (`update(x, y)`) are expressed in. */
|
|
2442
|
+
pointerDoc;
|
|
2069
2443
|
speed = { x: 0, y: 0 };
|
|
2070
2444
|
onTick;
|
|
2071
2445
|
edge;
|
|
@@ -2076,6 +2450,8 @@ class AutoScroller {
|
|
|
2076
2450
|
}
|
|
2077
2451
|
/** onTick اجرا میشود بعد از هر گام اسکرول، تا با آخرین موقعیت pointer، sort/placeholder بهروز شود. */
|
|
2078
2452
|
start(fromEl, onTick) {
|
|
2453
|
+
this.win = fromEl.ownerDocument.defaultView ?? undefined;
|
|
2454
|
+
this.pointerDoc = fromEl.ownerDocument;
|
|
2079
2455
|
this.container = findScrollableAncestor(fromEl);
|
|
2080
2456
|
this.onTick = onTick;
|
|
2081
2457
|
}
|
|
@@ -2084,21 +2460,37 @@ class AutoScroller {
|
|
|
2084
2460
|
this.container = findScrollableAncestor(fromEl);
|
|
2085
2461
|
}
|
|
2086
2462
|
update(clientX, clientY) {
|
|
2463
|
+
// The scroll container may live in another document (a page-builder iframe). Its rect is in
|
|
2464
|
+
// THAT document's viewport pixels, so bring the pointer into the same space first.
|
|
2465
|
+
let x = clientX;
|
|
2466
|
+
let y = clientY;
|
|
2467
|
+
const containerDoc = documentOf(this.container);
|
|
2468
|
+
if (this.pointerDoc && containerDoc && containerDoc !== this.pointerDoc) {
|
|
2469
|
+
const p = convertPointBetweenDocuments({ x, y }, this.pointerDoc, containerDoc);
|
|
2470
|
+
if (!p) {
|
|
2471
|
+
this.speed = { x: 0, y: 0 };
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
x = p.x;
|
|
2475
|
+
y = p.y;
|
|
2476
|
+
}
|
|
2087
2477
|
const rect = getScrollRect(this.container);
|
|
2088
2478
|
this.speed = {
|
|
2089
|
-
x: edgeSpeed(
|
|
2090
|
-
y: edgeSpeed(
|
|
2479
|
+
x: edgeSpeed(x, rect.left, rect.right, this.edge, this.maxSpeed),
|
|
2480
|
+
y: edgeSpeed(y, rect.top, rect.bottom, this.edge, this.maxSpeed),
|
|
2091
2481
|
};
|
|
2092
2482
|
if ((this.speed.x || this.speed.y) && !this.raf) {
|
|
2093
|
-
this.raf = requestAnimationFrame(this.tick);
|
|
2483
|
+
this.raf = this.win?.requestAnimationFrame(this.tick) ?? 0;
|
|
2094
2484
|
}
|
|
2095
2485
|
}
|
|
2096
2486
|
stop() {
|
|
2097
2487
|
if (this.raf)
|
|
2098
|
-
cancelAnimationFrame(this.raf);
|
|
2488
|
+
this.win?.cancelAnimationFrame(this.raf);
|
|
2099
2489
|
this.raf = 0;
|
|
2100
2490
|
this.speed = { x: 0, y: 0 };
|
|
2101
2491
|
this.onTick = undefined;
|
|
2492
|
+
this.win = undefined;
|
|
2493
|
+
this.pointerDoc = undefined;
|
|
2102
2494
|
}
|
|
2103
2495
|
tick = () => {
|
|
2104
2496
|
if (!this.speed.x && !this.speed.y) {
|
|
@@ -2110,13 +2502,15 @@ class AutoScroller {
|
|
|
2110
2502
|
container.scrollBy(this.speed.x, this.speed.y);
|
|
2111
2503
|
}
|
|
2112
2504
|
else {
|
|
2113
|
-
|
|
2114
|
-
container
|
|
2505
|
+
// Edge speed is measured in viewport pixels; scrollLeft/Top are local to the scroller.
|
|
2506
|
+
const local = viewportDeltaToLocal(container, this.speed);
|
|
2507
|
+
container.scrollLeft += local.x;
|
|
2508
|
+
container.scrollTop += local.y;
|
|
2115
2509
|
}
|
|
2116
2510
|
// Pointer ثابت مانده ولی محتوا اسکرول شده؛
|
|
2117
2511
|
// بنابراین موقعیت placeholder/sort را دوباره محاسبه کن.
|
|
2118
2512
|
this.onTick?.();
|
|
2119
|
-
this.raf = requestAnimationFrame(this.tick);
|
|
2513
|
+
this.raf = this.win?.requestAnimationFrame(this.tick) ?? 0;
|
|
2120
2514
|
};
|
|
2121
2515
|
}
|
|
2122
2516
|
function edgeSpeed(pos, min, max, edge, maxSpeed) {
|
|
@@ -2128,25 +2522,47 @@ function edgeSpeed(pos, min, max, edge, maxSpeed) {
|
|
|
2128
2522
|
}
|
|
2129
2523
|
function getScrollRect(container) {
|
|
2130
2524
|
if (isWindow(container)) {
|
|
2131
|
-
return { left: 0, top: 0, right:
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2525
|
+
return { left: 0, top: 0, right: container.innerWidth, bottom: container.innerHeight };
|
|
2526
|
+
}
|
|
2527
|
+
// A scroller taller than the screen (or partly outside it) is only "edge-sensitive" at the
|
|
2528
|
+
// edges the user can actually see: clip its rect to the document viewport.
|
|
2529
|
+
const r = container.getBoundingClientRect();
|
|
2530
|
+
const win = container.ownerDocument.defaultView;
|
|
2531
|
+
if (!win)
|
|
2532
|
+
return r;
|
|
2533
|
+
return {
|
|
2534
|
+
left: Math.max(r.left, 0),
|
|
2535
|
+
top: Math.max(r.top, 0),
|
|
2536
|
+
right: Math.min(r.right, win.innerWidth),
|
|
2537
|
+
bottom: Math.min(r.bottom, win.innerHeight),
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
function documentOf(container) {
|
|
2541
|
+
return isWindow(container) ? container.document : container.ownerDocument;
|
|
2134
2542
|
}
|
|
2135
2543
|
function findScrollableAncestor(el) {
|
|
2136
2544
|
let node = el;
|
|
2137
2545
|
while (node) {
|
|
2138
|
-
const
|
|
2546
|
+
const win = node.ownerDocument.defaultView;
|
|
2547
|
+
if (!win)
|
|
2548
|
+
break;
|
|
2549
|
+
const style = win.getComputedStyle(node);
|
|
2139
2550
|
const scrollable = /(auto|scroll|overlay)/.test(style.overflowY + style.overflowX);
|
|
2140
|
-
if (scrollable && node.scrollHeight > node.clientHeight) {
|
|
2551
|
+
if (scrollable && (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth)) {
|
|
2552
|
+
// <html>/<body> scroll the whole document: their own rect spans the full (tall) page, so
|
|
2553
|
+
// edge detection must use the window's viewport instead.
|
|
2554
|
+
const doc = node.ownerDocument;
|
|
2555
|
+
if (node === doc.documentElement || node === doc.body)
|
|
2556
|
+
return win;
|
|
2141
2557
|
return node;
|
|
2142
2558
|
}
|
|
2143
2559
|
node = node.parentElement;
|
|
2144
2560
|
}
|
|
2145
|
-
return el.ownerDocument.defaultView ??
|
|
2561
|
+
return el.ownerDocument.defaultView ?? el;
|
|
2146
2562
|
}
|
|
2147
2563
|
function getScrollPosition(container) {
|
|
2148
2564
|
if (isWindow(container)) {
|
|
2149
|
-
return { left:
|
|
2565
|
+
return { left: container.scrollX, top: container.scrollY };
|
|
2150
2566
|
}
|
|
2151
2567
|
return { left: container.scrollLeft, top: container.scrollTop };
|
|
2152
2568
|
}
|
|
@@ -2212,6 +2628,19 @@ class NgxDraggable {
|
|
|
2212
2628
|
/** Auto-scroll the nearest scrollable ancestor while dragging near its edge. */
|
|
2213
2629
|
autoScroll = input(true, /* @ts-ignore */
|
|
2214
2630
|
...(ngDevMode ? [{ debugName: "autoScroll" }] : /* istanbul ignore next */ []));
|
|
2631
|
+
/**
|
|
2632
|
+
* Presses that start inside an element matching this selector never start a drag (the element
|
|
2633
|
+
* stays fully usable: text caret, clicks, selection). Handy for editable regions of a block.
|
|
2634
|
+
*/
|
|
2635
|
+
noDragSelector = input('[data-no-drag]', /* @ts-ignore */
|
|
2636
|
+
...(ngDevMode ? [{ debugName: "noDragSelector" }] : /* istanbul ignore next */ []));
|
|
2637
|
+
/**
|
|
2638
|
+
* When `true`, presses on native interactive controls (`button, a, input, textarea, select`,
|
|
2639
|
+
* `[contenteditable]`) do not start a drag either. Off by default so that a page-builder canvas
|
|
2640
|
+
* block can be grabbed by its own button/link, which is exactly what you want while editing.
|
|
2641
|
+
*/
|
|
2642
|
+
ignoreInteractive = input(false, /* @ts-ignore */
|
|
2643
|
+
...(ngDevMode ? [{ debugName: "ignoreInteractive" }] : /* istanbul ignore next */ []));
|
|
2215
2644
|
set data(v) {
|
|
2216
2645
|
this._ref.data = v;
|
|
2217
2646
|
}
|
|
@@ -2222,7 +2651,6 @@ class NgxDraggable {
|
|
|
2222
2651
|
_ref = new DragRef();
|
|
2223
2652
|
dragging = signal(false, /* @ts-ignore */
|
|
2224
2653
|
...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
2225
|
-
doc = inject(DOCUMENT);
|
|
2226
2654
|
service = inject(DragDropService);
|
|
2227
2655
|
scroller = new AutoScroller();
|
|
2228
2656
|
down = false;
|
|
@@ -2236,11 +2664,13 @@ class NgxDraggable {
|
|
|
2236
2664
|
removeEscape;
|
|
2237
2665
|
removeCancel;
|
|
2238
2666
|
removeLostCapture;
|
|
2239
|
-
scrollTarget
|
|
2667
|
+
scrollTarget;
|
|
2240
2668
|
lastScroll = { left: 0, top: 0 };
|
|
2241
2669
|
lastWindowScroll = { left: 0, top: 0 };
|
|
2242
2670
|
removeContainerScroll;
|
|
2243
2671
|
removeWindowScroll;
|
|
2672
|
+
removeDocKeydown;
|
|
2673
|
+
keyboardDrag = false;
|
|
2244
2674
|
renderer = inject(Renderer2);
|
|
2245
2675
|
constructor(host) {
|
|
2246
2676
|
this.host = host;
|
|
@@ -2251,6 +2681,8 @@ class NgxDraggable {
|
|
|
2251
2681
|
});
|
|
2252
2682
|
}
|
|
2253
2683
|
ngOnInit() {
|
|
2684
|
+
this.scrollTarget =
|
|
2685
|
+
this.host.nativeElement.ownerDocument.defaultView ?? this.host.nativeElement;
|
|
2254
2686
|
this._ref.el = this.dragRootElement()
|
|
2255
2687
|
? (this.host.nativeElement.closest(this.dragRootElement()) ?? this.host.nativeElement)
|
|
2256
2688
|
: this.host.nativeElement;
|
|
@@ -2282,6 +2714,7 @@ class NgxDraggable {
|
|
|
2282
2714
|
this.removeEscape?.();
|
|
2283
2715
|
this.removeCancel?.();
|
|
2284
2716
|
this.removeLostCapture?.();
|
|
2717
|
+
this.removeDocKeydown?.();
|
|
2285
2718
|
this.scroller.stop();
|
|
2286
2719
|
this.stopScrollTracking();
|
|
2287
2720
|
// Destroyed mid-drag (e.g. the model changed underneath us): never leave a body clone behind.
|
|
@@ -2292,7 +2725,7 @@ class NgxDraggable {
|
|
|
2292
2725
|
pointerDown(e) {
|
|
2293
2726
|
if (this.disabled() ||
|
|
2294
2727
|
e.button !== 0 ||
|
|
2295
|
-
|
|
2728
|
+
this.isExcluded(e.target) ||
|
|
2296
2729
|
!this.isOnHandle(e.target))
|
|
2297
2730
|
return;
|
|
2298
2731
|
// Nested draggables: pointerdown bubbles, so without this an ancestor draggable would start
|
|
@@ -2302,20 +2735,33 @@ class NgxDraggable {
|
|
|
2302
2735
|
claimedPointerEvents.add(e);
|
|
2303
2736
|
e.preventDefault();
|
|
2304
2737
|
this.down = true;
|
|
2738
|
+
this.keyboardDrag = false;
|
|
2305
2739
|
this.pointerId = e.pointerId;
|
|
2306
|
-
this
|
|
2740
|
+
// Pointer capture keeps move/up events flowing to this document even while the pointer is
|
|
2741
|
+
// over an <iframe> (the page-builder canvas). It can throw (pointer already gone, synthetic
|
|
2742
|
+
// events from automation tools) — in that case we simply fall back to plain document events.
|
|
2743
|
+
try {
|
|
2744
|
+
this._ref.el.setPointerCapture(this.pointerId);
|
|
2745
|
+
}
|
|
2746
|
+
catch {
|
|
2747
|
+
/* not capturable: document level listeners below still work inside this document */
|
|
2748
|
+
}
|
|
2307
2749
|
this.start = { x: e.clientX, y: e.clientY };
|
|
2308
2750
|
this._ref.boundary = this.boundary();
|
|
2309
2751
|
this._ref.lockAxis = this.lockAxis();
|
|
2310
2752
|
this._ref.pointerDown(this.start);
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2753
|
+
const doc = this._ref.el.ownerDocument;
|
|
2754
|
+
// Listen on the DOCUMENT (captured events bubble up to it): this keeps working when the
|
|
2755
|
+
// dragged element is re-rendered / detached by the framework mid-drag, and it is the same
|
|
2756
|
+
// for real captured events and for events dispatched by test tooling.
|
|
2757
|
+
this.removeMove = this.renderer.listen(doc, 'pointermove', (ev) => this.pointerMove(ev));
|
|
2758
|
+
this.removeUp = this.renderer.listen(doc, 'pointerup', (ev) => this.pointerUp(ev));
|
|
2759
|
+
this.removeEscape = this.renderer.listen(doc, 'keydown', (ev) => {
|
|
2314
2760
|
if (ev.key === 'Escape' && this.dragging())
|
|
2315
2761
|
this.cancel();
|
|
2316
2762
|
});
|
|
2317
|
-
this.removeCancel = this.renderer.listen(
|
|
2318
|
-
this.removeLostCapture = this.renderer.listen(
|
|
2763
|
+
this.removeCancel = this.renderer.listen(doc, 'pointercancel', (ev) => this.pointerCancel(ev));
|
|
2764
|
+
this.removeLostCapture = this.renderer.listen(doc, 'lostpointercapture', (ev) => this.pointerCancel(ev));
|
|
2319
2765
|
}
|
|
2320
2766
|
applyDragUpdate(p) {
|
|
2321
2767
|
const before = this._ref.activeDropList;
|
|
@@ -2326,32 +2772,39 @@ class NgxDraggable {
|
|
|
2326
2772
|
const after = this._ref.activeDropList;
|
|
2327
2773
|
// Follow the hovered list; outside every list, scroll the page.
|
|
2328
2774
|
if (after !== before)
|
|
2329
|
-
this.scroller.retarget(after?.el ?? this.
|
|
2775
|
+
this.scroller.retarget(after?.el ?? this._ref.el.ownerDocument.body);
|
|
2330
2776
|
}
|
|
2331
2777
|
this.dragMove.emit(p);
|
|
2332
2778
|
}
|
|
2333
2779
|
/** فراخوانی میشود دقیقاً وقتی drag واقعاً شروع میشود (چه با موس، چه با کیبورد). */
|
|
2334
2780
|
startScrollTracking() {
|
|
2781
|
+
const win = this._ref.el.ownerDocument.defaultView;
|
|
2335
2782
|
this.scrollTarget = findScrollableAncestor(this._ref.el);
|
|
2336
2783
|
this.lastScroll = getScrollPosition(this.scrollTarget);
|
|
2337
|
-
this.lastWindowScroll = { left:
|
|
2338
|
-
if (this.scrollTarget !==
|
|
2784
|
+
this.lastWindowScroll = { left: win?.scrollX ?? 0, top: win?.scrollY ?? 0 };
|
|
2785
|
+
if (win && this.scrollTarget !== win) {
|
|
2339
2786
|
this.removeContainerScroll = this.renderer.listen(this.scrollTarget, 'scroll', () => this.handleContainerScroll());
|
|
2340
2787
|
}
|
|
2341
2788
|
// همیشه window رو هم جدا گوش میدیم؛ چون ممکنه container محلی نداشته باشیم
|
|
2342
2789
|
// ولی خودِ صفحه اسکرول بشه.
|
|
2343
|
-
|
|
2790
|
+
if (win) {
|
|
2791
|
+
this.removeWindowScroll = this.renderer.listen(win, 'scroll', () => this.handleWindowScroll());
|
|
2792
|
+
}
|
|
2344
2793
|
}
|
|
2345
2794
|
handleContainerScroll() {
|
|
2346
2795
|
const pos = getScrollPosition(this.scrollTarget);
|
|
2347
2796
|
const dx = pos.left - this.lastScroll.left;
|
|
2348
2797
|
const dy = pos.top - this.lastScroll.top;
|
|
2349
2798
|
this.lastScroll = pos;
|
|
2350
|
-
this.
|
|
2799
|
+
const viewportDelta = localDeltaToViewport(this.scrollTarget, { x: dx, y: dy });
|
|
2800
|
+
this._ref.notifyScroll(viewportDelta.x, viewportDelta.y);
|
|
2351
2801
|
}
|
|
2352
2802
|
handleWindowScroll() {
|
|
2353
|
-
const
|
|
2354
|
-
|
|
2803
|
+
const win = this._ref.el.ownerDocument.defaultView;
|
|
2804
|
+
if (!win)
|
|
2805
|
+
return;
|
|
2806
|
+
const left = win.scrollX;
|
|
2807
|
+
const top = win.scrollY;
|
|
2355
2808
|
const dx = left - this.lastWindowScroll.left;
|
|
2356
2809
|
const dy = top - this.lastWindowScroll.top;
|
|
2357
2810
|
this.lastWindowScroll = { left, top };
|
|
@@ -2387,20 +2840,21 @@ class NgxDraggable {
|
|
|
2387
2840
|
pointerUp(e) {
|
|
2388
2841
|
if (!this.down || e.pointerId !== this.pointerId)
|
|
2389
2842
|
return;
|
|
2390
|
-
|
|
2391
|
-
this._ref.el.releasePointerCapture(this.pointerId);
|
|
2392
|
-
}
|
|
2843
|
+
this.releaseCapture();
|
|
2393
2844
|
if (this.dragging()) {
|
|
2394
2845
|
// Also when released OUTSIDE every list: endDrag() rolls the drag back and cleans up.
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
this._ref.dragMove(p);
|
|
2401
|
-
this._ref.endDrag();
|
|
2846
|
+
// The release position is authoritative for list sorting (a fast flick may end with
|
|
2847
|
+
// a pointerup that has no preceding pointermove at that position).
|
|
2848
|
+
const p = { x: e.clientX, y: e.clientY };
|
|
2849
|
+
if (this._ref.isListDrag && (p.x !== this.lastPointer.x || p.y !== this.lastPointer.y)) {
|
|
2850
|
+
this._ref.dragMove(p);
|
|
2402
2851
|
}
|
|
2852
|
+
// endDrag must run for free drags too, otherwise their lifecycle never completes.
|
|
2853
|
+
this._ref.endDrag();
|
|
2403
2854
|
this.service.end(this._ref);
|
|
2855
|
+
// The browser still fires a `click` on the captured element after the button is released.
|
|
2856
|
+
// After a real drag that click must not select the block / follow its link.
|
|
2857
|
+
this.swallowNextClick();
|
|
2404
2858
|
}
|
|
2405
2859
|
this.finish();
|
|
2406
2860
|
this.dragEnd.emit(this._ref.pointer);
|
|
@@ -2409,18 +2863,33 @@ class NgxDraggable {
|
|
|
2409
2863
|
pointerCancel(e) {
|
|
2410
2864
|
if (!this.down || e.pointerId !== this.pointerId)
|
|
2411
2865
|
return;
|
|
2412
|
-
if (this.dragging()
|
|
2866
|
+
if (this.dragging()) {
|
|
2413
2867
|
this.cancel();
|
|
2414
2868
|
return;
|
|
2415
2869
|
}
|
|
2416
2870
|
this.pointerUp(e);
|
|
2417
2871
|
}
|
|
2872
|
+
releaseCapture() {
|
|
2873
|
+
if (this.pointerId < 0)
|
|
2874
|
+
return;
|
|
2875
|
+
try {
|
|
2876
|
+
if (this._ref.el.hasPointerCapture?.(this.pointerId)) {
|
|
2877
|
+
this._ref.el.releasePointerCapture(this.pointerId);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
catch {
|
|
2881
|
+
/* element detached or pointer already released */
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2418
2884
|
cancel() {
|
|
2419
2885
|
this._ref.cancelDrag();
|
|
2420
2886
|
this.service.end(this._ref);
|
|
2421
2887
|
this.finish();
|
|
2422
2888
|
}
|
|
2423
2889
|
finish() {
|
|
2890
|
+
this.releaseCapture();
|
|
2891
|
+
this.removeDocKeydown?.();
|
|
2892
|
+
this.removeDocKeydown = undefined;
|
|
2424
2893
|
this.dragging.set(false);
|
|
2425
2894
|
this.down = false;
|
|
2426
2895
|
this.scroller.stop();
|
|
@@ -2433,10 +2902,20 @@ class NgxDraggable {
|
|
|
2433
2902
|
this.removeMove = this.removeUp = this.removeEscape = undefined;
|
|
2434
2903
|
this.removeCancel = this.removeLostCapture = undefined;
|
|
2435
2904
|
}
|
|
2905
|
+
/** Keys already processed (the host listener and the document listener may both see one). */
|
|
2906
|
+
handledKeys = new WeakSet();
|
|
2436
2907
|
/** Arrow-key movement for keyboard/assistive-tech users. Shift multiplies the step by 4. */
|
|
2437
2908
|
keyDown(e) {
|
|
2438
|
-
if (this.disabled())
|
|
2909
|
+
if (this.disabled() || this.handledKeys.has(e))
|
|
2910
|
+
return;
|
|
2911
|
+
// Only the draggable ITSELF being focused means "move me". Arrow keys typed into an input,
|
|
2912
|
+
// a textarea or a contenteditable region inside the block belong to that control (caret
|
|
2913
|
+
// movement), and must never start a drag or be swallowed.
|
|
2914
|
+
if (e.target !== this.host.nativeElement && e.target !== this._ref.el && !this.dragging())
|
|
2915
|
+
return;
|
|
2916
|
+
if (this.dragging() && isEditableTarget(e.target))
|
|
2439
2917
|
return;
|
|
2918
|
+
this.handledKeys.add(e);
|
|
2440
2919
|
const step = this.keyboardStep() * (e.shiftKey ? 4 : 1);
|
|
2441
2920
|
const deltas = {
|
|
2442
2921
|
ArrowLeft: { x: -step, y: 0 },
|
|
@@ -2462,6 +2941,7 @@ class NgxDraggable {
|
|
|
2462
2941
|
this.dragging.set(true);
|
|
2463
2942
|
this.service.begin(this._ref);
|
|
2464
2943
|
this.startScrollTracking();
|
|
2944
|
+
this.beginKeyboardSession();
|
|
2465
2945
|
this.dragStart.emit(origin);
|
|
2466
2946
|
}
|
|
2467
2947
|
this._ref.nudge(delta.x, delta.y);
|
|
@@ -2473,29 +2953,63 @@ class NgxDraggable {
|
|
|
2473
2953
|
this._ref.endDrag();
|
|
2474
2954
|
this.service.end(this._ref);
|
|
2475
2955
|
this.dragEnd.emit(this._ref.pointer);
|
|
2476
|
-
this.
|
|
2477
|
-
this.
|
|
2956
|
+
this.finish();
|
|
2957
|
+
this.restoreFocus();
|
|
2478
2958
|
}
|
|
2479
2959
|
else if (e.key === 'Escape' && this.dragging()) {
|
|
2480
2960
|
e.preventDefault();
|
|
2481
2961
|
this._ref.cancelDrag();
|
|
2482
2962
|
this.service.end(this._ref);
|
|
2483
|
-
this.
|
|
2963
|
+
this.finish();
|
|
2964
|
+
this.restoreFocus();
|
|
2484
2965
|
}
|
|
2485
2966
|
}
|
|
2967
|
+
/**
|
|
2968
|
+
* A list item is `display:none` while it is being moved, so the browser drops its focus and
|
|
2969
|
+
* the following arrow/Enter/Escape presses would go to <body>. Listen on the document for the
|
|
2970
|
+
* duration of a keyboard drag so the gesture keeps working.
|
|
2971
|
+
*/
|
|
2972
|
+
beginKeyboardSession() {
|
|
2973
|
+
this.keyboardDrag = true;
|
|
2974
|
+
this.removeDocKeydown?.();
|
|
2975
|
+
this.removeDocKeydown = this.renderer.listen(this._ref.el.ownerDocument, 'keydown', (ev) => this.keyDown(ev));
|
|
2976
|
+
}
|
|
2977
|
+
restoreFocus() {
|
|
2978
|
+
this.removeDocKeydown?.();
|
|
2979
|
+
this.removeDocKeydown = undefined;
|
|
2980
|
+
if (this.keyboardDrag) {
|
|
2981
|
+
this.keyboardDrag = false;
|
|
2982
|
+
this._ref.el.focus?.({ preventScroll: true });
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
/** Swallow the single `click` the browser dispatches right after a completed drag. */
|
|
2986
|
+
swallowNextClick() {
|
|
2987
|
+
const doc = this._ref.el.ownerDocument;
|
|
2988
|
+
const stop = (ev) => {
|
|
2989
|
+
ev.stopPropagation();
|
|
2990
|
+
ev.preventDefault();
|
|
2991
|
+
};
|
|
2992
|
+
doc.addEventListener('click', stop, { capture: true, once: true });
|
|
2993
|
+
// If no click follows (pointer released elsewhere), do not eat the user's NEXT real click.
|
|
2994
|
+
doc.defaultView?.setTimeout(() => doc.removeEventListener('click', stop, { capture: true }), 0);
|
|
2995
|
+
}
|
|
2486
2996
|
isOnHandle(target) {
|
|
2487
2997
|
if (!this.dragHandle())
|
|
2488
2998
|
return true;
|
|
2489
2999
|
// TODO notsafe instanseof: node.nodeType === Node.ELEMENT_NODE
|
|
2490
|
-
return target
|
|
3000
|
+
return isElementTarget(target) && !!target.closest(this.dragHandle());
|
|
2491
3001
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
3002
|
+
/** Should a press on `target` be left alone (no drag)? */
|
|
3003
|
+
isExcluded(target) {
|
|
3004
|
+
if (!isElementTarget(target))
|
|
3005
|
+
return false;
|
|
3006
|
+
const selector = this.noDragSelector();
|
|
3007
|
+
if (selector && target.closest(selector))
|
|
3008
|
+
return true;
|
|
3009
|
+
return this.ignoreInteractive() && isEditableOrInteractive(target);
|
|
2496
3010
|
}
|
|
2497
3011
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDraggable, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2498
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.4", type: NgxDraggable, isStandalone: true, selector: "[NgxDraggable],[ngxDraggable]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, boundary: { classPropertyName: "boundary", publicName: "boundary", isSignal: true, isRequired: false, transformFunction: null }, dragRootElement: { classPropertyName: "dragRootElement", publicName: "dragRootElement", isSignal: true, isRequired: false, transformFunction: null }, dragHandle: { classPropertyName: "dragHandle", publicName: "dragHandle", isSignal: true, isRequired: false, transformFunction: null }, lockAxis: { classPropertyName: "lockAxis", publicName: "lockAxis", isSignal: true, isRequired: false, transformFunction: null }, dragThreshold: { classPropertyName: "dragThreshold", publicName: "dragThreshold", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null }, autoScroll: { classPropertyName: "autoScroll", publicName: "autoScroll", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", dragRootElement: "dragRootElementChange", dragHandle: "dragHandleChange", dragStart: "dragStart", dragMove: "dragMove", dragEnd: "dragEnd" }, host: { properties: { "style.touch-action": "\"none\"", "attr.tabindex": "disabled() ? null : 0", "attr.aria-grabbed": "dragging()", "class.disable-drag": "disabled()" }, classAttribute: "ngx-draggable" }, providers: [{ provide: NGX_DRAGGABLE, useExisting: NgxDraggable }], queries: [{ propertyName: "customPreview", first: true, predicate: NgxDragPreview, descendants: true }], ngImport: i0 });
|
|
3012
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.4", type: NgxDraggable, isStandalone: true, selector: "[NgxDraggable],[ngxDraggable]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, boundary: { classPropertyName: "boundary", publicName: "boundary", isSignal: true, isRequired: false, transformFunction: null }, dragRootElement: { classPropertyName: "dragRootElement", publicName: "dragRootElement", isSignal: true, isRequired: false, transformFunction: null }, dragHandle: { classPropertyName: "dragHandle", publicName: "dragHandle", isSignal: true, isRequired: false, transformFunction: null }, lockAxis: { classPropertyName: "lockAxis", publicName: "lockAxis", isSignal: true, isRequired: false, transformFunction: null }, dragThreshold: { classPropertyName: "dragThreshold", publicName: "dragThreshold", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null }, autoScroll: { classPropertyName: "autoScroll", publicName: "autoScroll", isSignal: true, isRequired: false, transformFunction: null }, noDragSelector: { classPropertyName: "noDragSelector", publicName: "noDragSelector", isSignal: true, isRequired: false, transformFunction: null }, ignoreInteractive: { classPropertyName: "ignoreInteractive", publicName: "ignoreInteractive", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", dragRootElement: "dragRootElementChange", dragHandle: "dragHandleChange", dragStart: "dragStart", dragMove: "dragMove", dragEnd: "dragEnd" }, host: { properties: { "style.touch-action": "\"none\"", "attr.tabindex": "disabled() ? null : 0", "attr.aria-grabbed": "dragging()", "class.disable-drag": "disabled()" }, classAttribute: "ngx-draggable" }, providers: [{ provide: NGX_DRAGGABLE, useExisting: NgxDraggable }], queries: [{ propertyName: "customPreview", first: true, predicate: NgxDragPreview, descendants: true }], ngImport: i0 });
|
|
2499
3013
|
}
|
|
2500
3014
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDraggable, decorators: [{
|
|
2501
3015
|
type: Directive,
|
|
@@ -2510,13 +3024,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
2510
3024
|
'[class.disable-drag]': 'disabled()',
|
|
2511
3025
|
},
|
|
2512
3026
|
}]
|
|
2513
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], boundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundary", required: false }] }], dragRootElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragRootElement", required: false }] }, { type: i0.Output, args: ["dragRootElementChange"] }], dragHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragHandle", required: false }] }, { type: i0.Output, args: ["dragHandleChange"] }], lockAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockAxis", required: false }] }], dragThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragThreshold", required: false }] }], keyboardStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardStep", required: false }] }], autoScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScroll", required: false }] }], data: [{
|
|
3027
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], boundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundary", required: false }] }], dragRootElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragRootElement", required: false }] }, { type: i0.Output, args: ["dragRootElementChange"] }], dragHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragHandle", required: false }] }, { type: i0.Output, args: ["dragHandleChange"] }], lockAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockAxis", required: false }] }], dragThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragThreshold", required: false }] }], keyboardStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardStep", required: false }] }], autoScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoScroll", required: false }] }], noDragSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "noDragSelector", required: false }] }], ignoreInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "ignoreInteractive", required: false }] }], data: [{
|
|
2514
3028
|
type: Input,
|
|
2515
3029
|
args: ['data']
|
|
2516
3030
|
}], customPreview: [{
|
|
2517
3031
|
type: ContentChild,
|
|
2518
3032
|
args: [NgxDragPreview]
|
|
2519
3033
|
}], dragStart: [{ type: i0.Output, args: ["dragStart"] }], dragMove: [{ type: i0.Output, args: ["dragMove"] }], dragEnd: [{ type: i0.Output, args: ["dragEnd"] }] } });
|
|
3034
|
+
function isEditableOrInteractive(el) {
|
|
3035
|
+
return !!el.closest('button,a[href],input,textarea,select,[contenteditable]:not([contenteditable="false"])');
|
|
3036
|
+
}
|
|
3037
|
+
function isEditableTarget(target) {
|
|
3038
|
+
if (!isElementTarget(target))
|
|
3039
|
+
return false;
|
|
3040
|
+
return !!target.closest('input,textarea,select,[contenteditable]:not([contenteditable="false"])');
|
|
3041
|
+
}
|
|
3042
|
+
function isElementTarget(target) {
|
|
3043
|
+
return (!!target &&
|
|
3044
|
+
typeof target === 'object' &&
|
|
3045
|
+
'nodeType' in target &&
|
|
3046
|
+
target.nodeType === 1 &&
|
|
3047
|
+
'closest' in target);
|
|
3048
|
+
}
|
|
2520
3049
|
|
|
2521
3050
|
const ALL_DIRECTIONS = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'];
|
|
2522
3051
|
const KEYBOARD_STEP = 8;
|
|
@@ -2558,9 +3087,19 @@ class NgxResizable {
|
|
|
2558
3087
|
beforeHeight = '';
|
|
2559
3088
|
beforeTransform = '';
|
|
2560
3089
|
pointerId = -1;
|
|
3090
|
+
startWidth = 0;
|
|
3091
|
+
startHeight = 0;
|
|
3092
|
+
removeDocumentListeners = [];
|
|
3093
|
+
renderer = inject(Renderer2);
|
|
2561
3094
|
constructor(el) {
|
|
2562
3095
|
this.el = el;
|
|
2563
3096
|
}
|
|
3097
|
+
ngOnDestroy() {
|
|
3098
|
+
if (this.resizing()) {
|
|
3099
|
+
this.restoreBeforeResize();
|
|
3100
|
+
this.finish();
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
2564
3103
|
onPointerDown(e) {
|
|
2565
3104
|
if (this.disabled() || e.button !== 0) {
|
|
2566
3105
|
return;
|
|
@@ -2585,6 +3124,10 @@ class NgxResizable {
|
|
|
2585
3124
|
y: e.clientY,
|
|
2586
3125
|
};
|
|
2587
3126
|
this.beforeTransition = element.style.transition;
|
|
3127
|
+
this.beforeWidth = element.style.width;
|
|
3128
|
+
this.beforeHeight = element.style.height;
|
|
3129
|
+
this.startWidth = element.offsetWidth;
|
|
3130
|
+
this.startHeight = element.offsetHeight;
|
|
2588
3131
|
/**
|
|
2589
3132
|
* Keep DragRef's transform as the immutable base.
|
|
2590
3133
|
*
|
|
@@ -2594,7 +3137,20 @@ class NgxResizable {
|
|
|
2594
3137
|
this.beforeTransform = element.style.transform;
|
|
2595
3138
|
this.resizing.set(true);
|
|
2596
3139
|
element.style.transition = 'none';
|
|
2597
|
-
|
|
3140
|
+
try {
|
|
3141
|
+
element.setPointerCapture?.(e.pointerId);
|
|
3142
|
+
}
|
|
3143
|
+
catch {
|
|
3144
|
+
/* synthetic / already-ended pointer: document listeners below still receive the events */
|
|
3145
|
+
}
|
|
3146
|
+
const doc = element.ownerDocument;
|
|
3147
|
+
this.removeDocumentListeners = [
|
|
3148
|
+
this.renderer.listen(doc, 'pointermove', (event) => this.onPointerMove(event)),
|
|
3149
|
+
this.renderer.listen(doc, 'pointerup', (event) => this.onPointerUp(event)),
|
|
3150
|
+
this.renderer.listen(doc, 'pointercancel', (event) => this.onPointerCancel(event)),
|
|
3151
|
+
this.renderer.listen(doc, 'lostpointercapture', (event) => this.onPointerCancel(event)),
|
|
3152
|
+
this.renderer.listen(doc, 'keydown', (event) => this.onDocumentKeyDown(event)),
|
|
3153
|
+
];
|
|
2598
3154
|
const out = this.calculate(e);
|
|
2599
3155
|
this.apply(out);
|
|
2600
3156
|
this.resizeStart.emit(out);
|
|
@@ -2624,16 +3180,30 @@ class NgxResizable {
|
|
|
2624
3180
|
return;
|
|
2625
3181
|
}
|
|
2626
3182
|
e.preventDefault();
|
|
3183
|
+
this.restoreBeforeResize();
|
|
3184
|
+
this.finish();
|
|
3185
|
+
}
|
|
3186
|
+
onPointerCancel(e) {
|
|
3187
|
+
if (!this.resizing() || e.pointerId !== this.pointerId)
|
|
3188
|
+
return;
|
|
3189
|
+
this.restoreBeforeResize();
|
|
3190
|
+
this.finish();
|
|
3191
|
+
}
|
|
3192
|
+
restoreBeforeResize() {
|
|
2627
3193
|
const element = this.el.nativeElement;
|
|
2628
3194
|
element.style.width = this.beforeWidth;
|
|
2629
3195
|
element.style.height = this.beforeHeight;
|
|
2630
3196
|
element.style.transform = this.beforeTransform;
|
|
2631
|
-
this.finish();
|
|
2632
3197
|
}
|
|
2633
3198
|
onHandleKeyDown(e) {
|
|
2634
3199
|
if (this.disabled()) {
|
|
2635
3200
|
return;
|
|
2636
3201
|
}
|
|
3202
|
+
// Only when the resizable element itself is focused: arrow keys pressed inside an input /
|
|
3203
|
+
// contenteditable that lives in the block are caret movement, not a resize request.
|
|
3204
|
+
if (e.target !== this.el.nativeElement) {
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
2637
3207
|
const rtl = isRtl(this.el.nativeElement);
|
|
2638
3208
|
const step = this.keyboardStep() * (e.shiftKey ? 4 : 1);
|
|
2639
3209
|
let dw = 0;
|
|
@@ -2655,10 +3225,11 @@ class NgxResizable {
|
|
|
2655
3225
|
}
|
|
2656
3226
|
e.preventDefault();
|
|
2657
3227
|
const rect = this.el.nativeElement.getBoundingClientRect();
|
|
2658
|
-
const width = this.clampGrid(Math.max(this.minWidth(), Math.min(this.maxWidth(),
|
|
2659
|
-
const height = this.clampGrid(Math.max(this.minHeight(), Math.min(this.maxHeight(),
|
|
2660
|
-
this.
|
|
2661
|
-
this.el.nativeElement.style.
|
|
3228
|
+
const width = this.clampGrid(Math.max(this.minWidth(), Math.min(this.maxWidth(), this.el.nativeElement.offsetWidth + dw)), 'x');
|
|
3229
|
+
const height = this.clampGrid(Math.max(this.minHeight(), Math.min(this.maxHeight(), this.el.nativeElement.offsetHeight + dh)), 'y');
|
|
3230
|
+
const styled = this.toStyleSize(width, height);
|
|
3231
|
+
this.el.nativeElement.style.width = `${styled.width}px`;
|
|
3232
|
+
this.el.nativeElement.style.height = `${styled.height}px`;
|
|
2662
3233
|
const out = {
|
|
2663
3234
|
width,
|
|
2664
3235
|
height,
|
|
@@ -2672,10 +3243,14 @@ class NgxResizable {
|
|
|
2672
3243
|
this.resizeEnd.emit(out);
|
|
2673
3244
|
}
|
|
2674
3245
|
calculate(e) {
|
|
2675
|
-
const
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
3246
|
+
const localDelta = viewportDeltaToLocal(this.el.nativeElement, {
|
|
3247
|
+
x: e.clientX - this.pointer.x,
|
|
3248
|
+
y: e.clientY - this.pointer.y,
|
|
3249
|
+
});
|
|
3250
|
+
const dx = localDelta.x;
|
|
3251
|
+
const dy = localDelta.y;
|
|
3252
|
+
let width = this.startWidth;
|
|
3253
|
+
let height = this.startHeight;
|
|
2679
3254
|
/**
|
|
2680
3255
|
* East
|
|
2681
3256
|
*/
|
|
@@ -2708,23 +3283,27 @@ class NgxResizable {
|
|
|
2708
3283
|
* This is useful for boundary calculations and
|
|
2709
3284
|
* emitted resize events.
|
|
2710
3285
|
*/
|
|
3286
|
+
const matrix = getLocalToViewportMatrix(this.el.nativeElement);
|
|
3287
|
+
const scaleX = Math.hypot(matrix.a, matrix.b) || 1;
|
|
3288
|
+
const scaleY = Math.hypot(matrix.c, matrix.d) || 1;
|
|
2711
3289
|
let left = this.start.left;
|
|
2712
3290
|
let top = this.start.top;
|
|
2713
3291
|
if (this.direction.includes('w')) {
|
|
2714
|
-
left = this.start.right - width;
|
|
3292
|
+
left = this.start.right - width * scaleX;
|
|
2715
3293
|
}
|
|
2716
3294
|
if (this.direction.includes('n')) {
|
|
2717
|
-
top = this.start.bottom - height;
|
|
3295
|
+
top = this.start.bottom - height * scaleY;
|
|
2718
3296
|
}
|
|
2719
3297
|
let moveLeft = left - this.start.left;
|
|
2720
3298
|
let moveTop = top - this.start.top;
|
|
2721
3299
|
/**
|
|
2722
3300
|
* Boundary constraint.
|
|
2723
3301
|
*/
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3302
|
+
const boundaryEl = this.boundary();
|
|
3303
|
+
if (boundaryEl) {
|
|
3304
|
+
const clamped = clampResizeWithinBoundary(boundaryEl.getBoundingClientRect(), this.start, width * scaleX, height * scaleY, moveLeft, moveTop);
|
|
3305
|
+
width = clamped.width / scaleX;
|
|
3306
|
+
height = clamped.height / scaleY;
|
|
2728
3307
|
moveLeft = clamped.left;
|
|
2729
3308
|
moveTop = clamped.top;
|
|
2730
3309
|
left = this.start.left + moveLeft;
|
|
@@ -2755,8 +3334,9 @@ class NgxResizable {
|
|
|
2755
3334
|
* Changing width/height may cause the browser to re-layout
|
|
2756
3335
|
* the element, especially for relative/flex/grid/static layouts.
|
|
2757
3336
|
*/
|
|
2758
|
-
|
|
2759
|
-
element.style.
|
|
3337
|
+
const style = this.toStyleSize(out.width, out.height);
|
|
3338
|
+
element.style.width = `${style.width}px`;
|
|
3339
|
+
element.style.height = `${style.height}px`;
|
|
2760
3340
|
/**
|
|
2761
3341
|
* Force/read layout after changing the size.
|
|
2762
3342
|
*/
|
|
@@ -2787,12 +3367,33 @@ class NgxResizable {
|
|
|
2787
3367
|
* correction required by Resize.
|
|
2788
3368
|
*/
|
|
2789
3369
|
const baseTransform = this.beforeTransform && this.beforeTransform !== 'none' ? `${this.beforeTransform} ` : '';
|
|
3370
|
+
const localCorrection = viewportDeltaToLocal(element, { x: correctionX, y: correctionY });
|
|
2790
3371
|
element.style.transform =
|
|
2791
|
-
`${baseTransform}translate3d(` + `${
|
|
3372
|
+
`${baseTransform}translate3d(` + `${localCorrection.x}px, ` + `${localCorrection.y}px, 0)`;
|
|
3373
|
+
}
|
|
3374
|
+
/**
|
|
3375
|
+
* `offsetWidth/Height` (what we measure and report) are BORDER-box sizes. The inline
|
|
3376
|
+
* `width/height` we write are interpreted according to `box-sizing`: for the CSS default
|
|
3377
|
+
* (`content-box`) writing the border-box number would make a padded/bordered block jump by
|
|
3378
|
+
* its padding+border the instant a resize starts. Convert so the box is exactly the size
|
|
3379
|
+
* reported in `IResizableOutput`.
|
|
3380
|
+
*/
|
|
3381
|
+
toStyleSize(width, height) {
|
|
3382
|
+
const el = this.el.nativeElement;
|
|
3383
|
+
const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
3384
|
+
if (!cs || cs.boxSizing === 'border-box')
|
|
3385
|
+
return { width, height };
|
|
3386
|
+
const px = (v) => Number.parseFloat(v) || 0;
|
|
3387
|
+
const extraX = px(cs.paddingLeft) + px(cs.paddingRight) + px(cs.borderLeftWidth) + px(cs.borderRightWidth);
|
|
3388
|
+
const extraY = px(cs.paddingTop) + px(cs.paddingBottom) + px(cs.borderTopWidth) + px(cs.borderBottomWidth);
|
|
3389
|
+
return { width: Math.max(0, width - extraX), height: Math.max(0, height - extraY) };
|
|
2792
3390
|
}
|
|
2793
3391
|
clampGrid(value, axis) {
|
|
2794
|
-
const
|
|
2795
|
-
|
|
3392
|
+
const g = this.grid();
|
|
3393
|
+
// NOTE: the previous code returned the input *signal function* for a numeric grid, so every
|
|
3394
|
+
// snapped size became NaN and the resize silently stopped working.
|
|
3395
|
+
const grid = typeof g === 'number' ? g : g?.[axis];
|
|
3396
|
+
if (!grid || !Number.isFinite(grid) || grid <= 0) {
|
|
2796
3397
|
return value;
|
|
2797
3398
|
}
|
|
2798
3399
|
return Math.round(value / grid) * grid;
|
|
@@ -2800,7 +3401,19 @@ class NgxResizable {
|
|
|
2800
3401
|
finish() {
|
|
2801
3402
|
this.resizing.set(false);
|
|
2802
3403
|
this.el.nativeElement.style.transition = this.beforeTransition;
|
|
3404
|
+
const pointerId = this.pointerId;
|
|
2803
3405
|
this.pointerId = -1;
|
|
3406
|
+
for (const remove of this.removeDocumentListeners)
|
|
3407
|
+
remove();
|
|
3408
|
+
this.removeDocumentListeners = [];
|
|
3409
|
+
try {
|
|
3410
|
+
if (pointerId >= 0 && this.el.nativeElement.hasPointerCapture?.(pointerId)) {
|
|
3411
|
+
this.el.nativeElement.releasePointerCapture(pointerId);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
catch {
|
|
3415
|
+
/* already released */
|
|
3416
|
+
}
|
|
2804
3417
|
this.beforeTransform = '';
|
|
2805
3418
|
}
|
|
2806
3419
|
resolvedDirections() {
|
|
@@ -2823,7 +3436,7 @@ class NgxResizable {
|
|
|
2823
3436
|
* Explicit resize handle has priority.
|
|
2824
3437
|
*/
|
|
2825
3438
|
// TODO notsafe instanceof:node.nodeType === Node.ELEMENT_NODE
|
|
2826
|
-
const handle = e.target
|
|
3439
|
+
const handle = isHTMLElementTarget(e.target)
|
|
2827
3440
|
? e.target.closest('[data-ngx-resize-handle]')
|
|
2828
3441
|
: null;
|
|
2829
3442
|
if (handle && element.contains(handle)) {
|
|
@@ -2866,7 +3479,7 @@ class NgxResizable {
|
|
|
2866
3479
|
return this.resolvedDirections().includes(direction) ? direction : null;
|
|
2867
3480
|
}
|
|
2868
3481
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxResizable, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2869
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.4", type: NgxResizable, isStandalone: true, selector: "[NgxResizable],[ngxResizable]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, directions: { classPropertyName: "directions", publicName: "directions", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, boundary: { classPropertyName: "boundary", publicName: "boundary", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", minWidth: "minWidthChange", minHeight: "minHeightChange", maxWidth: "maxWidthChange", maxHeight: "maxHeightChange", resizeStart: "resizeStart", resizeMove: "resizeMove", resizeEnd: "resizeEnd" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "
|
|
3482
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.4", type: NgxResizable, isStandalone: true, selector: "[NgxResizable],[ngxResizable]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, directions: { classPropertyName: "directions", publicName: "directions", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, boundary: { classPropertyName: "boundary", publicName: "boundary", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", minWidth: "minWidthChange", minHeight: "minHeightChange", maxWidth: "maxWidthChange", maxHeight: "maxHeightChange", resizeStart: "resizeStart", resizeMove: "resizeMove", resizeEnd: "resizeEnd" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "keydown": "onHandleKeyDown($event)" }, properties: { "style.touch-action": "\"none\"" }, classAttribute: "ngx-resizable" }, ngImport: i0 });
|
|
2870
3483
|
}
|
|
2871
3484
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxResizable, decorators: [{
|
|
2872
3485
|
type: Directive,
|
|
@@ -2880,19 +3493,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
2880
3493
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }, { type: i0.Output, args: ["minWidthChange"] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }, { type: i0.Output, args: ["minHeightChange"] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }, { type: i0.Output, args: ["maxWidthChange"] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }, { type: i0.Output, args: ["maxHeightChange"] }], directions: [{ type: i0.Input, args: [{ isSignal: true, alias: "directions", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], boundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundary", required: false }] }], keyboardStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardStep", required: false }] }], resizeStart: [{ type: i0.Output, args: ["resizeStart"] }], resizeMove: [{ type: i0.Output, args: ["resizeMove"] }], resizeEnd: [{ type: i0.Output, args: ["resizeEnd"] }], onPointerDown: [{
|
|
2881
3494
|
type: HostListener,
|
|
2882
3495
|
args: ['pointerdown', ['$event']]
|
|
2883
|
-
}], onPointerMove: [{
|
|
2884
|
-
type: HostListener,
|
|
2885
|
-
args: ['document:pointermove', ['$event']]
|
|
2886
|
-
}], onPointerUp: [{
|
|
2887
|
-
type: HostListener,
|
|
2888
|
-
args: ['document:pointerup', ['$event']]
|
|
2889
|
-
}], onDocumentKeyDown: [{
|
|
2890
|
-
type: HostListener,
|
|
2891
|
-
args: ['document:keydown', ['$event']]
|
|
2892
3496
|
}], onHandleKeyDown: [{
|
|
2893
3497
|
type: HostListener,
|
|
2894
3498
|
args: ['keydown', ['$event']]
|
|
2895
3499
|
}] } });
|
|
3500
|
+
function isHTMLElementTarget(target) {
|
|
3501
|
+
return (!!target &&
|
|
3502
|
+
typeof target === 'object' &&
|
|
3503
|
+
'nodeType' in target &&
|
|
3504
|
+
target.nodeType === 1 &&
|
|
3505
|
+
'closest' in target);
|
|
3506
|
+
}
|
|
2896
3507
|
|
|
2897
3508
|
const NGX_PLACEHOLDER = new InjectionToken('ngx-place-holder');
|
|
2898
3509
|
/**
|
|
@@ -2942,6 +3553,13 @@ class NgxDropList {
|
|
|
2942
3553
|
set disableSort(value) {
|
|
2943
3554
|
this._ref.disableSort = value === true;
|
|
2944
3555
|
}
|
|
3556
|
+
/** `'stretch'`: the placeholder only copies the item's size along the list's main axis. */
|
|
3557
|
+
set placeholderFit(value) {
|
|
3558
|
+
this._ref.placeholderFit = value === 'stretch' ? 'stretch' : 'item';
|
|
3559
|
+
}
|
|
3560
|
+
set sortAnimationDuration(value) {
|
|
3561
|
+
this._ref.sortAnimationDuration = value == null ? 200 : Math.max(0, value);
|
|
3562
|
+
}
|
|
2945
3563
|
drop = new EventEmitter();
|
|
2946
3564
|
customPlaceholder;
|
|
2947
3565
|
sub;
|
|
@@ -2973,7 +3591,7 @@ class NgxDropList {
|
|
|
2973
3591
|
this.service.removeDropList(this._ref);
|
|
2974
3592
|
}
|
|
2975
3593
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropList, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2976
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.4", type: NgxDropList, isStandalone: true, selector: "[NgxDropList],[ngxDropList]", inputs: { data: "data", connectedTo: "connectedTo", disableSort: "disableSort" }, outputs: { drop: "drop" }, host: { classAttribute: "ngx-drop-list" }, providers: [{ provide: NGX_DROPLIST, useExisting: NgxDropList }], queries: [{ propertyName: "customPlaceholder", first: true, predicate: NgxPlaceholder, descendants: true }], ngImport: i0 });
|
|
3594
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.4", type: NgxDropList, isStandalone: true, selector: "[NgxDropList],[ngxDropList]", inputs: { data: "data", connectedTo: "connectedTo", disableSort: "disableSort", placeholderFit: "placeholderFit", sortAnimationDuration: "sortAnimationDuration" }, outputs: { drop: "drop" }, host: { classAttribute: "ngx-drop-list" }, providers: [{ provide: NGX_DROPLIST, useExisting: NgxDropList }], queries: [{ propertyName: "customPlaceholder", first: true, predicate: NgxPlaceholder, descendants: true }], ngImport: i0 });
|
|
2977
3595
|
}
|
|
2978
3596
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxDropList, decorators: [{
|
|
2979
3597
|
type: Directive,
|
|
@@ -2989,6 +3607,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
2989
3607
|
type: Input
|
|
2990
3608
|
}], disableSort: [{
|
|
2991
3609
|
type: Input
|
|
3610
|
+
}], placeholderFit: [{
|
|
3611
|
+
type: Input
|
|
3612
|
+
}], sortAnimationDuration: [{
|
|
3613
|
+
type: Input
|
|
2992
3614
|
}], drop: [{
|
|
2993
3615
|
type: Output
|
|
2994
3616
|
}], customPlaceholder: [{
|
|
@@ -3061,9 +3683,54 @@ function clamp(v, max) {
|
|
|
3061
3683
|
return Math.max(0, Math.min(max, v));
|
|
3062
3684
|
}
|
|
3063
3685
|
|
|
3686
|
+
/**
|
|
3687
|
+
* Helpers for a page builder's "export HTML" step.
|
|
3688
|
+
*
|
|
3689
|
+
* While you edit, the directives decorate the canvas DOM: `ngx-*` classes, `tabindex="0"`,
|
|
3690
|
+
* `aria-grabbed`, `touch-action: none`, and — if an export happens mid-drag — a placeholder or a
|
|
3691
|
+
* preview clone. None of that belongs in the page a visitor receives. Run the canvas through
|
|
3692
|
+
* `getCleanHtml()` (or `stripEditorArtifacts()` on a clone you already have) before saving.
|
|
3693
|
+
*/
|
|
3694
|
+
const TRANSIENT_NODES = '.ngx-drag-placeholder, .ngx-drag-preview';
|
|
3695
|
+
/** Mutates `root` (and its subtree) in place. Pass a CLONE of live DOM, never the live canvas. */
|
|
3696
|
+
function stripEditorArtifacts(root, options = {}) {
|
|
3697
|
+
const prefix = options.classPrefix ?? 'ngx-';
|
|
3698
|
+
const dropClasses = new Set(['disable-drag', ...(options.extraClasses ?? [])]);
|
|
3699
|
+
const dropAttrs = ['aria-grabbed', ...(options.extraAttributes ?? [])];
|
|
3700
|
+
root.querySelectorAll(TRANSIENT_NODES).forEach((n) => n.remove());
|
|
3701
|
+
const all = [root, ...Array.from(root.querySelectorAll('*'))];
|
|
3702
|
+
for (const el of all) {
|
|
3703
|
+
// `tabindex="0"` is only ours on draggables (a real focusable of the page keeps its own).
|
|
3704
|
+
if (el.classList.contains('ngx-draggable') && el.getAttribute('tabindex') === '0') {
|
|
3705
|
+
el.removeAttribute('tabindex');
|
|
3706
|
+
}
|
|
3707
|
+
for (const attr of dropAttrs)
|
|
3708
|
+
el.removeAttribute(attr);
|
|
3709
|
+
for (const cls of Array.from(el.classList)) {
|
|
3710
|
+
if (cls.startsWith(prefix) || dropClasses.has(cls))
|
|
3711
|
+
el.classList.remove(cls);
|
|
3712
|
+
}
|
|
3713
|
+
if (!el.classList.length)
|
|
3714
|
+
el.removeAttribute('class');
|
|
3715
|
+
const style = el.style;
|
|
3716
|
+
if (style) {
|
|
3717
|
+
if (style.touchAction === 'none')
|
|
3718
|
+
style.removeProperty('touch-action');
|
|
3719
|
+
if (!el.getAttribute('style')?.trim())
|
|
3720
|
+
el.removeAttribute('style');
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
/** Serialised, editor-free HTML of `el`'s children (the canvas content). */
|
|
3725
|
+
function getCleanHtml(el, options) {
|
|
3726
|
+
const clone = el.cloneNode(true);
|
|
3727
|
+
stripEditorArtifacts(clone, options);
|
|
3728
|
+
return clone.innerHTML;
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3064
3731
|
/**
|
|
3065
3732
|
* Generated bundle index. Do not edit.
|
|
3066
3733
|
*/
|
|
3067
3734
|
|
|
3068
|
-
export { AutoScroller, DragDropService, DragRef, DropListGroupRef, DropListRef, NGX_DRAGGABLE, NGX_DRAG_PREVIEW, NGX_DROPLIST, NGX_DROPLIST_GROUP, NGX_PLACEHOLDER, NgxDragPreview, NgxDraggable, NgxDropList, NgxDropListGroup, NgxPlaceholder, NgxResizable, PlaceHolderRef, adjustForZoom, checkBoundX, checkBoundY, clampResizeWithinBoundary, copyArrayItem, findScrollableAncestor, getPageZoom, getScrollPosition, isRtl, moveItemInArray, resolveLogicalDirection, transferArrayItem };
|
|
3735
|
+
export { AutoScroller, DragDropService, DragRef, DropListGroupRef, DropListRef, NGX_DRAGGABLE, NGX_DRAG_PREVIEW, NGX_DROPLIST, NGX_DROPLIST_GROUP, NGX_PLACEHOLDER, NgxDragPreview, NgxDraggable, NgxDropList, NgxDropListGroup, NgxPlaceholder, NgxResizable, PlaceHolderRef, adjustForZoom, checkBoundX, checkBoundY, clampResizeWithinBoundary, copyArrayItem, findScrollableAncestor, getCleanHtml, getPageZoom, getScrollPosition, isRtl, moveItemInArray, resolveLogicalDirection, stripEditorArtifacts, transferArrayItem };
|
|
3069
3736
|
//# sourceMappingURL=ngx-kit-drag-resize.mjs.map
|