vgapp 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,332 @@
1
+ const DEFAULT_OPTIONS = {
2
+ enable: false,
3
+ selector: '.vg-modal-content',
4
+ threshold: 4,
5
+ resizeEdgeSize: 8,
6
+ debug: false,
7
+ };
8
+
9
+ class VGModalDrag {
10
+ constructor(modalElement, dialogElement, options = {}) {
11
+ this._modalElement = modalElement;
12
+ this._dialogElement = dialogElement;
13
+ this._options = this._normalizeOptions(options);
14
+ this._pointerId = null;
15
+ this._dragTarget = null;
16
+ this._isDragging = false;
17
+ this._startX = 0;
18
+ this._startY = 0;
19
+ this._currentX = 0;
20
+ this._currentY = 0;
21
+ this._dialogStartLeft = 0;
22
+ this._dialogStartTop = 0;
23
+ this._dialogWidth = 0;
24
+ this._dialogHeight = 0;
25
+ this._isEnabled = false;
26
+ this._previousUserSelect = '';
27
+ this._previousTransition = '';
28
+ this._previousWillChange = '';
29
+ this._initialRect = null;
30
+ this._lockedIframes = [];
31
+ this._debugElement = null;
32
+
33
+ this._onPointerMove = this._onPointerMove.bind(this);
34
+ this._onPointerUp = this._onPointerUp.bind(this);
35
+ this._onPointerDown = this._onPointerDown.bind(this);
36
+ this._onNativeDragStart = this._onNativeDragStart.bind(this);
37
+ }
38
+
39
+ setOptions(options = {}) {
40
+ this._options = this._normalizeOptions(options, this._options);
41
+ this._updateDebugOverlay();
42
+ }
43
+
44
+ enable() {
45
+ if (!this._dialogElement || this._isEnabled) return;
46
+
47
+ this._updateDebugOverlay();
48
+ this._dialogElement.addEventListener('pointerdown', this._onPointerDown);
49
+ this._isEnabled = true;
50
+ }
51
+
52
+ disable() {
53
+ if (!this._dialogElement || !this._isEnabled) return;
54
+
55
+ this._dialogElement.removeEventListener('pointerdown', this._onPointerDown);
56
+ document.removeEventListener('pointermove', this._onPointerMove);
57
+ document.removeEventListener('pointerup', this._onPointerUp);
58
+ document.removeEventListener('pointercancel', this._onPointerUp);
59
+ document.removeEventListener('dragstart', this._onNativeDragStart, true);
60
+ this._dialogElement.style.touchAction = '';
61
+ document.body.style.userSelect = this._previousUserSelect;
62
+ this._pointerId = null;
63
+ this._dragTarget = null;
64
+ this._isDragging = false;
65
+ this._initialRect = null;
66
+ this._unlockEmbeddedFrames();
67
+ this._restoreDragStyles();
68
+ delete this._modalElement.dataset.vgModalDragging;
69
+ this._setDebugVisibility(false);
70
+ this._isEnabled = false;
71
+ }
72
+
73
+ syncPosition() {
74
+ if (!this._isEnabled) return;
75
+ if (!this._isPreparedForInteraction()) return;
76
+
77
+ const rect = this._dialogElement.getBoundingClientRect();
78
+ const width = this._dialogElement.offsetWidth || rect.width;
79
+ const height = this._dialogElement.offsetHeight || rect.height;
80
+ const currentLeft = Number.parseFloat(this._dialogElement.style.left);
81
+ const currentTop = Number.parseFloat(this._dialogElement.style.top);
82
+ const left = Number.isFinite(currentLeft) ? currentLeft : rect.left;
83
+ const top = Number.isFinite(currentTop) ? currentTop : rect.top;
84
+ const maxLeft = Math.max(0, window.innerWidth - width);
85
+ const maxTop = Math.max(0, window.innerHeight - height);
86
+ const nextLeft = Math.min(maxLeft, Math.max(0, left));
87
+ const nextTop = Math.min(maxTop, Math.max(0, top));
88
+
89
+ this._dialogElement.style.left = `${nextLeft}px`;
90
+ this._dialogElement.style.top = `${nextTop}px`;
91
+ this._updateDebugValues('synced');
92
+ }
93
+
94
+ _onPointerDown(event) {
95
+ if (event.button !== 0) return;
96
+
97
+ const dragArea = this._resolveDragArea(event.target);
98
+ if (!dragArea || !this._dialogElement.contains(dragArea)) return;
99
+ if (this._isPointerOnResizeEdge(event)) return;
100
+ if (event.target.closest('input, textarea, select, button, a, [contenteditable="true"]')) return;
101
+ if (dragArea && dragArea.closest('[data-vg-dismiss="modal"]')) return;
102
+
103
+ this._pointerId = event.pointerId;
104
+ this._dragTarget = event.target;
105
+ this._isDragging = false;
106
+ event.preventDefault();
107
+ this._previousUserSelect = document.body.style.userSelect;
108
+ document.body.style.userSelect = 'none';
109
+ this._initialRect = this._dialogElement.getBoundingClientRect();
110
+ this._dialogStartLeft = this._initialRect.left;
111
+ this._dialogStartTop = this._initialRect.top;
112
+ this._dialogWidth = this._initialRect.width;
113
+ this._dialogHeight = this._initialRect.height;
114
+ this._startX = event.clientX;
115
+ this._startY = event.clientY;
116
+ this._currentX = event.clientX;
117
+ this._currentY = event.clientY;
118
+ this._lockEmbeddedFrames();
119
+ this._modalElement.dataset.vgModalDragging = 'true';
120
+ this._setDebugVisibility(true);
121
+ this._updateDebugValues('armed');
122
+
123
+ document.addEventListener('dragstart', this._onNativeDragStart, true);
124
+
125
+ if (this._dialogElement && this._dialogElement.setPointerCapture) {
126
+ this._dialogElement.setPointerCapture(event.pointerId);
127
+ }
128
+
129
+ document.addEventListener('pointermove', this._onPointerMove);
130
+ document.addEventListener('pointerup', this._onPointerUp);
131
+ document.addEventListener('pointercancel', this._onPointerUp);
132
+ }
133
+
134
+ _onPointerMove(event) {
135
+ if (event.pointerId !== this._pointerId) return;
136
+
137
+ this._currentX = event.clientX;
138
+ this._currentY = event.clientY;
139
+ if (!this._isDragging) {
140
+ const deltaX = this._currentX - this._startX;
141
+ const deltaY = this._currentY - this._startY;
142
+ const distance = Math.hypot(deltaX, deltaY);
143
+ if (distance < this._options.threshold) return;
144
+
145
+ this._isDragging = true;
146
+ this._dialogElement.style.touchAction = 'none';
147
+ this._applyDragStyles();
148
+ this._preparePosition(this._initialRect);
149
+ event.preventDefault();
150
+ this._updateDebugValues('dragging');
151
+ }
152
+ this._applyDragPosition();
153
+ }
154
+
155
+ _onPointerUp(event) {
156
+ if (event.pointerId !== this._pointerId) return;
157
+
158
+ document.removeEventListener('pointermove', this._onPointerMove);
159
+ document.removeEventListener('pointerup', this._onPointerUp);
160
+ document.removeEventListener('pointercancel', this._onPointerUp);
161
+ document.removeEventListener('dragstart', this._onNativeDragStart, true);
162
+ if (this._isDragging) {
163
+ this._applyDragPosition();
164
+ }
165
+ this._dialogElement.style.touchAction = '';
166
+ document.body.style.userSelect = this._previousUserSelect;
167
+
168
+ if (this._dialogElement && this._dialogElement.releasePointerCapture) {
169
+ this._dialogElement.releasePointerCapture(event.pointerId);
170
+ }
171
+
172
+ this._pointerId = null;
173
+ this._dragTarget = null;
174
+ this._isDragging = false;
175
+ this._initialRect = null;
176
+ this._unlockEmbeddedFrames();
177
+ this._restoreDragStyles();
178
+ delete this._modalElement.dataset.vgModalDragging;
179
+ this._setDebugVisibility(this._options.debug);
180
+ this._updateDebugValues('idle');
181
+ }
182
+
183
+ _preparePosition(rect = null) {
184
+ if (!this._dialogElement) return;
185
+
186
+ const currentRect = rect || this._dialogElement.getBoundingClientRect();
187
+ this._dialogElement.style.position = 'fixed';
188
+ this._dialogElement.style.margin = '0';
189
+ this._dialogElement.style.left = `${currentRect.left}px`;
190
+ this._dialogElement.style.top = `${currentRect.top}px`;
191
+ this._dialogElement.style.width = `${currentRect.width}px`;
192
+ this._dialogElement.style.height = `${currentRect.height}px`;
193
+ this._dialogElement.style.transform = 'none';
194
+ }
195
+
196
+ _isPreparedForInteraction() {
197
+ return this._dialogElement.style.position === 'fixed' && this._dialogElement.style.transform === 'none';
198
+ }
199
+
200
+ _isPointerOnResizeEdge(event) {
201
+ const rect = this._dialogElement.getBoundingClientRect();
202
+ const offsetX = event.clientX - rect.left;
203
+ const offsetY = event.clientY - rect.top;
204
+ const edgeSize = this._options.resizeEdgeSize;
205
+
206
+ const nearTop = offsetY >= 0 && offsetY <= edgeSize;
207
+ const nearBottom = offsetY <= rect.height && offsetY >= rect.height - edgeSize;
208
+ const nearLeft = offsetX >= 0 && offsetX <= edgeSize;
209
+ const nearRight = offsetX <= rect.width && offsetX >= rect.width - edgeSize;
210
+
211
+ return nearTop || nearBottom || nearLeft || nearRight;
212
+ }
213
+
214
+ _applyDragPosition() {
215
+ if (this._pointerId === null || !this._isDragging) return;
216
+
217
+ const deltaX = this._currentX - this._startX;
218
+ const deltaY = this._currentY - this._startY;
219
+ const maxLeft = Math.max(0, window.innerWidth - this._dialogWidth);
220
+ const maxTop = Math.max(0, window.innerHeight - this._dialogHeight);
221
+ const nextLeft = Math.min(maxLeft, Math.max(0, this._dialogStartLeft + deltaX));
222
+ const nextTop = Math.min(maxTop, Math.max(0, this._dialogStartTop + deltaY));
223
+
224
+ this._dialogElement.style.left = `${nextLeft}px`;
225
+ this._dialogElement.style.top = `${nextTop}px`;
226
+ this._updateDebugValues('dragging');
227
+ }
228
+
229
+ _onNativeDragStart(event) {
230
+ if (this._pointerId === null) return;
231
+ event.preventDefault();
232
+ }
233
+
234
+ _applyDragStyles() {
235
+ this._previousTransition = this._dialogElement.style.transition;
236
+ this._previousWillChange = this._dialogElement.style.willChange;
237
+ this._dialogElement.style.transition = 'none';
238
+ this._dialogElement.style.willChange = 'left, top';
239
+ }
240
+
241
+ _restoreDragStyles() {
242
+ this._dialogElement.style.transition = this._previousTransition;
243
+ this._dialogElement.style.willChange = this._previousWillChange;
244
+ }
245
+
246
+ _lockEmbeddedFrames() {
247
+ if (!this._dialogElement || this._lockedIframes.length) return;
248
+
249
+ const iframes = this._dialogElement.querySelectorAll('iframe');
250
+ for (const frame of iframes) {
251
+ this._lockedIframes.push({
252
+ element: frame,
253
+ pointerEvents: frame.style.pointerEvents,
254
+ });
255
+ frame.style.pointerEvents = 'none';
256
+ }
257
+ }
258
+
259
+ _unlockEmbeddedFrames() {
260
+ if (!this._lockedIframes.length) return;
261
+
262
+ for (const item of this._lockedIframes) {
263
+ item.element.style.pointerEvents = item.pointerEvents;
264
+ }
265
+ this._lockedIframes = [];
266
+ }
267
+
268
+ _normalizeOptions(options, base = DEFAULT_OPTIONS) {
269
+ const merged = {...base, ...options};
270
+ const threshold = Number(merged.threshold);
271
+ const resizeEdgeSize = Number(merged.resizeEdgeSize);
272
+ const selector = typeof merged.selector === 'string' && merged.selector.trim()
273
+ ? merged.selector
274
+ : DEFAULT_OPTIONS.selector;
275
+
276
+ return {
277
+ ...merged,
278
+ selector,
279
+ threshold: Number.isFinite(threshold) && threshold >= 0 ? threshold : DEFAULT_OPTIONS.threshold,
280
+ resizeEdgeSize: Number.isFinite(resizeEdgeSize) && resizeEdgeSize > 0 ? resizeEdgeSize : DEFAULT_OPTIONS.resizeEdgeSize,
281
+ debug: Boolean(merged.debug),
282
+ };
283
+ }
284
+
285
+ _resolveDragArea(target) {
286
+ try {
287
+ return target.closest(this._options.selector);
288
+ } catch (error) {
289
+ return target.closest(DEFAULT_OPTIONS.selector);
290
+ }
291
+ }
292
+
293
+ _updateDebugOverlay() {
294
+ if (!this._options.debug) {
295
+ this._setDebugVisibility(false);
296
+ return;
297
+ }
298
+
299
+ if (!this._debugElement) {
300
+ this._debugElement = document.createElement('div');
301
+ this._debugElement.style.position = 'absolute';
302
+ this._debugElement.style.left = '170px';
303
+ this._debugElement.style.bottom = '8px';
304
+ this._debugElement.style.zIndex = '7';
305
+ this._debugElement.style.padding = '4px 6px';
306
+ this._debugElement.style.borderRadius = '4px';
307
+ this._debugElement.style.background = 'rgba(0, 0, 0, 0.72)';
308
+ this._debugElement.style.color = '#fff';
309
+ this._debugElement.style.fontSize = '11px';
310
+ this._debugElement.style.lineHeight = '1.3';
311
+ this._debugElement.style.pointerEvents = 'none';
312
+ this._dialogElement.append(this._debugElement);
313
+ }
314
+
315
+ this._setDebugVisibility(true);
316
+ this._updateDebugValues(this._isDragging ? 'dragging' : 'idle');
317
+ }
318
+
319
+ _setDebugVisibility(visible) {
320
+ if (!this._debugElement) return;
321
+ this._debugElement.style.display = visible ? 'block' : 'none';
322
+ }
323
+
324
+ _updateDebugValues(state = 'idle') {
325
+ if (!this._debugElement || !this._options.debug) return;
326
+
327
+ const rect = this._dialogElement.getBoundingClientRect();
328
+ this._debugElement.textContent = `drag:${state} x:${Math.round(rect.left)} y:${Math.round(rect.top)} w:${Math.round(rect.width)} h:${Math.round(rect.height)}`;
329
+ }
330
+ }
331
+
332
+ export default VGModalDrag;
@@ -6,6 +6,8 @@ import EventHandler from "../../../utils/js/dom/event";
6
6
  import {Manipulator} from "../../../utils/js/dom/manipulator";
7
7
  import {execute, isDisabled, isRTL, mergeDeepObject, reflow} from "../../../utils/js/functions";
8
8
  import {dismissTrigger} from "../../module-fn";
9
+ import VGModalDrag from "./vgmodal.drag";
10
+ import VGModalResize from "./vgmodal.resize";
9
11
 
10
12
  /**
11
13
  * Constants
@@ -44,6 +46,23 @@ class VGModal extends BaseModule {
44
46
  constructor(element, params = {}) {
45
47
  super(element, params);
46
48
 
49
+ this._interactionDefaults = {
50
+ drag: {
51
+ enable: false,
52
+ selector: '.vg-modal-content',
53
+ threshold: 4,
54
+ resizeEdgeSize: 8,
55
+ debug: false,
56
+ },
57
+ resize: {
58
+ enable: false,
59
+ edgeSize: 8,
60
+ minWidth: 300,
61
+ minHeight: 160,
62
+ debug: false,
63
+ },
64
+ };
65
+
47
66
  this._params = this._getParams(element, mergeDeepObject({
48
67
  backdrop: true,
49
68
  focus: true,
@@ -52,6 +71,20 @@ class VGModal extends BaseModule {
52
71
  hash: false,
53
72
  centered: false,
54
73
  dismiss: true,
74
+ resize: {
75
+ enable: false,
76
+ edgeSize: 8,
77
+ minWidth: 300,
78
+ minHeight: 160,
79
+ debug: false,
80
+ },
81
+ drag: {
82
+ enable: false,
83
+ selector: '.vg-modal-content',
84
+ threshold: 4,
85
+ resizeEdgeSize: 8,
86
+ debug: false,
87
+ },
55
88
  sizes: {
56
89
  width: '',
57
90
  height: '',
@@ -87,6 +120,11 @@ class VGModal extends BaseModule {
87
120
  this._isShown = false;
88
121
  this._isTransitioning = false;
89
122
  this._scrollBar = new ScrollBarHelper();
123
+ this._dragHandler = new VGModalDrag(this._element, this._dialog);
124
+ this._resizeHandler = new VGModalResize(this._element, this._dialog);
125
+ this._interactionConfig = this._resolveInteractionConfig();
126
+ this._dragHandler.setOptions(this._interactionConfig.drag);
127
+ this._resizeHandler.setOptions(this._interactionConfig.resize);
90
128
 
91
129
  this._addEventListeners();
92
130
  this._dismissElement();
@@ -218,6 +256,7 @@ class VGModal extends BaseModule {
218
256
 
219
257
  _hideModal(openedModals, isLeaveBackDrop) {
220
258
  if (!isLeaveBackDrop) {
259
+ this._disableInteractionHandlers();
221
260
  this._element.style.display = 'none';
222
261
  this._element.removeAttribute('aria-modal');
223
262
  this._element.removeAttribute('role');
@@ -255,23 +294,83 @@ class VGModal extends BaseModule {
255
294
 
256
295
  reflow(this._element);
257
296
 
258
- this._element.classList.add(CLASS_NAME_SHOW)
297
+ this._element.classList.add(CLASS_NAME_SHOW);
298
+ this._toggleInteractionHandlers();
259
299
 
260
300
  const transitionComplete = () => {
261
301
  this._isTransitioning = false;
262
302
  EventHandler.trigger(this._element, EVENT_KEY_SHOWN, {
263
303
  relatedTarget
264
304
  });
305
+ this._syncInteractiveBounds();
265
306
 
266
307
  this._params = this._getParams(relatedTarget, this._params);
267
308
  this._route((status, data) => {
268
309
  EventHandler.trigger(this._element, EVENT_KEY_LOADED, {stats: status, data: data});
310
+ this._syncInteractiveBounds();
269
311
  });
270
312
  }
271
313
 
272
314
  this._queueCallback(transitionComplete, this._dialog, this._isAnimatedFade())
273
315
  }
274
316
 
317
+ _toggleInteractionHandlers() {
318
+ this._interactionConfig = this._resolveInteractionConfig();
319
+ this._dragHandler.setOptions(this._interactionConfig.drag);
320
+ this._resizeHandler.setOptions(this._interactionConfig.resize);
321
+
322
+ if (this._interactionConfig.drag.enable) {
323
+ this._dragHandler.enable();
324
+ } else {
325
+ this._dragHandler.disable();
326
+ }
327
+
328
+ if (this._interactionConfig.resize.enable) {
329
+ this._resizeHandler.enable();
330
+ } else {
331
+ this._resizeHandler.disable();
332
+ }
333
+ }
334
+
335
+ _disableInteractionHandlers() {
336
+ this._dragHandler.disable();
337
+ this._resizeHandler.disable();
338
+ }
339
+
340
+ _syncInteractiveBounds() {
341
+ if (this._interactionConfig.resize.enable) {
342
+ this._resizeHandler.syncToViewport();
343
+ }
344
+
345
+ if (this._interactionConfig.drag.enable) {
346
+ this._dragHandler.syncPosition();
347
+ }
348
+ }
349
+
350
+ _resolveInteractionConfig() {
351
+ return {
352
+ drag: this._normalizeInteractionParams(this._params.drag, this._interactionDefaults.drag),
353
+ resize: this._normalizeInteractionParams(this._params.resize, this._interactionDefaults.resize),
354
+ };
355
+ }
356
+
357
+ _normalizeInteractionParams(paramsValue, defaults) {
358
+ if (typeof paramsValue === 'boolean') {
359
+ return {...defaults, enable: paramsValue};
360
+ }
361
+
362
+ if (paramsValue && typeof paramsValue === 'object') {
363
+ const hasEnable = Object.prototype.hasOwnProperty.call(paramsValue, 'enable');
364
+ return {
365
+ ...defaults,
366
+ ...paramsValue,
367
+ enable: hasEnable ? Boolean(paramsValue.enable) : true,
368
+ };
369
+ }
370
+
371
+ return {...defaults};
372
+ }
373
+
275
374
  _isAnimatedFade() {
276
375
  return this._element.classList.contains(CLASS_NAME_FADE)
277
376
  }
@@ -310,7 +409,10 @@ class VGModal extends BaseModule {
310
409
  });
311
410
 
312
411
  EventHandler.on(window, EVENT_KEY_RESIZE, () => {
313
- if (this._isShown && !this._isTransitioning) this._adjustDialog();
412
+ if (this._isShown && !this._isTransitioning) {
413
+ this._adjustDialog();
414
+ this._syncInteractiveBounds();
415
+ }
314
416
  });
315
417
 
316
418
  EventHandler.on(this._element, EVENT_KEY_MOUSEDOWN_DISMISS, event => {
@@ -409,4 +511,4 @@ EventHandler.on(document, EVENT_KEY_DOM_LOADED_DATA_API, function () {
409
511
  }
410
512
  })
411
513
 
412
- export default VGModal;
514
+ export default VGModal;