autumnnote 1.0.3 → 1.0.4
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.es.js +923 -869
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +11 -11
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/index.umd.js +12 -12
- package/src/js/module/ImageResizer.js +41 -26
- package/src/js/module/ImageTooltip.js +40 -6
- 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/package.json
CHANGED
package/src/js/index.umd.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* index.umd.js — UMD entry point for AutumnNote
|
|
3
|
-
*
|
|
4
|
-
* Re-exports only the default export so the UMD global is the factory object
|
|
5
|
-
* directly, enabling the script-tag usage documented in the README:
|
|
6
|
-
*
|
|
7
|
-
* <script src="dist/autumnnote.umd.js"></script>
|
|
8
|
-
* <script>
|
|
9
|
-
* const editor = AutumnNote.create('#my-editor');
|
|
10
|
-
* </script>
|
|
11
|
-
*/
|
|
12
|
-
export { default } from './index.js';
|
|
1
|
+
/**
|
|
2
|
+
* index.umd.js — UMD entry point for AutumnNote
|
|
3
|
+
*
|
|
4
|
+
* Re-exports only the default export so the UMD global is the factory object
|
|
5
|
+
* directly, enabling the script-tag usage documented in the README:
|
|
6
|
+
*
|
|
7
|
+
* <script src="dist/autumnnote.umd.js"></script>
|
|
8
|
+
* <script>
|
|
9
|
+
* const editor = AutumnNote.create('#my-editor');
|
|
10
|
+
* </script>
|
|
11
|
+
*/
|
|
12
|
+
export { default } from './index.js';
|
|
@@ -38,6 +38,14 @@ export class ImageResizer {
|
|
|
38
38
|
|
|
39
39
|
const editable = this.context.layoutInfo.editable;
|
|
40
40
|
|
|
41
|
+
// Debounce window resize — _updateOverlayPosition already has rAF gating but
|
|
42
|
+
// every call cancels + re-schedules it; a debounce reduces that churn.
|
|
43
|
+
let _resizeDebounce = null;
|
|
44
|
+
const onWindowResize = () => {
|
|
45
|
+
clearTimeout(_resizeDebounce);
|
|
46
|
+
_resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
|
|
47
|
+
};
|
|
48
|
+
|
|
41
49
|
this._disposers.push(
|
|
42
50
|
on(editable, 'click', (e) => this._onEditorClick(e)),
|
|
43
51
|
// Also select on right-click so the highlight shows before the context menu
|
|
@@ -47,7 +55,7 @@ export class ImageResizer {
|
|
|
47
55
|
}),
|
|
48
56
|
on(document, 'click', (e) => this._onDocClick(e)),
|
|
49
57
|
on(window, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
50
|
-
on(window, 'resize',
|
|
58
|
+
on(window, 'resize', onWindowResize),
|
|
51
59
|
on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
52
60
|
);
|
|
53
61
|
|
|
@@ -187,37 +195,44 @@ export class ImageResizer {
|
|
|
187
195
|
const isCorner = pos.length === 2; // 'nw','ne','se','sw'
|
|
188
196
|
|
|
189
197
|
const editable = this.context.layoutInfo.editable;
|
|
198
|
+
let _raf = null; // rAF handle — ensures at most one write per paint frame
|
|
199
|
+
|
|
190
200
|
const onMove = (me) => {
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
if (_raf !== null) return; // frame already pending, discard this event
|
|
202
|
+
const clientX = me.clientX;
|
|
203
|
+
const clientY = me.clientY;
|
|
204
|
+
_raf = requestAnimationFrame(() => {
|
|
205
|
+
_raf = null;
|
|
206
|
+
const dx = clientX - startX;
|
|
207
|
+
const dy = clientY - startY;
|
|
208
|
+
const maxW = editable.clientWidth || Infinity;
|
|
209
|
+
let newW = startW;
|
|
210
|
+
let newH = startH;
|
|
211
|
+
|
|
212
|
+
if (pos.includes('e')) newW = Math.max(20, startW + dx);
|
|
213
|
+
if (pos.includes('w')) newW = Math.max(20, startW - dx);
|
|
214
|
+
if (pos.includes('s')) newH = Math.max(20, startH + dy);
|
|
215
|
+
if (pos.includes('n')) newH = Math.max(20, startH - dy);
|
|
216
|
+
|
|
217
|
+
newW = Math.min(newW, maxW);
|
|
218
|
+
|
|
219
|
+
if (isCorner) {
|
|
220
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
221
|
+
newH = Math.max(20, Math.round(newW / aspectRatio));
|
|
222
|
+
} else {
|
|
223
|
+
newW = Math.min(Math.max(20, Math.round(newH * aspectRatio)), maxW);
|
|
224
|
+
newH = Math.max(20, Math.round(newW / aspectRatio));
|
|
225
|
+
}
|
|
212
226
|
}
|
|
213
|
-
}
|
|
214
227
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
228
|
+
img.style.width = `${newW}px`;
|
|
229
|
+
img.style.height = `${newH}px`;
|
|
230
|
+
this._updateOverlayPosition();
|
|
231
|
+
});
|
|
218
232
|
};
|
|
219
233
|
|
|
220
234
|
const onUp = () => {
|
|
235
|
+
if (_raf !== null) { cancelAnimationFrame(_raf); _raf = null; }
|
|
221
236
|
document.removeEventListener('mousemove', onMove);
|
|
222
237
|
document.removeEventListener('mouseup', onUp);
|
|
223
238
|
this._dragDisposers = null;
|
|
@@ -11,6 +11,8 @@ 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>`,
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
const SHOW_DELAY = 100;
|
|
@@ -40,13 +42,13 @@ export class ImageTooltip {
|
|
|
40
42
|
if (img && editable.contains(img) && !img.closest('a[href]')) {
|
|
41
43
|
this._scheduleShow(img);
|
|
42
44
|
}
|
|
43
|
-
}),
|
|
45
|
+
}, { passive: true }),
|
|
44
46
|
on(editable, 'mouseout', (e) => {
|
|
45
47
|
const to = e.relatedTarget;
|
|
46
48
|
if (!to || (!editable.contains(to) && !this._el.contains(to))) {
|
|
47
49
|
this._scheduleHide();
|
|
48
50
|
}
|
|
49
|
-
}),
|
|
51
|
+
}, { passive: true }),
|
|
50
52
|
// Hide when image is deselected by clicking elsewhere
|
|
51
53
|
on(document, 'click', (e) => {
|
|
52
54
|
if (this._activeImg && !this._activeImg.contains(e.target) && !this._el.contains(e.target)) {
|
|
@@ -103,6 +105,11 @@ export class ImageTooltip {
|
|
|
103
105
|
|
|
104
106
|
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
105
107
|
|
|
108
|
+
el.appendChild(this._makeBtn(ICONS.rotateLeft, 'Rotate Left', () => this._rotate(-90)));
|
|
109
|
+
el.appendChild(this._makeBtn(ICONS.rotateRight, 'Rotate Right', () => this._rotate(90)));
|
|
110
|
+
|
|
111
|
+
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
112
|
+
|
|
106
113
|
this._captionBtn = this._makeBtn(ICONS.caption, 'Add / Edit Caption', () => this._toggleCaption());
|
|
107
114
|
el.appendChild(this._captionBtn);
|
|
108
115
|
|
|
@@ -166,7 +173,10 @@ export class ImageTooltip {
|
|
|
166
173
|
|
|
167
174
|
_show(img) {
|
|
168
175
|
this._el.style.display = 'flex';
|
|
169
|
-
|
|
176
|
+
// Defer positioning: offsetWidth on a newly-visible element forces layout
|
|
177
|
+
requestAnimationFrame(() => {
|
|
178
|
+
if (this._activeImg) this._positionNear(this._activeImg);
|
|
179
|
+
});
|
|
170
180
|
}
|
|
171
181
|
|
|
172
182
|
_hide() {
|
|
@@ -213,7 +223,7 @@ export class ImageTooltip {
|
|
|
213
223
|
target.style.marginRight = value === 'left' ? '12px' : '';
|
|
214
224
|
this.context.invoke('editor.afterCommand');
|
|
215
225
|
this.context.invoke('imageResizer.updateOverlay');
|
|
216
|
-
this._positionNear(
|
|
226
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
217
227
|
}
|
|
218
228
|
|
|
219
229
|
_setCenter() {
|
|
@@ -226,7 +236,7 @@ export class ImageTooltip {
|
|
|
226
236
|
target.style.marginRight = 'auto';
|
|
227
237
|
this.context.invoke('editor.afterCommand');
|
|
228
238
|
this.context.invoke('imageResizer.updateOverlay');
|
|
229
|
-
this._positionNear(
|
|
239
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
230
240
|
}
|
|
231
241
|
|
|
232
242
|
_resetSize() {
|
|
@@ -236,7 +246,31 @@ export class ImageTooltip {
|
|
|
236
246
|
img.style.height = '';
|
|
237
247
|
this.context.invoke('editor.afterCommand');
|
|
238
248
|
this.context.invoke('imageResizer.updateOverlay');
|
|
239
|
-
this._positionNear(
|
|
249
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Rotate the active image by `delta` degrees (±90).
|
|
254
|
+
* Reads the existing rotate() value from the transform style so
|
|
255
|
+
* repeated clicks accumulate correctly.
|
|
256
|
+
* @param {number} delta
|
|
257
|
+
*/
|
|
258
|
+
_rotate(delta) {
|
|
259
|
+
const img = this._activeImg;
|
|
260
|
+
if (!img) return;
|
|
261
|
+
// Parse current rotation angle from inline transform
|
|
262
|
+
const current = img.style.transform || '';
|
|
263
|
+
const match = current.match(/rotate\((-?[\d.]+)deg\)/);
|
|
264
|
+
const prev = match ? parseFloat(match[1]) : 0;
|
|
265
|
+
const next = (prev + delta + 360) % 360; // normalise to [0, 360)
|
|
266
|
+
// Preserve any other transform functions (e.g. scale), replace only rotate()
|
|
267
|
+
const cleaned = current.replace(/rotate\(-?[\d.]+deg\)/, '').trim();
|
|
268
|
+
img.style.transform = cleaned
|
|
269
|
+
? `${cleaned} rotate(${next}deg)`
|
|
270
|
+
: next === 0 ? '' : `rotate(${next}deg)`;
|
|
271
|
+
this.context.invoke('editor.afterCommand');
|
|
272
|
+
this.context.invoke('imageResizer.updateOverlay');
|
|
273
|
+
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
240
274
|
}
|
|
241
275
|
|
|
242
276
|
_delete() {
|
|
@@ -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() {
|