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,437 @@
1
+ const DEFAULT_OPTIONS = {
2
+ enable: false,
3
+ minWidth: 300,
4
+ minHeight: 160,
5
+ edgeSize: 8,
6
+ debug: false,
7
+ };
8
+
9
+ const DIRECTION_TO_CURSOR = {
10
+ n: 'ns-resize',
11
+ s: 'ns-resize',
12
+ e: 'ew-resize',
13
+ w: 'ew-resize',
14
+ ne: 'nesw-resize',
15
+ sw: 'nesw-resize',
16
+ nw: 'nwse-resize',
17
+ se: 'nwse-resize',
18
+ };
19
+
20
+ class VGToastResize {
21
+ constructor(modalElement, dialogElement, options = {}) {
22
+ this._modalElement = modalElement;
23
+ this._dialogElement = dialogElement;
24
+ this._contentElement = this._dialogElement ? this._dialogElement.querySelector('.vg-toast-wrapper') : null;
25
+ this._options = this._normalizeOptions(options);
26
+ this._pointerId = null;
27
+ this._direction = '';
28
+ this._startX = 0;
29
+ this._startY = 0;
30
+ this._currentX = 0;
31
+ this._currentY = 0;
32
+ this._startWidth = 0;
33
+ this._startHeight = 0;
34
+ this._startLeft = 0;
35
+ this._startTop = 0;
36
+ this._startRight = 0;
37
+ this._startBottom = 0;
38
+ this._isEnabled = false;
39
+ this._observer = null;
40
+ this._syncFrameId = null;
41
+ this._resizeFrameId = null;
42
+ this._lockedIframes = [];
43
+ this._debugElement = null;
44
+ this._previousMaxWidth = '';
45
+ this._previousMaxHeight = '';
46
+ this._previousPointerEvents = '';
47
+ this._previousMinHeight = '';
48
+ this._previousOverflow = '';
49
+ this._previousTransition = '';
50
+ this._previousWillChange = '';
51
+ this._previousContentHeight = '';
52
+ this._previousContentMaxHeight = '';
53
+ this._previousContentOverflow = '';
54
+
55
+ this._onPointerDown = this._onPointerDown.bind(this);
56
+ this._onPointerMove = this._onPointerMove.bind(this);
57
+ this._onPointerUp = this._onPointerUp.bind(this);
58
+ this._onPointerLeave = this._onPointerLeave.bind(this);
59
+ this._applyResizePosition = this._applyResizePosition.bind(this);
60
+ }
61
+
62
+ setOptions(options = {}) {
63
+ this._options = this._normalizeOptions(options, this._options);
64
+ this._updateDebugOverlay();
65
+ }
66
+
67
+ enable() {
68
+ if (!this._dialogElement || this._isEnabled) return;
69
+
70
+ this._updateDebugOverlay();
71
+ this._modalElement.addEventListener('pointermove', this._onPointerMove);
72
+ this._modalElement.addEventListener('pointerdown', this._onPointerDown);
73
+ this._modalElement.addEventListener('pointerleave', this._onPointerLeave);
74
+ this._startObserveSizeChanges();
75
+ this._isEnabled = true;
76
+ }
77
+
78
+ disable() {
79
+ if (!this._isEnabled) return;
80
+
81
+ this._modalElement.removeEventListener('pointermove', this._onPointerMove);
82
+ this._modalElement.removeEventListener('pointerdown', this._onPointerDown);
83
+ this._modalElement.removeEventListener('pointerleave', this._onPointerLeave);
84
+ document.removeEventListener('pointermove', this._onPointerMove);
85
+ document.removeEventListener('pointerup', this._onPointerUp);
86
+ document.removeEventListener('pointercancel', this._onPointerUp);
87
+ this._cancelResizeFrame();
88
+ this._stopObserveSizeChanges();
89
+ this._pointerId = null;
90
+ this._direction = '';
91
+ this._dialogElement.style.cursor = '';
92
+ delete this._modalElement.dataset.vgToastResizing;
93
+ this._unlockEmbeddedFrames();
94
+ this._restoreResizeStyles();
95
+ this._setDebugVisibility(false);
96
+ this._isEnabled = false;
97
+ }
98
+
99
+ syncToViewport() {
100
+ if (this._modalElement && this._modalElement.dataset.vgToastDragging === 'true') return;
101
+ if (!this._isPreparedForInteraction()) return;
102
+
103
+ const rect = this._dialogElement.getBoundingClientRect();
104
+ const currentWidth = this._dialogElement.offsetWidth || rect.width;
105
+ const currentHeight = this._dialogElement.offsetHeight || rect.height;
106
+ const currentLeftValue = Number.parseFloat(this._dialogElement.style.left);
107
+ const currentTopValue = Number.parseFloat(this._dialogElement.style.top);
108
+ const currentLeft = Number.isFinite(currentLeftValue) ? currentLeftValue : rect.left;
109
+ const currentTop = Number.isFinite(currentTopValue) ? currentTopValue : rect.top;
110
+ const maxWidth = window.innerWidth;
111
+ const maxHeight = window.innerHeight;
112
+ const width = Math.max(this._options.minWidth, Math.min(currentWidth, maxWidth));
113
+ const height = Math.max(this._options.minHeight, Math.min(currentHeight, maxHeight));
114
+ const maxLeft = Math.max(0, window.innerWidth - width);
115
+ const maxTop = Math.max(0, window.innerHeight - height);
116
+ const left = Math.min(maxLeft, Math.max(0, currentLeft));
117
+ const top = Math.min(maxTop, Math.max(0, currentTop));
118
+
119
+ if (Math.abs(currentWidth - width) > 0.5) {
120
+ this._dialogElement.style.width = `${width}px`;
121
+ }
122
+
123
+ if (Math.abs(currentHeight - height) > 0.5) {
124
+ this._dialogElement.style.height = `${height}px`;
125
+ }
126
+
127
+ this._dialogElement.style.left = `${left}px`;
128
+ this._dialogElement.style.top = `${top}px`;
129
+ this._updateDebugValues();
130
+ }
131
+
132
+ _onPointerDown(event) {
133
+ if (event.button !== 0) return;
134
+ if (event.defaultPrevented) return;
135
+
136
+ const direction = this._getDirectionFromPointer(event);
137
+ if (!direction) return;
138
+
139
+ event.preventDefault();
140
+ this._pointerId = event.pointerId;
141
+ this._direction = direction;
142
+
143
+ const rect = this._dialogElement.getBoundingClientRect();
144
+ this._applyResizeStyles();
145
+ this._preparePosition(rect);
146
+ this._startX = event.clientX;
147
+ this._startY = event.clientY;
148
+ this._currentX = event.clientX;
149
+ this._currentY = event.clientY;
150
+ this._startWidth = rect.width;
151
+ this._startHeight = rect.height;
152
+ this._startLeft = rect.left;
153
+ this._startTop = rect.top;
154
+ this._startRight = rect.right;
155
+ this._startBottom = rect.bottom;
156
+ this._lockEmbeddedFrames();
157
+ this._modalElement.dataset.vgToastResizing = 'true';
158
+ this._setDebugVisibility(true);
159
+ this._updateDebugValues();
160
+
161
+ document.addEventListener('pointermove', this._onPointerMove);
162
+ document.addEventListener('pointerup', this._onPointerUp);
163
+ document.addEventListener('pointercancel', this._onPointerUp);
164
+ }
165
+
166
+ _onPointerMove(event) {
167
+ if (this._pointerId === null) {
168
+ const direction = this._getDirectionFromPointer(event);
169
+ this._dialogElement.style.cursor = direction ? DIRECTION_TO_CURSOR[direction] : '';
170
+ return;
171
+ }
172
+
173
+ if (event.pointerId !== this._pointerId) return;
174
+
175
+ this._currentX = event.clientX;
176
+ this._currentY = event.clientY;
177
+ if (this._resizeFrameId !== null) return;
178
+ this._resizeFrameId = window.requestAnimationFrame(this._applyResizePosition);
179
+ }
180
+
181
+ _applyResizePosition() {
182
+ this._resizeFrameId = null;
183
+ if (this._pointerId === null) return;
184
+
185
+ const deltaX = this._currentX - this._startX;
186
+ const deltaY = this._currentY - this._startY;
187
+ let width = this._startWidth;
188
+ let height = this._startHeight;
189
+ let left = this._startLeft;
190
+ let top = this._startTop;
191
+
192
+ if (this._direction.includes('e')) {
193
+ const maxWidth = Math.max(this._options.minWidth, window.innerWidth - this._startLeft);
194
+ width = Math.min(maxWidth, Math.max(this._options.minWidth, this._startWidth + deltaX));
195
+ }
196
+
197
+ if (this._direction.includes('s')) {
198
+ const maxHeight = Math.max(this._options.minHeight, window.innerHeight - this._startTop);
199
+ height = Math.min(maxHeight, Math.max(this._options.minHeight, this._startHeight + deltaY));
200
+ }
201
+
202
+ if (this._direction.includes('w')) {
203
+ const maxLeft = this._startRight - this._options.minWidth;
204
+ left = Math.min(maxLeft, Math.max(0, this._startLeft + deltaX));
205
+ width = this._startRight - left;
206
+ }
207
+
208
+ if (this._direction.includes('n')) {
209
+ const maxTop = this._startBottom - this._options.minHeight;
210
+ top = Math.min(maxTop, Math.max(0, this._startTop + deltaY));
211
+ height = this._startBottom - top;
212
+ }
213
+
214
+ this._dialogElement.style.left = `${left}px`;
215
+ this._dialogElement.style.top = `${top}px`;
216
+ this._dialogElement.style.width = `${width}px`;
217
+ this._dialogElement.style.height = `${height}px`;
218
+ this._updateDebugValues();
219
+ }
220
+
221
+ _onPointerUp(event) {
222
+ if (event.pointerId !== this._pointerId) return;
223
+
224
+ document.removeEventListener('pointermove', this._onPointerMove);
225
+ document.removeEventListener('pointerup', this._onPointerUp);
226
+ document.removeEventListener('pointercancel', this._onPointerUp);
227
+ this._cancelResizeFrame();
228
+ this._applyResizePosition();
229
+ this._pointerId = null;
230
+ this._direction = '';
231
+ delete this._modalElement.dataset.vgToastResizing;
232
+ this._unlockEmbeddedFrames();
233
+ this._setDebugVisibility(this._options.debug);
234
+ this._updateDebugValues();
235
+ }
236
+
237
+ _onPointerLeave() {
238
+ if (this._pointerId !== null) return;
239
+ this._dialogElement.style.cursor = '';
240
+ }
241
+
242
+ _getDirectionFromPointer(event) {
243
+ const rect = this._dialogElement.getBoundingClientRect();
244
+ const offsetX = event.clientX - rect.left;
245
+ const offsetY = event.clientY - rect.top;
246
+ const edgeSize = this._options.edgeSize;
247
+ const insideDialog = offsetX >= 0 && offsetX <= rect.width && offsetY >= 0 && offsetY <= rect.height;
248
+ if (!insideDialog) return '';
249
+
250
+ const nearTop = offsetY >= 0 && offsetY <= edgeSize;
251
+ const nearBottom = offsetY <= rect.height && offsetY >= rect.height - edgeSize;
252
+ const nearLeft = offsetX >= 0 && offsetX <= edgeSize;
253
+ const nearRight = offsetX <= rect.width && offsetX >= rect.width - edgeSize;
254
+
255
+ if (nearTop && nearLeft) return 'nw';
256
+ if (nearTop && nearRight) return 'ne';
257
+ if (nearBottom && nearLeft) return 'sw';
258
+ if (nearBottom && nearRight) return 'se';
259
+ if (nearTop) return 'n';
260
+ if (nearBottom) return 's';
261
+ if (nearLeft) return 'w';
262
+ if (nearRight) return 'e';
263
+
264
+ return '';
265
+ }
266
+
267
+ _preparePosition(rect) {
268
+ this._dialogElement.style.position = 'fixed';
269
+ this._dialogElement.style.margin = '0';
270
+ this._dialogElement.style.left = `${rect.left}px`;
271
+ this._dialogElement.style.top = `${rect.top}px`;
272
+ this._dialogElement.style.width = `${rect.width}px`;
273
+ this._dialogElement.style.height = `${rect.height}px`;
274
+ this._dialogElement.style.transform = 'none';
275
+ this._dialogElement.style.translate = 'none';
276
+ }
277
+
278
+ _isPreparedForInteraction() {
279
+ return this._dialogElement.style.position === 'fixed' && this._dialogElement.style.transform === 'none';
280
+ }
281
+
282
+ _startObserveSizeChanges() {
283
+ if (typeof ResizeObserver === 'undefined' || this._observer) return;
284
+
285
+ this._observer = new ResizeObserver(() => {
286
+ if (this._pointerId !== null) return;
287
+ if (this._modalElement && this._modalElement.dataset.vgToastDragging === 'true') return;
288
+ if (this._syncFrameId !== null) return;
289
+
290
+ this._syncFrameId = window.requestAnimationFrame(() => {
291
+ this._syncFrameId = null;
292
+ this.syncToViewport();
293
+ });
294
+ });
295
+
296
+ this._observer.observe(this._dialogElement);
297
+ }
298
+
299
+ _stopObserveSizeChanges() {
300
+ if (this._observer) {
301
+ this._observer.disconnect();
302
+ this._observer = null;
303
+ }
304
+
305
+ if (this._syncFrameId !== null) {
306
+ window.cancelAnimationFrame(this._syncFrameId);
307
+ this._syncFrameId = null;
308
+ }
309
+ }
310
+
311
+ _applyResizeStyles() {
312
+ this._previousMaxWidth = this._dialogElement.style.maxWidth;
313
+ this._previousMaxHeight = this._dialogElement.style.maxHeight;
314
+ this._previousPointerEvents = this._dialogElement.style.pointerEvents;
315
+ this._previousMinHeight = this._dialogElement.style.minHeight;
316
+ this._previousOverflow = this._dialogElement.style.overflow;
317
+ this._previousTransition = this._dialogElement.style.transition;
318
+ this._previousWillChange = this._dialogElement.style.willChange;
319
+
320
+ this._dialogElement.style.maxWidth = 'none';
321
+ this._dialogElement.style.maxHeight = 'none';
322
+ this._dialogElement.style.pointerEvents = 'auto';
323
+ this._dialogElement.style.minHeight = `${this._options.minHeight}px`;
324
+ this._dialogElement.style.overflow = 'hidden';
325
+ this._dialogElement.style.transition = 'none';
326
+ this._dialogElement.style.willChange = 'left, top, width, height';
327
+
328
+ if (this._contentElement) {
329
+ this._previousContentHeight = this._contentElement.style.height;
330
+ this._previousContentMaxHeight = this._contentElement.style.maxHeight;
331
+ this._previousContentOverflow = this._contentElement.style.overflow;
332
+ this._contentElement.style.height = '100%';
333
+ this._contentElement.style.maxHeight = '100%';
334
+ this._contentElement.style.overflow = 'auto';
335
+ }
336
+ }
337
+
338
+ _restoreResizeStyles() {
339
+ this._dialogElement.style.maxWidth = this._previousMaxWidth;
340
+ this._dialogElement.style.maxHeight = this._previousMaxHeight;
341
+ this._dialogElement.style.pointerEvents = this._previousPointerEvents;
342
+ this._dialogElement.style.minHeight = this._previousMinHeight;
343
+ this._dialogElement.style.overflow = this._previousOverflow;
344
+ this._dialogElement.style.transition = this._previousTransition;
345
+ this._dialogElement.style.willChange = this._previousWillChange;
346
+
347
+ if (this._contentElement) {
348
+ this._contentElement.style.height = this._previousContentHeight;
349
+ this._contentElement.style.maxHeight = this._previousContentMaxHeight;
350
+ this._contentElement.style.overflow = this._previousContentOverflow;
351
+ }
352
+ }
353
+
354
+ _normalizeOptions(options, base = DEFAULT_OPTIONS) {
355
+ const merged = {...base, ...options};
356
+ const minWidth = Number(merged.minWidth);
357
+ const minHeight = Number(merged.minHeight);
358
+ const edgeSize = Number(merged.edgeSize);
359
+
360
+ return {
361
+ ...merged,
362
+ minWidth: Number.isFinite(minWidth) && minWidth > 0 ? minWidth : DEFAULT_OPTIONS.minWidth,
363
+ minHeight: Number.isFinite(minHeight) && minHeight > 0 ? minHeight : DEFAULT_OPTIONS.minHeight,
364
+ edgeSize: Number.isFinite(edgeSize) && edgeSize > 0 ? edgeSize : DEFAULT_OPTIONS.edgeSize,
365
+ debug: Boolean(merged.debug),
366
+ };
367
+ }
368
+
369
+ _cancelResizeFrame() {
370
+ if (this._resizeFrameId === null) return;
371
+ window.cancelAnimationFrame(this._resizeFrameId);
372
+ this._resizeFrameId = null;
373
+ }
374
+
375
+ _lockEmbeddedFrames() {
376
+ if (!this._dialogElement || this._lockedIframes.length) return;
377
+
378
+ const iframes = this._dialogElement.querySelectorAll('iframe');
379
+ for (const frame of iframes) {
380
+ this._lockedIframes.push({
381
+ element: frame,
382
+ pointerEvents: frame.style.pointerEvents,
383
+ });
384
+ frame.style.pointerEvents = 'none';
385
+ }
386
+ }
387
+
388
+ _unlockEmbeddedFrames() {
389
+ if (!this._lockedIframes.length) return;
390
+
391
+ for (const item of this._lockedIframes) {
392
+ item.element.style.pointerEvents = item.pointerEvents;
393
+ }
394
+ this._lockedIframes = [];
395
+ }
396
+
397
+ _updateDebugOverlay() {
398
+ if (!this._options.debug) {
399
+ this._setDebugVisibility(false);
400
+ return;
401
+ }
402
+
403
+ if (!this._debugElement) {
404
+ this._debugElement = document.createElement('div');
405
+ this._debugElement.style.position = 'absolute';
406
+ this._debugElement.style.left = '8px';
407
+ this._debugElement.style.bottom = '8px';
408
+ this._debugElement.style.zIndex = '6';
409
+ this._debugElement.style.padding = '4px 6px';
410
+ this._debugElement.style.borderRadius = '4px';
411
+ this._debugElement.style.background = 'rgba(0, 0, 0, 0.72)';
412
+ this._debugElement.style.color = '#fff';
413
+ this._debugElement.style.fontSize = '11px';
414
+ this._debugElement.style.lineHeight = '1.3';
415
+ this._debugElement.style.pointerEvents = 'none';
416
+ this._dialogElement.append(this._debugElement);
417
+ }
418
+
419
+ this._setDebugVisibility(true);
420
+ this._updateDebugValues();
421
+ }
422
+
423
+ _setDebugVisibility(visible) {
424
+ if (!this._debugElement) return;
425
+ this._debugElement.style.display = visible ? 'block' : 'none';
426
+ }
427
+
428
+ _updateDebugValues() {
429
+ if (!this._debugElement || !this._options.debug) return;
430
+
431
+ const rect = this._dialogElement.getBoundingClientRect();
432
+ this._debugElement.textContent = `w:${Math.round(rect.width)} h:${Math.round(rect.height)} x:${Math.round(rect.left)} y:${Math.round(rect.top)}`;
433
+ }
434
+ }
435
+
436
+ export default VGToastResize;
437
+
@@ -0,0 +1,140 @@
1
+ const VIDEO_EXTENSIONS = new Set([
2
+ 'mp4',
3
+ 'webm',
4
+ 'mov',
5
+ 'mkv',
6
+ 'avi',
7
+ 'm4v',
8
+ 'ogv'
9
+ ]);
10
+
11
+ const getFileExtension = (file) => {
12
+ const fileName = String(file?.name || '').toLowerCase();
13
+ const dot = fileName.lastIndexOf('.');
14
+ if (dot < 0 || dot >= fileName.length - 1) {
15
+ return '';
16
+ }
17
+ return fileName.slice(dot + 1);
18
+ };
19
+
20
+ const waitEvent = (node, eventName) => new Promise((resolve, reject) => {
21
+ const onResolve = () => {
22
+ cleanup();
23
+ resolve(true);
24
+ };
25
+
26
+ const onReject = () => {
27
+ cleanup();
28
+ reject(new Error(eventName));
29
+ };
30
+
31
+ const cleanup = () => {
32
+ node.removeEventListener(eventName, onResolve);
33
+ node.removeEventListener('error', onReject);
34
+ };
35
+
36
+ node.addEventListener(eventName, onResolve, { once: true });
37
+ node.addEventListener('error', onReject, { once: true });
38
+ });
39
+
40
+ const seekVideo = (video, time) => new Promise((resolve, reject) => {
41
+ const onSeeked = () => {
42
+ cleanup();
43
+ resolve(true);
44
+ };
45
+
46
+ const onError = () => {
47
+ cleanup();
48
+ reject(new Error('seek'));
49
+ };
50
+
51
+ const cleanup = () => {
52
+ video.removeEventListener('seeked', onSeeked);
53
+ video.removeEventListener('error', onError);
54
+ };
55
+
56
+ video.addEventListener('seeked', onSeeked, { once: true });
57
+ video.addEventListener('error', onError, { once: true });
58
+ video.currentTime = time;
59
+ });
60
+
61
+ const canvasToBlob = (canvas, type = 'image/jpeg', quality = 0.92) => new Promise((resolve) => {
62
+ canvas.toBlob((blob) => resolve(blob || null), type, quality);
63
+ });
64
+
65
+ const extractVideoMetadata = async (file) => {
66
+ if (!(file instanceof File)) {
67
+ return null;
68
+ }
69
+
70
+ const ext = getFileExtension(file);
71
+ const isVideoType = String(file.type || '').toLowerCase().startsWith('video/');
72
+ if (!isVideoType && !VIDEO_EXTENSIONS.has(ext)) {
73
+ return null;
74
+ }
75
+
76
+ if (!file.size) {
77
+ return null;
78
+ }
79
+
80
+ let objectUrl = '';
81
+ try {
82
+ objectUrl = URL.createObjectURL(file);
83
+
84
+ const video = document.createElement('video');
85
+ video.preload = 'metadata';
86
+ video.muted = true;
87
+ video.playsInline = true;
88
+ video.crossOrigin = 'anonymous';
89
+ video.src = objectUrl;
90
+
91
+ await waitEvent(video, 'loadedmetadata');
92
+
93
+ const width = Number(video.videoWidth || 0);
94
+ const height = Number(video.videoHeight || 0);
95
+ const duration = Number(video.duration || 0);
96
+
97
+ if (!width || !height || !Number.isFinite(width) || !Number.isFinite(height)) {
98
+ return {
99
+ duration: Number.isFinite(duration) ? duration : 0,
100
+ width: 0,
101
+ height: 0
102
+ };
103
+ }
104
+
105
+ const seekTime = duration > 1 ? Math.min(1, Math.max(0, duration / 2)) : 0;
106
+ await seekVideo(video, seekTime);
107
+
108
+ const canvas = document.createElement('canvas');
109
+ canvas.width = width;
110
+ canvas.height = height;
111
+ const ctx = canvas.getContext('2d');
112
+ if (!ctx) {
113
+ return {
114
+ duration: Number.isFinite(duration) ? duration : 0,
115
+ width,
116
+ height
117
+ };
118
+ }
119
+
120
+ ctx.drawImage(video, 0, 0, width, height);
121
+ const posterBlob = await canvasToBlob(canvas, 'image/jpeg', 0.92);
122
+
123
+ return {
124
+ duration: Number.isFinite(duration) ? duration : 0,
125
+ width,
126
+ height,
127
+ posterBlob: posterBlob || null
128
+ };
129
+ } catch {
130
+ return null;
131
+ } finally {
132
+ if (objectUrl) {
133
+ URL.revokeObjectURL(objectUrl);
134
+ }
135
+ }
136
+ };
137
+
138
+ export {
139
+ extractVideoMetadata
140
+ };