view-anchor 0.2.0 → 0.2.1

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/docs/index.html CHANGED
@@ -3,8 +3,8 @@
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 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.">
6
+ <title>view-anchor 3D demo: align anything outside the DOM to a DOM element</title>
7
+ <meta name="description" content="view-anchor is a high-performance geometry bridge that keeps an anchored view — an Electron WebContentsView, a native webview, a cross-origin iframe aligned to a DOM element. This interactive 3D demo runs the real core in your browser.">
8
8
  <style>
9
9
  :root {
10
10
  --bg: hsl( 0 0% 7%);
@@ -57,7 +57,7 @@
57
57
  display: flex; align-items: center; justify-content: center;
58
58
  }
59
59
  /* the deck tilts as a whole on "explode" — both the DOM window and the
60
- native view ride it, so they stay in registration in the 3D side view. */
60
+ anchored view ride it, so they stay in registration in the 3D side view. */
61
61
  .deck {
62
62
  position: relative;
63
63
  width: 560px; height: 300px;
@@ -117,7 +117,7 @@
117
117
  .placeholder .ph-tag { font-size: 11px; color: var(--placeholder-bd); text-align: center; padding: 0 8px; }
118
118
  .placeholder .ph-tag small { color: var(--text-3); display:block; margin-top:2px; font-size:10px; }
119
119
 
120
- /* top layer: native WebContentsView. A direct child of the deck so it lives
120
+ /* top layer: the anchored view. A direct child of the deck so it lives
121
121
  in the same 3D space; lifted in Z on explode to float above the DOM. */
122
122
  .follower {
123
123
  position: absolute;
@@ -213,7 +213,7 @@
213
213
  <img alt="Node" src="https://img.shields.io/badge/node-%3E%3D24-339933">
214
214
  </div>
215
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>
216
+ <p class="tag-en">Align an <b style="color:var(--native-bd)">anchored view</b> — an Electron <code>WebContentsView</code>, a native webview, a cross-origin iframe — to a DOM element's geometry. Resize the splitter, toggle <code>present</code>, or explode the 3D scene below: the green anchored view is positioned by the real view-anchor core running in this page.</p>
217
217
  <div class="actions">
218
218
  <a href="https://github.com/lbb00/view-anchor">GitHub</a>
219
219
  <a href="https://www.npmjs.com/package/view-anchor">npm</a>
@@ -236,13 +236,13 @@
236
236
  </div>
237
237
  </div>
238
238
 
239
- <!-- connector pillar: from the placeholder up to the native view along Z -->
239
+ <!-- connector pillar: from the placeholder up to the anchored view along Z -->
240
240
  <div class="pillar" id="pillar"></div>
241
241
 
242
- <!-- top layer: the native WebContentsView, driven by view-anchor -->
242
+ <!-- top layer: the anchored view, driven by view-anchor -->
243
243
  <div class="follower" id="follower">
244
244
  <div class="row-marker"></div>
245
- <div class="nv-label">WebContentsView</div>
245
+ <div class="nv-label">Anchored view</div>
246
246
  </div>
247
247
  </div>
248
248
  </div>
@@ -272,7 +272,7 @@
272
272
  <div class="legend">
273
273
  <span><span class="sw r"></span> DOM window</span>
274
274
  <span><span class="sw p"></span> Placeholder div</span>
275
- <span><span class="sw n"></span> WebContentsView</span>
275
+ <span><span class="sw n"></span> Anchored view</span>
276
276
  </div>
277
277
  </div>
278
278
 
@@ -282,7 +282,7 @@
282
282
  <a href="https://www.npmjs.com/package/view-anchor">npm · view-anchor</a>
283
283
  <span style="color:var(--text-3)">MIT licensed</span>
284
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>
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 anchored view's bounds come directly from view-anchor's <code>publish</code> callback.</div>
286
286
  </footer>
287
287
 
288
288
  </div>
@@ -293,393 +293,408 @@
293
293
  block cross-origin ES module imports. -->
294
294
  <script>
295
295
  /* __VIEW_ANCHOR_CORE_START__ — generated from src/view-anchor.ts by `pnpm build:docs`; do not edit */
296
- "use strict";
297
- var __viewAnchorCore = (() => {
298
- var __defProp = Object.defineProperty;
299
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
300
- var __getOwnPropNames = Object.getOwnPropertyNames;
301
- var __hasOwnProp = Object.prototype.hasOwnProperty;
302
- var __export = (target, all) => {
303
- for (var name in all)
304
- __defProp(target, name, { get: all[name], enumerable: true });
305
- };
306
- var __copyProps = (to, from, except, desc) => {
307
- if (from && typeof from === "object" || typeof from === "function") {
308
- for (let key of __getOwnPropNames(from))
309
- if (!__hasOwnProp.call(to, key) && key !== except)
310
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
311
- }
312
- return to;
313
- };
314
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
315
-
316
- // src/view-anchor.ts
317
- var view_anchor_exports = {};
318
- __export(view_anchor_exports, {
319
- createPlacementAnchor: () => createPlacementAnchor,
320
- createViewAnchor: () => createViewAnchor,
321
- measurePlacement: () => measurePlacement
322
- });
323
- var ZERO = { x: 0, y: 0, width: 0, height: 0 };
324
- var clampRect = (r) => ({
325
- x: Math.round(r.x),
326
- y: Math.round(r.y),
327
- width: Math.max(0, Math.round(r.width)),
328
- height: Math.max(0, Math.round(r.height))
329
- });
330
- function createViewAnchor(target, opts) {
331
- let present = opts.present;
332
- let publish = opts.publish;
333
- let observer = null;
334
- let lastPublished = null;
335
- let publicationRevision = 0;
336
- let disposed = false;
337
- const measure = () => {
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;
340
- return clampRect({ x: r.left, y: r.top, width: r.width, height: r.height });
341
- };
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
- };
356
- const emit = () => {
357
- if (disposed || !present) return;
358
- const m = measure();
359
- if (!m) return;
360
- if (lastPublished && sameRect(lastPublished, m)) return;
361
- publishCandidate(m);
362
- };
363
- const startObserving = () => {
364
- if (observer) return;
365
- observer = new ResizeObserver(emit);
366
- observer.observe(target);
367
- window.addEventListener("resize", emit);
368
- };
369
- const stopObserving = () => {
370
- if (observer) {
371
- observer.disconnect();
372
- observer = null;
373
- }
374
- window.removeEventListener("resize", emit);
375
- };
376
- const apply = () => {
377
- lastPublished = null;
378
- if (present) {
379
- startObserving();
380
- const measured = measure();
381
- if (measured) publishCandidate(measured);
382
- } else {
383
- stopObserving();
384
- publishCandidate(ZERO);
385
- }
386
- };
387
- apply();
388
- return {
389
- update(next) {
390
- if (disposed) return;
391
- publish = next.publish;
392
- present = next.present;
393
- apply();
394
- },
395
- dispose() {
396
- if (disposed) return;
397
- disposed = true;
398
- stopObserving();
399
- }
400
- };
401
- }
402
- function measurePlacement(target) {
403
- const r = target.getBoundingClientRect();
404
- return {
405
- visible: true,
406
- bounds: clampRect({ x: r.left, y: r.top, width: r.width, height: r.height })
407
- };
408
- }
409
- var samePlacement = (a, b) => {
410
- if (a.visible !== b.visible) return false;
411
- if (a.visible && b.visible) {
412
- return a.bounds.x === b.bounds.x && a.bounds.y === b.bounds.y && a.bounds.width === b.bounds.width && a.bounds.height === b.bounds.height;
413
- }
414
- return true;
415
- };
416
- function createPlacementAnchor(target, opts) {
417
- let visible = opts.visible;
418
- let publish = opts.publish;
419
- let guardDisplayNone = opts.guardDisplayNone ?? false;
420
- let followScroll = opts.followScroll ?? false;
421
- let followGeometry = opts.followGeometry ?? false;
422
- let observer = null;
423
- let io = null;
424
- let scrollListening = false;
425
- let geometryListening = false;
426
- const capture = { capture: true };
427
- const passiveCapture = { capture: true, passive: true };
428
- let lastPublished = null;
429
- let publicationRevision = 0;
430
- let disposed = false;
431
- let rafId = null;
432
- let steadyFrames = 0;
433
- const STEADY_CLOSE_FRAMES = 2;
434
- const MAX_HIDDEN_FOLLOW_FRAMES = 30;
435
- const MAX_INVALID_FOLLOW_FRAMES = 30;
436
- let invalidFrames = 0;
437
- let pointerHeld = false;
438
- let activePointerId = null;
439
- let sentinelDeadline = null;
440
- const computePlacement = () => {
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;
443
- if (guardDisplayNone && p.visible && (p.bounds.width === 0 || p.bounds.height === 0)) {
444
- return { visible: false };
445
- }
446
- return p;
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
- };
461
- const emit = () => {
462
- if (disposed || !visible) return;
463
- const p = computePlacement();
464
- if (!p) return;
465
- if (lastPublished && samePlacement(lastPublished, p)) return;
466
- publishCandidate(p);
467
- };
468
- const shouldCloseOnHiddenPoll = () => {
469
- if (lastPublished?.visible === false) return true;
470
- return steadyFrames++ >= MAX_HIDDEN_FOLLOW_FRAMES;
471
- };
472
- const sentinelFrame = () => {
473
- rafId = null;
474
- if (disposed || !visible) {
475
- sentinelDeadline = null;
476
- return;
477
- }
478
- if (sentinelDeadline !== null && performance.now() >= sentinelDeadline) {
479
- sentinelDeadline = null;
480
- return;
481
- }
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;
496
- if (!p.visible) {
497
- if (shouldCloseOnHiddenPoll()) {
498
- sentinelDeadline = null;
499
- return;
500
- }
501
- if (!disposed && visible && followGeometry) {
502
- rafId = requestAnimationFrame(sentinelFrame);
503
- } else {
504
- sentinelDeadline = null;
505
- }
506
- return;
507
- }
508
- if (lastPublished && samePlacement(lastPublished, p)) {
509
- steadyFrames++;
510
- if (steadyFrames >= STEADY_CLOSE_FRAMES && !pointerHeld) {
511
- sentinelDeadline = null;
512
- return;
513
- }
514
- } else {
515
- publishCandidate(p);
516
- steadyFrames = 0;
517
- }
518
- if (!disposed && visible && followGeometry) {
519
- rafId = requestAnimationFrame(sentinelFrame);
520
- } else {
521
- sentinelDeadline = null;
522
- }
523
- };
524
- const openSentinel = () => {
525
- if (!followGeometry || disposed) return;
526
- steadyFrames = 0;
527
- if (rafId === null) {
528
- invalidFrames = 0;
529
- rafId = requestAnimationFrame(sentinelFrame);
530
- }
531
- };
532
- const closeSentinel = () => {
533
- if (rafId !== null) {
534
- cancelAnimationFrame(rafId);
535
- rafId = null;
536
- }
537
- steadyFrames = 0;
538
- invalidFrames = 0;
539
- sentinelDeadline = null;
540
- pointerHeld = false;
541
- activePointerId = null;
542
- };
543
- const onScroll = () => {
544
- if (followGeometry) openSentinel();
545
- else emit();
546
- };
547
- const onPointerDown = (e) => {
548
- const t = e.target;
549
- if (t && t.closest && t.closest('[role="separator"]')) {
550
- if (!pointerHeld) activePointerId = e.pointerId;
551
- pointerHeld = true;
552
- openSentinel();
553
- }
554
- };
555
- const releasePointer = (e) => {
556
- if (!pointerHeld) return;
557
- if (e && activePointerId !== e.pointerId) return;
558
- pointerHeld = false;
559
- activePointerId = null;
560
- openSentinel();
561
- };
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") {
573
- io = new IntersectionObserver(emit);
574
- io.observe(target);
575
- }
576
- if (followScroll && !scrollListening) {
577
- window.addEventListener("scroll", onScroll, passiveCapture);
578
- scrollListening = true;
579
- }
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;
586
- }
587
- };
588
- const stopOptionalObserving = () => {
589
- if (io && !guardDisplayNone) {
590
- io.disconnect();
591
- io = null;
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 = () => {
607
- if (io) {
608
- io.disconnect();
609
- io = null;
610
- }
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
- }
622
- closeSentinel();
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
- };
639
- const apply = () => {
640
- lastPublished = null;
641
- if (visible) {
642
- startObserving();
643
- const placement = computePlacement();
644
- if (placement) publishCandidate(placement);
645
- } else {
646
- stopObserving();
647
- const hidden = { visible: false };
648
- publishCandidate(hidden);
649
- }
650
- };
651
- apply();
652
- return {
653
- update(next) {
654
- if (disposed) return;
655
- publish = next.publish;
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
- }
664
- apply();
665
- },
666
- dispose() {
667
- if (disposed) return;
668
- disposed = true;
669
- stopObserving();
670
- },
671
- pulse(durationMs) {
672
- if (disposed || !followGeometry) return;
673
- if (durationMs !== void 0 && durationMs > 0) {
674
- const next = performance.now() + durationMs;
675
- sentinelDeadline = sentinelDeadline === null ? next : Math.max(sentinelDeadline, next);
676
- }
677
- openSentinel();
678
- }
679
- };
680
- }
681
- return __toCommonJS(view_anchor_exports);
682
- })();
296
+ var __viewAnchorCore = (function(exports) {
297
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
298
+ //#region src/view-anchor.ts
299
+ const ZERO = {
300
+ x: 0,
301
+ y: 0,
302
+ width: 0,
303
+ height: 0
304
+ };
305
+ const NOOP_PUBLISH = () => false;
306
+ const clampRect = (r) => ({
307
+ x: Math.round(r.x),
308
+ y: Math.round(r.y),
309
+ width: Math.max(0, Math.round(r.width)),
310
+ height: Math.max(0, Math.round(r.height))
311
+ });
312
+ /**
313
+ * Bind a native view or external surface to the geometry of `target`.
314
+ *
315
+ * - `present === true`: measures `target.getBoundingClientRect()` and publishes
316
+ * immediately, then re-measures synchronously on ResizeObserver and window resize.
317
+ * - `present === false`: publishes a zero rect ({ x: 0, y: 0, width: 0, height: 0 })
318
+ * and stops observing.
319
+ * - `update(opts)`: re-applies options immediately.
320
+ * - `dispose()`: stops observing and prevents any further publishes.
321
+ *
322
+ * Synchronous publishing: measurement and publishing occur directly in the
323
+ * observer tick. Cross-process setBounds calls already have a compositor delay;
324
+ * adding requestAnimationFrame would add a second frame of visual lag during drag
325
+ * operations. High-frequency updates are deduplicated against the last accepted rect.
326
+ */
327
+ function createViewAnchor(target, opts) {
328
+ let present = opts.present;
329
+ let publish = opts.publish;
330
+ let observer = null;
331
+ let targetRef = target;
332
+ let lastPublished = null;
333
+ let publicationRevision = 0;
334
+ let disposed = false;
335
+ const measure = () => {
336
+ const r = targetRef.getBoundingClientRect();
337
+ if (!Number.isFinite(r.left) || !Number.isFinite(r.top) || !Number.isFinite(r.width) || !Number.isFinite(r.height)) return null;
338
+ return clampRect({
339
+ x: r.left,
340
+ y: r.top,
341
+ width: r.width,
342
+ height: r.height
343
+ });
344
+ };
345
+ const sameRect = (a, b) => a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
346
+ const publishCandidate = (candidate) => {
347
+ const previous = lastPublished;
348
+ const attempt = ++publicationRevision;
349
+ lastPublished = candidate;
350
+ try {
351
+ const accepted = publish(candidate) !== false;
352
+ if (!accepted && publicationRevision === attempt && !disposed) lastPublished = previous;
353
+ return accepted;
354
+ } catch (error) {
355
+ if (publicationRevision === attempt && !disposed) lastPublished = previous;
356
+ throw error;
357
+ }
358
+ };
359
+ const emit = () => {
360
+ if (disposed || !present) return;
361
+ const m = measure();
362
+ if (!m) return;
363
+ if (lastPublished && sameRect(lastPublished, m)) return;
364
+ publishCandidate(m);
365
+ };
366
+ const startObserving = () => {
367
+ if (observer) return;
368
+ observer = new ResizeObserver(emit);
369
+ observer.observe(targetRef);
370
+ window.addEventListener("resize", emit);
371
+ };
372
+ const stopObserving = () => {
373
+ if (observer) {
374
+ observer.disconnect();
375
+ observer = null;
376
+ }
377
+ window.removeEventListener("resize", emit);
378
+ };
379
+ const apply = () => {
380
+ lastPublished = null;
381
+ if (present) {
382
+ startObserving();
383
+ const measured = measure();
384
+ if (measured) publishCandidate(measured);
385
+ } else {
386
+ stopObserving();
387
+ publishCandidate(ZERO);
388
+ }
389
+ };
390
+ apply();
391
+ return {
392
+ update(next) {
393
+ if (disposed) return;
394
+ publish = next.publish;
395
+ present = next.present;
396
+ apply();
397
+ },
398
+ dispose() {
399
+ if (disposed) return;
400
+ disposed = true;
401
+ stopObserving();
402
+ targetRef = null;
403
+ publish = NOOP_PUBLISH;
404
+ lastPublished = null;
405
+ }
406
+ };
407
+ }
408
+ /**
409
+ * Read target's current rect and return { visible: true, bounds }.
410
+ * Does not infer visibility from dimensions.
411
+ */
412
+ function measurePlacement(target) {
413
+ const r = target.getBoundingClientRect();
414
+ return {
415
+ visible: true,
416
+ bounds: clampRect({
417
+ x: r.left,
418
+ y: r.top,
419
+ width: r.width,
420
+ height: r.height
421
+ })
422
+ };
423
+ }
424
+ const samePlacement = (a, b) => {
425
+ if (a.visible !== b.visible) return false;
426
+ if (a.visible && b.visible) return a.bounds.x === b.bounds.x && a.bounds.y === b.bounds.y && a.bounds.width === b.bounds.width && a.bounds.height === b.bounds.height;
427
+ return true;
428
+ };
429
+ /**
430
+ * Explicit-visibility variant of createViewAnchor.
431
+ */
432
+ function createPlacementAnchor(target, opts) {
433
+ let visible = opts.visible;
434
+ let publish = opts.publish;
435
+ let guardDisplayNone = opts.guardDisplayNone ?? false;
436
+ let followScroll = opts.followScroll ?? false;
437
+ let followGeometry = opts.followGeometry ?? false;
438
+ let targetRef = target;
439
+ let observer = null;
440
+ let io = null;
441
+ let scrollListening = false;
442
+ let geometryListening = false;
443
+ const capture = { capture: true };
444
+ const passiveCapture = {
445
+ capture: true,
446
+ passive: true
447
+ };
448
+ let lastPublished = null;
449
+ let publicationRevision = 0;
450
+ let disposed = false;
451
+ let rafId = null;
452
+ let steadyFrames = 0;
453
+ const STEADY_CLOSE_FRAMES = 2;
454
+ const MAX_HIDDEN_FOLLOW_FRAMES = 30;
455
+ const MAX_INVALID_FOLLOW_FRAMES = 30;
456
+ let invalidFrames = 0;
457
+ let pointerHeld = false;
458
+ let activePointerId = null;
459
+ let sentinelDeadline = null;
460
+ const computePlacement = () => {
461
+ const p = measurePlacement(targetRef);
462
+ 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;
463
+ if (guardDisplayNone && p.visible && (p.bounds.width === 0 || p.bounds.height === 0)) return { visible: false };
464
+ return p;
465
+ };
466
+ const publishCandidate = (candidate) => {
467
+ const previous = lastPublished;
468
+ const attempt = ++publicationRevision;
469
+ lastPublished = candidate;
470
+ try {
471
+ const accepted = publish(candidate) !== false;
472
+ if (!accepted && publicationRevision === attempt && !disposed) lastPublished = previous;
473
+ return accepted;
474
+ } catch (error) {
475
+ if (publicationRevision === attempt && !disposed) lastPublished = previous;
476
+ throw error;
477
+ }
478
+ };
479
+ const emit = () => {
480
+ if (disposed || !visible) return;
481
+ const p = computePlacement();
482
+ if (!p) return;
483
+ if (lastPublished && samePlacement(lastPublished, p)) return;
484
+ publishCandidate(p);
485
+ };
486
+ const shouldCloseOnHiddenPoll = () => {
487
+ if (lastPublished?.visible === false) return true;
488
+ return steadyFrames++ >= MAX_HIDDEN_FOLLOW_FRAMES;
489
+ };
490
+ const sentinelFrame = () => {
491
+ rafId = null;
492
+ if (disposed || !visible) {
493
+ sentinelDeadline = null;
494
+ return;
495
+ }
496
+ if (sentinelDeadline !== null && performance.now() >= sentinelDeadline) {
497
+ sentinelDeadline = null;
498
+ return;
499
+ }
500
+ const p = computePlacement();
501
+ if (!p) {
502
+ if (invalidFrames++ >= MAX_INVALID_FOLLOW_FRAMES) {
503
+ sentinelDeadline = null;
504
+ return;
505
+ }
506
+ if (!disposed && visible && followGeometry) rafId = requestAnimationFrame(sentinelFrame);
507
+ else sentinelDeadline = null;
508
+ return;
509
+ }
510
+ invalidFrames = 0;
511
+ if (!p.visible) {
512
+ if (shouldCloseOnHiddenPoll()) {
513
+ sentinelDeadline = null;
514
+ return;
515
+ }
516
+ if (!disposed && visible && followGeometry) rafId = requestAnimationFrame(sentinelFrame);
517
+ else sentinelDeadline = null;
518
+ return;
519
+ }
520
+ if (lastPublished && samePlacement(lastPublished, p)) {
521
+ steadyFrames++;
522
+ if (steadyFrames >= STEADY_CLOSE_FRAMES && !pointerHeld) {
523
+ sentinelDeadline = null;
524
+ return;
525
+ }
526
+ } else {
527
+ publishCandidate(p);
528
+ steadyFrames = 0;
529
+ }
530
+ if (!disposed && visible && followGeometry) rafId = requestAnimationFrame(sentinelFrame);
531
+ else sentinelDeadline = null;
532
+ };
533
+ const openSentinel = () => {
534
+ if (!followGeometry || disposed) return;
535
+ steadyFrames = 0;
536
+ if (rafId === null) {
537
+ invalidFrames = 0;
538
+ rafId = requestAnimationFrame(sentinelFrame);
539
+ }
540
+ };
541
+ const closeSentinel = () => {
542
+ if (rafId !== null) {
543
+ cancelAnimationFrame(rafId);
544
+ rafId = null;
545
+ }
546
+ steadyFrames = 0;
547
+ invalidFrames = 0;
548
+ sentinelDeadline = null;
549
+ pointerHeld = false;
550
+ activePointerId = null;
551
+ };
552
+ const onScroll = () => {
553
+ if (followGeometry) openSentinel();
554
+ else emit();
555
+ };
556
+ const onPointerDown = (e) => {
557
+ const t = e.target;
558
+ if (t && t.closest && t.closest("[role=\"separator\"]")) {
559
+ if (!pointerHeld) activePointerId = e.pointerId;
560
+ pointerHeld = true;
561
+ openSentinel();
562
+ }
563
+ };
564
+ const releasePointer = (e) => {
565
+ if (!pointerHeld) return;
566
+ if (e && activePointerId !== e.pointerId) return;
567
+ pointerHeld = false;
568
+ activePointerId = null;
569
+ openSentinel();
570
+ };
571
+ const onPointerUp = (e) => {
572
+ releasePointer(e);
573
+ };
574
+ const onPointerCancel = (e) => {
575
+ releasePointer(e);
576
+ };
577
+ const onWindowBlur = () => {
578
+ releasePointer();
579
+ };
580
+ const startOptionalObserving = () => {
581
+ if (guardDisplayNone && !io && typeof IntersectionObserver !== "undefined") {
582
+ io = new IntersectionObserver(emit);
583
+ io.observe(targetRef);
584
+ }
585
+ if (followScroll && !scrollListening) {
586
+ window.addEventListener("scroll", onScroll, passiveCapture);
587
+ scrollListening = true;
588
+ }
589
+ if (followGeometry && !geometryListening) {
590
+ window.addEventListener("pointerdown", onPointerDown, capture);
591
+ window.addEventListener("pointerup", onPointerUp, capture);
592
+ window.addEventListener("pointercancel", onPointerCancel, capture);
593
+ window.addEventListener("blur", onWindowBlur);
594
+ geometryListening = true;
595
+ }
596
+ };
597
+ const stopOptionalObserving = () => {
598
+ if (io && !guardDisplayNone) {
599
+ io.disconnect();
600
+ io = null;
601
+ }
602
+ if (scrollListening && !followScroll) {
603
+ window.removeEventListener("scroll", onScroll, passiveCapture);
604
+ scrollListening = false;
605
+ }
606
+ if (geometryListening && !followGeometry) {
607
+ window.removeEventListener("pointerdown", onPointerDown, capture);
608
+ window.removeEventListener("pointerup", onPointerUp, capture);
609
+ window.removeEventListener("pointercancel", onPointerCancel, capture);
610
+ window.removeEventListener("blur", onWindowBlur);
611
+ geometryListening = false;
612
+ closeSentinel();
613
+ }
614
+ };
615
+ const stopAllOptionalObserving = () => {
616
+ if (io) {
617
+ io.disconnect();
618
+ io = null;
619
+ }
620
+ if (scrollListening) {
621
+ window.removeEventListener("scroll", onScroll, passiveCapture);
622
+ scrollListening = false;
623
+ }
624
+ if (geometryListening) {
625
+ window.removeEventListener("pointerdown", onPointerDown, capture);
626
+ window.removeEventListener("pointerup", onPointerUp, capture);
627
+ window.removeEventListener("pointercancel", onPointerCancel, capture);
628
+ window.removeEventListener("blur", onWindowBlur);
629
+ geometryListening = false;
630
+ }
631
+ closeSentinel();
632
+ };
633
+ const startObserving = () => {
634
+ if (observer) return;
635
+ observer = new ResizeObserver(emit);
636
+ observer.observe(targetRef);
637
+ window.addEventListener("resize", emit);
638
+ startOptionalObserving();
639
+ };
640
+ const stopObserving = () => {
641
+ if (observer) {
642
+ observer.disconnect();
643
+ observer = null;
644
+ }
645
+ window.removeEventListener("resize", emit);
646
+ stopAllOptionalObserving();
647
+ };
648
+ const apply = () => {
649
+ lastPublished = null;
650
+ if (visible) {
651
+ startObserving();
652
+ const placement = computePlacement();
653
+ if (placement) publishCandidate(placement);
654
+ } else {
655
+ stopObserving();
656
+ publishCandidate({ visible: false });
657
+ }
658
+ };
659
+ apply();
660
+ return {
661
+ update(next) {
662
+ if (disposed) return;
663
+ publish = next.publish;
664
+ visible = next.visible;
665
+ guardDisplayNone = next.guardDisplayNone ?? guardDisplayNone;
666
+ followScroll = next.followScroll ?? followScroll;
667
+ followGeometry = next.followGeometry ?? followGeometry;
668
+ if (visible && observer) {
669
+ stopOptionalObserving();
670
+ startOptionalObserving();
671
+ }
672
+ apply();
673
+ },
674
+ dispose() {
675
+ if (disposed) return;
676
+ disposed = true;
677
+ stopObserving();
678
+ targetRef = null;
679
+ publish = NOOP_PUBLISH;
680
+ lastPublished = null;
681
+ },
682
+ pulse(durationMs) {
683
+ if (disposed || !followGeometry) return;
684
+ if (durationMs !== void 0 && durationMs > 0) {
685
+ const next = performance.now() + durationMs;
686
+ sentinelDeadline = sentinelDeadline === null ? next : Math.max(sentinelDeadline, next);
687
+ }
688
+ openSentinel();
689
+ }
690
+ };
691
+ }
692
+ //#endregion
693
+ exports.createPlacementAnchor = createPlacementAnchor;
694
+ exports.createViewAnchor = createViewAnchor;
695
+ exports.measurePlacement = measurePlacement;
696
+ return exports;
697
+ })({});
683
698
  /* __VIEW_ANCHOR_CORE_END__ */
684
699
  </script>
685
700
 
@@ -699,7 +714,7 @@ var __viewAnchorCore = (() => {
699
714
  const readout = document.getElementById('readout')
700
715
 
701
716
  // The placeholder's rect inside the deck (accumulated along the offsetParent
702
- // chain, independent of the 3D transform) — flat or tilted, the native view
717
+ // chain, independent of the 3D transform) — flat or tilted, the anchored view
703
718
  // stays registered directly above the placeholder.
704
719
  function deckLocalRect(el) {
705
720
  let x = 0, y = 0, n = el