akari-video 1.2.0-beta.3 → 1.2.0-beta.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/package.json +1 -1
- package/vendor/docs/contract-2026-10-07-material-ranges-v0.md +5 -3
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/edit-store/lib/tasks-store.js +5 -1
- package/vendor/packages/overlay-runtime/src/element-geometry.d.ts +8 -0
- package/vendor/packages/overlay-runtime/src/element-geometry.mjs +75 -0
- package/vendor/packages/overlay-runtime/src/interaction.css +1 -0
- package/vendor/packages/overlay-runtime/src/interaction.js +378 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "1.2.0-beta.
|
|
3
|
+
"version": "1.2.0-beta.4",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -6,8 +6,10 @@ The project stores non-destructive source selections in `.akari/material-ranges.
|
|
|
6
6
|
{ "version": 1, "ranges": { "assets/example.mp4": { "in": 2.5, "out": 8 } } }
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Keys are project-relative media paths. `in` and `out` are seconds in the original media; `out` is greater than `in`. Removing a selection deletes its key. A missing or malformed file means no selections. Updates preserve unrelated top-level keys and other material entries and replace the file by writing a temporary file and renaming it.
|
|
9
|
+
Keys are project-relative media paths. `in` and `out` are seconds in the original media; `out` is greater than `in`. Handle changes round both values to 0.01 seconds before saving. Removing a selection deletes its key. A missing or malformed file means no selections. Updates preserve unrelated top-level keys and other material entries and replace the file by writing a temporary file and renaming it.
|
|
10
10
|
|
|
11
|
-
The selected list strip exposes two handles. Its minimum width is the larger of one second and 24 pixels expressed in source seconds, capped by the media duration. Dragging one handle keeps the other fixed.
|
|
11
|
+
The selected list strip exposes two handles. Its minimum width is the larger of one second and 24 pixels expressed in source seconds, capped by the media duration. Dragging one handle keeps the other fixed. Pressing × clears the selection and announces the change; double-clicking the strip leaves the selection intact. An unselected strip shows an orange line at the saved range. Changes are saved after 150 ms.
|
|
12
12
|
|
|
13
|
-
Material drag payloads and
|
|
13
|
+
Material drag payloads and placement requests may carry `in` and `out`. The range applies when a person places material through panel drag and drop, the panel's Add to Timeline action, a drop on the central output preview, or placement from the material preview. Other callers of `akari.timeline.addMaterialAtPlayhead` and `akari.timeline.addMaterialAtOutputPoint` use the full source unless their request explicitly supplies a valid pair. Video and audio placement cap `out` at the media duration; if `in` reaches or exceeds the media duration, placement uses the full source. `akari.materials.range.get` and `.set` expose the stored range to other views. Changes emit `akari.materials.range.changed` on `window` with `{ relativePath, range, source }` when the setter supplies `source`; views can use it to ignore echoes of their own changes.
|
|
14
|
+
|
|
15
|
+
Ranged audio is placed as an audio-track item by both playhead placement and output-preview drop, because the legacy `audio.sfx` form cannot store `in` and `out`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "1.2.0-beta.
|
|
3
|
+
"version": "1.2.0-beta.4",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -16,7 +16,7 @@ const TASKS_VERSION = 0;
|
|
|
16
16
|
const PATCH_FIELDS = new Set([
|
|
17
17
|
'state', 'via', 'priority', 'rank', 'outcome', 'title', 'body', 'target', 'anchor',
|
|
18
18
|
'ref', 'attachments', 'gate', 'undo', 'batchId', 'sentAt', 'sentTo', 'response',
|
|
19
|
-
'needsConfirm', 'updatedAt', 'orphaned', 'kind', 'targetDetail', 'evidence',
|
|
19
|
+
'needsConfirm', 'confirmedAt', 'updatedAt', 'orphaned', 'kind', 'targetDetail', 'evidence',
|
|
20
20
|
'confidence', 'question', 'risk', 'route', 'dependsOn', 'origin', 'mergedFrom'
|
|
21
21
|
]);
|
|
22
22
|
const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -132,6 +132,10 @@ function applyTaskPatch(task, patch, actor) {
|
|
|
132
132
|
for (const key of Object.keys(patch))
|
|
133
133
|
if (!PATCH_FIELDS.has(key))
|
|
134
134
|
throw new Error(`${key} は変更できません。`);
|
|
135
|
+
if (patch.needsConfirm === false && actor !== 'human')
|
|
136
|
+
throw new Error('案の承認は人が行ってください。');
|
|
137
|
+
if (task.needsConfirm === true && patch.needsConfirm === false && !patch.confirmedAt)
|
|
138
|
+
throw new Error('承認時刻が必要です。');
|
|
135
139
|
if (isObject(task.ref) && task.ref.kind === 'annotation') {
|
|
136
140
|
for (const key of ['title', 'body', 'target', 'anchor', 'via', 'ref']) {
|
|
137
141
|
if (key in patch)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function elementAxes(center: {x:number;y:number}, xProbe:{x:number;y:number}, yProbe:{x:number;y:number}): {x:{x:number;y:number};y:{x:number;y:number}};
|
|
2
|
+
export declare function elementPoint(geometry: any, horizontal:number, vertical:number): {x:number;y:number};
|
|
3
|
+
export declare function elementHandlePoints(geometry:any, name:string): {dragged:{x:number;y:number};anchor:{x:number;y:number};signs:number[]} | null;
|
|
4
|
+
export declare function elementBoxSize(width:number, height:number, name:string, delta:{x:number;y:number}, shift:boolean): {width:number;height:number};
|
|
5
|
+
export declare function elementLocalDelta(screen:{x:number;y:number}, axes:any): {x:number;y:number};
|
|
6
|
+
export declare function elementAngle(value:number, shift:boolean): number;
|
|
7
|
+
export declare function elementHandleLayout(width:number, height:number): {hideHorizontalEdges:boolean;hideVerticalEdges:boolean;outsideX:boolean;outsideY:boolean;cornerOffsetX:number;cornerOffsetY:number;rotateTop:number};
|
|
8
|
+
export declare function elementBoxCompanions(style:CSSStyleDeclaration, parentStyle:CSSStyleDeclaration | null, width:number | null, height:number | null): Record<string,string>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Geometry shared with the marked copy in interaction.js. Coordinates are client pixels.
|
|
2
|
+
// BEGIN element-geometry
|
|
3
|
+
function elementAxes(center, xProbe, yProbe) {
|
|
4
|
+
return { x: { x: xProbe.x - center.x, y: xProbe.y - center.y },
|
|
5
|
+
y: { x: yProbe.x - center.x, y: yProbe.y - center.y } };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function elementPoint(geometry, horizontal, vertical) {
|
|
9
|
+
return { x: geometry.center.x + horizontal * geometry.width * geometry.axes.x.x / 2
|
|
10
|
+
+ vertical * geometry.height * geometry.axes.y.x / 2,
|
|
11
|
+
y: geometry.center.y + horizontal * geometry.width * geometry.axes.x.y / 2
|
|
12
|
+
+ vertical * geometry.height * geometry.axes.y.y / 2 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function elementHandlePoints(geometry, name) {
|
|
16
|
+
const signs = { n: [0, -1], e: [1, 0], s: [0, 1], w: [-1, 0],
|
|
17
|
+
nw: [-1, -1], ne: [1, -1], se: [1, 1], sw: [-1, 1] }[name];
|
|
18
|
+
if (!signs) return null;
|
|
19
|
+
return { dragged: elementPoint(geometry, ...signs),
|
|
20
|
+
anchor: elementPoint(geometry, -signs[0], -signs[1]), signs };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function elementBoxSize(width, height, name, delta, shift) {
|
|
24
|
+
const signs = elementHandlePoints({ center: { x: 0, y: 0 }, width, height,
|
|
25
|
+
axes: { x: { x: 1, y: 0 }, y: { x: 0, y: 1 } } }, name)?.signs;
|
|
26
|
+
if (!signs) return { width, height };
|
|
27
|
+
let nextWidth = signs[0] ? Math.max(4, width + signs[0] * delta.x) : width;
|
|
28
|
+
let nextHeight = signs[1] ? Math.max(4, height + signs[1] * delta.y) : height;
|
|
29
|
+
if (signs[0] && signs[1] && !shift) {
|
|
30
|
+
const ratio = Math.max(4 / width, 4 / height,
|
|
31
|
+
(width * nextWidth + height * nextHeight) / (width * width + height * height));
|
|
32
|
+
nextWidth = width * ratio;
|
|
33
|
+
nextHeight = height * ratio;
|
|
34
|
+
}
|
|
35
|
+
return { width: Math.round(nextWidth * 100) / 100,
|
|
36
|
+
height: Math.round(nextHeight * 100) / 100 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function elementLocalDelta(screen, axes) {
|
|
40
|
+
const determinant = axes.x.x * axes.y.y - axes.y.x * axes.x.y;
|
|
41
|
+
if (Math.abs(determinant) < 1e-6) return { x: 0, y: 0 };
|
|
42
|
+
return { x: (screen.x * axes.y.y - screen.y * axes.y.x) / determinant,
|
|
43
|
+
y: (screen.y * axes.x.x - screen.x * axes.x.y) / determinant };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function elementAngle(value, shift) {
|
|
47
|
+
const angle = ((value + 180) % 360 + 360) % 360 - 180;
|
|
48
|
+
return Math.round((shift ? Math.round(angle / 15) * 15 : angle) * 100) / 100;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function elementHandleLayout(width, height) {
|
|
52
|
+
return { hideHorizontalEdges: width < 30, hideVerticalEdges: height < 30,
|
|
53
|
+
outsideX: width < 36, outsideY: height < 36,
|
|
54
|
+
cornerOffsetX: width < 36 ? 14 : 0, cornerOffsetY: height < 36 ? 14 : 0,
|
|
55
|
+
rotateTop: -32 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function elementBoxCompanions(style, parentStyle, width, height) {
|
|
59
|
+
const result = {};
|
|
60
|
+
if (style.boxSizing !== 'border-box') result['box-sizing'] = 'border-box';
|
|
61
|
+
if (style.display === 'inline') result.display = 'inline-block';
|
|
62
|
+
if (['flex', 'inline-flex'].includes(parentStyle?.display) && style.flex !== '0 0 auto') result.flex = '0 0 auto';
|
|
63
|
+
if (width !== null) {
|
|
64
|
+
if (Number.parseFloat(style.minWidth) > width) result['min-width'] = '0px';
|
|
65
|
+
if (Number.parseFloat(style.maxWidth) < width) result['max-width'] = 'none';
|
|
66
|
+
}
|
|
67
|
+
if (height !== null) {
|
|
68
|
+
if (Number.parseFloat(style.minHeight) > height) result['min-height'] = '0px';
|
|
69
|
+
if (Number.parseFloat(style.maxHeight) < height) result['max-height'] = 'none';
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
// END element-geometry
|
|
74
|
+
export { elementAxes, elementPoint, elementHandlePoints, elementBoxSize,
|
|
75
|
+
elementLocalDelta, elementAngle, elementHandleLayout, elementBoxCompanions };
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
.akari-interaction-handle.is-w { top: 50%; left: 0; transform: translate(-50%, -50%); cursor: ew-resize; }
|
|
118
118
|
.akari-interaction-selection-frame:not([data-akari-selection-kind="leaf"]) .is-edge { display: none; }
|
|
119
119
|
.akari-interaction-selection-frame.is-telop .is-edge { display: none; }
|
|
120
|
+
.akari-interaction-selection-frame.is-element-focus.is-telop .is-edge { display: block; }
|
|
120
121
|
.akari-interaction-selection-frame.is-line .akari-interaction-handle:not(.is-line-start):not(.is-line-end):not(.akari-interaction-action) { display: none; }
|
|
121
122
|
.akari-interaction-selection-frame:not(.is-line) .is-line-start,
|
|
122
123
|
.akari-interaction-selection-frame:not(.is-line) .is-line-end { display: none; }
|
|
@@ -163,6 +163,79 @@ function elementLabel(root, element) {
|
|
|
163
163
|
}
|
|
164
164
|
// END element-selection
|
|
165
165
|
|
|
166
|
+
// BEGIN element-geometry
|
|
167
|
+
function elementAxes(center, xProbe, yProbe) {
|
|
168
|
+
return { x: { x: xProbe.x - center.x, y: xProbe.y - center.y },
|
|
169
|
+
y: { x: yProbe.x - center.x, y: yProbe.y - center.y } };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function elementPoint(geometry, horizontal, vertical) {
|
|
173
|
+
return { x: geometry.center.x + horizontal * geometry.width * geometry.axes.x.x / 2
|
|
174
|
+
+ vertical * geometry.height * geometry.axes.y.x / 2,
|
|
175
|
+
y: geometry.center.y + horizontal * geometry.width * geometry.axes.x.y / 2
|
|
176
|
+
+ vertical * geometry.height * geometry.axes.y.y / 2 };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function elementHandlePoints(geometry, name) {
|
|
180
|
+
const signs = { n: [0, -1], e: [1, 0], s: [0, 1], w: [-1, 0],
|
|
181
|
+
nw: [-1, -1], ne: [1, -1], se: [1, 1], sw: [-1, 1] }[name];
|
|
182
|
+
if (!signs) return null;
|
|
183
|
+
return { dragged: elementPoint(geometry, ...signs),
|
|
184
|
+
anchor: elementPoint(geometry, -signs[0], -signs[1]), signs };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function elementBoxSize(width, height, name, delta, shift) {
|
|
188
|
+
const signs = elementHandlePoints({ center: { x: 0, y: 0 }, width, height,
|
|
189
|
+
axes: { x: { x: 1, y: 0 }, y: { x: 0, y: 1 } } }, name)?.signs;
|
|
190
|
+
if (!signs) return { width, height };
|
|
191
|
+
let nextWidth = signs[0] ? Math.max(4, width + signs[0] * delta.x) : width;
|
|
192
|
+
let nextHeight = signs[1] ? Math.max(4, height + signs[1] * delta.y) : height;
|
|
193
|
+
if (signs[0] && signs[1] && !shift) {
|
|
194
|
+
const ratio = Math.max(4 / width, 4 / height,
|
|
195
|
+
(width * nextWidth + height * nextHeight) / (width * width + height * height));
|
|
196
|
+
nextWidth = width * ratio;
|
|
197
|
+
nextHeight = height * ratio;
|
|
198
|
+
}
|
|
199
|
+
return { width: Math.round(nextWidth * 100) / 100,
|
|
200
|
+
height: Math.round(nextHeight * 100) / 100 };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function elementLocalDelta(screen, axes) {
|
|
204
|
+
const determinant = axes.x.x * axes.y.y - axes.y.x * axes.x.y;
|
|
205
|
+
if (Math.abs(determinant) < 1e-6) return { x: 0, y: 0 };
|
|
206
|
+
return { x: (screen.x * axes.y.y - screen.y * axes.y.x) / determinant,
|
|
207
|
+
y: (screen.y * axes.x.x - screen.x * axes.x.y) / determinant };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function elementAngle(value, shift) {
|
|
211
|
+
const angle = ((value + 180) % 360 + 360) % 360 - 180;
|
|
212
|
+
return Math.round((shift ? Math.round(angle / 15) * 15 : angle) * 100) / 100;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function elementHandleLayout(width, height) {
|
|
216
|
+
return { hideHorizontalEdges: width < 30, hideVerticalEdges: height < 30,
|
|
217
|
+
outsideX: width < 36, outsideY: height < 36,
|
|
218
|
+
cornerOffsetX: width < 36 ? 14 : 0, cornerOffsetY: height < 36 ? 14 : 0,
|
|
219
|
+
rotateTop: -32 };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function elementBoxCompanions(style, parentStyle, width, height) {
|
|
223
|
+
const result = {};
|
|
224
|
+
if (style.boxSizing !== 'border-box') result['box-sizing'] = 'border-box';
|
|
225
|
+
if (style.display === 'inline') result.display = 'inline-block';
|
|
226
|
+
if (['flex', 'inline-flex'].includes(parentStyle?.display) && style.flex !== '0 0 auto') result.flex = '0 0 auto';
|
|
227
|
+
if (width !== null) {
|
|
228
|
+
if (Number.parseFloat(style.minWidth) > width) result['min-width'] = '0px';
|
|
229
|
+
if (Number.parseFloat(style.maxWidth) < width) result['max-width'] = 'none';
|
|
230
|
+
}
|
|
231
|
+
if (height !== null) {
|
|
232
|
+
if (Number.parseFloat(style.minHeight) > height) result['min-height'] = '0px';
|
|
233
|
+
if (Number.parseFloat(style.maxHeight) < height) result['max-height'] = 'none';
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
// END element-geometry
|
|
238
|
+
|
|
166
239
|
const stage = document.getElementById("overlay-stage");
|
|
167
240
|
const dragStartDistance = 4;
|
|
168
241
|
const SNAP_DISTANCE = 6;
|
|
@@ -216,6 +289,7 @@ function elementLabel(root, element) {
|
|
|
216
289
|
let selectedId = null;
|
|
217
290
|
let selectedIds = [];
|
|
218
291
|
let elementFocus = null;
|
|
292
|
+
let elementGeometryCache = null;
|
|
219
293
|
let elementNudge = null;
|
|
220
294
|
let elementNudgeTimer = null;
|
|
221
295
|
let scopeId = null;
|
|
@@ -223,6 +297,7 @@ function elementLabel(root, element) {
|
|
|
223
297
|
scopeId = floorScopeId;
|
|
224
298
|
let groupSelection = false;
|
|
225
299
|
let selectionFrame = null;
|
|
300
|
+
let elementHandlePlacementFrame = null;
|
|
226
301
|
let selectionTrackingFrame = null;
|
|
227
302
|
let activeDrag = null;
|
|
228
303
|
let activeResize = null;
|
|
@@ -1305,7 +1380,13 @@ function elementLabel(root, element) {
|
|
|
1305
1380
|
const transform = readTransform(selectedOverlay);
|
|
1306
1381
|
const lineRect = lineFrameGeometry(selectedOverlay);
|
|
1307
1382
|
const focused = focusedElement();
|
|
1308
|
-
const
|
|
1383
|
+
const focusedGeometry = focused ? elementScreenGeometry(focused) : null;
|
|
1384
|
+
const focusedWidth = focusedGeometry && focusedGeometry.width * Math.hypot(focusedGeometry.axes.x.x, focusedGeometry.axes.x.y);
|
|
1385
|
+
const focusedHeight = focusedGeometry && focusedGeometry.height * Math.hypot(focusedGeometry.axes.y.x, focusedGeometry.axes.y.y);
|
|
1386
|
+
const focusedAngle = focusedGeometry && Math.atan2(focusedGeometry.axes.x.y, focusedGeometry.axes.x.x) * 180 / Math.PI;
|
|
1387
|
+
const rect = focusedGeometry ? { left: focusedGeometry.center.x - focusedWidth / 2,
|
|
1388
|
+
top: focusedGeometry.center.y - focusedHeight / 2, width: focusedWidth, height: focusedHeight }
|
|
1389
|
+
: selectedOverlay.dataset.role === 'shape-line' ? lineRect
|
|
1309
1390
|
: transform.rotate ? unrotatedLeafBounds(selectedOverlay) : fragmentBounds(selectedOverlay);
|
|
1310
1391
|
if (!rect) {
|
|
1311
1392
|
if (selectionFrame) selectionFrame.hidden = true;
|
|
@@ -1335,15 +1416,46 @@ function elementLabel(root, element) {
|
|
|
1335
1416
|
// 背景・ロック中は動かせない選択であることを視覚でも伝える(拡縮ハンドルを消し、枠を破線にする。
|
|
1336
1417
|
// interaction.css の .is-locked)。isMovable ではなく isLockedItem を見るのは、
|
|
1337
1418
|
// 選択自体は許すが移動系操作だけを塞ぐという役割分担を CSS 側にも一致させるため。
|
|
1419
|
+
if (!focused && elementHandlePlacementFrame === selectionFrame) {
|
|
1420
|
+
const replacement = createSelectionFrame();
|
|
1421
|
+
selectionFrame.replaceWith(replacement);
|
|
1422
|
+
selectionFrame = replacement;
|
|
1423
|
+
elementHandlePlacementFrame = null;
|
|
1424
|
+
}
|
|
1338
1425
|
selectionFrame.classList.toggle("is-locked", isLockedItem(selectedOverlay));
|
|
1339
1426
|
selectionFrame.classList.toggle('is-busy', Boolean(activeDrag || activeResize || activeRotate || activeLine));
|
|
1340
1427
|
selectionFrame.classList.toggle('is-moving', Boolean(activeDrag || activeRotate));
|
|
1341
1428
|
selectionFrame.classList.toggle('is-text', selectedOverlay.dataset.role === 'text');
|
|
1342
1429
|
selectionFrame.classList.toggle('is-telop', isTelopOverlay(selectedOverlay));
|
|
1343
1430
|
selectionFrame.classList.toggle('is-line', selectedOverlay.dataset.role === 'shape-line');
|
|
1431
|
+
if (focused) elementHandlePlacementFrame = selectionFrame;
|
|
1432
|
+
selectionFrame.classList.toggle('is-element-focus', Boolean(focused));
|
|
1344
1433
|
if (window.akari.capabilities?.elementSelection === true) {
|
|
1345
1434
|
for (const handle of selectionFrame.querySelectorAll('.akari-interaction-handle')) {
|
|
1346
|
-
|
|
1435
|
+
if (!focused) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const layout = elementHandleLayout(focusedWidth, focusedHeight);
|
|
1439
|
+
const name = [...handle.classList].find(token => /^is-(?:n|e|s|w|nw|ne|se|sw|rotate|move)$/.test(token))?.slice(3);
|
|
1440
|
+
handle.style.display = name === 'move' ||
|
|
1441
|
+
(['n', 's'].includes(name) && layout.hideHorizontalEdges) ||
|
|
1442
|
+
(['e', 'w'].includes(name) && layout.hideVerticalEdges) ? 'none' : '';
|
|
1443
|
+
if (name === 'rotate') {
|
|
1444
|
+
Object.assign(handle.style, { top: `${layout.rotateTop}px`, bottom: 'auto',
|
|
1445
|
+
left: '50%', transform: 'translate(-50%, -50%)' });
|
|
1446
|
+
} else if (name && name.length === 2) {
|
|
1447
|
+
const x = name.includes('w') ? -layout.cornerOffsetX : layout.cornerOffsetX;
|
|
1448
|
+
const y = name.includes('n') ? -layout.cornerOffsetY : layout.cornerOffsetY;
|
|
1449
|
+
if (x || y) handle.style.translate = `${x}px ${y}px`;
|
|
1450
|
+
else handle.style.removeProperty('translate');
|
|
1451
|
+
}
|
|
1452
|
+
if (name && name !== 'move' && name !== 'rotate') {
|
|
1453
|
+
const directions = ['ew-resize', 'nwse-resize', 'ns-resize', 'nesw-resize'];
|
|
1454
|
+
const base = name.length === 2
|
|
1455
|
+
? (name === 'ne' || name === 'sw' ? 3 : 1)
|
|
1456
|
+
: name === 'n' || name === 's' ? 2 : 0;
|
|
1457
|
+
handle.style.cursor = directions[((base + Math.round(focusedAngle / 45)) % 4 + 4) % 4];
|
|
1458
|
+
}
|
|
1347
1459
|
}
|
|
1348
1460
|
}
|
|
1349
1461
|
for (const endpoint of selectionFrame.querySelectorAll('.is-line-start, .is-line-end')) {
|
|
@@ -1371,9 +1483,9 @@ function elementLabel(root, element) {
|
|
|
1371
1483
|
return;
|
|
1372
1484
|
}
|
|
1373
1485
|
const pivot = leafPivotClient(transform);
|
|
1374
|
-
selectionFrame.style.transformOrigin = pivot
|
|
1486
|
+
selectionFrame.style.transformOrigin = focused ? 'center' : pivot
|
|
1375
1487
|
? `${pivot.x - rect.left}px ${pivot.y - rect.top}px` : 'center';
|
|
1376
|
-
selectionFrame.style.transform = focused ?
|
|
1488
|
+
selectionFrame.style.transform = focused ? `rotate(${focusedAngle}deg)` : transform.rotate ? `rotate(${transform.rotate}deg)` : '';
|
|
1377
1489
|
}
|
|
1378
1490
|
|
|
1379
1491
|
function trackSelectionFrame() {
|
|
@@ -2005,6 +2117,220 @@ function elementLabel(root, element) {
|
|
|
2005
2117
|
const parts = String(value ?? '').match(/[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?px/giu) ?? [];
|
|
2006
2118
|
return { x: Number.parseFloat(parts[0]) || 0, y: Number.parseFloat(parts[1]) || 0 };
|
|
2007
2119
|
}
|
|
2120
|
+
function elementScreenGeometry(element) {
|
|
2121
|
+
const rect = element.getBoundingClientRect();
|
|
2122
|
+
const center = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
2123
|
+
const style = getComputedStyle(element);
|
|
2124
|
+
const size = { width: element.offsetWidth || Number.parseFloat(style.width) || rect.width,
|
|
2125
|
+
height: element.offsetHeight || Number.parseFloat(style.height) || rect.height };
|
|
2126
|
+
const stageRect = stage?.getBoundingClientRect();
|
|
2127
|
+
const ancestors = [];
|
|
2128
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
2129
|
+
const computed = getComputedStyle(node);
|
|
2130
|
+
ancestors.push([computed.translate, computed.rotate, computed.scale, computed.transform,
|
|
2131
|
+
computed.transformOrigin, node.getAttribute('style')]);
|
|
2132
|
+
}
|
|
2133
|
+
const signature = JSON.stringify([rect.left, rect.top, rect.width, rect.height,
|
|
2134
|
+
size.width, size.height, style.translate, style.rotate, style.scale, style.transform,
|
|
2135
|
+
style.transformOrigin, element.getAttribute('style'), selectedOverlay?.getAttribute('style'),
|
|
2136
|
+
stageRect && [stageRect.left, stageRect.top, stageRect.width, stageRect.height], ancestors]);
|
|
2137
|
+
if (elementGeometryCache?.element === element && elementGeometryCache.signature === signature) {
|
|
2138
|
+
return elementGeometryCache.geometry;
|
|
2139
|
+
}
|
|
2140
|
+
const original = element.style.translate;
|
|
2141
|
+
const base = parseElementTranslate(style.translate);
|
|
2142
|
+
element.style.translate = `${base.x + 1}px ${base.y}px`;
|
|
2143
|
+
const xRect = element.getBoundingClientRect();
|
|
2144
|
+
element.style.translate = `${base.x}px ${base.y + 1}px`;
|
|
2145
|
+
const yRect = element.getBoundingClientRect();
|
|
2146
|
+
element.style.translate = original;
|
|
2147
|
+
const axes = elementAxes(center,
|
|
2148
|
+
{ x: xRect.left + xRect.width / 2, y: xRect.top + xRect.height / 2 },
|
|
2149
|
+
{ x: yRect.left + yRect.width / 2, y: yRect.top + yRect.height / 2 });
|
|
2150
|
+
if (Math.hypot(axes.x.x, axes.x.y) < 0.01 || Math.hypot(axes.y.x, axes.y.y) < 0.01) {
|
|
2151
|
+
axes.x = { x: rect.width / size.width, y: 0 };
|
|
2152
|
+
axes.y = { x: 0, y: rect.height / size.height };
|
|
2153
|
+
}
|
|
2154
|
+
const scale = style.scale === 'none' ? [1, 1] : style.scale.split(/\s+/u).map(Number);
|
|
2155
|
+
const own = new DOMMatrix().rotate(Number.parseFloat(style.rotate) || 0)
|
|
2156
|
+
.scale(scale[0] || 1, scale[1] || scale[0] || 1)
|
|
2157
|
+
.multiply(new DOMMatrix(style.transform === 'none' ? undefined : style.transform));
|
|
2158
|
+
const baseX = axes.x, baseY = axes.y;
|
|
2159
|
+
axes.x = { x: baseX.x * own.a + baseY.x * own.b,
|
|
2160
|
+
y: baseX.y * own.a + baseY.y * own.b };
|
|
2161
|
+
axes.y = { x: baseX.x * own.c + baseY.x * own.d,
|
|
2162
|
+
y: baseX.y * own.c + baseY.y * own.d };
|
|
2163
|
+
const geometry = { center, axes, width: size.width, height: size.height };
|
|
2164
|
+
elementGeometryCache = { element, signature, geometry };
|
|
2165
|
+
return geometry;
|
|
2166
|
+
}
|
|
2167
|
+
function moveElementPointTo(gesture, horizontal, vertical, target) {
|
|
2168
|
+
const geometry = elementScreenGeometry(gesture.element);
|
|
2169
|
+
const point = elementPoint(geometry, horizontal, vertical);
|
|
2170
|
+
if (Math.hypot(target.x - point.x, target.y - point.y) <= 0.2) return;
|
|
2171
|
+
moveElementToCenter(gesture, geometry.center.x + target.x - point.x,
|
|
2172
|
+
geometry.center.y + target.y - point.y);
|
|
2173
|
+
}
|
|
2174
|
+
const elementInlineProperties = ['width', 'height', 'rotate', 'translate', 'display',
|
|
2175
|
+
'box-sizing', 'min-width', 'min-height', 'max-width', 'max-height', 'flex'];
|
|
2176
|
+
function snapshotElementInline(element) {
|
|
2177
|
+
return Object.fromEntries(elementInlineProperties.map(name => [name,
|
|
2178
|
+
[element.style.getPropertyValue(name), element.style.getPropertyPriority(name)]]));
|
|
2179
|
+
}
|
|
2180
|
+
function restoreElementInline(element, snapshot) {
|
|
2181
|
+
for (const [name, [value, priority]] of Object.entries(snapshot)) {
|
|
2182
|
+
if (value) element.style.setProperty(name, value, priority);
|
|
2183
|
+
else element.style.removeProperty(name);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
function elementWriteStyle(gesture, dimensionNames = []) {
|
|
2187
|
+
const style = {};
|
|
2188
|
+
for (const name of dimensionNames) style[name] = gesture.element.style.getPropertyValue(name);
|
|
2189
|
+
for (const name of elementInlineProperties) {
|
|
2190
|
+
if (name === 'translate' || dimensionNames.includes(name)) continue;
|
|
2191
|
+
const now = gesture.element.style.getPropertyValue(name);
|
|
2192
|
+
if (now && now !== gesture.originalInline[name][0]) style[name] = now;
|
|
2193
|
+
}
|
|
2194
|
+
const translate = gesture.element.style.translate;
|
|
2195
|
+
if (translate && translate !== gesture.originalInline.translate[0]) style.translate = translate;
|
|
2196
|
+
return style;
|
|
2197
|
+
}
|
|
2198
|
+
function beginElementGesture(event, handleEl) {
|
|
2199
|
+
const element = focusedElement();
|
|
2200
|
+
if (!element || !elementFocus) return null;
|
|
2201
|
+
hideSnapGuides();
|
|
2202
|
+
const before = elementScreenGeometry(element);
|
|
2203
|
+
const originalInline = snapshotElementInline(element);
|
|
2204
|
+
const computed = getComputedStyle(element);
|
|
2205
|
+
const startStyle = Object.fromEntries(['boxSizing', 'display', 'flex', 'minWidth', 'minHeight',
|
|
2206
|
+
'maxWidth', 'maxHeight'].map(name => [name, computed[name]]));
|
|
2207
|
+
const source = window.akari.state?.summary?.overlays?.find(overlay => overlay.id === selectedId);
|
|
2208
|
+
const declared = source?.elements?.[elementFocus.ref]?.style ?? {};
|
|
2209
|
+
const translate = parseElementTranslate(declared.translate ?? computed.translate);
|
|
2210
|
+
const gesture = { element, elementRef: elementFocus.ref, elementTag: elementFocus.tag,
|
|
2211
|
+
container: selectedOverlay, overlayId: selectedId, handleEl, pointerId: event.pointerId,
|
|
2212
|
+
originalInline, startStyle, parentStyle: element.parentElement
|
|
2213
|
+
? { display: getComputedStyle(element.parentElement).display } : null,
|
|
2214
|
+
startGeometry: before, startClientX: event.clientX, startClientY: event.clientY,
|
|
2215
|
+
startWidth: Number.parseFloat(declared.width) || before.width,
|
|
2216
|
+
startHeight: Number.parseFloat(declared.height) || before.height,
|
|
2217
|
+
x: translate.x, y: translate.y, moved: false, writeContext: captureWriteContext() };
|
|
2218
|
+
if (startStyle.display === 'inline') {
|
|
2219
|
+
element.style.display = 'inline-block';
|
|
2220
|
+
const next = element.getBoundingClientRect();
|
|
2221
|
+
if (Math.hypot(next.left + next.width / 2 - before.center.x,
|
|
2222
|
+
next.top + next.height / 2 - before.center.y) > 0.2) {
|
|
2223
|
+
moveElementToCenter(gesture, before.center.x, before.center.y);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
try { handleEl.setPointerCapture?.(event.pointerId); } catch { /* synthetic pointer */ }
|
|
2227
|
+
if (event.cancelable) event.preventDefault();
|
|
2228
|
+
return gesture;
|
|
2229
|
+
}
|
|
2230
|
+
function showElementHandleHint(event, label) {
|
|
2231
|
+
handleHint?.remove();
|
|
2232
|
+
handleHint = document.createElement('div');
|
|
2233
|
+
handleHint.className = 'akari-interaction-hint';
|
|
2234
|
+
handleHint.setAttribute('data-akari-interaction', 'handle-hint');
|
|
2235
|
+
handleHint.textContent = label;
|
|
2236
|
+
handleHint.style.left = `${event.clientX + 12}px`;
|
|
2237
|
+
handleHint.style.top = `${event.clientY + 12}px`;
|
|
2238
|
+
document.body.appendChild(handleHint);
|
|
2239
|
+
}
|
|
2240
|
+
function beginElementResize(event, handleEl) {
|
|
2241
|
+
if (activeEdit) void commitEdit();
|
|
2242
|
+
const gesture = beginElementGesture(event, handleEl);
|
|
2243
|
+
if (!gesture) return;
|
|
2244
|
+
const name = [...handleEl.classList].find(token => /^is-(?:n|e|s|w|nw|ne|se|sw)$/.test(token))?.slice(3);
|
|
2245
|
+
if (!name) { restoreElementInline(gesture.element, gesture.originalInline); return; }
|
|
2246
|
+
gesture.name = name;
|
|
2247
|
+
gesture.anchor = elementHandlePoints(gesture.startGeometry, name).anchor;
|
|
2248
|
+
activeResize = gesture;
|
|
2249
|
+
showElementHandleHint(event, name.length === 2 ? '大きさ' : ['n', 's'].includes(name) ? '高さ' : '幅');
|
|
2250
|
+
refreshSelectionFrame();
|
|
2251
|
+
}
|
|
2252
|
+
function updateElementResize(event) {
|
|
2253
|
+
const resize = activeResize;
|
|
2254
|
+
const delta = elementLocalDelta({ x: event.clientX - resize.startClientX,
|
|
2255
|
+
y: event.clientY - resize.startClientY }, resize.startGeometry.axes);
|
|
2256
|
+
const size = elementBoxSize(resize.startWidth, resize.startHeight, resize.name, delta, event.shiftKey);
|
|
2257
|
+
const dimensionNames = resize.name.length === 2 ? ['width', 'height']
|
|
2258
|
+
: ['n', 's'].includes(resize.name) ? ['height'] : ['width'];
|
|
2259
|
+
const companions = elementBoxCompanions(resize.startStyle, resize.parentStyle,
|
|
2260
|
+
dimensionNames.includes('width') ? size.width : null,
|
|
2261
|
+
dimensionNames.includes('height') ? size.height : null);
|
|
2262
|
+
for (const name of elementInlineProperties) {
|
|
2263
|
+
if (['translate', 'rotate', 'width', 'height'].includes(name)) continue;
|
|
2264
|
+
const [original, priority] = resize.originalInline[name];
|
|
2265
|
+
if (companions[name]) resize.element.style.setProperty(name, companions[name]);
|
|
2266
|
+
else if (original) resize.element.style.setProperty(name, original, priority);
|
|
2267
|
+
else resize.element.style.removeProperty(name);
|
|
2268
|
+
}
|
|
2269
|
+
if (dimensionNames.includes('width')) resize.element.style.width = `${size.width}px`;
|
|
2270
|
+
if (dimensionNames.includes('height')) resize.element.style.height = `${size.height}px`;
|
|
2271
|
+
const signs = elementHandlePoints(resize.startGeometry, resize.name).signs;
|
|
2272
|
+
moveElementPointTo(resize, -signs[0], -signs[1], resize.anchor);
|
|
2273
|
+
resize.moved = Math.abs(size.width - resize.startWidth) > 0.01
|
|
2274
|
+
|| Math.abs(size.height - resize.startHeight) > 0.01;
|
|
2275
|
+
handleHint.style.left = `${event.clientX + 12}px`;
|
|
2276
|
+
handleHint.style.top = `${event.clientY + 12}px`;
|
|
2277
|
+
refreshSelectionFrame();
|
|
2278
|
+
if (event.cancelable) event.preventDefault();
|
|
2279
|
+
}
|
|
2280
|
+
function finishElementGesture(gesture, dimensions = []) {
|
|
2281
|
+
handleHint?.remove(); handleHint = null;
|
|
2282
|
+
releaseResizePointer(gesture);
|
|
2283
|
+
if (!gesture.moved) {
|
|
2284
|
+
restoreElementInline(gesture.element, gesture.originalInline);
|
|
2285
|
+
refreshSelectionFrame();
|
|
2286
|
+
return null;
|
|
2287
|
+
}
|
|
2288
|
+
const style = elementWriteStyle(gesture, dimensions);
|
|
2289
|
+
const record = enqueueWrite(gesture.writeContext, gesture.overlayId,
|
|
2290
|
+
{ element: { ref: gesture.elementRef, tag: gesture.elementTag, style } }, 'element');
|
|
2291
|
+
record.promise.catch(() => {
|
|
2292
|
+
restoreElementInline(gesture.element, gesture.originalInline);
|
|
2293
|
+
refreshSelectionFrame();
|
|
2294
|
+
});
|
|
2295
|
+
refreshSelectionFrame();
|
|
2296
|
+
return record;
|
|
2297
|
+
}
|
|
2298
|
+
function cancelElementGesture(gesture) {
|
|
2299
|
+
handleHint?.remove(); handleHint = null;
|
|
2300
|
+
releaseResizePointer(gesture);
|
|
2301
|
+
restoreElementInline(gesture.element, gesture.originalInline);
|
|
2302
|
+
refreshSelectionFrame();
|
|
2303
|
+
}
|
|
2304
|
+
function beginElementRotate(event, handleEl) {
|
|
2305
|
+
if (activeEdit) void commitEdit();
|
|
2306
|
+
const gesture = beginElementGesture(event, handleEl);
|
|
2307
|
+
if (!gesture) return;
|
|
2308
|
+
const geometry = elementScreenGeometry(gesture.element);
|
|
2309
|
+
const source = window.akari.state?.summary?.overlays?.find(overlay => overlay.id === selectedId);
|
|
2310
|
+
const declared = source?.elements?.[elementFocus.ref]?.style?.rotate;
|
|
2311
|
+
gesture.startRotate = Number.parseFloat(declared ?? getComputedStyle(gesture.element).rotate) || 0;
|
|
2312
|
+
gesture.center = geometry.center;
|
|
2313
|
+
gesture.startAngle = Math.atan2(event.clientY - geometry.center.y, event.clientX - geometry.center.x);
|
|
2314
|
+
activeRotate = gesture;
|
|
2315
|
+
showElementHandleHint(event, '回転');
|
|
2316
|
+
}
|
|
2317
|
+
function updateElementRotate(event) {
|
|
2318
|
+
const rotation = activeRotate;
|
|
2319
|
+
const currentAngle = Math.atan2(event.clientY - rotation.center.y, event.clientX - rotation.center.x);
|
|
2320
|
+
const degrees = rotation.startRotate + (currentAngle - rotation.startAngle) * 180 / Math.PI;
|
|
2321
|
+
const angle = elementAngle(degrees, event.shiftKey);
|
|
2322
|
+
rotation.element.style.rotate = `${angle}deg`;
|
|
2323
|
+
const rect = rotation.element.getBoundingClientRect();
|
|
2324
|
+
if (Math.hypot(rect.left + rect.width / 2 - rotation.center.x,
|
|
2325
|
+
rect.top + rect.height / 2 - rotation.center.y) > 0.2) {
|
|
2326
|
+
moveElementToCenter(rotation, rotation.center.x, rotation.center.y);
|
|
2327
|
+
}
|
|
2328
|
+
rotation.moved = Math.abs(angle - rotation.startRotate) > 0.01;
|
|
2329
|
+
handleHint.style.left = `${event.clientX + 12}px`;
|
|
2330
|
+
handleHint.style.top = `${event.clientY + 12}px`;
|
|
2331
|
+
refreshSelectionFrame();
|
|
2332
|
+
if (event.cancelable) event.preventDefault();
|
|
2333
|
+
}
|
|
2008
2334
|
function moveElementToCenter(gesture, targetX, targetY) {
|
|
2009
2335
|
const element = gesture.element;
|
|
2010
2336
|
const center = () => {
|
|
@@ -2038,12 +2364,16 @@ function elementLabel(root, element) {
|
|
|
2038
2364
|
if (!gesture) return;
|
|
2039
2365
|
if (Math.abs(gesture.x - gesture.startX) < 0.5 && Math.abs(gesture.y - gesture.startY) < 0.5) {
|
|
2040
2366
|
gesture.element.style.translate = gesture.originalInline;
|
|
2367
|
+
if (gesture.originalDisplay !== undefined) gesture.element.style.display = gesture.originalDisplay;
|
|
2041
2368
|
return;
|
|
2042
2369
|
}
|
|
2370
|
+
const style = { translate: `${gesture.x}px ${gesture.y}px` };
|
|
2371
|
+
if (gesture.originalDisplay !== undefined) style.display = 'inline-block';
|
|
2043
2372
|
enqueueWrite(gesture.writeContext, gesture.overlayId,
|
|
2044
2373
|
{ element: { ref: gesture.ref, tag: gesture.tag,
|
|
2045
|
-
style
|
|
2374
|
+
style } }, 'element').promise.catch(() => {
|
|
2046
2375
|
gesture.element.style.translate = gesture.originalInline;
|
|
2376
|
+
if (gesture.originalDisplay !== undefined) gesture.element.style.display = gesture.originalDisplay;
|
|
2047
2377
|
refreshSelectionFrame();
|
|
2048
2378
|
});
|
|
2049
2379
|
}
|
|
@@ -2088,6 +2418,13 @@ function elementLabel(root, element) {
|
|
|
2088
2418
|
elementNudge = { element, ref: elementFocus.ref, tag: elementFocus.tag,
|
|
2089
2419
|
originalInline: element.style.translate, startX: current.x, startY: current.y,
|
|
2090
2420
|
x: current.x, y: current.y, overlayId: selectedId, writeContext: captureWriteContext() };
|
|
2421
|
+
if (getComputedStyle(element).display === 'inline') {
|
|
2422
|
+
elementNudge.originalDisplay = element.style.display;
|
|
2423
|
+
const before = element.getBoundingClientRect();
|
|
2424
|
+
element.style.display = 'inline-block';
|
|
2425
|
+
moveElementToCenter(elementNudge, before.left + before.width / 2,
|
|
2426
|
+
before.top + before.height / 2);
|
|
2427
|
+
}
|
|
2091
2428
|
}
|
|
2092
2429
|
const step = event.shiftKey ? 10 : 1;
|
|
2093
2430
|
const rect = elementNudge.element.getBoundingClientRect();
|
|
@@ -2223,6 +2560,7 @@ function elementLabel(root, element) {
|
|
|
2223
2560
|
activeDrag = null;
|
|
2224
2561
|
if (drag.element) {
|
|
2225
2562
|
drag.element.style.translate = drag.originalInline;
|
|
2563
|
+
if (drag.originalDisplay !== undefined) drag.element.style.display = drag.originalDisplay;
|
|
2226
2564
|
releasePointer(drag);
|
|
2227
2565
|
refreshSelectionFrame();
|
|
2228
2566
|
return;
|
|
@@ -2256,14 +2594,18 @@ function elementLabel(root, element) {
|
|
|
2256
2594
|
const outputDy = (rect.top + rect.height / 2 - drag.startCenterY) / displayScale;
|
|
2257
2595
|
if (!drag.moved || (Math.abs(outputDx) < 0.5 && Math.abs(outputDy) < 0.5)) {
|
|
2258
2596
|
drag.element.style.translate = drag.originalInline;
|
|
2597
|
+
if (drag.originalDisplay !== undefined) drag.element.style.display = drag.originalDisplay;
|
|
2259
2598
|
refreshSelectionFrame();
|
|
2260
2599
|
return null;
|
|
2261
2600
|
}
|
|
2601
|
+
const style = { translate: `${drag.x}px ${drag.y}px` };
|
|
2602
|
+
if (drag.originalDisplay !== undefined) style.display = 'inline-block';
|
|
2262
2603
|
const record = enqueueWrite(drag.writeContext, drag.overlayId,
|
|
2263
2604
|
{ element: { ref: drag.elementRef, tag: drag.elementTag,
|
|
2264
|
-
style
|
|
2605
|
+
style } }, 'element');
|
|
2265
2606
|
record.promise.catch(() => {
|
|
2266
2607
|
drag.element.style.translate = drag.originalInline;
|
|
2608
|
+
if (drag.originalDisplay !== undefined) drag.element.style.display = drag.originalDisplay;
|
|
2267
2609
|
refreshSelectionFrame();
|
|
2268
2610
|
});
|
|
2269
2611
|
return record;
|
|
@@ -2918,6 +3260,10 @@ function elementLabel(root, element) {
|
|
|
2918
3260
|
}
|
|
2919
3261
|
|
|
2920
3262
|
function beginResize(event, container, handleEl) {
|
|
3263
|
+
if (selectedElementFocus() && focusedElement()) {
|
|
3264
|
+
beginElementResize(event, handleEl);
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
2921
3267
|
if (isTelopOverlay(container) && handleEdge(handleEl)) return;
|
|
2922
3268
|
if (activeEdit) void commitEdit();
|
|
2923
3269
|
|
|
@@ -3024,6 +3370,10 @@ function elementLabel(root, element) {
|
|
|
3024
3370
|
}
|
|
3025
3371
|
|
|
3026
3372
|
function beginRotate(event, handleEl) {
|
|
3373
|
+
if (selectedElementFocus() && focusedElement()) {
|
|
3374
|
+
beginElementRotate(event, handleEl);
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3027
3377
|
if (activeEdit) void commitEdit();
|
|
3028
3378
|
const group = groupSelection;
|
|
3029
3379
|
const members = group ? selectionMembers().map(element => ({ element, transform: readTransform(element) })) : [];
|
|
@@ -3052,6 +3402,7 @@ function elementLabel(root, element) {
|
|
|
3052
3402
|
function updateRotate(event) {
|
|
3053
3403
|
const rotation = activeRotate;
|
|
3054
3404
|
if (!rotation || event.pointerId !== rotation.pointerId) return;
|
|
3405
|
+
if (rotation.element) { updateElementRotate(event); return; }
|
|
3055
3406
|
const pointer = stageLocalPoint(event.clientX, event.clientY);
|
|
3056
3407
|
if (!pointer) return;
|
|
3057
3408
|
let angle = (Math.atan2(pointer.y - rotation.center.y, pointer.x - rotation.center.x)
|
|
@@ -3101,6 +3452,11 @@ function elementLabel(root, element) {
|
|
|
3101
3452
|
function cancelRotate() {
|
|
3102
3453
|
if (!activeRotate) return;
|
|
3103
3454
|
const rotation = activeRotate;
|
|
3455
|
+
if (rotation.element) {
|
|
3456
|
+
activeRotate = null;
|
|
3457
|
+
cancelElementGesture(rotation);
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3104
3460
|
reportLiveValues(rotation.overlayId, undefined, true);
|
|
3105
3461
|
activeRotate = null;
|
|
3106
3462
|
rotationBadge?.remove(); rotationBadge = null;
|
|
@@ -3119,6 +3475,7 @@ function elementLabel(root, element) {
|
|
|
3119
3475
|
if (!activeRotate) return null;
|
|
3120
3476
|
const rotation = activeRotate;
|
|
3121
3477
|
activeRotate = null;
|
|
3478
|
+
if (rotation.element) return finishElementGesture(rotation);
|
|
3122
3479
|
rotationBadge?.remove(); rotationBadge = null;
|
|
3123
3480
|
document.body.style.cursor = '';
|
|
3124
3481
|
releaseResizePointer(rotation);
|
|
@@ -3332,6 +3689,7 @@ function elementLabel(root, element) {
|
|
|
3332
3689
|
function updateResize(event) {
|
|
3333
3690
|
const resize = activeResize;
|
|
3334
3691
|
if (!resize || event.pointerId !== resize.pointerId) return;
|
|
3692
|
+
if (resize.element) { updateElementResize(event); return; }
|
|
3335
3693
|
|
|
3336
3694
|
const pointer = stageLocalPoint(event.clientX, event.clientY);
|
|
3337
3695
|
if (!pointer) return;
|
|
@@ -3430,6 +3788,11 @@ function elementLabel(root, element) {
|
|
|
3430
3788
|
if (!activeResize) return;
|
|
3431
3789
|
|
|
3432
3790
|
const resize = activeResize;
|
|
3791
|
+
if (resize.element) {
|
|
3792
|
+
activeResize = null;
|
|
3793
|
+
cancelElementGesture(resize);
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3433
3796
|
reportLiveValues(resize.overlayId, undefined, true);
|
|
3434
3797
|
activeResize = null;
|
|
3435
3798
|
handleHint?.remove(); handleHint = null;
|
|
@@ -3456,6 +3819,8 @@ function elementLabel(root, element) {
|
|
|
3456
3819
|
|
|
3457
3820
|
const resize = activeResize;
|
|
3458
3821
|
activeResize = null;
|
|
3822
|
+
if (resize.element) return finishElementGesture(resize,
|
|
3823
|
+
resize.name.length === 2 ? ['width', 'height'] : ['n', 's'].includes(resize.name) ? ['height'] : ['width']);
|
|
3459
3824
|
handleHint?.remove(); handleHint = null;
|
|
3460
3825
|
releaseResizePointer(resize);
|
|
3461
3826
|
hideSnapGuides();
|
|
@@ -3657,6 +4022,11 @@ function elementLabel(root, element) {
|
|
|
3657
4022
|
startCenterY: rect.top + rect.height / 2, startX: initial.x, startY: initial.y,
|
|
3658
4023
|
x: initial.x, y: initial.y,
|
|
3659
4024
|
originalInline, moved: false, writeContext: captureWriteContext() };
|
|
4025
|
+
if (getComputedStyle(element).display === 'inline') {
|
|
4026
|
+
activeDrag.originalDisplay = element.style.display;
|
|
4027
|
+
element.style.display = 'inline-block';
|
|
4028
|
+
moveElementToCenter(activeDrag, activeDrag.startCenterX, activeDrag.startCenterY);
|
|
4029
|
+
}
|
|
3660
4030
|
try { container.setPointerCapture?.(event.pointerId); } catch { /* synthetic pointer */ }
|
|
3661
4031
|
return;
|
|
3662
4032
|
}
|
|
@@ -4437,6 +4807,8 @@ function elementLabel(root, element) {
|
|
|
4437
4807
|
if (selectedElementFocus() && !activeEdit) {
|
|
4438
4808
|
const stop = () => { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); };
|
|
4439
4809
|
if (event.key === 'Escape' && activeDrag) { cancelDrag(); stop(); return; }
|
|
4810
|
+
if (event.key === 'Escape' && activeResize) { cancelResize(); stop(); return; }
|
|
4811
|
+
if (event.key === 'Escape' && activeRotate) { cancelRotate(); stop(); return; }
|
|
4440
4812
|
if (event.key === 'Escape' || event.key === 'Enter' && event.shiftKey) {
|
|
4441
4813
|
const root = fragmentRoot(selectedOverlay);
|
|
4442
4814
|
const parent = nearestSelectableElement(root, focusedElement()?.parentElement,
|