autumnnote 1.0.3 → 1.0.5
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/dist/autumnnote.css +1 -1
- package/dist/autumnnote.es.js +2319 -2001
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +59 -27
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/Context.js +7 -2
- package/src/js/editing/Style.js +45 -48
- package/src/js/index.js +1 -1
- package/src/js/index.umd.js +12 -12
- package/src/js/module/ImageCropOverlay.js +541 -0
- package/src/js/module/ImageResizer.js +41 -26
- package/src/js/module/ImageTooltip.js +55 -6
- package/src/js/module/ShortcutsDialog.js +1 -1
- package/src/js/module/TableTooltip.js +93 -68
- package/src/js/module/VideoResizer.js +45 -33
- package/src/js/module/VideoTooltip.js +9 -6
- package/src/styles/autumnnote.scss +49 -0
- package/types/index.d.ts +56 -9
|
@@ -11,6 +11,9 @@ const ICONS = {
|
|
|
11
11
|
originalSize:`<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>`,
|
|
12
12
|
deleteImg: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`,
|
|
13
13
|
caption: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="11" rx="2"/><line x1="6" y1="18" x2="18" y2="18"/><line x1="9" y1="21" x2="15" y2="21"/></svg>`,
|
|
14
|
+
rotateLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><polyline points="3 3 3 8 8 8"/></svg>`,
|
|
15
|
+
rotateRight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><polyline points="21 3 21 8 16 8"/></svg>`,
|
|
16
|
+
crop: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 2 6 18 22 18"/><polyline points="2 6 18 6 18 22"/></svg>`,
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
const SHOW_DELAY = 100;
|
|
@@ -40,13 +43,13 @@ export class ImageTooltip {
|
|
|
40
43
|
if (img && editable.contains(img) && !img.closest('a[href]')) {
|
|
41
44
|
this._scheduleShow(img);
|
|
42
45
|
}
|
|
43
|
-
}),
|
|
46
|
+
}, { passive: true }),
|
|
44
47
|
on(editable, 'mouseout', (e) => {
|
|
45
48
|
const to = e.relatedTarget;
|
|
46
49
|
if (!to || (!editable.contains(to) && !this._el.contains(to))) {
|
|
47
50
|
this._scheduleHide();
|
|
48
51
|
}
|
|
49
|
-
}),
|
|
52
|
+
}, { passive: true }),
|
|
50
53
|
// Hide when image is deselected by clicking elsewhere
|
|
51
54
|
on(document, 'click', (e) => {
|
|
52
55
|
if (this._activeImg && !this._activeImg.contains(e.target) && !this._el.contains(e.target)) {
|
|
@@ -103,6 +106,16 @@ export class ImageTooltip {
|
|
|
103
106
|
|
|
104
107
|
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
105
108
|
|
|
109
|
+
el.appendChild(this._makeBtn(ICONS.rotateLeft, 'Rotate Left', () => this._rotate(-90)));
|
|
110
|
+
el.appendChild(this._makeBtn(ICONS.rotateRight, 'Rotate Right', () => this._rotate(90)));
|
|
111
|
+
|
|
112
|
+
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
113
|
+
|
|
114
|
+
this._cropBtn = this._makeBtn(ICONS.crop, 'Crop Image', () => this._crop());
|
|
115
|
+
el.appendChild(this._cropBtn);
|
|
116
|
+
|
|
117
|
+
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
118
|
+
|
|
106
119
|
this._captionBtn = this._makeBtn(ICONS.caption, 'Add / Edit Caption', () => this._toggleCaption());
|
|
107
120
|
el.appendChild(this._captionBtn);
|
|
108
121
|
|
|
@@ -166,7 +179,10 @@ export class ImageTooltip {
|
|
|
166
179
|
|
|
167
180
|
_show(img) {
|
|
168
181
|
this._el.style.display = 'flex';
|
|
169
|
-
|
|
182
|
+
// Defer positioning: offsetWidth on a newly-visible element forces layout
|
|
183
|
+
requestAnimationFrame(() => {
|
|
184
|
+
if (this._activeImg) this._positionNear(this._activeImg);
|
|
185
|
+
});
|
|
170
186
|
}
|
|
171
187
|
|
|
172
188
|
_hide() {
|
|
@@ -213,7 +229,7 @@ export class ImageTooltip {
|
|
|
213
229
|
target.style.marginRight = value === 'left' ? '12px' : '';
|
|
214
230
|
this.context.invoke('editor.afterCommand');
|
|
215
231
|
this.context.invoke('imageResizer.updateOverlay');
|
|
216
|
-
this._positionNear(
|
|
232
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
217
233
|
}
|
|
218
234
|
|
|
219
235
|
_setCenter() {
|
|
@@ -226,7 +242,7 @@ export class ImageTooltip {
|
|
|
226
242
|
target.style.marginRight = 'auto';
|
|
227
243
|
this.context.invoke('editor.afterCommand');
|
|
228
244
|
this.context.invoke('imageResizer.updateOverlay');
|
|
229
|
-
this._positionNear(
|
|
245
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
230
246
|
}
|
|
231
247
|
|
|
232
248
|
_resetSize() {
|
|
@@ -236,7 +252,31 @@ export class ImageTooltip {
|
|
|
236
252
|
img.style.height = '';
|
|
237
253
|
this.context.invoke('editor.afterCommand');
|
|
238
254
|
this.context.invoke('imageResizer.updateOverlay');
|
|
239
|
-
this._positionNear(
|
|
255
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Rotate the active image by `delta` degrees (±90).
|
|
260
|
+
* Reads the existing rotate() value from the transform style so
|
|
261
|
+
* repeated clicks accumulate correctly.
|
|
262
|
+
* @param {number} delta
|
|
263
|
+
*/
|
|
264
|
+
_rotate(delta) {
|
|
265
|
+
const img = this._activeImg;
|
|
266
|
+
if (!img) return;
|
|
267
|
+
// Parse current rotation angle from inline transform
|
|
268
|
+
const current = img.style.transform || '';
|
|
269
|
+
const match = current.match(/rotate\((-?[\d.]+)deg\)/);
|
|
270
|
+
const prev = match ? parseFloat(match[1]) : 0;
|
|
271
|
+
const next = (prev + delta + 360) % 360; // normalise to [0, 360)
|
|
272
|
+
// Preserve any other transform functions (e.g. scale), replace only rotate()
|
|
273
|
+
const cleaned = current.replace(/rotate\(-?[\d.]+deg\)/, '').trim();
|
|
274
|
+
img.style.transform = cleaned
|
|
275
|
+
? `${cleaned} rotate(${next}deg)`
|
|
276
|
+
: next === 0 ? '' : `rotate(${next}deg)`;
|
|
277
|
+
this.context.invoke('editor.afterCommand');
|
|
278
|
+
this.context.invoke('imageResizer.updateOverlay');
|
|
279
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
240
280
|
}
|
|
241
281
|
|
|
242
282
|
_delete() {
|
|
@@ -253,6 +293,15 @@ export class ImageTooltip {
|
|
|
253
293
|
this.context.invoke('editor.afterCommand');
|
|
254
294
|
}
|
|
255
295
|
|
|
296
|
+
_crop() {
|
|
297
|
+
const img = this._activeImg;
|
|
298
|
+
if (!img) return;
|
|
299
|
+
// Hide the tooltip while the crop overlay is active
|
|
300
|
+
this._hide();
|
|
301
|
+
// Delegate to imageCropOverlay module
|
|
302
|
+
this.context.invoke('imageCropOverlay.open', img);
|
|
303
|
+
}
|
|
304
|
+
|
|
256
305
|
_toggleCaption() {
|
|
257
306
|
const img = this._activeImg;
|
|
258
307
|
if (!img) return;
|
|
@@ -110,7 +110,7 @@ export class TableTooltip {
|
|
|
110
110
|
if (cell) this._activeCell = cell;
|
|
111
111
|
this._scheduleShow(table);
|
|
112
112
|
}
|
|
113
|
-
}),
|
|
113
|
+
}, { passive: true }),
|
|
114
114
|
on(editable, 'mouseout', (e) => {
|
|
115
115
|
const to = e.relatedTarget;
|
|
116
116
|
if (!to || (
|
|
@@ -120,7 +120,7 @@ export class TableTooltip {
|
|
|
120
120
|
)) {
|
|
121
121
|
this._scheduleHide();
|
|
122
122
|
}
|
|
123
|
-
}),
|
|
123
|
+
}, { passive: true }),
|
|
124
124
|
on(document, 'click', (e) => {
|
|
125
125
|
if (this._activeTable &&
|
|
126
126
|
!this._activeTable.contains(e.target) &&
|
|
@@ -155,8 +155,11 @@ export class TableTooltip {
|
|
|
155
155
|
let _startW = 0;
|
|
156
156
|
let _startH = 0;
|
|
157
157
|
let _colIdx = -1;
|
|
158
|
+
let _colCells = null; // cells cached at drag-start — avoids querySelectorAll every frame
|
|
158
159
|
let _row = null;
|
|
159
160
|
let _table = null;
|
|
161
|
+
let _rafDocMove = null; // pending rAF handle for resize drag
|
|
162
|
+
let _rafEditorMove = null; // pending rAF handle for cursor detection
|
|
160
163
|
|
|
161
164
|
const clearHover = () => {
|
|
162
165
|
if (_nearCell) { _nearCell.style.cursor = ''; _nearCell = null; }
|
|
@@ -165,27 +168,29 @@ export class TableTooltip {
|
|
|
165
168
|
|
|
166
169
|
const onEditorMove = (e) => {
|
|
167
170
|
if (_resizing) return;
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
171
|
+
// Throttle to one check per animation frame — getBoundingClientRect forces reflow
|
|
172
|
+
if (_rafEditorMove !== null) return;
|
|
173
|
+
const target = e.target;
|
|
174
|
+
const clientX = e.clientX;
|
|
175
|
+
const clientY = e.clientY;
|
|
176
|
+
_rafEditorMove = requestAnimationFrame(() => {
|
|
177
|
+
_rafEditorMove = null;
|
|
178
|
+
const cell = target.closest('td, th');
|
|
179
|
+
if (!cell || !editable.contains(cell)) { clearHover(); return; }
|
|
180
|
+
if (_nearCell && _nearCell !== cell) _nearCell.style.cursor = '';
|
|
181
|
+
const rect = cell.getBoundingClientRect();
|
|
182
|
+
const onRight = Math.abs(clientX - rect.right) < HIT;
|
|
183
|
+
const onBottom = Math.abs(clientY - rect.bottom) < HIT;
|
|
184
|
+
if (onRight) {
|
|
185
|
+
cell.style.cursor = 'col-resize';
|
|
186
|
+
_nearCell = cell; _nearEdge = 'col';
|
|
187
|
+
} else if (onBottom) {
|
|
188
|
+
cell.style.cursor = 'row-resize';
|
|
189
|
+
_nearCell = cell; _nearEdge = 'row';
|
|
190
|
+
} else {
|
|
191
|
+
clearHover();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
189
194
|
};
|
|
190
195
|
|
|
191
196
|
const onEditorDown = (e) => {
|
|
@@ -196,8 +201,14 @@ export class TableTooltip {
|
|
|
196
201
|
_startY = e.clientY;
|
|
197
202
|
_table = _nearCell.closest('table');
|
|
198
203
|
if (_edge === 'col') {
|
|
199
|
-
_startW
|
|
200
|
-
_colIdx
|
|
204
|
+
_startW = _nearCell.offsetWidth;
|
|
205
|
+
_colIdx = getVisualColIndex(_nearCell);
|
|
206
|
+
// Cache column cells once so onDocMove never runs querySelectorAll per frame
|
|
207
|
+
_colCells = _colIdx >= 0
|
|
208
|
+
? Array.from(_table.querySelectorAll('tr'))
|
|
209
|
+
.map(r => getCellAtVisualCol(r, _colIdx))
|
|
210
|
+
.filter(Boolean)
|
|
211
|
+
: [];
|
|
201
212
|
document.body.style.cursor = 'col-resize';
|
|
202
213
|
} else {
|
|
203
214
|
_row = _nearCell.closest('tr');
|
|
@@ -211,34 +222,42 @@ export class TableTooltip {
|
|
|
211
222
|
|
|
212
223
|
const onDocMove = (e) => {
|
|
213
224
|
if (!_resizing) return;
|
|
214
|
-
if
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
// Skip if a frame is already scheduled — avoids per-pixel style thrashing
|
|
226
|
+
if (_rafDocMove !== null) return;
|
|
227
|
+
const clientX = e.clientX;
|
|
228
|
+
const clientY = e.clientY;
|
|
229
|
+
_rafDocMove = requestAnimationFrame(() => {
|
|
230
|
+
_rafDocMove = null;
|
|
231
|
+
if (_edge === 'col') {
|
|
232
|
+
const newW = Math.max(30, _startW + (clientX - _startX));
|
|
233
|
+
for (const c of _colCells) {
|
|
234
|
+
c.style.width = `${newW}px`;
|
|
235
|
+
c.style.minWidth = `${newW}px`;
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
const newH = Math.max(20, _startH + (clientY - _startY));
|
|
239
|
+
if (_row) {
|
|
240
|
+
for (const c of _row.cells) {
|
|
241
|
+
c.style.height = `${newH}px`;
|
|
242
|
+
c.style.minHeight = `${newH}px`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
229
245
|
}
|
|
230
|
-
}
|
|
246
|
+
});
|
|
231
247
|
};
|
|
232
248
|
|
|
233
249
|
const onDocUp = () => {
|
|
234
250
|
if (!_resizing) return;
|
|
251
|
+
// Cancel any in-flight rAF so stale writes don't land after mouseup
|
|
252
|
+
if (_rafDocMove !== null) { cancelAnimationFrame(_rafDocMove); _rafDocMove = null; }
|
|
235
253
|
_resizing = false;
|
|
236
254
|
document.body.style.userSelect = '';
|
|
237
255
|
document.body.style.cursor = '';
|
|
238
|
-
_edge
|
|
239
|
-
_table
|
|
240
|
-
_row
|
|
241
|
-
_colIdx
|
|
256
|
+
_edge = null;
|
|
257
|
+
_table = null;
|
|
258
|
+
_row = null;
|
|
259
|
+
_colIdx = -1;
|
|
260
|
+
_colCells = null;
|
|
242
261
|
this.context.invoke('editor.afterCommand');
|
|
243
262
|
};
|
|
244
263
|
|
|
@@ -375,7 +394,10 @@ export class TableTooltip {
|
|
|
375
394
|
_show() {
|
|
376
395
|
if (!this._activeTable) return;
|
|
377
396
|
this._el.style.display = 'flex';
|
|
378
|
-
|
|
397
|
+
// Defer: offsetWidth on a newly-visible element forces layout synchronously
|
|
398
|
+
requestAnimationFrame(() => {
|
|
399
|
+
if (this._activeTable) this._positionNear(this._activeTable);
|
|
400
|
+
});
|
|
379
401
|
}
|
|
380
402
|
|
|
381
403
|
_hide() {
|
|
@@ -452,7 +474,8 @@ export class TableTooltip {
|
|
|
452
474
|
}
|
|
453
475
|
if (position === 'above') row.parentElement?.insertBefore(newRow, row);
|
|
454
476
|
else row.insertAdjacentElement('afterend', newRow);
|
|
455
|
-
|
|
477
|
+
// Defer positioning until the browser has painted the new layout
|
|
478
|
+
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
456
479
|
this.context.invoke('editor.afterCommand');
|
|
457
480
|
}
|
|
458
481
|
|
|
@@ -462,18 +485,16 @@ export class TableTooltip {
|
|
|
462
485
|
const table = cell.closest('table');
|
|
463
486
|
if (!table) return;
|
|
464
487
|
const visualColIdx = getVisualColIndex(cell);
|
|
465
|
-
Array.from(table.querySelectorAll('tr'))
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
r.insertBefore(newCell, ref);
|
|
474
|
-
}
|
|
488
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
489
|
+
// Batch reads first, then writes — prevents layout thrashing inside the loop
|
|
490
|
+
const refs = rows.map(r => position === 'left'
|
|
491
|
+
? getCellAtVisualCol(r, visualColIdx)
|
|
492
|
+
: getCellAfterVisualCol(r, visualColIdx));
|
|
493
|
+
const isHeaders = rows.map(r => r.closest('thead') !== null);
|
|
494
|
+
rows.forEach((r, i) => {
|
|
495
|
+
r.insertBefore(createElement(isHeaders[i] ? 'th' : 'td', {}, ['\u00a0']), refs[i]);
|
|
475
496
|
});
|
|
476
|
-
this._positionNear(this._activeTable);
|
|
497
|
+
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
477
498
|
this.context.invoke('editor.afterCommand');
|
|
478
499
|
}
|
|
479
500
|
|
|
@@ -489,7 +510,7 @@ export class TableTooltip {
|
|
|
489
510
|
if (bodyRows <= 1 && row.closest('tbody')) return;
|
|
490
511
|
this._activeCell = null;
|
|
491
512
|
row.parentElement?.removeChild(row);
|
|
492
|
-
this._positionNear(this._activeTable);
|
|
513
|
+
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
493
514
|
this.context.invoke('editor.afterCommand');
|
|
494
515
|
}
|
|
495
516
|
|
|
@@ -502,11 +523,11 @@ export class TableTooltip {
|
|
|
502
523
|
if (row && row.cells.length <= 1) return;
|
|
503
524
|
const visualColIdx = getVisualColIndex(cell);
|
|
504
525
|
this._activeCell = null;
|
|
505
|
-
Array.from(table.querySelectorAll('tr'))
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
});
|
|
509
|
-
this._positionNear(this._activeTable);
|
|
526
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
527
|
+
// Batch reads before writes to avoid forced reflows inside the loop
|
|
528
|
+
const cells = rows.map(r => getCellAtVisualCol(r, visualColIdx));
|
|
529
|
+
cells.forEach((c, i) => { if (c) rows[i].removeChild(c); });
|
|
530
|
+
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
510
531
|
this.context.invoke('editor.afterCommand');
|
|
511
532
|
}
|
|
512
533
|
|
|
@@ -628,15 +649,19 @@ export class TableTooltip {
|
|
|
628
649
|
|
|
629
650
|
this._sizeApply = (val) => {
|
|
630
651
|
if (isCol) {
|
|
631
|
-
const table
|
|
652
|
+
const table = cell.closest('table');
|
|
632
653
|
const visualColIdx = getVisualColIndex(cell);
|
|
633
|
-
Array.from(table.querySelectorAll('tr'))
|
|
634
|
-
|
|
654
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
655
|
+
// Batch reads, then writes
|
|
656
|
+
const cells = rows.map(r => getCellAtVisualCol(r, visualColIdx));
|
|
657
|
+
cells.forEach(c => {
|
|
635
658
|
if (c) { c.style.width = `${val}px`; c.style.minWidth = `${val}px`; }
|
|
636
659
|
});
|
|
637
660
|
} else {
|
|
638
661
|
const row = cell.closest('tr');
|
|
639
|
-
if (row)
|
|
662
|
+
if (row) {
|
|
663
|
+
for (const c of row.cells) { c.style.height = `${val}px`; c.style.minHeight = `${val}px`; }
|
|
664
|
+
}
|
|
640
665
|
}
|
|
641
666
|
this.context.invoke('editor.afterCommand');
|
|
642
667
|
};
|
|
@@ -32,6 +32,12 @@ export class VideoResizer {
|
|
|
32
32
|
|
|
33
33
|
const editable = this.context.layoutInfo.editable;
|
|
34
34
|
|
|
35
|
+
let _resizeDebounce = null;
|
|
36
|
+
const onWindowResize = () => {
|
|
37
|
+
clearTimeout(_resizeDebounce);
|
|
38
|
+
_resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
|
|
39
|
+
};
|
|
40
|
+
|
|
35
41
|
this._disposers.push(
|
|
36
42
|
on(editable, 'click', (e) => this._onEditorClick(e)),
|
|
37
43
|
on(editable, 'contextmenu', (e) => {
|
|
@@ -40,7 +46,7 @@ export class VideoResizer {
|
|
|
40
46
|
}),
|
|
41
47
|
on(document, 'click', (e) => this._onDocClick(e)),
|
|
42
48
|
on(window, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
43
|
-
on(window, 'resize',
|
|
49
|
+
on(window, 'resize', onWindowResize),
|
|
44
50
|
on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
45
51
|
);
|
|
46
52
|
|
|
@@ -192,43 +198,49 @@ export class VideoResizer {
|
|
|
192
198
|
const isCorner = pos.length === 2;
|
|
193
199
|
|
|
194
200
|
const editable = this.context.layoutInfo.editable;
|
|
201
|
+
let _raf = null; // rAF handle — at most one write per paint frame
|
|
202
|
+
|
|
195
203
|
const onMove = (me) => {
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
204
|
+
if (_raf !== null) return;
|
|
205
|
+
const clientX = me.clientX;
|
|
206
|
+
const clientY = me.clientY;
|
|
207
|
+
_raf = requestAnimationFrame(() => {
|
|
208
|
+
_raf = null;
|
|
209
|
+
const dx = clientX - startX;
|
|
210
|
+
const dy = clientY - startY;
|
|
211
|
+
const maxW = editable.clientWidth || Infinity;
|
|
212
|
+
let newW = startW;
|
|
213
|
+
let newH = startH;
|
|
214
|
+
|
|
215
|
+
if (pos.includes('e')) newW = Math.max(80, startW + dx);
|
|
216
|
+
if (pos.includes('w')) newW = Math.max(80, startW - dx);
|
|
217
|
+
if (pos.includes('s')) newH = Math.max(45, startH + dy);
|
|
218
|
+
if (pos.includes('n')) newH = Math.max(45, startH - dy);
|
|
219
|
+
|
|
220
|
+
newW = Math.min(newW, maxW);
|
|
221
|
+
|
|
222
|
+
if (isCorner) {
|
|
223
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
224
|
+
newH = Math.max(45, Math.round(newW / aspectRatio));
|
|
225
|
+
} else {
|
|
226
|
+
newW = Math.min(Math.max(80, Math.round(newH * aspectRatio)), maxW);
|
|
227
|
+
newH = Math.max(45, Math.round(newW / aspectRatio));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Resize wrapper and inner embed via CSS only — attribute writes are redundant
|
|
232
|
+
wrapper.style.width = `${newW}px`;
|
|
233
|
+
wrapper.style.height = `${newH}px`;
|
|
234
|
+
if (embed) {
|
|
235
|
+
embed.style.width = `${newW}px`;
|
|
236
|
+
embed.style.height = `${newH}px`;
|
|
216
237
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Resize both the wrapper and the inner embed element
|
|
220
|
-
wrapper.style.width = `${newW}px`;
|
|
221
|
-
wrapper.style.height = `${newH}px`;
|
|
222
|
-
if (embed) {
|
|
223
|
-
embed.width = newW;
|
|
224
|
-
embed.height = newH;
|
|
225
|
-
embed.style.width = `${newW}px`;
|
|
226
|
-
embed.style.height = `${newH}px`;
|
|
227
|
-
}
|
|
228
|
-
this._updateOverlayPosition();
|
|
238
|
+
this._updateOverlayPosition();
|
|
239
|
+
});
|
|
229
240
|
};
|
|
230
241
|
|
|
231
242
|
const onUp = () => {
|
|
243
|
+
if (_raf !== null) { cancelAnimationFrame(_raf); _raf = null; }
|
|
232
244
|
document.removeEventListener('mousemove', onMove);
|
|
233
245
|
document.removeEventListener('mouseup', onUp);
|
|
234
246
|
this._dragDisposers = null;
|
|
@@ -42,13 +42,13 @@ export class VideoTooltip {
|
|
|
42
42
|
if (wrapper && editable.contains(wrapper)) {
|
|
43
43
|
this._scheduleShow(wrapper);
|
|
44
44
|
}
|
|
45
|
-
}),
|
|
45
|
+
}, { passive: true }),
|
|
46
46
|
on(editable, 'mouseout', (e) => {
|
|
47
47
|
const to = e.relatedTarget;
|
|
48
48
|
if (!to || (!editable.contains(to) && !this._el.contains(to))) {
|
|
49
49
|
this._scheduleHide();
|
|
50
50
|
}
|
|
51
|
-
}),
|
|
51
|
+
}, { passive: true }),
|
|
52
52
|
on(document, 'click', (e) => {
|
|
53
53
|
if (
|
|
54
54
|
this._activeWrapper &&
|
|
@@ -174,7 +174,10 @@ export class VideoTooltip {
|
|
|
174
174
|
|
|
175
175
|
_show(wrapper) {
|
|
176
176
|
this._el.style.display = 'flex';
|
|
177
|
-
|
|
177
|
+
// Defer: offsetWidth on a newly-visible element forces synchronous layout
|
|
178
|
+
requestAnimationFrame(() => {
|
|
179
|
+
if (this._activeWrapper) this._positionNear(this._activeWrapper);
|
|
180
|
+
});
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
_hide() {
|
|
@@ -221,7 +224,7 @@ export class VideoTooltip {
|
|
|
221
224
|
wrapper.style.marginRight = value === 'left' ? '12px' : '';
|
|
222
225
|
this.context.invoke('editor.afterCommand');
|
|
223
226
|
this.context.invoke('videoResizer.updateOverlay');
|
|
224
|
-
this._positionNear(
|
|
227
|
+
requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
_setCenter() {
|
|
@@ -233,7 +236,7 @@ export class VideoTooltip {
|
|
|
233
236
|
wrapper.style.marginRight = 'auto';
|
|
234
237
|
this.context.invoke('editor.afterCommand');
|
|
235
238
|
this.context.invoke('videoResizer.updateOverlay');
|
|
236
|
-
this._positionNear(
|
|
239
|
+
requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
|
|
237
240
|
}
|
|
238
241
|
|
|
239
242
|
_resetSize() {
|
|
@@ -250,7 +253,7 @@ export class VideoTooltip {
|
|
|
250
253
|
}
|
|
251
254
|
this.context.invoke('editor.afterCommand');
|
|
252
255
|
this.context.invoke('videoResizer.updateOverlay');
|
|
253
|
-
this._positionNear(
|
|
256
|
+
requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
_delete() {
|
|
@@ -969,6 +969,55 @@
|
|
|
969
969
|
.an-resize-w { top: calc(50% - 4px); left: -4px; cursor: w-resize; }
|
|
970
970
|
}
|
|
971
971
|
|
|
972
|
+
// ---------------------------------------------------------------------------
|
|
973
|
+
// Image crop overlay
|
|
974
|
+
// ---------------------------------------------------------------------------
|
|
975
|
+
|
|
976
|
+
// The scrim and crop box are appended to <body> and positioned fixed, so they
|
|
977
|
+
// live outside .an-container. All styles are standalone (no SCSS nesting).
|
|
978
|
+
|
|
979
|
+
.an-crop-scrim {
|
|
980
|
+
// Inline styles handle position/z-index/background for performance;
|
|
981
|
+
// clip-path is updated by JS every frame so we only declare the transition.
|
|
982
|
+
transition: clip-path 0s; // disable transition — updated synchronously on drag
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
.an-crop-box {
|
|
986
|
+
// Core layout & interaction via inline styles in JS (position changes every drag frame).
|
|
987
|
+
// Only declare the animated ring so it stays in sync with $an-primary.
|
|
988
|
+
outline: none !important;
|
|
989
|
+
|
|
990
|
+
// Subtle inner shadow that frames the "live" area
|
|
991
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45),
|
|
992
|
+
inset 0 0 0 1px rgba(255, 255, 255, 0.25);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
.an-crop-handle {
|
|
996
|
+
// Inline styles handle size/position; declare hover highlight only.
|
|
997
|
+
&:hover, &:active {
|
|
998
|
+
background: #74affa !important; // $an-primary lightened ~14%
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.an-crop-toolbar {
|
|
1003
|
+
// Inline styles handle position; add a subtle entrance animation.
|
|
1004
|
+
animation: an-crop-toolbar-in 0.12s ease;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
@keyframes an-crop-toolbar-in {
|
|
1008
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
1009
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Hide canvas-based crop overlay when printing
|
|
1013
|
+
@media print {
|
|
1014
|
+
.an-crop-scrim,
|
|
1015
|
+
.an-crop-box,
|
|
1016
|
+
.an-crop-toolbar {
|
|
1017
|
+
display: none !important;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
972
1021
|
// ---------------------------------------------------------------------------
|
|
973
1022
|
// Video resize overlay
|
|
974
1023
|
// ---------------------------------------------------------------------------
|