view-anchor 0.1.2 → 0.2.0
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 +118 -34
- package/README.zh-CN.md +128 -44
- package/dist/index.d.ts +4 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -14
- package/dist/measure-loop.d.ts +9 -28
- package/dist/measure-loop.d.ts.map +1 -1
- package/dist/measure-loop.js +37 -16
- package/dist/protocol-publisher.d.ts +41 -0
- package/dist/protocol-publisher.d.ts.map +1 -0
- package/dist/protocol-publisher.js +191 -0
- package/dist/protocol-types.d.ts +36 -0
- package/dist/protocol-types.d.ts.map +1 -0
- package/dist/protocol-types.js +10 -0
- package/dist/protocol.d.ts +35 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +131 -0
- package/dist/react.d.ts +18 -30
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +125 -122
- package/dist/size-advertiser.d.ts +9 -14
- package/dist/size-advertiser.d.ts.map +1 -1
- package/dist/size-advertiser.js +20 -28
- package/dist/types.d.ts +29 -73
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -15
- package/dist/view-anchor.d.ts +36 -77
- package/dist/view-anchor.d.ts.map +1 -1
- package/dist/view-anchor.js +206 -174
- package/docs/bidirectional-design.md +64 -96
- package/docs/{anchor-3d.html → index.html} +215 -73
- package/docs/mechanism.mdx +55 -49
- package/docs/performance-report.md +63 -0
- package/docs/protocol.md +79 -0
- package/package.json +30 -4
- package/src/index.ts +6 -15
- package/src/measure-loop.ts +36 -41
- package/src/protocol-publisher.ts +236 -0
- package/src/protocol-types.ts +43 -0
- package/src/protocol.ts +193 -0
- package/src/react.ts +186 -141
- package/src/size-advertiser.ts +24 -31
- package/src/types.ts +34 -79
- package/src/view-anchor.ts +228 -212
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>view-anchor
|
|
6
|
+
<title>view-anchor 3D demo: align an Electron WebContentsView, webview or iframe to a DOM element</title>
|
|
7
|
+
<meta name="description" content="view-anchor is a high-performance geometry bridge that keeps an Electron WebContentsView, native webview or cross-origin iframe aligned to a DOM element. This interactive 3D demo runs the real core in your browser.">
|
|
7
8
|
<style>
|
|
8
9
|
:root {
|
|
9
10
|
--bg: hsl( 0 0% 7%);
|
|
@@ -189,22 +190,44 @@
|
|
|
189
190
|
|
|
190
191
|
footer.note { margin-top: 22px; color: var(--text-3); font-size: 12px; line-height: 1.6; }
|
|
191
192
|
footer.note code { color: var(--text-2); }
|
|
193
|
+
|
|
194
|
+
/* ---- landing intro ---- */
|
|
195
|
+
.badges { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 16px; }
|
|
196
|
+
.badges img { display: block; }
|
|
197
|
+
.tag-en { color: var(--text-2); font-size: 14.5px; margin: 10px 0 0; max-width: 72ch; }
|
|
198
|
+
.actions { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 12px; font-size: 13px; }
|
|
199
|
+
.actions a { color: var(--placeholder-bd); text-decoration: none; border-bottom: 1px solid hsl(249 100% 72% / 0.3); }
|
|
200
|
+
.actions a:hover { color: var(--text); border-bottom-color: var(--text-2); }
|
|
201
|
+
footer.note { display: flex; flex-direction: column; gap: 10px; }
|
|
202
|
+
footer.note .hint { color: var(--text-3); font-size: 12.5px; }
|
|
203
|
+
|
|
192
204
|
</style>
|
|
193
205
|
</head>
|
|
194
206
|
<body>
|
|
195
207
|
<div class="wrap">
|
|
196
208
|
<header>
|
|
197
|
-
<
|
|
198
|
-
|
|
209
|
+
<div class="badges">
|
|
210
|
+
<img alt="npm version" src="https://img.shields.io/npm/v/view-anchor">
|
|
211
|
+
<img alt="npm downloads" src="https://img.shields.io/npm/dm/view-anchor">
|
|
212
|
+
<img alt="License" src="https://img.shields.io/npm/l/view-anchor">
|
|
213
|
+
<img alt="Node" src="https://img.shields.io/badge/node-%3E%3D24-339933">
|
|
214
|
+
</div>
|
|
215
|
+
<h1>view-anchor: interactive 3D demo</h1>
|
|
216
|
+
<p class="tag-en">Align a native <b style="color:var(--native-bd)">WebContentsView</b> to a DOM element's geometry. Resize the splitter, toggle <code>present</code>, or explode the 3D scene below: the green view is positioned by the real view-anchor core running in this page.</p>
|
|
217
|
+
<div class="actions">
|
|
218
|
+
<a href="https://github.com/lbb00/view-anchor">GitHub</a>
|
|
219
|
+
<a href="https://www.npmjs.com/package/view-anchor">npm</a>
|
|
220
|
+
<a href="https://github.com/lbb00/view-anchor/blob/main/README.md">README</a>
|
|
221
|
+
</div>
|
|
199
222
|
</header>
|
|
200
223
|
|
|
201
|
-
|
|
224
|
+
<div class="stage-shell">
|
|
202
225
|
<div class="scene" id="scene">
|
|
203
226
|
<div class="deck" id="deck">
|
|
204
|
-
<!--
|
|
227
|
+
<!-- bottom layer: the renderer / DOM window -->
|
|
205
228
|
<div class="window">
|
|
206
229
|
<div class="chrome-bar"><div class="traffic"></div></div>
|
|
207
|
-
<div class="caption"><b
|
|
230
|
+
<div class="caption"><b>Renderer DOM</b></div>
|
|
208
231
|
<div class="dom-area">
|
|
209
232
|
<div class="dom-sidebar"><i></i><i></i><i></i><i></i><i></i></div>
|
|
210
233
|
<div class="placeholder" id="placeholder">
|
|
@@ -213,10 +236,10 @@
|
|
|
213
236
|
</div>
|
|
214
237
|
</div>
|
|
215
238
|
|
|
216
|
-
<!--
|
|
239
|
+
<!-- connector pillar: from the placeholder up to the native view along Z -->
|
|
217
240
|
<div class="pillar" id="pillar"></div>
|
|
218
241
|
|
|
219
|
-
<!--
|
|
242
|
+
<!-- top layer: the native WebContentsView, driven by view-anchor -->
|
|
220
243
|
<div class="follower" id="follower">
|
|
221
244
|
<div class="row-marker"></div>
|
|
222
245
|
<div class="nv-label">WebContentsView</div>
|
|
@@ -233,31 +256,41 @@
|
|
|
233
256
|
<label for="present"><code>present</code></label>
|
|
234
257
|
</div>
|
|
235
258
|
<div class="ctl">
|
|
236
|
-
<label for="split"
|
|
259
|
+
<label for="split">Split</label>
|
|
237
260
|
<input type="range" id="split" min="34" max="78" value="58">
|
|
238
261
|
</div>
|
|
239
262
|
<div class="ctl">
|
|
240
263
|
<span class="switch">
|
|
241
|
-
<input type="checkbox" id="explode">
|
|
264
|
+
<input type="checkbox" id="explode" checked>
|
|
242
265
|
<span class="track"></span><span class="thumb"></span>
|
|
243
266
|
</span>
|
|
244
|
-
<label for="explode"
|
|
267
|
+
<label for="explode">Explode</label>
|
|
245
268
|
</div>
|
|
246
269
|
<div class="readout" id="readout"></div>
|
|
247
270
|
</div>
|
|
248
271
|
|
|
249
272
|
<div class="legend">
|
|
250
|
-
<span><span class="sw r"></span> DOM
|
|
251
|
-
<span><span class="sw p"></span>
|
|
273
|
+
<span><span class="sw r"></span> DOM window</span>
|
|
274
|
+
<span><span class="sw p"></span> Placeholder div</span>
|
|
252
275
|
<span><span class="sw n"></span> WebContentsView</span>
|
|
253
276
|
</div>
|
|
254
277
|
</div>
|
|
278
|
+
|
|
279
|
+
<footer class="note">
|
|
280
|
+
<div class="actions">
|
|
281
|
+
<a href="https://github.com/lbb00/view-anchor">GitHub repository</a>
|
|
282
|
+
<a href="https://www.npmjs.com/package/view-anchor">npm · view-anchor</a>
|
|
283
|
+
<span style="color:var(--text-3)">MIT licensed</span>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="hint">Drag <code>Split</code> to resize the placeholder, toggle <code>present</code> to collapse or restore, and toggle <code>Explode</code> for the 3D perspective. The green view's bounds come directly from view-anchor's <code>publish</code> callback.</div>
|
|
286
|
+
</footer>
|
|
287
|
+
|
|
255
288
|
</div>
|
|
256
289
|
|
|
257
|
-
<!-- view-anchor
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
290
|
+
<!-- Bootstrapped from src/view-anchor.ts by `pnpm build:docs` and inlined here
|
|
291
|
+
as an esbuild IIFE — this block is GENERATED, do not hand-edit. Inlined
|
|
292
|
+
rather than imported so the page can open from file://, where browsers
|
|
293
|
+
block cross-origin ES module imports. -->
|
|
261
294
|
<script>
|
|
262
295
|
/* __VIEW_ANCHOR_CORE_START__ — generated from src/view-anchor.ts by `pnpm build:docs`; do not edit */
|
|
263
296
|
"use strict";
|
|
@@ -299,18 +332,33 @@ var __viewAnchorCore = (() => {
|
|
|
299
332
|
let publish = opts.publish;
|
|
300
333
|
let observer = null;
|
|
301
334
|
let lastPublished = null;
|
|
335
|
+
let publicationRevision = 0;
|
|
302
336
|
let disposed = false;
|
|
303
337
|
const measure = () => {
|
|
304
338
|
const r = target.getBoundingClientRect();
|
|
339
|
+
if (!Number.isFinite(r.left) || !Number.isFinite(r.top) || !Number.isFinite(r.width) || !Number.isFinite(r.height)) return null;
|
|
305
340
|
return clampRect({ x: r.left, y: r.top, width: r.width, height: r.height });
|
|
306
341
|
};
|
|
307
342
|
const sameRect = (a, b) => a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
343
|
+
const publishCandidate = (candidate) => {
|
|
344
|
+
const previous = lastPublished;
|
|
345
|
+
const attempt = ++publicationRevision;
|
|
346
|
+
lastPublished = candidate;
|
|
347
|
+
try {
|
|
348
|
+
const accepted = publish(candidate) !== false;
|
|
349
|
+
if (!accepted && publicationRevision === attempt) lastPublished = previous;
|
|
350
|
+
return accepted;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (publicationRevision === attempt) lastPublished = previous;
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
308
356
|
const emit = () => {
|
|
309
357
|
if (disposed || !present) return;
|
|
310
358
|
const m = measure();
|
|
359
|
+
if (!m) return;
|
|
311
360
|
if (lastPublished && sameRect(lastPublished, m)) return;
|
|
312
|
-
|
|
313
|
-
publish(m);
|
|
361
|
+
publishCandidate(m);
|
|
314
362
|
};
|
|
315
363
|
const startObserving = () => {
|
|
316
364
|
if (observer) return;
|
|
@@ -329,11 +377,11 @@ var __viewAnchorCore = (() => {
|
|
|
329
377
|
lastPublished = null;
|
|
330
378
|
if (present) {
|
|
331
379
|
startObserving();
|
|
332
|
-
|
|
333
|
-
|
|
380
|
+
const measured = measure();
|
|
381
|
+
if (measured) publishCandidate(measured);
|
|
334
382
|
} else {
|
|
335
383
|
stopObserving();
|
|
336
|
-
|
|
384
|
+
publishCandidate(ZERO);
|
|
337
385
|
}
|
|
338
386
|
};
|
|
339
387
|
apply();
|
|
@@ -368,32 +416,54 @@ var __viewAnchorCore = (() => {
|
|
|
368
416
|
function createPlacementAnchor(target, opts) {
|
|
369
417
|
let visible = opts.visible;
|
|
370
418
|
let publish = opts.publish;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
419
|
+
let guardDisplayNone = opts.guardDisplayNone ?? false;
|
|
420
|
+
let followScroll = opts.followScroll ?? false;
|
|
421
|
+
let followGeometry = opts.followGeometry ?? false;
|
|
374
422
|
let observer = null;
|
|
375
423
|
let io = null;
|
|
424
|
+
let scrollListening = false;
|
|
425
|
+
let geometryListening = false;
|
|
426
|
+
const capture = { capture: true };
|
|
427
|
+
const passiveCapture = { capture: true, passive: true };
|
|
376
428
|
let lastPublished = null;
|
|
429
|
+
let publicationRevision = 0;
|
|
377
430
|
let disposed = false;
|
|
378
431
|
let rafId = null;
|
|
379
432
|
let steadyFrames = 0;
|
|
380
433
|
const STEADY_CLOSE_FRAMES = 2;
|
|
381
434
|
const MAX_HIDDEN_FOLLOW_FRAMES = 30;
|
|
435
|
+
const MAX_INVALID_FOLLOW_FRAMES = 30;
|
|
436
|
+
let invalidFrames = 0;
|
|
382
437
|
let pointerHeld = false;
|
|
438
|
+
let activePointerId = null;
|
|
383
439
|
let sentinelDeadline = null;
|
|
384
440
|
const computePlacement = () => {
|
|
385
441
|
const p = measurePlacement(target);
|
|
442
|
+
if (p.visible && (!Number.isFinite(p.bounds.x) || !Number.isFinite(p.bounds.y) || !Number.isFinite(p.bounds.width) || !Number.isFinite(p.bounds.height))) return null;
|
|
386
443
|
if (guardDisplayNone && p.visible && (p.bounds.width === 0 || p.bounds.height === 0)) {
|
|
387
444
|
return { visible: false };
|
|
388
445
|
}
|
|
389
446
|
return p;
|
|
390
447
|
};
|
|
448
|
+
const publishCandidate = (candidate) => {
|
|
449
|
+
const previous = lastPublished;
|
|
450
|
+
const attempt = ++publicationRevision;
|
|
451
|
+
lastPublished = candidate;
|
|
452
|
+
try {
|
|
453
|
+
const accepted = publish(candidate) !== false;
|
|
454
|
+
if (!accepted && publicationRevision === attempt) lastPublished = previous;
|
|
455
|
+
return accepted;
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (publicationRevision === attempt) lastPublished = previous;
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
391
461
|
const emit = () => {
|
|
392
462
|
if (disposed || !visible) return;
|
|
393
463
|
const p = computePlacement();
|
|
464
|
+
if (!p) return;
|
|
394
465
|
if (lastPublished && samePlacement(lastPublished, p)) return;
|
|
395
|
-
|
|
396
|
-
publish(p);
|
|
466
|
+
publishCandidate(p);
|
|
397
467
|
};
|
|
398
468
|
const shouldCloseOnHiddenPoll = () => {
|
|
399
469
|
if (lastPublished?.visible === false) return true;
|
|
@@ -410,12 +480,29 @@ var __viewAnchorCore = (() => {
|
|
|
410
480
|
return;
|
|
411
481
|
}
|
|
412
482
|
const p = computePlacement();
|
|
483
|
+
if (!p) {
|
|
484
|
+
if (invalidFrames++ >= MAX_INVALID_FOLLOW_FRAMES) {
|
|
485
|
+
sentinelDeadline = null;
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (!disposed && visible && followGeometry) {
|
|
489
|
+
rafId = requestAnimationFrame(sentinelFrame);
|
|
490
|
+
} else {
|
|
491
|
+
sentinelDeadline = null;
|
|
492
|
+
}
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
invalidFrames = 0;
|
|
413
496
|
if (!p.visible) {
|
|
414
497
|
if (shouldCloseOnHiddenPoll()) {
|
|
415
498
|
sentinelDeadline = null;
|
|
416
499
|
return;
|
|
417
500
|
}
|
|
418
|
-
|
|
501
|
+
if (!disposed && visible && followGeometry) {
|
|
502
|
+
rafId = requestAnimationFrame(sentinelFrame);
|
|
503
|
+
} else {
|
|
504
|
+
sentinelDeadline = null;
|
|
505
|
+
}
|
|
419
506
|
return;
|
|
420
507
|
}
|
|
421
508
|
if (lastPublished && samePlacement(lastPublished, p)) {
|
|
@@ -425,11 +512,10 @@ var __viewAnchorCore = (() => {
|
|
|
425
512
|
return;
|
|
426
513
|
}
|
|
427
514
|
} else {
|
|
428
|
-
|
|
429
|
-
publish(p);
|
|
515
|
+
publishCandidate(p);
|
|
430
516
|
steadyFrames = 0;
|
|
431
517
|
}
|
|
432
|
-
if (!disposed && visible) {
|
|
518
|
+
if (!disposed && visible && followGeometry) {
|
|
433
519
|
rafId = requestAnimationFrame(sentinelFrame);
|
|
434
520
|
} else {
|
|
435
521
|
sentinelDeadline = null;
|
|
@@ -438,7 +524,10 @@ var __viewAnchorCore = (() => {
|
|
|
438
524
|
const openSentinel = () => {
|
|
439
525
|
if (!followGeometry || disposed) return;
|
|
440
526
|
steadyFrames = 0;
|
|
441
|
-
if (rafId === null)
|
|
527
|
+
if (rafId === null) {
|
|
528
|
+
invalidFrames = 0;
|
|
529
|
+
rafId = requestAnimationFrame(sentinelFrame);
|
|
530
|
+
}
|
|
442
531
|
};
|
|
443
532
|
const closeSentinel = () => {
|
|
444
533
|
if (rafId !== null) {
|
|
@@ -446,8 +535,10 @@ var __viewAnchorCore = (() => {
|
|
|
446
535
|
rafId = null;
|
|
447
536
|
}
|
|
448
537
|
steadyFrames = 0;
|
|
538
|
+
invalidFrames = 0;
|
|
449
539
|
sentinelDeadline = null;
|
|
450
540
|
pointerHeld = false;
|
|
541
|
+
activePointerId = null;
|
|
451
542
|
};
|
|
452
543
|
const onScroll = () => {
|
|
453
544
|
if (followGeometry) openSentinel();
|
|
@@ -456,67 +547,105 @@ var __viewAnchorCore = (() => {
|
|
|
456
547
|
const onPointerDown = (e) => {
|
|
457
548
|
const t = e.target;
|
|
458
549
|
if (t && t.closest && t.closest('[role="separator"]')) {
|
|
550
|
+
if (!pointerHeld) activePointerId = e.pointerId;
|
|
459
551
|
pointerHeld = true;
|
|
460
552
|
openSentinel();
|
|
461
553
|
}
|
|
462
554
|
};
|
|
463
|
-
const
|
|
555
|
+
const releasePointer = (e) => {
|
|
464
556
|
if (!pointerHeld) return;
|
|
557
|
+
if (e && activePointerId !== e.pointerId) return;
|
|
465
558
|
pointerHeld = false;
|
|
559
|
+
activePointerId = null;
|
|
466
560
|
openSentinel();
|
|
467
561
|
};
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
562
|
+
const onPointerUp = (e) => {
|
|
563
|
+
releasePointer(e);
|
|
564
|
+
};
|
|
565
|
+
const onPointerCancel = (e) => {
|
|
566
|
+
releasePointer(e);
|
|
567
|
+
};
|
|
568
|
+
const onWindowBlur = () => {
|
|
569
|
+
releasePointer();
|
|
570
|
+
};
|
|
571
|
+
const startOptionalObserving = () => {
|
|
572
|
+
if (guardDisplayNone && !io && typeof IntersectionObserver !== "undefined") {
|
|
474
573
|
io = new IntersectionObserver(emit);
|
|
475
574
|
io.observe(target);
|
|
476
575
|
}
|
|
477
|
-
if (followScroll) {
|
|
478
|
-
window.addEventListener("scroll", onScroll,
|
|
479
|
-
|
|
480
|
-
passive: true
|
|
481
|
-
});
|
|
576
|
+
if (followScroll && !scrollListening) {
|
|
577
|
+
window.addEventListener("scroll", onScroll, passiveCapture);
|
|
578
|
+
scrollListening = true;
|
|
482
579
|
}
|
|
483
|
-
if (followGeometry) {
|
|
484
|
-
window.addEventListener("pointerdown", onPointerDown,
|
|
485
|
-
window.addEventListener("pointerup", onPointerUp,
|
|
580
|
+
if (followGeometry && !geometryListening) {
|
|
581
|
+
window.addEventListener("pointerdown", onPointerDown, capture);
|
|
582
|
+
window.addEventListener("pointerup", onPointerUp, capture);
|
|
583
|
+
window.addEventListener("pointercancel", onPointerCancel, capture);
|
|
584
|
+
window.addEventListener("blur", onWindowBlur);
|
|
585
|
+
geometryListening = true;
|
|
486
586
|
}
|
|
487
587
|
};
|
|
488
|
-
const
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
|
|
588
|
+
const stopOptionalObserving = () => {
|
|
589
|
+
if (io && !guardDisplayNone) {
|
|
590
|
+
io.disconnect();
|
|
591
|
+
io = null;
|
|
492
592
|
}
|
|
593
|
+
if (scrollListening && !followScroll) {
|
|
594
|
+
window.removeEventListener("scroll", onScroll, passiveCapture);
|
|
595
|
+
scrollListening = false;
|
|
596
|
+
}
|
|
597
|
+
if (geometryListening && !followGeometry) {
|
|
598
|
+
window.removeEventListener("pointerdown", onPointerDown, capture);
|
|
599
|
+
window.removeEventListener("pointerup", onPointerUp, capture);
|
|
600
|
+
window.removeEventListener("pointercancel", onPointerCancel, capture);
|
|
601
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
602
|
+
geometryListening = false;
|
|
603
|
+
closeSentinel();
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
const stopAllOptionalObserving = () => {
|
|
493
607
|
if (io) {
|
|
494
608
|
io.disconnect();
|
|
495
609
|
io = null;
|
|
496
610
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
capture
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
611
|
+
if (scrollListening) {
|
|
612
|
+
window.removeEventListener("scroll", onScroll, passiveCapture);
|
|
613
|
+
scrollListening = false;
|
|
614
|
+
}
|
|
615
|
+
if (geometryListening) {
|
|
616
|
+
window.removeEventListener("pointerdown", onPointerDown, capture);
|
|
617
|
+
window.removeEventListener("pointerup", onPointerUp, capture);
|
|
618
|
+
window.removeEventListener("pointercancel", onPointerCancel, capture);
|
|
619
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
620
|
+
geometryListening = false;
|
|
621
|
+
}
|
|
507
622
|
closeSentinel();
|
|
508
623
|
};
|
|
624
|
+
const startObserving = () => {
|
|
625
|
+
if (observer) return;
|
|
626
|
+
observer = new ResizeObserver(emit);
|
|
627
|
+
observer.observe(target);
|
|
628
|
+
window.addEventListener("resize", emit);
|
|
629
|
+
startOptionalObserving();
|
|
630
|
+
};
|
|
631
|
+
const stopObserving = () => {
|
|
632
|
+
if (observer) {
|
|
633
|
+
observer.disconnect();
|
|
634
|
+
observer = null;
|
|
635
|
+
}
|
|
636
|
+
window.removeEventListener("resize", emit);
|
|
637
|
+
stopAllOptionalObserving();
|
|
638
|
+
};
|
|
509
639
|
const apply = () => {
|
|
510
640
|
lastPublished = null;
|
|
511
641
|
if (visible) {
|
|
512
642
|
startObserving();
|
|
513
|
-
|
|
514
|
-
|
|
643
|
+
const placement = computePlacement();
|
|
644
|
+
if (placement) publishCandidate(placement);
|
|
515
645
|
} else {
|
|
516
646
|
stopObserving();
|
|
517
647
|
const hidden = { visible: false };
|
|
518
|
-
|
|
519
|
-
publish(hidden);
|
|
648
|
+
publishCandidate(hidden);
|
|
520
649
|
}
|
|
521
650
|
};
|
|
522
651
|
apply();
|
|
@@ -525,6 +654,13 @@ var __viewAnchorCore = (() => {
|
|
|
525
654
|
if (disposed) return;
|
|
526
655
|
publish = next.publish;
|
|
527
656
|
visible = next.visible;
|
|
657
|
+
guardDisplayNone = next.guardDisplayNone ?? guardDisplayNone;
|
|
658
|
+
followScroll = next.followScroll ?? followScroll;
|
|
659
|
+
followGeometry = next.followGeometry ?? followGeometry;
|
|
660
|
+
if (visible && observer) {
|
|
661
|
+
stopOptionalObserving();
|
|
662
|
+
startOptionalObserving();
|
|
663
|
+
}
|
|
528
664
|
apply();
|
|
529
665
|
},
|
|
530
666
|
dispose() {
|
|
@@ -562,16 +698,18 @@ var __viewAnchorCore = (() => {
|
|
|
562
698
|
const scene = document.getElementById('scene')
|
|
563
699
|
const readout = document.getElementById('readout')
|
|
564
700
|
|
|
565
|
-
//
|
|
566
|
-
//
|
|
701
|
+
// The placeholder's rect inside the deck (accumulated along the offsetParent
|
|
702
|
+
// chain, independent of the 3D transform) — flat or tilted, the native view
|
|
703
|
+
// stays registered directly above the placeholder.
|
|
567
704
|
function deckLocalRect(el) {
|
|
568
705
|
let x = 0, y = 0, n = el
|
|
569
706
|
while (n && n !== deck) { x += n.offsetLeft; y += n.offsetTop; n = n.offsetParent }
|
|
570
707
|
return { x, y, width: el.offsetWidth, height: el.offsetHeight }
|
|
571
708
|
}
|
|
572
709
|
|
|
573
|
-
//
|
|
574
|
-
// IPC → WebContentsView.setBounds
|
|
710
|
+
// The host-side publish — view-anchor hands it the measured rect. A real app
|
|
711
|
+
// would go IPC → WebContentsView.setBounds; here we place the view in the
|
|
712
|
+
// deck's local coordinates.
|
|
575
713
|
function publish(bounds) {
|
|
576
714
|
if (bounds.width === 0 || bounds.height === 0) {
|
|
577
715
|
follower.classList.add('collapsed')
|
|
@@ -584,7 +722,8 @@ var __viewAnchorCore = (() => {
|
|
|
584
722
|
follower.style.top = r.y + 'px'
|
|
585
723
|
follower.style.width = r.width + 'px'
|
|
586
724
|
follower.style.height = r.height + 'px'
|
|
587
|
-
//
|
|
725
|
+
// Pillar stands on the placeholder's centerline, rising along Z to the
|
|
726
|
+
// view's height (120px).
|
|
588
727
|
pillar.style.left = (r.x + r.width / 2) + 'px'
|
|
589
728
|
pillar.style.top = (r.y + r.height / 2) + 'px'
|
|
590
729
|
pillar.style.height = '120px'
|
|
@@ -595,10 +734,10 @@ var __viewAnchorCore = (() => {
|
|
|
595
734
|
readout.innerHTML =
|
|
596
735
|
'<span class="k">publish</span>({ x:' + b.x + ', y:' + b.y +
|
|
597
736
|
', w:' + b.width + ', h:' + b.height + ' })' +
|
|
598
|
-
(present ? '' : ' <span class="k">//
|
|
737
|
+
(present ? '' : ' <span class="k">// detached</span>')
|
|
599
738
|
}
|
|
600
739
|
|
|
601
|
-
//
|
|
740
|
+
// Start the real anchor; split resizes are driven by view-anchor's own ResizeObserver.
|
|
602
741
|
const handle = createViewAnchor(placeholder, { present: presentEl.checked, publish })
|
|
603
742
|
|
|
604
743
|
presentEl.addEventListener('change', () => {
|
|
@@ -607,6 +746,9 @@ var __viewAnchorCore = (() => {
|
|
|
607
746
|
splitEl.addEventListener('input', () => {
|
|
608
747
|
placeholder.style.flexBasis = splitEl.value + '%'
|
|
609
748
|
})
|
|
749
|
+
// Explode defaults ON: apply the exploded pose at boot — uncheck to see the flat view.
|
|
750
|
+
if (explodeEl.checked) scene.classList.add('exploded')
|
|
751
|
+
|
|
610
752
|
explodeEl.addEventListener('change', () => {
|
|
611
753
|
scene.classList.toggle('exploded', explodeEl.checked)
|
|
612
754
|
})
|