@grahlnn/comps 0.1.7 → 0.1.9

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/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // torph/src/components/Torph.tsx
2
2
  import {
3
- useLayoutEffect,
3
+ useLayoutEffect as useLayoutEffect2,
4
4
  useMemo,
5
- useRef,
6
- useState
5
+ useRef as useRef2,
6
+ useState as useState2
7
7
  } from "react";
8
8
 
9
9
  // torph/src/vendor/pretext/bidi.ts
@@ -2516,8 +2516,12 @@ function measureMorphSnapshotWithPretext(text, layoutContext) {
2516
2516
  };
2517
2517
  }
2518
2518
 
2519
- // torph/src/components/Torph.tsx
2520
- import { jsxDEV } from "react/jsx-dev-runtime";
2519
+ // torph/src/core/math.ts
2520
+ function nearlyEqual(a, b, epsilon = 0.5) {
2521
+ return Math.abs(a - b) <= epsilon;
2522
+ }
2523
+
2524
+ // torph/src/core/types.ts
2521
2525
  var MORPH = {
2522
2526
  durationMs: 280,
2523
2527
  maxFadeMs: 150,
@@ -2526,8 +2530,6 @@ var MORPH = {
2526
2530
  contentWidthLockEpsilon: 2,
2527
2531
  lineGroupingEpsilon: 1
2528
2532
  };
2529
- var MORPH_SEGMENT_CACHE_LIMIT = 256;
2530
- var DOM_MEASUREMENT_SNAPSHOT_CACHE_LIMIT = 8;
2531
2533
  var EMPTY_STATE = {
2532
2534
  stage: "idle",
2533
2535
  measurement: null,
@@ -2548,22 +2550,19 @@ var ZERO_BRIDGE = {
2548
2550
  offsetY: 0
2549
2551
  };
2550
2552
  var EMPTY_SEGMENTS = [];
2551
- var morphSegmentCache = new Map;
2552
- var pretextMorphTrustCache = new Map;
2553
- var morphMeasurementEpoch = 1;
2554
- var activeMorphMeasurementConsumers = 0;
2555
- var detachMorphMeasurementInvalidationListeners = null;
2556
- var SCREEN_READER_ONLY_STYLE = {
2553
+ function resolveContentWidthLockInlineSize(layoutHint) {
2554
+ if (layoutHint.flowInlineSize !== null) {
2555
+ return layoutHint.flowInlineSize;
2556
+ }
2557
+ return layoutHint.snapshot.width;
2558
+ }
2559
+
2560
+ // torph/src/core/render.ts
2561
+ var OVERLAY_STYLE = {
2557
2562
  position: "absolute",
2558
- width: "1px",
2559
- height: "1px",
2560
- margin: "-1px",
2561
- padding: 0,
2562
- border: 0,
2563
- clip: "rect(0 0 0 0)",
2564
- clipPath: "inset(50%)",
2565
- overflow: "hidden",
2566
- whiteSpace: "nowrap"
2563
+ inset: 0,
2564
+ minWidth: 0,
2565
+ pointerEvents: "none"
2567
2566
  };
2568
2567
  var MEASUREMENT_LAYER_STYLE = {
2569
2568
  pointerEvents: "none",
@@ -2574,111 +2573,177 @@ var MEASUREMENT_LAYER_STYLE = {
2574
2573
  right: 0,
2575
2574
  display: "block"
2576
2575
  };
2577
- var FALLBACK_TEXT_STYLE = {
2578
- display: "block",
2579
- gridArea: "1 / 1"
2580
- };
2581
- var OVERLAY_STYLE = {
2582
- position: "absolute",
2583
- inset: 0,
2584
- minWidth: 0,
2585
- pointerEvents: "none"
2586
- };
2587
- var SHARED_GLYPH_TYPOGRAPHY_STYLE = {
2588
- font: "inherit",
2589
- fontKerning: "inherit",
2590
- fontFeatureSettings: "inherit",
2591
- fontOpticalSizing: "inherit",
2592
- fontStretch: "inherit",
2593
- fontStyle: "inherit",
2594
- fontVariant: "inherit",
2595
- fontVariantNumeric: "inherit",
2596
- fontVariationSettings: "inherit",
2597
- fontWeight: "inherit",
2598
- letterSpacing: "inherit",
2599
- textTransform: "inherit",
2600
- wordSpacing: "inherit",
2601
- direction: "inherit"
2602
- };
2603
- var MEASUREMENT_GLYPH_STYLE = {
2604
- ...SHARED_GLYPH_TYPOGRAPHY_STYLE,
2605
- display: "inline"
2606
- };
2607
- var ABSOLUTE_GLYPH_STYLE = {
2608
- ...SHARED_GLYPH_TYPOGRAPHY_STYLE,
2609
- position: "absolute",
2610
- display: "block",
2611
- overflow: "visible",
2612
- transformOrigin: "left top",
2613
- whiteSpace: "pre"
2614
- };
2615
- var graphemeSegmenter = null;
2616
- var domMeasurementService = null;
2617
- function parsePx(value) {
2618
- const parsed = Number.parseFloat(value);
2619
- if (Number.isFinite(parsed)) {
2620
- return parsed;
2576
+ function getFadeDuration(fraction) {
2577
+ return Math.min(MORPH.durationMs * fraction, MORPH.maxFadeMs);
2578
+ }
2579
+ function getOverlayStyle(stage, plan) {
2580
+ if (stage === "idle") {
2581
+ return OVERLAY_STYLE;
2621
2582
  }
2622
- return null;
2583
+ return {
2584
+ ...OVERLAY_STYLE,
2585
+ right: "auto",
2586
+ bottom: "auto",
2587
+ width: plan.frameWidth,
2588
+ height: plan.frameHeight
2589
+ };
2623
2590
  }
2624
- function readFont(styles) {
2625
- if (styles.font.length > 0) {
2626
- return styles.font;
2591
+ function getLiveTransform(item, stage, visualBridge) {
2592
+ if (stage !== "prepare") {
2593
+ return "translate(0px, 0px)";
2627
2594
  }
2628
- return `${styles.fontStyle} ${styles.fontVariant} ${styles.fontWeight} ${styles.fontSize} / ${styles.lineHeight} ${styles.fontFamily}`;
2595
+ if (item.kind === "move") {
2596
+ return `translate(${(item.fromLeft ?? item.left) - item.left + visualBridge.offsetX}px, ${(item.fromTop ?? item.top) - item.top + visualBridge.offsetY}px)`;
2597
+ }
2598
+ return `translate(${visualBridge.offsetX}px, ${visualBridge.offsetY}px)`;
2629
2599
  }
2630
- function readLineHeightPx(styles) {
2631
- const lineHeightPx = parsePx(styles.lineHeight);
2632
- if (lineHeightPx !== null) {
2633
- return lineHeightPx;
2600
+ function getLiveTransition(item, stage) {
2601
+ if (stage !== "animate") {
2602
+ return;
2634
2603
  }
2635
- const fontSizePx = parsePx(styles.fontSize);
2636
- return (fontSizePx ?? 0) * 1.2;
2604
+ if (item.kind === "enter") {
2605
+ return `opacity ${getFadeDuration(0.5)}ms linear ${getFadeDuration(0.25)}ms`;
2606
+ }
2607
+ return `transform ${MORPH.durationMs}ms ${MORPH.ease}, opacity ${getFadeDuration(0.25)}ms linear`;
2637
2608
  }
2638
- function readSpacingPx(value) {
2639
- if (value === "normal") {
2640
- return 0;
2609
+ function getExitTransform(visualBridge) {
2610
+ return `translate(${visualBridge.offsetX}px, ${visualBridge.offsetY}px)`;
2611
+ }
2612
+ function getExitTransition(stage) {
2613
+ if (stage !== "animate") {
2614
+ return;
2641
2615
  }
2642
- return parsePx(value) ?? 0;
2616
+ return `transform ${MORPH.durationMs}ms ${MORPH.ease}, opacity ${getFadeDuration(0.25)}ms linear`;
2643
2617
  }
2644
- function readWhiteSpace(value) {
2645
- if (value === "normal" || value === "nowrap" || value === "pre-wrap") {
2646
- return value;
2618
+ function supportsIntrinsicWidthLock(display, parentDisplay) {
2619
+ let parentNeedsReservation = false;
2620
+ if (parentDisplay === "flex" || parentDisplay === "inline-flex" || parentDisplay === "grid" || parentDisplay === "inline-grid") {
2621
+ parentNeedsReservation = true;
2647
2622
  }
2648
- throw new Error(`Torph only supports white-space: normal | nowrap | pre-wrap. Received: ${value}`);
2623
+ if (display === "inline" || display === "inline-block" || display === "inline-flex" || display === "inline-grid") {
2624
+ return true;
2625
+ }
2626
+ return parentNeedsReservation;
2649
2627
  }
2650
- function readContentWidth(node, styles) {
2651
- const rectWidth = node.getBoundingClientRect().width;
2652
- const paddingLeft = parsePx(styles.paddingLeft) ?? 0;
2653
- const paddingRight = parsePx(styles.paddingRight) ?? 0;
2654
- const borderLeft = parsePx(styles.borderLeftWidth) ?? 0;
2655
- const borderRight = parsePx(styles.borderRightWidth) ?? 0;
2656
- return Math.max(0, rectWidth - paddingLeft - paddingRight - borderLeft - borderRight);
2628
+ function getRootDisplay(layoutContext) {
2629
+ if (layoutContext === null) {
2630
+ return "grid";
2631
+ }
2632
+ if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
2633
+ return "inline-grid";
2634
+ }
2635
+ return "grid";
2657
2636
  }
2658
- function readLayoutContext(node, width) {
2659
- const styles = getComputedStyle(node);
2660
- const parentDisplay = (node.parentElement && getComputedStyle(node.parentElement).display) ?? "block";
2637
+ function getRootStyle(stage, plan, measurement, layoutContext) {
2638
+ let width = measurement?.reservedInlineSize;
2639
+ if (measurement !== null && measurement.flowInlineSize !== null) {
2640
+ width = measurement.layoutInlineSize;
2641
+ }
2642
+ if (plan !== null) {
2643
+ width = plan.layoutInlineSizeTo;
2644
+ }
2645
+ let height;
2646
+ if (plan !== null) {
2647
+ height = plan.frameHeight;
2648
+ }
2649
+ const style = {
2650
+ position: "relative",
2651
+ display: getRootDisplay(layoutContext)
2652
+ };
2653
+ if (width !== null && width !== undefined) {
2654
+ style.width = width;
2655
+ }
2656
+ if (height !== undefined) {
2657
+ style.height = height;
2658
+ }
2659
+ return style;
2660
+ }
2661
+ function getMeasurementLayerStyle(layoutContext, useContentInlineSize = false) {
2662
+ let intrinsicWidthLock = false;
2663
+ if (layoutContext !== null) {
2664
+ if (useContentInlineSize) {
2665
+ intrinsicWidthLock = true;
2666
+ } else if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
2667
+ intrinsicWidthLock = true;
2668
+ }
2669
+ }
2670
+ if (!intrinsicWidthLock) {
2671
+ return MEASUREMENT_LAYER_STYLE;
2672
+ }
2661
2673
  return {
2662
- display: styles.display,
2663
- direction: styles.direction,
2664
- font: readFont(styles),
2665
- fontFeatureSettings: styles.fontFeatureSettings,
2666
- fontVariationSettings: styles.fontVariationSettings,
2667
- letterSpacingPx: readSpacingPx(styles.letterSpacing),
2668
- lineHeightPx: readLineHeightPx(styles),
2669
- parentDisplay,
2670
- textTransform: styles.textTransform,
2671
- whiteSpace: readWhiteSpace(styles.whiteSpace),
2672
- width: width ?? readContentWidth(node, styles),
2673
- wordSpacingPx: readSpacingPx(styles.wordSpacing),
2674
- measurementVersion: 0
2674
+ ...MEASUREMENT_LAYER_STYLE,
2675
+ right: "auto",
2676
+ width: "max-content"
2675
2677
  };
2676
2678
  }
2677
- function sameLayoutContext(a, b) {
2678
- if (a === null) {
2679
- return false;
2679
+ function resolveFlowText(committedMeasurement, stateMeasurement, text) {
2680
+ return stateMeasurement?.snapshot.text ?? committedMeasurement?.snapshot.text ?? text;
2681
+ }
2682
+ function shouldRenderGlyphLayer(stage, plan, measurement) {
2683
+ if (stage === "idle") {
2684
+ return measurement !== null;
2680
2685
  }
2681
- return a.display === b.display && a.direction === b.direction && a.font === b.font && a.fontFeatureSettings === b.fontFeatureSettings && a.fontVariationSettings === b.fontVariationSettings && Math.abs(a.letterSpacingPx - b.letterSpacingPx) < MORPH.geometryEpsilon && Math.abs(a.lineHeightPx - b.lineHeightPx) < MORPH.geometryEpsilon && a.parentDisplay === b.parentDisplay && a.textTransform === b.textTransform && a.whiteSpace === b.whiteSpace && Math.abs(a.width - b.width) < MORPH.geometryEpsilon && Math.abs(a.wordSpacingPx - b.wordSpacingPx) < MORPH.geometryEpsilon;
2686
+ return plan !== null;
2687
+ }
2688
+ function resolveGlyphSliceWhiteSpace(snapshot) {
2689
+ if (snapshot === null) {
2690
+ return "inherit";
2691
+ }
2692
+ return "nowrap";
2693
+ }
2694
+ function toSteadyLiveItem(grapheme) {
2695
+ return {
2696
+ ...grapheme,
2697
+ kind: "move",
2698
+ fromLeft: grapheme.left,
2699
+ fromTop: grapheme.top
2700
+ };
2701
+ }
2702
+ function createSteadyGlyphPlan(measurement) {
2703
+ const snapshot = measurement.snapshot;
2704
+ return {
2705
+ frameWidth: snapshot.width,
2706
+ frameHeight: snapshot.height,
2707
+ layoutInlineSizeFrom: measurement.layoutInlineSize,
2708
+ layoutInlineSizeTo: measurement.layoutInlineSize,
2709
+ sourceRenderText: snapshot.renderText,
2710
+ targetRenderText: snapshot.renderText,
2711
+ sourceRootOrigin: measurement.rootOrigin,
2712
+ visualBridge: ZERO_BRIDGE,
2713
+ liveItems: snapshot.graphemes.map(toSteadyLiveItem),
2714
+ exitItems: []
2715
+ };
2716
+ }
2717
+
2718
+ // torph/src/core/snapshot.ts
2719
+ function isSingleLineSnapshot(snapshot) {
2720
+ if (snapshot.graphemes.length <= 1) {
2721
+ return true;
2722
+ }
2723
+ const firstTop = snapshot.graphemes[0].top;
2724
+ return snapshot.graphemes.every((grapheme) => nearlyEqual(grapheme.top, firstTop, MORPH.lineGroupingEpsilon));
2725
+ }
2726
+ function assertSingleLineSnapshot(snapshot) {
2727
+ if (isSingleLineSnapshot(snapshot)) {
2728
+ return snapshot;
2729
+ }
2730
+ throw new Error(`Torph only supports single-line text layout. Received wrapped text: "${snapshot.renderText}"`);
2731
+ }
2732
+
2733
+ // torph/src/core/measurement-policy.ts
2734
+ var MORPH_SEGMENT_CACHE_LIMIT = 256;
2735
+ var morphSegmentCache = new Map;
2736
+ var pretextMorphTrustCache = new Map;
2737
+ var morphMeasurementEpoch = 1;
2738
+ var graphemeSegmenter = null;
2739
+ function getGraphemeSegmenter() {
2740
+ if (graphemeSegmenter !== null) {
2741
+ return graphemeSegmenter;
2742
+ }
2743
+ graphemeSegmenter = new Intl.Segmenter(undefined, {
2744
+ granularity: "grapheme"
2745
+ });
2746
+ return graphemeSegmenter;
2682
2747
  }
2683
2748
  function clearPretextMorphTrustCache() {
2684
2749
  pretextMorphTrustCache.clear();
@@ -2694,33 +2759,620 @@ function bumpMorphMeasurementEpoch() {
2694
2759
  function getMorphMeasurementEpoch() {
2695
2760
  return morphMeasurementEpoch;
2696
2761
  }
2697
- function isSingleLineSnapshot(snapshot) {
2698
- if (snapshot.graphemes.length <= 1) {
2699
- return true;
2762
+ function readCachedMorphSegments(text) {
2763
+ const cached = morphSegmentCache.get(text);
2764
+ if (cached !== undefined) {
2765
+ morphSegmentCache.delete(text);
2766
+ morphSegmentCache.set(text, cached);
2767
+ return cached;
2700
2768
  }
2701
- const firstTop = snapshot.graphemes[0].top;
2702
- return snapshot.graphemes.every((grapheme) => nearlyEqual(grapheme.top, firstTop, MORPH.lineGroupingEpsilon));
2703
- }
2704
- function acquireMorphMeasurementInvalidationListeners() {
2705
- activeMorphMeasurementConsumers += 1;
2706
- if (detachMorphMeasurementInvalidationListeners === null) {
2707
- const handleFontChange = () => {
2708
- clearMorphMeasurementCaches();
2709
- bumpMorphMeasurementEpoch();
2710
- };
2711
- document.fonts.ready.then(handleFontChange);
2712
- if (typeof document.fonts.addEventListener === "function") {
2713
- document.fonts.addEventListener("loadingdone", handleFontChange);
2769
+ const segments = Array.from(getGraphemeSegmenter().segment(text), (segment, index) => ({
2770
+ glyph: segment.segment,
2771
+ key: `${segment.segment}:${index}`
2772
+ }));
2773
+ morphSegmentCache.set(text, segments);
2774
+ if (morphSegmentCache.size > MORPH_SEGMENT_CACHE_LIMIT) {
2775
+ const oldest = morphSegmentCache.keys().next();
2776
+ if (!oldest.done) {
2777
+ morphSegmentCache.delete(oldest.value);
2714
2778
  }
2715
- detachMorphMeasurementInvalidationListeners = () => {
2716
- if (typeof document.fonts.removeEventListener === "function") {
2717
- document.fonts.removeEventListener("loadingdone", handleFontChange);
2718
- }
2719
- };
2720
2779
  }
2780
+ return segments;
2721
2781
  }
2722
- function releaseMorphMeasurementInvalidationListeners() {
2723
- activeMorphMeasurementConsumers = Math.max(0, activeMorphMeasurementConsumers - 1);
2782
+ function shouldMeasureUsingContentInlineSize(layoutContext, layoutHint) {
2783
+ if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
2784
+ return true;
2785
+ }
2786
+ if (layoutContext.whiteSpace === "nowrap") {
2787
+ return true;
2788
+ }
2789
+ if (layoutHint === null) {
2790
+ return false;
2791
+ }
2792
+ if (!isSingleLineSnapshot(layoutHint.snapshot)) {
2793
+ return false;
2794
+ }
2795
+ return nearlyEqual(layoutHint.layoutInlineSize, resolveContentWidthLockInlineSize(layoutHint), MORPH.contentWidthLockEpsilon);
2796
+ }
2797
+ function getTrustedPretextMeasurementBackend(text, renderText, layoutContext, useContentInlineSize) {
2798
+ const backend = getPretextMorphMeasurementBackend(text, layoutContext);
2799
+ if (backend !== "probe") {
2800
+ return backend;
2801
+ }
2802
+ const signature = getPretextMorphTrustSignature({
2803
+ renderText,
2804
+ layoutContext,
2805
+ useContentInlineSize
2806
+ });
2807
+ if (signature === null) {
2808
+ return "dom";
2809
+ }
2810
+ const trusted = pretextMorphTrustCache.get(signature);
2811
+ if (trusted === undefined) {
2812
+ return "probe";
2813
+ }
2814
+ if (trusted) {
2815
+ return "pretext";
2816
+ }
2817
+ return "dom";
2818
+ }
2819
+ function getDomMeasurementRequestKey(text, renderText, layoutContext, useContentInlineSize) {
2820
+ let inlineSizeMode = "container";
2821
+ if (useContentInlineSize) {
2822
+ inlineSizeMode = "content";
2823
+ }
2824
+ return `dom\x00${inlineSizeMode}\x00${text}\x00${renderText}\x00${layoutContext.measurementVersion}\x00${getMorphMeasurementEpoch()}`;
2825
+ }
2826
+ function needsMeasurementLayer(measurementBackend, renderText) {
2827
+ if (measurementBackend === "pretext") {
2828
+ return false;
2829
+ }
2830
+ return renderText.length > 0;
2831
+ }
2832
+ function canCacheMeasurementLayerSnapshot(measurementBackend) {
2833
+ return measurementBackend === "dom";
2834
+ }
2835
+ function createMorphMeasurementRequest({
2836
+ text,
2837
+ layoutContext,
2838
+ layoutHint
2839
+ }) {
2840
+ if (layoutContext === null) {
2841
+ return null;
2842
+ }
2843
+ const renderText = getPretextMorphRenderedText(text, layoutContext);
2844
+ const useContentInlineSize = shouldMeasureUsingContentInlineSize(layoutContext, layoutHint);
2845
+ const measurementBackend = getTrustedPretextMeasurementBackend(text, renderText, layoutContext, useContentInlineSize);
2846
+ let segments = readCachedMorphSegments(renderText);
2847
+ if (measurementBackend === "pretext") {
2848
+ segments = EMPTY_SEGMENTS;
2849
+ }
2850
+ let domMeasurementKey = null;
2851
+ if (needsMeasurementLayer(measurementBackend, renderText)) {
2852
+ domMeasurementKey = getDomMeasurementRequestKey(text, renderText, layoutContext, useContentInlineSize);
2853
+ }
2854
+ return {
2855
+ text,
2856
+ renderText,
2857
+ segments,
2858
+ measurementBackend,
2859
+ useContentInlineSize,
2860
+ domMeasurementKey
2861
+ };
2862
+ }
2863
+ function rememberPretextMeasurementTrust({
2864
+ renderText,
2865
+ layoutContext,
2866
+ useContentInlineSize,
2867
+ trusted
2868
+ }) {
2869
+ const signature = getPretextMorphTrustSignature({
2870
+ renderText,
2871
+ layoutContext,
2872
+ useContentInlineSize
2873
+ });
2874
+ if (signature === null) {
2875
+ return;
2876
+ }
2877
+ pretextMorphTrustCache.set(signature, trusted);
2878
+ }
2879
+ function areSnapshotsEquivalentForPretextTrust(left, right) {
2880
+ if (left.renderText !== right.renderText || left.graphemes.length !== right.graphemes.length) {
2881
+ return false;
2882
+ }
2883
+ if (Math.abs(left.width - right.width) > MORPH.geometryEpsilon) {
2884
+ return false;
2885
+ }
2886
+ if (Math.abs(left.height - right.height) > MORPH.geometryEpsilon) {
2887
+ return false;
2888
+ }
2889
+ for (let index = 0;index < left.graphemes.length; index += 1) {
2890
+ const from = left.graphemes[index];
2891
+ const to = right.graphemes[index];
2892
+ if (from.glyph !== to.glyph) {
2893
+ return false;
2894
+ }
2895
+ if (Math.abs(from.left - to.left) > MORPH.geometryEpsilon) {
2896
+ return false;
2897
+ }
2898
+ if (Math.abs(from.top - to.top) > MORPH.geometryEpsilon) {
2899
+ return false;
2900
+ }
2901
+ if (Math.abs(from.width - to.width) > MORPH.geometryEpsilon) {
2902
+ return false;
2903
+ }
2904
+ if (Math.abs(from.height - to.height) > MORPH.geometryEpsilon) {
2905
+ return false;
2906
+ }
2907
+ }
2908
+ return true;
2909
+ }
2910
+
2911
+ // torph/src/core/finalize-barrier.ts
2912
+ function createMorphFinalizeBarrier(hasMoveTransitions) {
2913
+ return {
2914
+ waitForLiveTransform: hasMoveTransitions,
2915
+ sawLiveTransform: false
2916
+ };
2917
+ }
2918
+ function recordMorphFinalizeSignal(barrier, signal) {
2919
+ return {
2920
+ ...barrier,
2921
+ sawLiveTransform: signal === "live-transform"
2922
+ };
2923
+ }
2924
+ function isMorphFinalizeBarrierSatisfied(barrier) {
2925
+ if (barrier.waitForLiveTransform && !barrier.sawLiveTransform) {
2926
+ return false;
2927
+ }
2928
+ return true;
2929
+ }
2930
+ function summarizeMorphFinalizeBarrier(barrier) {
2931
+ return {
2932
+ waitForLiveTransform: barrier.waitForLiveTransform,
2933
+ sawLiveTransform: barrier.sawLiveTransform,
2934
+ satisfied: isMorphFinalizeBarrierSatisfied(barrier)
2935
+ };
2936
+ }
2937
+
2938
+ // torph/src/core/session.ts
2939
+ function bucketByGlyph(graphemes) {
2940
+ const buckets = new Map;
2941
+ for (const grapheme of graphemes) {
2942
+ const bucket = buckets.get(grapheme.glyph);
2943
+ if (bucket !== undefined) {
2944
+ bucket.push(grapheme);
2945
+ continue;
2946
+ }
2947
+ buckets.set(grapheme.glyph, [grapheme]);
2948
+ }
2949
+ return buckets;
2950
+ }
2951
+ function pairMorphCharacters(previous, next) {
2952
+ const previousBuckets = bucketByGlyph(previous);
2953
+ const nextBuckets = bucketByGlyph(next);
2954
+ const pairings = [];
2955
+ for (const [glyph, previousItems] of previousBuckets) {
2956
+ const nextItems = nextBuckets.get(glyph) ?? [];
2957
+ const shared = Math.min(previousItems.length, nextItems.length);
2958
+ for (let index = 0;index < shared; index += 1) {
2959
+ pairings.push({
2960
+ kind: "move",
2961
+ from: previousItems[index],
2962
+ to: nextItems[index]
2963
+ });
2964
+ }
2965
+ for (let index = shared;index < previousItems.length; index += 1) {
2966
+ pairings.push({
2967
+ kind: "exit",
2968
+ from: previousItems[index]
2969
+ });
2970
+ }
2971
+ }
2972
+ for (const [glyph, nextItems] of nextBuckets) {
2973
+ const previousItems = previousBuckets.get(glyph) ?? [];
2974
+ const shared = Math.min(previousItems.length, nextItems.length);
2975
+ for (let index = shared;index < nextItems.length; index += 1) {
2976
+ pairings.push({
2977
+ kind: "enter",
2978
+ to: nextItems[index]
2979
+ });
2980
+ }
2981
+ }
2982
+ return pairings;
2983
+ }
2984
+ function resolveMorphFrameBounds(previous, next) {
2985
+ return {
2986
+ width: Math.max(previous.width, next.width),
2987
+ height: Math.max(previous.height, next.height)
2988
+ };
2989
+ }
2990
+ function buildMorphPlan(previous, next, visualBridge = ZERO_BRIDGE) {
2991
+ const pairings = pairMorphCharacters(previous.snapshot.graphemes, next.snapshot.graphemes);
2992
+ const movesByDestinationKey = new Map;
2993
+ const exitItems = [];
2994
+ for (const pairing of pairings) {
2995
+ if (pairing.kind === "move") {
2996
+ movesByDestinationKey.set(pairing.to.key, pairing);
2997
+ continue;
2998
+ }
2999
+ if (pairing.kind === "exit") {
3000
+ exitItems.push(pairing.from);
3001
+ }
3002
+ }
3003
+ const frame = resolveMorphFrameBounds(previous.snapshot, next.snapshot);
3004
+ return {
3005
+ frameWidth: frame.width,
3006
+ frameHeight: frame.height,
3007
+ layoutInlineSizeFrom: previous.layoutInlineSize,
3008
+ layoutInlineSizeTo: next.layoutInlineSize,
3009
+ sourceRenderText: previous.snapshot.renderText,
3010
+ targetRenderText: next.snapshot.renderText,
3011
+ sourceRootOrigin: previous.rootOrigin,
3012
+ visualBridge,
3013
+ liveItems: next.snapshot.graphemes.map((grapheme) => {
3014
+ const move = movesByDestinationKey.get(grapheme.key);
3015
+ if (move !== undefined) {
3016
+ return {
3017
+ ...grapheme,
3018
+ kind: "move",
3019
+ fromLeft: move.from.left,
3020
+ fromTop: move.from.top
3021
+ };
3022
+ }
3023
+ return {
3024
+ ...grapheme,
3025
+ kind: "enter",
3026
+ fromLeft: null,
3027
+ fromTop: null
3028
+ };
3029
+ }),
3030
+ exitItems
3031
+ };
3032
+ }
3033
+ function sameSnapshot(a, b) {
3034
+ if (a === b) {
3035
+ return true;
3036
+ }
3037
+ if (a.text !== b.text || a.renderText !== b.renderText || a.graphemes.length !== b.graphemes.length) {
3038
+ return false;
3039
+ }
3040
+ if (!nearlyEqual(a.width, b.width, MORPH.geometryEpsilon)) {
3041
+ return false;
3042
+ }
3043
+ if (!nearlyEqual(a.height, b.height, MORPH.geometryEpsilon)) {
3044
+ return false;
3045
+ }
3046
+ for (let index = 0;index < a.graphemes.length; index += 1) {
3047
+ const left = a.graphemes[index];
3048
+ const right = b.graphemes[index];
3049
+ if (left.glyph !== right.glyph || left.key !== right.key) {
3050
+ return false;
3051
+ }
3052
+ if (!nearlyEqual(left.left, right.left, MORPH.geometryEpsilon)) {
3053
+ return false;
3054
+ }
3055
+ if (!nearlyEqual(left.top, right.top, MORPH.geometryEpsilon)) {
3056
+ return false;
3057
+ }
3058
+ if (!nearlyEqual(left.width, right.width, MORPH.geometryEpsilon)) {
3059
+ return false;
3060
+ }
3061
+ if (!nearlyEqual(left.height, right.height, MORPH.geometryEpsilon)) {
3062
+ return false;
3063
+ }
3064
+ }
3065
+ return true;
3066
+ }
3067
+ function sameMeasurement(a, b) {
3068
+ const sameReservedInlineSize = a.reservedInlineSize === null && b.reservedInlineSize === null || a.reservedInlineSize !== null && b.reservedInlineSize !== null && nearlyEqual(a.reservedInlineSize, b.reservedInlineSize, MORPH.geometryEpsilon);
3069
+ const sameFlowInlineSize = a.flowInlineSize === null && b.flowInlineSize === null || a.flowInlineSize !== null && b.flowInlineSize !== null && nearlyEqual(a.flowInlineSize, b.flowInlineSize, MORPH.geometryEpsilon);
3070
+ return sameSnapshot(a.snapshot, b.snapshot) && nearlyEqual(a.layoutInlineSize, b.layoutInlineSize, MORPH.geometryEpsilon) && sameReservedInlineSize && sameFlowInlineSize && nearlyEqual(a.rootOrigin.left, b.rootOrigin.left, MORPH.geometryEpsilon) && nearlyEqual(a.rootOrigin.top, b.rootOrigin.top, MORPH.geometryEpsilon);
3071
+ }
3072
+ function selectMorphLayoutHint(session) {
3073
+ if (session.animating && session.target !== null) {
3074
+ return session.target;
3075
+ }
3076
+ return session.committed;
3077
+ }
3078
+ function decideMorphSessionUpdate({
3079
+ committed,
3080
+ target,
3081
+ animating,
3082
+ nextMeasurement,
3083
+ fontsReady
3084
+ }) {
3085
+ let source = committed;
3086
+ if (animating && target !== null) {
3087
+ source = target;
3088
+ }
3089
+ if (source === null) {
3090
+ return {
3091
+ kind: "commit-static",
3092
+ measurement: nextMeasurement
3093
+ };
3094
+ }
3095
+ if (!fontsReady) {
3096
+ return {
3097
+ kind: "commit-static",
3098
+ measurement: nextMeasurement
3099
+ };
3100
+ }
3101
+ if (animating && target !== null) {
3102
+ if (nextMeasurement.snapshot.renderText === target.snapshot.renderText) {
3103
+ return {
3104
+ kind: "freeze-animating-target",
3105
+ target
3106
+ };
3107
+ }
3108
+ }
3109
+ if (committed !== null) {
3110
+ if (committed.snapshot.renderText === nextMeasurement.snapshot.renderText) {
3111
+ if (sameMeasurement(committed, nextMeasurement)) {
3112
+ return {
3113
+ kind: "commit-static",
3114
+ measurement: committed
3115
+ };
3116
+ }
3117
+ return {
3118
+ kind: "commit-static",
3119
+ measurement: nextMeasurement
3120
+ };
3121
+ }
3122
+ }
3123
+ return {
3124
+ kind: "start-morph",
3125
+ source,
3126
+ target: nextMeasurement
3127
+ };
3128
+ }
3129
+ function pinMeasurementToCurrentOrigin(measurement, origin) {
3130
+ if (nearlyEqual(measurement.rootOrigin.left, origin.left, MORPH.geometryEpsilon) && nearlyEqual(measurement.rootOrigin.top, origin.top, MORPH.geometryEpsilon)) {
3131
+ return measurement;
3132
+ }
3133
+ return {
3134
+ snapshot: measurement.snapshot,
3135
+ layoutInlineSize: measurement.layoutInlineSize,
3136
+ reservedInlineSize: measurement.reservedInlineSize,
3137
+ flowInlineSize: measurement.flowInlineSize,
3138
+ rootOrigin: origin
3139
+ };
3140
+ }
3141
+ function createStaticState(measurement) {
3142
+ return {
3143
+ stage: "idle",
3144
+ measurement,
3145
+ plan: null
3146
+ };
3147
+ }
3148
+ function areFontsReady() {
3149
+ return document.fonts.status === "loaded";
3150
+ }
3151
+ function cancelTimeline(timeline) {
3152
+ if (timeline.prepareFrame !== null) {
3153
+ cancelAnimationFrame(timeline.prepareFrame);
3154
+ timeline.prepareFrame = null;
3155
+ }
3156
+ if (timeline.animateFrame !== null) {
3157
+ cancelAnimationFrame(timeline.animateFrame);
3158
+ timeline.animateFrame = null;
3159
+ }
3160
+ if (timeline.finalizeTimer !== null) {
3161
+ window.clearTimeout(timeline.finalizeTimer);
3162
+ timeline.finalizeTimer = null;
3163
+ }
3164
+ }
3165
+ function resetMorph(session, timeline, setState) {
3166
+ cancelTimeline(timeline);
3167
+ session.committed = null;
3168
+ session.target = null;
3169
+ session.animating = false;
3170
+ setState(EMPTY_STATE);
3171
+ }
3172
+ function commitStaticMeasurement(session, measurement, setState) {
3173
+ if (!session.animating && session.target === null && session.committed !== null && sameMeasurement(session.committed, measurement)) {
3174
+ return;
3175
+ }
3176
+ session.committed = measurement;
3177
+ session.target = null;
3178
+ session.animating = false;
3179
+ setState((current) => {
3180
+ if (current.stage === "idle" && current.plan === null && current.measurement !== null && sameMeasurement(current.measurement, measurement)) {
3181
+ return current;
3182
+ }
3183
+ return createStaticState(measurement);
3184
+ });
3185
+ }
3186
+ function applyMorphSessionDecision({
3187
+ decision,
3188
+ session,
3189
+ timeline,
3190
+ setState
3191
+ }) {
3192
+ if (decision.kind === "freeze-animating-target") {
3193
+ return decision.target;
3194
+ }
3195
+ cancelTimeline(timeline);
3196
+ if (decision.kind === "commit-static") {
3197
+ commitStaticMeasurement(session, decision.measurement, setState);
3198
+ return decision.measurement;
3199
+ }
3200
+ startMorph({
3201
+ source: decision.source,
3202
+ target: decision.target,
3203
+ session,
3204
+ timeline,
3205
+ setState
3206
+ });
3207
+ return decision.target;
3208
+ }
3209
+ function reconcileMorphSessionUpdate({
3210
+ session,
3211
+ timeline,
3212
+ nextMeasurement,
3213
+ fontsReady,
3214
+ setState
3215
+ }) {
3216
+ const decision = decideMorphSessionUpdate({
3217
+ committed: session.committed,
3218
+ target: session.target,
3219
+ animating: session.animating,
3220
+ nextMeasurement,
3221
+ fontsReady
3222
+ });
3223
+ return applyMorphSessionDecision({
3224
+ decision,
3225
+ session,
3226
+ timeline,
3227
+ setState
3228
+ });
3229
+ }
3230
+ function finalizeMorphTransition({
3231
+ session,
3232
+ timeline,
3233
+ measurement,
3234
+ setState
3235
+ }) {
3236
+ cancelTimeline(timeline);
3237
+ commitStaticMeasurement(session, session.target ?? measurement, setState);
3238
+ }
3239
+ function resolvePreparedMeasurementOrigin(measurement, origin) {
3240
+ return pinMeasurementToCurrentOrigin(measurement, origin);
3241
+ }
3242
+ function resolvePreparedPlanVisualBridge(plan, origin) {
3243
+ const offsetX = plan.sourceRootOrigin.left - origin.left;
3244
+ const offsetY = plan.sourceRootOrigin.top - origin.top;
3245
+ if (nearlyEqual(plan.visualBridge.offsetX, offsetX, MORPH.geometryEpsilon) && nearlyEqual(plan.visualBridge.offsetY, offsetY, MORPH.geometryEpsilon)) {
3246
+ return plan;
3247
+ }
3248
+ return {
3249
+ ...plan,
3250
+ visualBridge: {
3251
+ offsetX,
3252
+ offsetY
3253
+ }
3254
+ };
3255
+ }
3256
+ function startMorph({
3257
+ source,
3258
+ target,
3259
+ session,
3260
+ timeline,
3261
+ setState
3262
+ }) {
3263
+ const plan = buildMorphPlan(source, target, ZERO_BRIDGE);
3264
+ session.target = target;
3265
+ session.animating = true;
3266
+ setState({
3267
+ stage: "prepare",
3268
+ measurement: target,
3269
+ plan
3270
+ });
3271
+ }
3272
+
3273
+ // torph/src/core/layout-observer.ts
3274
+ import { useLayoutEffect, useRef, useState } from "react";
3275
+ var activeMorphMeasurementConsumers = 0;
3276
+ var detachMorphMeasurementInvalidationListeners = null;
3277
+ var morphMeasurementInvalidationSubscribers = new Set;
3278
+ function parsePx(value) {
3279
+ const parsed = Number.parseFloat(value);
3280
+ if (Number.isFinite(parsed)) {
3281
+ return parsed;
3282
+ }
3283
+ return null;
3284
+ }
3285
+ function readFont(styles) {
3286
+ if (styles.font.length > 0) {
3287
+ return styles.font;
3288
+ }
3289
+ return `${styles.fontStyle} ${styles.fontVariant} ${styles.fontWeight} ${styles.fontSize} / ${styles.lineHeight} ${styles.fontFamily}`;
3290
+ }
3291
+ function readLineHeightPx(styles) {
3292
+ const lineHeightPx = parsePx(styles.lineHeight);
3293
+ if (lineHeightPx !== null) {
3294
+ return lineHeightPx;
3295
+ }
3296
+ const fontSizePx = parsePx(styles.fontSize);
3297
+ return (fontSizePx ?? 0) * 1.2;
3298
+ }
3299
+ function readSpacingPx(value) {
3300
+ if (value === "normal") {
3301
+ return 0;
3302
+ }
3303
+ return parsePx(value) ?? 0;
3304
+ }
3305
+ function readWhiteSpace(value) {
3306
+ if (value === "normal" || value === "nowrap" || value === "pre-wrap") {
3307
+ return value;
3308
+ }
3309
+ throw new Error(`Torph only supports white-space: normal | nowrap | pre-wrap. Received: ${value}`);
3310
+ }
3311
+ function readContentWidth(node, styles) {
3312
+ const rectWidth = node.getBoundingClientRect().width;
3313
+ const paddingLeft = parsePx(styles.paddingLeft) ?? 0;
3314
+ const paddingRight = parsePx(styles.paddingRight) ?? 0;
3315
+ const borderLeft = parsePx(styles.borderLeftWidth) ?? 0;
3316
+ const borderRight = parsePx(styles.borderRightWidth) ?? 0;
3317
+ return Math.max(0, rectWidth - paddingLeft - paddingRight - borderLeft - borderRight);
3318
+ }
3319
+ function readLayoutContext(node, width) {
3320
+ const styles = getComputedStyle(node);
3321
+ const parentDisplay = (node.parentElement && getComputedStyle(node.parentElement).display) ?? "block";
3322
+ return {
3323
+ display: styles.display,
3324
+ direction: styles.direction,
3325
+ font: readFont(styles),
3326
+ fontFeatureSettings: styles.fontFeatureSettings,
3327
+ fontVariationSettings: styles.fontVariationSettings,
3328
+ letterSpacingPx: readSpacingPx(styles.letterSpacing),
3329
+ lineHeightPx: readLineHeightPx(styles),
3330
+ parentDisplay,
3331
+ textTransform: styles.textTransform,
3332
+ whiteSpace: readWhiteSpace(styles.whiteSpace),
3333
+ width: width ?? readContentWidth(node, styles),
3334
+ wordSpacingPx: readSpacingPx(styles.wordSpacing),
3335
+ measurementVersion: 0
3336
+ };
3337
+ }
3338
+ function sameLayoutContext(a, b) {
3339
+ if (a === null) {
3340
+ return false;
3341
+ }
3342
+ return a.display === b.display && a.direction === b.direction && a.font === b.font && a.fontFeatureSettings === b.fontFeatureSettings && a.fontVariationSettings === b.fontVariationSettings && Math.abs(a.letterSpacingPx - b.letterSpacingPx) < MORPH.geometryEpsilon && Math.abs(a.lineHeightPx - b.lineHeightPx) < MORPH.geometryEpsilon && a.parentDisplay === b.parentDisplay && a.textTransform === b.textTransform && a.whiteSpace === b.whiteSpace && Math.abs(a.width - b.width) < MORPH.geometryEpsilon && Math.abs(a.wordSpacingPx - b.wordSpacingPx) < MORPH.geometryEpsilon;
3343
+ }
3344
+ function notifyMorphMeasurementInvalidationSubscribers() {
3345
+ for (const subscriber of morphMeasurementInvalidationSubscribers) {
3346
+ subscriber();
3347
+ }
3348
+ }
3349
+ function subscribeMorphMeasurementInvalidation(subscriber) {
3350
+ morphMeasurementInvalidationSubscribers.add(subscriber);
3351
+ return () => {
3352
+ morphMeasurementInvalidationSubscribers.delete(subscriber);
3353
+ };
3354
+ }
3355
+ function acquireMorphMeasurementInvalidationListeners() {
3356
+ activeMorphMeasurementConsumers += 1;
3357
+ if (detachMorphMeasurementInvalidationListeners === null) {
3358
+ const handleFontChange = () => {
3359
+ clearMorphMeasurementCaches();
3360
+ bumpMorphMeasurementEpoch();
3361
+ notifyMorphMeasurementInvalidationSubscribers();
3362
+ };
3363
+ document.fonts.ready.then(handleFontChange);
3364
+ if (typeof document.fonts.addEventListener === "function") {
3365
+ document.fonts.addEventListener("loadingdone", handleFontChange);
3366
+ }
3367
+ detachMorphMeasurementInvalidationListeners = () => {
3368
+ if (typeof document.fonts.removeEventListener === "function") {
3369
+ document.fonts.removeEventListener("loadingdone", handleFontChange);
3370
+ }
3371
+ };
3372
+ }
3373
+ }
3374
+ function releaseMorphMeasurementInvalidationListeners() {
3375
+ activeMorphMeasurementConsumers = Math.max(0, activeMorphMeasurementConsumers - 1);
2724
3376
  if (activeMorphMeasurementConsumers === 0 && detachMorphMeasurementInvalidationListeners !== null) {
2725
3377
  detachMorphMeasurementInvalidationListeners();
2726
3378
  detachMorphMeasurementInvalidationListeners = null;
@@ -2729,6 +3381,7 @@ function releaseMorphMeasurementInvalidationListeners() {
2729
3381
  function useObservedLayoutContext(deps) {
2730
3382
  const ref = useRef(null);
2731
3383
  const [layoutContext, setLayoutContext] = useState(null);
3384
+ const syncLayoutContextRef = useRef(null);
2732
3385
  useLayoutEffect(() => {
2733
3386
  const node = ref.current;
2734
3387
  if (node === null) {
@@ -2757,9 +3410,15 @@ function useObservedLayoutContext(deps) {
2757
3410
  const next = readLayoutContext(node, width);
2758
3411
  commitLayoutContext(next, refreshMeasurements);
2759
3412
  };
3413
+ syncLayoutContextRef.current = syncLayoutContext;
2760
3414
  const initialLayoutContext = readLayoutContext(node);
2761
3415
  const shouldObserveWrappingWidth = initialLayoutContext.whiteSpace !== "nowrap" && !supportsIntrinsicWidthLock(initialLayoutContext.display, initialLayoutContext.parentDisplay);
2762
3416
  commitLayoutContext(initialLayoutContext, true);
3417
+ const unsubscribeInvalidation = subscribeMorphMeasurementInvalidation(() => {
3418
+ syncLayoutContext({
3419
+ refreshMeasurements: true
3420
+ });
3421
+ });
2763
3422
  let resizeObserver = null;
2764
3423
  if (shouldObserveWrappingWidth) {
2765
3424
  resizeObserver = new ResizeObserver(([entry]) => {
@@ -2772,43 +3431,27 @@ function useObservedLayoutContext(deps) {
2772
3431
  acquireMorphMeasurementInvalidationListeners();
2773
3432
  return () => {
2774
3433
  disposed = true;
3434
+ syncLayoutContextRef.current = null;
3435
+ unsubscribeInvalidation();
2775
3436
  resizeObserver?.disconnect();
2776
3437
  releaseMorphMeasurementInvalidationListeners();
2777
3438
  };
2778
3439
  }, deps);
2779
3440
  return { ref, layoutContext };
2780
3441
  }
2781
- function getSegmenter() {
2782
- if (graphemeSegmenter !== null) {
2783
- return graphemeSegmenter;
3442
+
3443
+ // torph/src/core/dom-measurement.ts
3444
+ var domMeasurementService = null;
3445
+ function readFirstTextNode(node) {
3446
+ if (node === null) {
3447
+ return null;
2784
3448
  }
2785
- const segmenterConstructor = Intl.Segmenter;
2786
- if (segmenterConstructor === undefined) {
2787
- throw new Error("Torph requires Intl.Segmenter for grapheme-safe pairing.");
3449
+ for (const childNode of node.childNodes) {
3450
+ if (childNode.nodeType === Node.TEXT_NODE) {
3451
+ return childNode;
3452
+ }
2788
3453
  }
2789
- graphemeSegmenter = new segmenterConstructor(undefined, {
2790
- granularity: "grapheme"
2791
- });
2792
- return graphemeSegmenter;
2793
- }
2794
- function createMeasurementGlyphNode() {
2795
- const node = document.createElement("span");
2796
- node.style.font = "inherit";
2797
- node.style.fontKerning = "inherit";
2798
- node.style.fontFeatureSettings = "inherit";
2799
- node.style.fontOpticalSizing = "inherit";
2800
- node.style.fontStretch = "inherit";
2801
- node.style.fontStyle = "inherit";
2802
- node.style.fontVariant = "inherit";
2803
- node.style.fontVariantNumeric = "inherit";
2804
- node.style.fontVariationSettings = "inherit";
2805
- node.style.fontWeight = "inherit";
2806
- node.style.letterSpacing = "inherit";
2807
- node.style.textTransform = "inherit";
2808
- node.style.wordSpacing = "inherit";
2809
- node.style.direction = "inherit";
2810
- node.style.display = "inline";
2811
- return node;
3454
+ return null;
2812
3455
  }
2813
3456
  function getDomMeasurementService() {
2814
3457
  if (domMeasurementService !== null) {
@@ -2831,25 +3474,10 @@ function getDomMeasurementService() {
2831
3474
  document.body.appendChild(root);
2832
3475
  domMeasurementService = {
2833
3476
  root,
2834
- host,
2835
- glyphNodes: []
3477
+ host
2836
3478
  };
2837
3479
  return domMeasurementService;
2838
3480
  }
2839
- function syncMeasurementGlyphNodes(service, segments) {
2840
- while (service.glyphNodes.length < segments.length) {
2841
- const node = createMeasurementGlyphNode();
2842
- service.host.appendChild(node);
2843
- service.glyphNodes.push(node);
2844
- }
2845
- while (service.glyphNodes.length > segments.length) {
2846
- const node = service.glyphNodes.pop();
2847
- node?.remove();
2848
- }
2849
- for (let index = 0;index < segments.length; index += 1) {
2850
- service.glyphNodes[index].textContent = segments[index].glyph;
2851
- }
2852
- }
2853
3481
  function applyMeasurementHostStyle({
2854
3482
  host,
2855
3483
  root,
@@ -2895,51 +3523,6 @@ function applyMeasurementHostStyle({
2895
3523
  host.style.width = "max-content";
2896
3524
  }
2897
3525
  }
2898
- function segmentTorphText(text) {
2899
- const segments = [];
2900
- let ordinal = 0;
2901
- for (const segment of getSegmenter().segment(text)) {
2902
- segments.push({
2903
- glyph: segment.segment,
2904
- key: `${segment.segment}:${ordinal}`
2905
- });
2906
- ordinal += 1;
2907
- }
2908
- return segments;
2909
- }
2910
- function readCachedMorphSegments(text) {
2911
- const cached = morphSegmentCache.get(text);
2912
- if (cached !== undefined) {
2913
- morphSegmentCache.delete(text);
2914
- morphSegmentCache.set(text, cached);
2915
- return cached;
2916
- }
2917
- const segments = segmentTorphText(text);
2918
- morphSegmentCache.set(text, segments);
2919
- if (morphSegmentCache.size > MORPH_SEGMENT_CACHE_LIMIT) {
2920
- const oldest = morphSegmentCache.keys().next();
2921
- if (!oldest.done) {
2922
- morphSegmentCache.delete(oldest.value);
2923
- }
2924
- }
2925
- return segments;
2926
- }
2927
- function getDomMeasurementRequestKey(text, renderText, layoutContext, useContentInlineSize) {
2928
- let inlineSizeMode = "container";
2929
- if (useContentInlineSize) {
2930
- inlineSizeMode = "content";
2931
- }
2932
- return `dom\x00${inlineSizeMode}\x00${text}\x00${renderText}\x00${layoutContext.measurementVersion}\x00${getMorphMeasurementEpoch()}`;
2933
- }
2934
- function needsMeasurementLayer(measurementBackend, renderText) {
2935
- if (measurementBackend === "pretext") {
2936
- return false;
2937
- }
2938
- return renderText.length > 0;
2939
- }
2940
- function canCacheMeasurementLayerSnapshot(measurementBackend) {
2941
- return measurementBackend === "dom";
2942
- }
2943
3526
  function readCachedMorphSnapshot(cache, cacheKey) {
2944
3527
  const cached = cache.get(cacheKey);
2945
3528
  if (cached === undefined) {
@@ -2952,58 +3535,45 @@ function readCachedMorphSnapshot(cache, cacheKey) {
2952
3535
  function rememberCachedMorphSnapshot(cache, cacheKey, snapshot) {
2953
3536
  cache.delete(cacheKey);
2954
3537
  cache.set(cacheKey, snapshot);
2955
- if (cache.size > DOM_MEASUREMENT_SNAPSHOT_CACHE_LIMIT) {
3538
+ if (cache.size > 8) {
2956
3539
  const oldest = cache.keys().next();
2957
3540
  if (!oldest.done) {
2958
3541
  cache.delete(oldest.value);
2959
3542
  }
2960
3543
  }
2961
3544
  }
2962
- function assertMeasurementLayer(layer, segments) {
3545
+ function assertMeasurementLayer(layer) {
2963
3546
  if (layer === null) {
2964
3547
  throw new Error("Torph measurement layer is missing.");
2965
3548
  }
2966
- if (layer.children.length !== segments.length) {
2967
- throw new Error(`Torph measurement layer is out of sync. Expected ${segments.length} glyph nodes, received ${layer.children.length}.`);
2968
- }
2969
3549
  return layer;
2970
3550
  }
2971
3551
  function readMeasuredGlyphLayouts(layer, layerRect, segments) {
2972
3552
  const measuredGlyphs = [];
2973
- const layerOffsetTop = layer.offsetTop;
3553
+ const textNode = readFirstTextNode(layer);
3554
+ if (textNode === null) {
3555
+ throw new Error("Torph measurement layer text node is missing.");
3556
+ }
3557
+ const range = document.createRange();
3558
+ let offset = 0;
2974
3559
  for (let index = 0;index < segments.length; index += 1) {
2975
3560
  const segment = segments[index];
2976
- const child = layer.children[index];
2977
- if (!(child instanceof HTMLElement)) {
2978
- throw new Error(`Torph glyph node ${index} is not an HTMLElement.`);
2979
- }
2980
- const rect = child.getBoundingClientRect();
3561
+ const nextOffset = offset + segment.glyph.length;
3562
+ range.setStart(textNode, offset);
3563
+ range.setEnd(textNode, nextOffset);
3564
+ const rect = range.getBoundingClientRect();
2981
3565
  measuredGlyphs.push({
2982
3566
  glyph: segment.glyph,
2983
3567
  key: segment.key,
2984
3568
  left: rect.left - layerRect.left,
2985
- top: child.offsetTop - layerOffsetTop,
2986
- width: rect.width
3569
+ top: rect.top - layerRect.top,
3570
+ width: rect.width,
3571
+ height: rect.height
2987
3572
  });
3573
+ offset = nextOffset;
2988
3574
  }
2989
3575
  return measuredGlyphs;
2990
3576
  }
2991
- function assignMeasuredGlyphLineIndices(measuredGlyphs) {
2992
- const lineIndices = [];
2993
- let lineCount = 0;
2994
- let currentLineTop = null;
2995
- for (const glyph of measuredGlyphs) {
2996
- if (currentLineTop === null || Math.abs(glyph.top - currentLineTop) > MORPH.lineGroupingEpsilon) {
2997
- currentLineTop = glyph.top;
2998
- lineCount += 1;
2999
- }
3000
- lineIndices.push(lineCount - 1);
3001
- }
3002
- return {
3003
- lineCount,
3004
- lineIndices
3005
- };
3006
- }
3007
3577
  function measureMorphSnapshotFromLayer(text, renderText, segments, layer) {
3008
3578
  if (renderText.length === 0) {
3009
3579
  return {
@@ -3014,28 +3584,19 @@ function measureMorphSnapshotFromLayer(text, renderText, segments, layer) {
3014
3584
  graphemes: []
3015
3585
  };
3016
3586
  }
3017
- const measurementLayer = assertMeasurementLayer(layer, segments);
3587
+ const measurementLayer = assertMeasurementLayer(layer);
3018
3588
  const layerRect = measurementLayer.getBoundingClientRect();
3019
3589
  const measuredGlyphs = readMeasuredGlyphLayouts(measurementLayer, layerRect, segments);
3020
- const { lineCount, lineIndices } = assignMeasuredGlyphLineIndices(measuredGlyphs);
3021
- let lineHeight = 0;
3022
- if (lineCount !== 0) {
3023
- lineHeight = layerRect.height / lineCount;
3024
- }
3025
3590
  let width = 0;
3026
- const graphemes = measuredGlyphs.map((glyph, index) => {
3027
- const lineIndex = lineIndices[index];
3028
- if (lineIndex === undefined) {
3029
- throw new Error("Torph failed to assign a line index.");
3030
- }
3591
+ const graphemes = measuredGlyphs.map((glyph) => {
3031
3592
  width = Math.max(width, glyph.left + glyph.width);
3032
3593
  return {
3033
3594
  glyph: glyph.glyph,
3034
3595
  key: glyph.key,
3035
3596
  left: glyph.left,
3036
- top: lineIndex * lineHeight,
3597
+ top: glyph.top,
3037
3598
  width: glyph.width,
3038
- height: lineHeight
3599
+ height: glyph.height
3039
3600
  };
3040
3601
  });
3041
3602
  return {
@@ -3070,121 +3631,143 @@ function measureMorphSnapshotWithDomService({
3070
3631
  layoutContext,
3071
3632
  useContentInlineSize
3072
3633
  });
3073
- syncMeasurementGlyphNodes(service, segments);
3634
+ service.host.textContent = renderText;
3074
3635
  return measureMorphSnapshotFromLayer(text, renderText, segments, service.host);
3075
3636
  }
3076
3637
  function readRootOrigin(node) {
3077
3638
  const rect = node.getBoundingClientRect();
3078
3639
  return { left: rect.left, top: rect.top };
3079
3640
  }
3080
- function readFlowInlineSize(node) {
3081
- if (node === null) {
3082
- return null;
3083
- }
3084
- return node.getBoundingClientRect().width;
3085
- }
3086
- function getTrustedPretextMeasurementBackend(text, renderText, layoutContext, useContentInlineSize) {
3087
- const backend = getPretextMorphMeasurementBackend(text, layoutContext);
3088
- if (backend !== "probe") {
3089
- return backend;
3090
- }
3091
- const signature = getPretextMorphTrustSignature({
3092
- renderText,
3093
- layoutContext,
3094
- useContentInlineSize
3095
- });
3096
- if (signature === null) {
3097
- return "dom";
3098
- }
3099
- const trusted = pretextMorphTrustCache.get(signature);
3100
- if (trusted === undefined) {
3101
- return "probe";
3102
- }
3103
- if (trusted) {
3104
- return "pretext";
3105
- }
3106
- return "dom";
3107
- }
3108
- function resolveContentWidthLockInlineSize(layoutHint) {
3109
- if (layoutHint.flowInlineSize !== null) {
3110
- return layoutHint.flowInlineSize;
3111
- }
3112
- return layoutHint.snapshot.width;
3113
- }
3114
- function shouldMeasureUsingContentInlineSize(layoutContext, layoutHint) {
3115
- if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
3116
- return true;
3117
- }
3118
- if (layoutContext.whiteSpace === "nowrap") {
3119
- return true;
3120
- }
3121
- if (layoutHint === null || !isSingleLineSnapshot(layoutHint.snapshot)) {
3122
- return false;
3123
- }
3124
- return nearlyEqual(layoutHint.layoutInlineSize, resolveContentWidthLockInlineSize(layoutHint), MORPH.contentWidthLockEpsilon);
3125
- }
3126
- function createMorphMeasurementRequest({
3127
- text,
3128
- layoutContext,
3129
- layoutHint
3130
- }) {
3131
- if (layoutContext === null) {
3641
+ function measureLiveFlowSnapshot(root, flowTextNode) {
3642
+ const textNode = readFirstTextNode(flowTextNode);
3643
+ if (textNode === null) {
3132
3644
  return null;
3133
3645
  }
3134
- const renderText = getPretextMorphRenderedText(text, layoutContext);
3135
- const useContentInlineSize = shouldMeasureUsingContentInlineSize(layoutContext, layoutHint);
3136
- const measurementBackend = getTrustedPretextMeasurementBackend(text, renderText, layoutContext, useContentInlineSize);
3137
- let segments = readCachedMorphSegments(renderText);
3138
- if (measurementBackend === "pretext") {
3139
- segments = EMPTY_SEGMENTS;
3646
+ const text = textNode.data;
3647
+ const renderText = text;
3648
+ if (renderText.length === 0) {
3649
+ return {
3650
+ text,
3651
+ renderText,
3652
+ width: 0,
3653
+ height: 0,
3654
+ graphemes: []
3655
+ };
3140
3656
  }
3141
- let domMeasurementKey = null;
3142
- if (needsMeasurementLayer(measurementBackend, renderText)) {
3143
- domMeasurementKey = getDomMeasurementRequestKey(text, renderText, layoutContext, useContentInlineSize);
3657
+ const rootRect = root.getBoundingClientRect();
3658
+ const range = document.createRange();
3659
+ const graphemes = [];
3660
+ let width = 0;
3661
+ let height = 0;
3662
+ let offset = 0;
3663
+ const segments = readCachedMorphSegments(renderText);
3664
+ for (const segment of segments) {
3665
+ const nextOffset = offset + segment.glyph.length;
3666
+ range.setStart(textNode, offset);
3667
+ range.setEnd(textNode, nextOffset);
3668
+ const rect = range.getBoundingClientRect();
3669
+ graphemes.push({
3670
+ glyph: segment.glyph,
3671
+ key: segment.key,
3672
+ left: rect.left - rootRect.left,
3673
+ top: rect.top - rootRect.top,
3674
+ width: rect.width,
3675
+ height: rect.height
3676
+ });
3677
+ width = Math.max(width, rect.right - rootRect.left);
3678
+ height = Math.max(height, rect.bottom - rootRect.top);
3679
+ offset = nextOffset;
3144
3680
  }
3145
3681
  return {
3146
3682
  text,
3147
3683
  renderText,
3148
- segments,
3149
- measurementBackend,
3150
- useContentInlineSize,
3151
- domMeasurementKey
3684
+ width,
3685
+ height,
3686
+ graphemes
3152
3687
  };
3153
3688
  }
3154
- function rememberPretextMeasurementTrust({
3155
- renderText,
3156
- layoutContext,
3157
- useContentInlineSize,
3158
- trusted
3159
- }) {
3160
- const signature = getPretextMorphTrustSignature({
3161
- renderText,
3162
- layoutContext,
3163
- useContentInlineSize
3164
- });
3165
- if (signature === null) {
3166
- return;
3689
+ function measureSnapshotDrift(expected, actual) {
3690
+ const comparedGlyphs = Math.min(expected.graphemes.length, actual.graphemes.length);
3691
+ const mismatches = [];
3692
+ let maxAbsLeftDelta = 0;
3693
+ let maxAbsTopDelta = 0;
3694
+ let maxAbsWidthDelta = 0;
3695
+ let maxAbsHeightDelta = 0;
3696
+ for (let index = 0;index < comparedGlyphs; index += 1) {
3697
+ const expectedGlyph = expected.graphemes[index];
3698
+ const actualGlyph = actual.graphemes[index];
3699
+ const leftDelta = actualGlyph.left - expectedGlyph.left;
3700
+ const topDelta = actualGlyph.top - expectedGlyph.top;
3701
+ const widthDelta = actualGlyph.width - expectedGlyph.width;
3702
+ const heightDelta = actualGlyph.height - expectedGlyph.height;
3703
+ maxAbsLeftDelta = Math.max(maxAbsLeftDelta, Math.abs(leftDelta));
3704
+ maxAbsTopDelta = Math.max(maxAbsTopDelta, Math.abs(topDelta));
3705
+ maxAbsWidthDelta = Math.max(maxAbsWidthDelta, Math.abs(widthDelta));
3706
+ maxAbsHeightDelta = Math.max(maxAbsHeightDelta, Math.abs(heightDelta));
3707
+ if (mismatches.length < 8 && (Math.abs(leftDelta) > MORPH.geometryEpsilon || Math.abs(topDelta) > MORPH.geometryEpsilon || Math.abs(widthDelta) > MORPH.geometryEpsilon || Math.abs(heightDelta) > MORPH.geometryEpsilon)) {
3708
+ mismatches.push({
3709
+ index,
3710
+ glyph: expectedGlyph.glyph,
3711
+ leftDelta,
3712
+ topDelta,
3713
+ widthDelta,
3714
+ heightDelta
3715
+ });
3716
+ }
3167
3717
  }
3168
- pretextMorphTrustCache.set(signature, trusted);
3718
+ return {
3719
+ comparedGlyphs,
3720
+ expectedGlyphs: expected.graphemes.length,
3721
+ actualGlyphs: actual.graphemes.length,
3722
+ maxAbsLeftDelta,
3723
+ maxAbsTopDelta,
3724
+ maxAbsWidthDelta,
3725
+ maxAbsHeightDelta,
3726
+ snapshotWidthDelta: actual.width - expected.width,
3727
+ snapshotHeightDelta: actual.height - expected.height,
3728
+ mismatches
3729
+ };
3169
3730
  }
3170
- function areSnapshotsEquivalentForPretextTrust(left, right) {
3171
- if (left.renderText !== right.renderText || left.graphemes.length !== right.graphemes.length) {
3172
- return false;
3173
- }
3174
- if (Math.abs(left.width - right.width) > MORPH.geometryEpsilon || Math.abs(left.height - right.height) > MORPH.geometryEpsilon) {
3175
- return false;
3731
+ function measureOverlayBoxSnapshot(root, overlayRoot, role) {
3732
+ const nodes = overlayRoot.querySelectorAll(`[data-morph-role='${role}']`);
3733
+ if (nodes.length === 0) {
3734
+ return null;
3176
3735
  }
3177
- for (let index = 0;index < left.graphemes.length; index += 1) {
3178
- const from = left.graphemes[index];
3179
- const to = right.graphemes[index];
3180
- if (from.glyph !== to.glyph) {
3181
- return false;
3182
- }
3183
- if (Math.abs(from.left - to.left) > MORPH.geometryEpsilon || Math.abs(from.top - to.top) > MORPH.geometryEpsilon || Math.abs(from.width - to.width) > MORPH.geometryEpsilon || Math.abs(from.height - to.height) > MORPH.geometryEpsilon) {
3184
- return false;
3736
+ const rootRect = root.getBoundingClientRect();
3737
+ const graphemes = [];
3738
+ let width = 0;
3739
+ let height = 0;
3740
+ let renderText = "";
3741
+ for (const node of nodes) {
3742
+ const key = node.dataset.morphKey;
3743
+ const glyph = node.dataset.morphGlyph;
3744
+ if (key === undefined || glyph === undefined) {
3745
+ return null;
3185
3746
  }
3747
+ const rect = node.getBoundingClientRect();
3748
+ const left = rect.left - rootRect.left;
3749
+ const top = rect.top - rootRect.top;
3750
+ const boxWidth = rect.width;
3751
+ const boxHeight = rect.height;
3752
+ graphemes.push({
3753
+ glyph,
3754
+ key,
3755
+ left,
3756
+ top,
3757
+ width: boxWidth,
3758
+ height: boxHeight
3759
+ });
3760
+ renderText += glyph;
3761
+ width = Math.max(width, left + boxWidth);
3762
+ height = Math.max(height, top + boxHeight);
3186
3763
  }
3187
- return true;
3764
+ return {
3765
+ text: renderText,
3766
+ renderText,
3767
+ width,
3768
+ height,
3769
+ graphemes
3770
+ };
3188
3771
  }
3189
3772
  function measureFromNodes({
3190
3773
  root,
@@ -3205,7 +3788,7 @@ function measureFromNodes({
3205
3788
  width: Number.MAX_SAFE_INTEGER / 4
3206
3789
  };
3207
3790
  }
3208
- const snapshot = snapshotOverride ?? (() => {
3791
+ const snapshot = assertSingleLineSnapshot(snapshotOverride ?? (() => {
3209
3792
  let pretextSnapshot = null;
3210
3793
  if (measurementBackend !== "dom") {
3211
3794
  pretextSnapshot = measureMorphSnapshotWithPretext(text, measurementLayoutContext);
@@ -3243,7 +3826,7 @@ function measureFromNodes({
3243
3826
  throw new Error("Torph failed to resolve a measurement snapshot.");
3244
3827
  }
3245
3828
  return resolvedSnapshot;
3246
- })();
3829
+ })());
3247
3830
  let layoutInlineSize = layoutContext.width;
3248
3831
  if (useContentInlineSize) {
3249
3832
  layoutInlineSize = snapshot.width;
@@ -3252,276 +3835,366 @@ function measureFromNodes({
3252
3835
  if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
3253
3836
  reservedInlineSize = snapshot.width;
3254
3837
  }
3838
+ let flowInlineSize = null;
3839
+ if (useContentInlineSize) {
3840
+ flowInlineSize = snapshot.width;
3841
+ }
3255
3842
  return {
3256
3843
  snapshot,
3257
3844
  layoutInlineSize,
3258
3845
  reservedInlineSize,
3259
- flowInlineSize: null,
3846
+ flowInlineSize,
3260
3847
  rootOrigin: readRootOrigin(root)
3261
3848
  };
3262
3849
  }
3263
- function pinMeasurementToCurrentOrigin(measurement, origin) {
3264
- if (nearlyEqual(measurement.rootOrigin.left, origin.left) && nearlyEqual(measurement.rootOrigin.top, origin.top)) {
3265
- return measurement;
3266
- }
3267
- return {
3268
- snapshot: measurement.snapshot,
3269
- layoutInlineSize: measurement.layoutInlineSize,
3270
- reservedInlineSize: measurement.reservedInlineSize,
3271
- flowInlineSize: measurement.flowInlineSize,
3272
- rootOrigin: origin
3273
- };
3850
+
3851
+ // torph/src/debug/trace.ts
3852
+ var TORPH_TRACE_MAX_BYTES = 4 * 1024 * 1024;
3853
+ var TORPH_TRACE_MAX_LINES = 4000;
3854
+ var TORPH_TRACE_SCHEMA_VERSION = 7;
3855
+ var DEFAULT_TORPH_DEBUG_CONFIG = {
3856
+ capture: false,
3857
+ console: false
3858
+ };
3859
+ var torphDebugInstanceOrdinal = 0;
3860
+ function nextTorphDebugInstanceId() {
3861
+ torphDebugInstanceOrdinal += 1;
3862
+ return torphDebugInstanceOrdinal;
3274
3863
  }
3275
- function bucketByGlyph(graphemes) {
3276
- const buckets = new Map;
3277
- for (const grapheme of graphemes) {
3278
- const bucket = buckets.get(grapheme.glyph);
3279
- if (bucket) {
3280
- bucket.push(grapheme);
3281
- } else {
3282
- buckets.set(grapheme.glyph, [grapheme]);
3283
- }
3284
- }
3285
- return buckets;
3864
+ function readTorphDebugConfig() {
3865
+ const scope = globalThis;
3866
+ return scope.__TORPH_DEBUG__ ?? DEFAULT_TORPH_DEBUG_CONFIG;
3286
3867
  }
3287
- function pairMorphCharacters(previous, next) {
3288
- const previousBuckets = bucketByGlyph(previous);
3289
- const nextBuckets = bucketByGlyph(next);
3290
- const pairings = [];
3291
- for (const [glyph, previousItems] of previousBuckets) {
3292
- const nextItems = nextBuckets.get(glyph) ?? [];
3293
- const shared = Math.min(previousItems.length, nextItems.length);
3294
- for (let index = 0;index < shared; index += 1) {
3295
- pairings.push({
3296
- kind: "move",
3297
- from: previousItems[index],
3298
- to: nextItems[index]
3299
- });
3300
- }
3301
- for (let index = shared;index < previousItems.length; index += 1) {
3302
- pairings.push({
3303
- kind: "exit",
3304
- from: previousItems[index]
3305
- });
3306
- }
3868
+ function shouldCaptureTorphTrace(config) {
3869
+ if (config === null) {
3870
+ return false;
3307
3871
  }
3308
- for (const [glyph, nextItems] of nextBuckets) {
3309
- const previousItems = previousBuckets.get(glyph) ?? [];
3310
- const shared = Math.min(previousItems.length, nextItems.length);
3311
- for (let index = shared;index < nextItems.length; index += 1) {
3312
- pairings.push({
3313
- kind: "enter",
3314
- to: nextItems[index]
3315
- });
3316
- }
3872
+ if (typeof config === "boolean") {
3873
+ return config;
3317
3874
  }
3318
- return pairings;
3875
+ if (config.capture === true) {
3876
+ return true;
3877
+ }
3878
+ return false;
3319
3879
  }
3320
- function resolveMorphFrameBounds(previous, next) {
3321
- return {
3322
- width: Math.max(previous.width, next.width),
3323
- height: Math.max(previous.height, next.height)
3324
- };
3880
+ function isTorphDebugEnabled(config) {
3881
+ if (config === null) {
3882
+ return false;
3883
+ }
3884
+ if (typeof config === "boolean") {
3885
+ return config;
3886
+ }
3887
+ if (config.console !== undefined) {
3888
+ return config.console;
3889
+ }
3890
+ if (config.enabled === true) {
3891
+ return true;
3892
+ }
3893
+ return false;
3325
3894
  }
3326
- function buildMorphVisualBridge(previous, next) {
3327
- return {
3328
- offsetX: previous.rootOrigin.left - next.rootOrigin.left,
3329
- offsetY: previous.rootOrigin.top - next.rootOrigin.top
3330
- };
3895
+ function shouldRunTorphInstrumentation(config) {
3896
+ if (shouldCaptureTorphTrace(config)) {
3897
+ return true;
3898
+ }
3899
+ return isTorphDebugEnabled(config);
3331
3900
  }
3332
- function buildMorphPlan(previous, next, visualBridge = ZERO_BRIDGE) {
3333
- const pairings = pairMorphCharacters(previous.snapshot.graphemes, next.snapshot.graphemes);
3334
- const movesByDestinationKey = new Map;
3335
- const exitItems = [];
3336
- for (const pairing of pairings) {
3337
- if (pairing.kind === "move") {
3338
- movesByDestinationKey.set(pairing.to.key, pairing);
3339
- continue;
3340
- }
3341
- if (pairing.kind === "exit") {
3342
- exitItems.push(pairing.from);
3343
- }
3901
+ function getTorphTraceStore() {
3902
+ const scope = globalThis;
3903
+ let store = scope.__TORPH_TRACE_STORE__;
3904
+ if (store !== undefined) {
3905
+ return store;
3344
3906
  }
3345
- const frame = resolveMorphFrameBounds(previous.snapshot, next.snapshot);
3346
- return {
3347
- frameWidth: frame.width,
3348
- frameHeight: frame.height,
3349
- layoutInlineSizeFrom: previous.layoutInlineSize,
3350
- layoutInlineSizeTo: next.layoutInlineSize,
3351
- visualBridge,
3352
- liveItems: next.snapshot.graphemes.map((grapheme) => {
3353
- const move = movesByDestinationKey.get(grapheme.key);
3354
- if (move) {
3355
- return {
3356
- ...grapheme,
3357
- kind: "move",
3358
- fromLeft: move.from.left,
3359
- fromTop: move.from.top
3360
- };
3361
- }
3362
- return {
3363
- ...grapheme,
3364
- kind: "enter",
3365
- fromLeft: null,
3366
- fromTop: null
3367
- };
3368
- }),
3369
- exitItems
3907
+ store = {
3908
+ lines: [],
3909
+ nextSeq: 1,
3910
+ totalBytes: 0
3370
3911
  };
3912
+ scope.__TORPH_TRACE_STORE__ = store;
3913
+ return store;
3371
3914
  }
3372
- function nearlyEqual(a, b, epsilon = MORPH.geometryEpsilon) {
3373
- return Math.abs(a - b) <= epsilon;
3915
+ function getTorphTraceText() {
3916
+ return getTorphTraceStore().lines.join("");
3374
3917
  }
3375
- function sameSnapshot(a, b) {
3376
- if (a === b) {
3377
- return true;
3378
- }
3379
- if (a.text !== b.text || a.renderText !== b.renderText || a.graphemes.length !== b.graphemes.length) {
3380
- return false;
3381
- }
3382
- if (!nearlyEqual(a.width, b.width) || !nearlyEqual(a.height, b.height)) {
3383
- return false;
3918
+ function clearTorphTrace() {
3919
+ const store = getTorphTraceStore();
3920
+ store.lines = [];
3921
+ store.nextSeq = 1;
3922
+ store.totalBytes = 0;
3923
+ }
3924
+ function downloadTorphTrace(filename) {
3925
+ if (typeof document === "undefined") {
3926
+ return null;
3384
3927
  }
3385
- for (let index = 0;index < a.graphemes.length; index += 1) {
3386
- const left = a.graphemes[index];
3387
- const right = b.graphemes[index];
3388
- if (left.glyph !== right.glyph || left.key !== right.key) {
3389
- return false;
3390
- }
3391
- if (!nearlyEqual(left.left, right.left) || !nearlyEqual(left.top, right.top) || !nearlyEqual(left.width, right.width) || !nearlyEqual(left.height, right.height)) {
3392
- return false;
3928
+ const text = getTorphTraceText();
3929
+ const blob = new Blob([text], { type: "application/x-ndjson;charset=utf-8" });
3930
+ const href = URL.createObjectURL(blob);
3931
+ const anchor = document.createElement("a");
3932
+ let resolvedFilename = filename;
3933
+ if (resolvedFilename === undefined) {
3934
+ resolvedFilename = `torph-trace-${new Date().toISOString().replaceAll(":", "-")}.jsonl`;
3935
+ }
3936
+ anchor.href = href;
3937
+ anchor.download = resolvedFilename;
3938
+ anchor.click();
3939
+ window.setTimeout(() => {
3940
+ URL.revokeObjectURL(href);
3941
+ }, 0);
3942
+ return resolvedFilename;
3943
+ }
3944
+ function ensureTorphTraceApi() {
3945
+ const scope = globalThis;
3946
+ if (scope.__TORPH_TRACE__ !== undefined) {
3947
+ return scope.__TORPH_TRACE__;
3948
+ }
3949
+ const api = {
3950
+ clear: clearTorphTrace,
3951
+ count: () => getTorphTraceStore().lines.length,
3952
+ download: downloadTorphTrace,
3953
+ text: getTorphTraceText
3954
+ };
3955
+ scope.__TORPH_TRACE__ = api;
3956
+ return api;
3957
+ }
3958
+ function appendTorphTrace(instanceId, event, payload) {
3959
+ ensureTorphTraceApi();
3960
+ const store = getTorphTraceStore();
3961
+ const entry = {
3962
+ instanceId,
3963
+ event,
3964
+ payload,
3965
+ seq: store.nextSeq,
3966
+ time: new Date().toISOString()
3967
+ };
3968
+ store.nextSeq += 1;
3969
+ const line = `${JSON.stringify(entry)}
3970
+ `;
3971
+ store.lines.push(line);
3972
+ store.totalBytes += line.length;
3973
+ while (store.lines.length > TORPH_TRACE_MAX_LINES || store.totalBytes > TORPH_TRACE_MAX_BYTES) {
3974
+ const removed = store.lines.shift();
3975
+ if (removed === undefined) {
3976
+ break;
3393
3977
  }
3978
+ store.totalBytes = Math.max(0, store.totalBytes - removed.length);
3394
3979
  }
3395
- return true;
3396
- }
3397
- function sameMeasurement(a, b) {
3398
- if (a === b) {
3399
- return true;
3980
+ }
3981
+ function roundDebugValue(value) {
3982
+ if (value === null || value === undefined) {
3983
+ return value;
3400
3984
  }
3401
- return sameSnapshot(a.snapshot, b.snapshot) && nearlyEqual(a.layoutInlineSize, b.layoutInlineSize) && (a.reservedInlineSize === null && b.reservedInlineSize === null || a.reservedInlineSize !== null && b.reservedInlineSize !== null && nearlyEqual(a.reservedInlineSize, b.reservedInlineSize)) && (a.flowInlineSize === null && b.flowInlineSize === null || a.flowInlineSize !== null && b.flowInlineSize !== null && nearlyEqual(a.flowInlineSize, b.flowInlineSize)) && nearlyEqual(a.rootOrigin.left, b.rootOrigin.left) && nearlyEqual(a.rootOrigin.top, b.rootOrigin.top);
3985
+ return Math.round(value * 1e4) / 1e4;
3402
3986
  }
3403
- function refreshAnimatingTarget(activeTarget, measurement) {
3404
- if (sameMeasurement(activeTarget, measurement)) {
3405
- return activeTarget;
3987
+ function summarizeDebugSnapshot(snapshot) {
3988
+ if (snapshot === null) {
3989
+ return null;
3406
3990
  }
3407
3991
  return {
3408
- snapshot: activeTarget.snapshot,
3409
- layoutInlineSize: measurement.layoutInlineSize,
3410
- reservedInlineSize: measurement.reservedInlineSize,
3411
- flowInlineSize: activeTarget.flowInlineSize,
3412
- rootOrigin: measurement.rootOrigin
3992
+ text: snapshot.text,
3993
+ renderText: snapshot.renderText,
3994
+ width: roundDebugValue(snapshot.width),
3995
+ height: roundDebugValue(snapshot.height),
3996
+ graphemes: snapshot.graphemes.length
3413
3997
  };
3414
3998
  }
3415
- function reuseCommittedMeasurement(committed, measurement) {
3416
- if (sameMeasurement(committed, measurement)) {
3417
- return committed;
3999
+ function summarizeDebugGlyphs(snapshot) {
4000
+ if (snapshot === null) {
4001
+ return null;
4002
+ }
4003
+ return snapshot.graphemes.map((grapheme, index) => ({
4004
+ index,
4005
+ glyph: grapheme.glyph,
4006
+ key: grapheme.key,
4007
+ left: roundDebugValue(grapheme.left),
4008
+ top: roundDebugValue(grapheme.top),
4009
+ width: roundDebugValue(grapheme.width),
4010
+ height: roundDebugValue(grapheme.height)
4011
+ }));
4012
+ }
4013
+ function summarizeDebugMeasurement(measurement) {
4014
+ if (measurement === null) {
4015
+ return null;
3418
4016
  }
3419
- return measurement;
4017
+ return {
4018
+ layoutInlineSize: roundDebugValue(measurement.layoutInlineSize),
4019
+ reservedInlineSize: roundDebugValue(measurement.reservedInlineSize),
4020
+ flowInlineSize: roundDebugValue(measurement.flowInlineSize),
4021
+ rootOrigin: {
4022
+ left: roundDebugValue(measurement.rootOrigin.left),
4023
+ top: roundDebugValue(measurement.rootOrigin.top)
4024
+ },
4025
+ snapshot: summarizeDebugSnapshot(measurement.snapshot)
4026
+ };
3420
4027
  }
3421
- function createStaticState(measurement) {
4028
+ function summarizeDebugLayoutContext(layoutContext) {
4029
+ if (layoutContext === null) {
4030
+ return null;
4031
+ }
3422
4032
  return {
3423
- stage: "idle",
3424
- measurement,
3425
- plan: null
4033
+ display: layoutContext.display,
4034
+ parentDisplay: layoutContext.parentDisplay,
4035
+ whiteSpace: layoutContext.whiteSpace,
4036
+ width: roundDebugValue(layoutContext.width),
4037
+ measurementVersion: layoutContext.measurementVersion
3426
4038
  };
3427
4039
  }
3428
- function areFontsReady() {
3429
- return document.fonts.status === "loaded";
4040
+ function summarizeDebugRect(rect) {
4041
+ if (rect === null) {
4042
+ return null;
4043
+ }
4044
+ return {
4045
+ left: roundDebugValue(rect.left),
4046
+ top: roundDebugValue(rect.top),
4047
+ width: roundDebugValue(rect.width),
4048
+ height: roundDebugValue(rect.height)
4049
+ };
3430
4050
  }
3431
- function cancelTimeline(timeline) {
3432
- if (timeline.prepareFrame !== null) {
3433
- cancelAnimationFrame(timeline.prepareFrame);
3434
- timeline.prepareFrame = null;
4051
+ function collectDebugAnchorIndices(length) {
4052
+ const indices = new Set;
4053
+ if (length <= 0) {
4054
+ return [];
3435
4055
  }
3436
- if (timeline.animateFrame !== null) {
3437
- cancelAnimationFrame(timeline.animateFrame);
3438
- timeline.animateFrame = null;
4056
+ indices.add(0);
4057
+ if (length > 1) {
4058
+ indices.add(1);
4059
+ indices.add(length - 2);
3439
4060
  }
3440
- if (timeline.finalizeTimer !== null) {
3441
- window.clearTimeout(timeline.finalizeTimer);
3442
- timeline.finalizeTimer = null;
4061
+ if (length > 2) {
4062
+ indices.add(Math.floor((length - 1) / 2));
3443
4063
  }
4064
+ indices.add(length - 1);
4065
+ return Array.from(indices).sort((left, right) => left - right);
3444
4066
  }
3445
- function resetMorph(session, timeline, setState) {
3446
- cancelTimeline(timeline);
3447
- session.committed = null;
3448
- session.target = null;
3449
- session.animating = false;
3450
- setState(EMPTY_STATE);
3451
- }
3452
- function commitStaticMeasurement(session, measurement, setState) {
3453
- if (!session.animating && session.target === null && session.committed !== null && sameMeasurement(session.committed, measurement)) {
3454
- return;
4067
+ function summarizeDebugViewportAnchors(snapshot, rootRect) {
4068
+ if (snapshot === null || rootRect === null) {
4069
+ return null;
3455
4070
  }
3456
- session.committed = measurement;
3457
- session.target = null;
3458
- session.animating = false;
3459
- setState((current) => {
3460
- if (current.stage === "idle" && current.plan === null && current.measurement !== null && sameMeasurement(current.measurement, measurement)) {
3461
- return current;
4071
+ const anchors = [];
4072
+ const anchorIndices = collectDebugAnchorIndices(snapshot.graphemes.length);
4073
+ for (const index of anchorIndices) {
4074
+ const grapheme = snapshot.graphemes[index];
4075
+ if (grapheme === undefined) {
4076
+ continue;
3462
4077
  }
3463
- return createStaticState(measurement);
3464
- });
3465
- }
3466
- function scheduleMorphTimeline({
3467
- session,
3468
- timeline,
3469
- measurement,
3470
- plan,
3471
- setState
3472
- }) {
3473
- timeline.prepareFrame = requestAnimationFrame(() => {
3474
- timeline.prepareFrame = null;
3475
- setState((current) => {
3476
- if (current.measurement !== measurement || current.plan !== plan) {
3477
- return current;
3478
- }
3479
- return {
3480
- stage: "animate",
3481
- measurement,
3482
- plan
3483
- };
4078
+ anchors.push({
4079
+ index,
4080
+ glyph: grapheme.glyph,
4081
+ left: roundDebugValue(rootRect.left + grapheme.left),
4082
+ top: roundDebugValue(rootRect.top + grapheme.top),
4083
+ width: roundDebugValue(grapheme.width),
4084
+ height: roundDebugValue(grapheme.height)
3484
4085
  });
3485
- timeline.animateFrame = requestAnimationFrame(() => {
3486
- timeline.animateFrame = null;
3487
- timeline.finalizeTimer = window.setTimeout(() => {
3488
- timeline.finalizeTimer = null;
3489
- commitStaticMeasurement(session, session.target ?? measurement, setState);
3490
- }, MORPH.durationMs);
3491
- });
3492
- });
4086
+ }
4087
+ return anchors;
3493
4088
  }
3494
- function startMorph({
3495
- nextMeasurement,
3496
- session,
3497
- timeline,
3498
- setState
3499
- }) {
3500
- let sourceMeasurement = session.committed;
3501
- if (session.animating && session.target) {
3502
- sourceMeasurement = session.target;
4089
+ function summarizeDebugRootOriginDrift(measurement, rootRect) {
4090
+ if (measurement === null || rootRect === null) {
4091
+ return null;
3503
4092
  }
3504
- if (sourceMeasurement === null) {
3505
- commitStaticMeasurement(session, nextMeasurement, setState);
4093
+ return {
4094
+ expectedLeft: roundDebugValue(measurement.rootOrigin.left),
4095
+ expectedTop: roundDebugValue(measurement.rootOrigin.top),
4096
+ actualLeft: roundDebugValue(rootRect.left),
4097
+ actualTop: roundDebugValue(rootRect.top),
4098
+ deltaLeft: roundDebugValue(rootRect.left - measurement.rootOrigin.left),
4099
+ deltaTop: roundDebugValue(rootRect.top - measurement.rootOrigin.top)
4100
+ };
4101
+ }
4102
+ function summarizeSnapshotDrift(drift) {
4103
+ return {
4104
+ comparedGlyphs: drift.comparedGlyphs,
4105
+ expectedGlyphs: drift.expectedGlyphs,
4106
+ actualGlyphs: drift.actualGlyphs,
4107
+ snapshotWidthDelta: roundDebugValue(drift.snapshotWidthDelta),
4108
+ snapshotHeightDelta: roundDebugValue(drift.snapshotHeightDelta),
4109
+ maxAbsLeftDelta: roundDebugValue(drift.maxAbsLeftDelta),
4110
+ maxAbsTopDelta: roundDebugValue(drift.maxAbsTopDelta),
4111
+ maxAbsWidthDelta: roundDebugValue(drift.maxAbsWidthDelta),
4112
+ maxAbsHeightDelta: roundDebugValue(drift.maxAbsHeightDelta),
4113
+ mismatches: drift.mismatches.map((mismatch) => ({
4114
+ index: mismatch.index,
4115
+ glyph: mismatch.glyph,
4116
+ leftDelta: roundDebugValue(mismatch.leftDelta),
4117
+ topDelta: roundDebugValue(mismatch.topDelta),
4118
+ widthDelta: roundDebugValue(mismatch.widthDelta),
4119
+ heightDelta: roundDebugValue(mismatch.heightDelta)
4120
+ }))
4121
+ };
4122
+ }
4123
+ function logTorphDebug(instanceId, event, payload) {
4124
+ const config = readTorphDebugConfig();
4125
+ const captureTrace = shouldCaptureTorphTrace(config);
4126
+ const logToConsole = isTorphDebugEnabled(config);
4127
+ if (!captureTrace && !logToConsole) {
3506
4128
  return;
3507
4129
  }
3508
- const previousMeasurement = pinMeasurementToCurrentOrigin(sourceMeasurement, nextMeasurement.rootOrigin);
3509
- const visualBridge = buildMorphVisualBridge(previousMeasurement, nextMeasurement);
3510
- const plan = buildMorphPlan(previousMeasurement, nextMeasurement, visualBridge);
3511
- session.target = nextMeasurement;
3512
- session.animating = true;
3513
- setState({
3514
- stage: "prepare",
3515
- measurement: nextMeasurement,
3516
- plan
3517
- });
3518
- scheduleMorphTimeline({
3519
- session,
3520
- timeline,
3521
- measurement: nextMeasurement,
3522
- plan,
3523
- setState
3524
- });
4130
+ if (captureTrace) {
4131
+ appendTorphTrace(instanceId, event, payload);
4132
+ }
4133
+ if (logToConsole) {
4134
+ console.log(`[Torph#${instanceId}] ${event}`, payload);
4135
+ }
4136
+ }
4137
+
4138
+ // torph/src/components/Torph.tsx
4139
+ import { jsxDEV } from "react/jsx-dev-runtime";
4140
+ var SCREEN_READER_ONLY_STYLE = {
4141
+ position: "absolute",
4142
+ width: "1px",
4143
+ height: "1px",
4144
+ margin: "-1px",
4145
+ padding: 0,
4146
+ border: 0,
4147
+ clip: "rect(0 0 0 0)",
4148
+ clipPath: "inset(50%)",
4149
+ overflow: "hidden",
4150
+ whiteSpace: "nowrap"
4151
+ };
4152
+ var FALLBACK_TEXT_STYLE = {
4153
+ display: "block",
4154
+ gridArea: "1 / 1"
4155
+ };
4156
+ var SHARED_GLYPH_TYPOGRAPHY_STYLE = {
4157
+ font: "inherit",
4158
+ fontKerning: "inherit",
4159
+ fontFeatureSettings: "inherit",
4160
+ fontOpticalSizing: "inherit",
4161
+ fontStretch: "inherit",
4162
+ fontStyle: "inherit",
4163
+ fontVariant: "inherit",
4164
+ fontVariantNumeric: "inherit",
4165
+ fontVariationSettings: "inherit",
4166
+ fontWeight: "inherit",
4167
+ letterSpacing: "inherit",
4168
+ textTransform: "inherit",
4169
+ wordSpacing: "inherit",
4170
+ direction: "inherit"
4171
+ };
4172
+ var ABSOLUTE_GLYPH_STYLE = {
4173
+ position: "absolute",
4174
+ display: "block",
4175
+ overflow: "hidden",
4176
+ transformOrigin: "left top"
4177
+ };
4178
+ var CONTEXT_SLICE_TEXT_STYLE = {
4179
+ ...SHARED_GLYPH_TYPOGRAPHY_STYLE,
4180
+ position: "absolute",
4181
+ display: "block",
4182
+ minWidth: 0,
4183
+ whiteSpace: "inherit"
4184
+ };
4185
+ var debugDomNodeIds = new WeakMap;
4186
+ var debugDomNodeOrdinal = 0;
4187
+ function getDebugDomNodeId(node) {
4188
+ if (node === null) {
4189
+ return null;
4190
+ }
4191
+ const existing = debugDomNodeIds.get(node);
4192
+ if (existing !== undefined) {
4193
+ return existing;
4194
+ }
4195
+ debugDomNodeOrdinal += 1;
4196
+ debugDomNodeIds.set(node, debugDomNodeOrdinal);
4197
+ return debugDomNodeOrdinal;
3525
4198
  }
3526
4199
  function reconcileMorphChange({
3527
4200
  root,
@@ -3543,10 +4216,7 @@ function reconcileMorphChange({
3543
4216
  if (measurementBackend === null) {
3544
4217
  throw new Error("Torph measurement backend is missing.");
3545
4218
  }
3546
- let layoutHint = session.committed;
3547
- if (session.animating && session.target !== null) {
3548
- layoutHint = session.target;
3549
- }
4219
+ const layoutHint = selectMorphLayoutHint(session);
3550
4220
  const nextMeasurement = measureFromNodes({
3551
4221
  root,
3552
4222
  layoutContext,
@@ -3558,72 +4228,13 @@ function reconcileMorphChange({
3558
4228
  renderText,
3559
4229
  segments
3560
4230
  });
3561
- if (session.animating && session.target !== null) {
3562
- if (nextMeasurement.snapshot.renderText === session.target.snapshot.renderText) {
3563
- session.target = refreshAnimatingTarget(session.target, nextMeasurement);
3564
- return nextMeasurement;
3565
- }
3566
- }
3567
- cancelTimeline(timeline);
3568
- if (session.committed === null) {
3569
- commitStaticMeasurement(session, nextMeasurement, setState);
3570
- return nextMeasurement;
3571
- }
3572
- if (!areFontsReady()) {
3573
- commitStaticMeasurement(session, nextMeasurement, setState);
3574
- return nextMeasurement;
3575
- }
3576
- if (session.committed.snapshot.renderText === nextMeasurement.snapshot.renderText) {
3577
- commitStaticMeasurement(session, reuseCommittedMeasurement(session.committed, nextMeasurement), setState);
3578
- return nextMeasurement;
3579
- }
3580
- startMorph({
3581
- nextMeasurement,
4231
+ return reconcileMorphSessionUpdate({
3582
4232
  session,
3583
4233
  timeline,
4234
+ nextMeasurement,
4235
+ fontsReady: areFontsReady(),
3584
4236
  setState
3585
4237
  });
3586
- return nextMeasurement;
3587
- }
3588
- function syncCommittedRootOriginWhenIdle({
3589
- root,
3590
- flowTextRef,
3591
- layoutContext,
3592
- state,
3593
- session
3594
- }) {
3595
- if (root === null || layoutContext === null) {
3596
- return;
3597
- }
3598
- if (state.stage !== "idle" || state.measurement === null) {
3599
- return;
3600
- }
3601
- const nextRootOrigin = readRootOrigin(root);
3602
- const nextFlowInlineSize = readFlowInlineSize(flowTextRef.current);
3603
- const committedMeasurement = state.measurement;
3604
- if (nearlyEqual(committedMeasurement.rootOrigin.left, nextRootOrigin.left) && nearlyEqual(committedMeasurement.rootOrigin.top, nextRootOrigin.top) && (committedMeasurement.flowInlineSize === null && nextFlowInlineSize === null || committedMeasurement.flowInlineSize !== null && nextFlowInlineSize !== null && nearlyEqual(committedMeasurement.flowInlineSize, nextFlowInlineSize))) {
3605
- session.committed = committedMeasurement;
3606
- return;
3607
- }
3608
- session.committed = {
3609
- snapshot: committedMeasurement.snapshot,
3610
- layoutInlineSize: committedMeasurement.layoutInlineSize,
3611
- reservedInlineSize: committedMeasurement.reservedInlineSize,
3612
- flowInlineSize: nextFlowInlineSize,
3613
- rootOrigin: nextRootOrigin
3614
- };
3615
- }
3616
- function getFadeDuration(fraction) {
3617
- return Math.min(MORPH.durationMs * fraction, MORPH.maxFadeMs);
3618
- }
3619
- function getLiveTransform(item, stage, visualBridge) {
3620
- if (stage !== "prepare") {
3621
- return "translate(0px, 0px)";
3622
- }
3623
- if (item.kind === "move") {
3624
- return `translate(${(item.fromLeft ?? item.left) - item.left + visualBridge.offsetX}px, ${(item.fromTop ?? item.top) - item.top + visualBridge.offsetY}px)`;
3625
- }
3626
- return `translate(${visualBridge.offsetX}px, ${visualBridge.offsetY}px)`;
3627
4238
  }
3628
4239
  function getLiveOpacity(item, stage) {
3629
4240
  if (stage === "prepare" && item.kind === "enter") {
@@ -3631,96 +4242,14 @@ function getLiveOpacity(item, stage) {
3631
4242
  }
3632
4243
  return 1;
3633
4244
  }
3634
- function getLiveTransition(item, stage) {
3635
- if (stage !== "animate") {
3636
- return;
3637
- }
3638
- if (item.kind === "enter") {
3639
- return `opacity ${getFadeDuration(0.5)}ms linear ${getFadeDuration(0.25)}ms`;
3640
- }
3641
- return `transform ${MORPH.durationMs}ms ${MORPH.ease}, opacity ${getFadeDuration(0.25)}ms linear`;
3642
- }
3643
- function getExitOpacity(stage) {
3644
- if (stage === "animate") {
3645
- return 0;
3646
- }
3647
- return 1;
3648
- }
3649
- function getExitTransform(visualBridge) {
3650
- return `translate(${visualBridge.offsetX}px, ${visualBridge.offsetY}px)`;
3651
- }
3652
- function getExitTransition(stage) {
3653
- if (stage !== "animate") {
3654
- return;
3655
- }
3656
- return `transform ${MORPH.durationMs}ms ${MORPH.ease}, opacity ${getFadeDuration(0.25)}ms linear`;
3657
- }
3658
- function supportsIntrinsicWidthLock(display, parentDisplay) {
3659
- const parentNeedsReservation = parentDisplay === "flex" || parentDisplay === "inline-flex" || parentDisplay === "grid" || parentDisplay === "inline-grid";
3660
- return display === "inline" || display === "inline-block" || display === "inline-flex" || display === "inline-grid" || parentNeedsReservation;
3661
- }
3662
- function getRootDisplay(layoutContext) {
3663
- if (layoutContext === null) {
3664
- return "grid";
3665
- }
3666
- if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
3667
- return "inline-grid";
3668
- }
3669
- return "grid";
3670
- }
3671
- function getRootStyle(stage, plan, measurement, layoutContext) {
3672
- let width = measurement?.reservedInlineSize ?? undefined;
3673
- if (plan !== null) {
3674
- width = plan.layoutInlineSizeTo;
3675
- if (stage === "prepare") {
3676
- width = plan.layoutInlineSizeFrom;
3677
- }
3678
- }
3679
- let height;
3680
- if (plan !== null) {
3681
- height = plan.frameHeight;
3682
- }
3683
- const shouldTransitionWidth = stage === "animate" && plan !== null && !nearlyEqual(plan.layoutInlineSizeFrom, plan.layoutInlineSizeTo);
3684
- const style = {
3685
- position: "relative",
3686
- display: getRootDisplay(layoutContext)
3687
- };
3688
- if (width !== undefined) {
3689
- style.width = width;
3690
- }
3691
- if (height !== undefined) {
3692
- style.height = height;
3693
- }
3694
- if (shouldTransitionWidth) {
3695
- style.transition = `width ${MORPH.durationMs}ms ${MORPH.ease}`;
3696
- }
3697
- return style;
3698
- }
3699
- function getMeasurementLayerStyle(layoutContext, useContentInlineSize = false) {
3700
- const intrinsicWidthLock = layoutContext !== null && (useContentInlineSize || supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay));
3701
- if (!intrinsicWidthLock) {
3702
- return MEASUREMENT_LAYER_STYLE;
3703
- }
3704
- return {
3705
- ...MEASUREMENT_LAYER_STYLE,
3706
- right: "auto",
3707
- width: "max-content"
3708
- };
3709
- }
3710
- function resolveFlowText(committedMeasurement, stateMeasurement, text) {
3711
- return committedMeasurement?.snapshot.text ?? stateMeasurement?.snapshot.text ?? text;
3712
- }
3713
- function getOverlayStyle(plan) {
3714
- return {
3715
- ...OVERLAY_STYLE,
3716
- right: "auto",
3717
- bottom: "auto",
3718
- width: plan.frameWidth,
3719
- height: plan.frameHeight
3720
- };
3721
- }
3722
- function getFallbackTextStyle(shouldRenderOverlay) {
3723
- if (!shouldRenderOverlay) {
4245
+ function getExitOpacity(stage) {
4246
+ if (stage === "animate") {
4247
+ return 0;
4248
+ }
4249
+ return 1;
4250
+ }
4251
+ function getFallbackTextStyle(shouldHideFlowText) {
4252
+ if (!shouldHideFlowText) {
3724
4253
  return FALLBACK_TEXT_STYLE;
3725
4254
  }
3726
4255
  return {
@@ -3736,7 +4265,6 @@ function getLiveGlyphStyle(item, stage, visualBridge) {
3736
4265
  top: item.top,
3737
4266
  width: item.width,
3738
4267
  height: item.height,
3739
- lineHeight: `${item.height}px`,
3740
4268
  opacity: getLiveOpacity(item, stage),
3741
4269
  transform: getLiveTransform(item, stage, visualBridge),
3742
4270
  transition: getLiveTransition(item, stage)
@@ -3749,28 +4277,173 @@ function getExitGlyphStyle(item, stage, visualBridge) {
3749
4277
  top: item.top,
3750
4278
  width: item.width,
3751
4279
  height: item.height,
3752
- lineHeight: `${item.height}px`,
3753
4280
  opacity: getExitOpacity(stage),
3754
4281
  transform: getExitTransform(visualBridge),
3755
4282
  transition: getExitTransition(stage)
3756
4283
  };
3757
4284
  }
3758
- function MorphOverlay({ stage, plan }) {
4285
+ function getContextSliceStyle(layoutInlineSize, item, whiteSpace) {
4286
+ return {
4287
+ ...CONTEXT_SLICE_TEXT_STYLE,
4288
+ left: -item.left,
4289
+ top: -item.top,
4290
+ width: layoutInlineSize,
4291
+ whiteSpace
4292
+ };
4293
+ }
4294
+ function summarizePreciseRect(rect) {
4295
+ if (rect === null) {
4296
+ return null;
4297
+ }
4298
+ return {
4299
+ left: roundDebugValue(rect.left),
4300
+ top: roundDebugValue(rect.top),
4301
+ width: roundDebugValue(rect.width),
4302
+ height: roundDebugValue(rect.height)
4303
+ };
4304
+ }
4305
+ function summarizePreciseGlyphs(snapshot, rootRect) {
4306
+ if (snapshot === null) {
4307
+ return null;
4308
+ }
4309
+ return snapshot.graphemes.map((grapheme, index) => ({
4310
+ index,
4311
+ glyph: grapheme.glyph,
4312
+ key: grapheme.key,
4313
+ left: roundDebugValue(grapheme.left),
4314
+ top: roundDebugValue(grapheme.top),
4315
+ width: roundDebugValue(grapheme.width),
4316
+ height: roundDebugValue(grapheme.height),
4317
+ viewportLeft: rootRect === null ? null : roundDebugValue(rootRect.left + grapheme.left),
4318
+ viewportTop: rootRect === null ? null : roundDebugValue(rootRect.top + grapheme.top)
4319
+ }));
4320
+ }
4321
+ function summarizeLiveNodeStyles(overlayNode) {
4322
+ if (overlayNode === null) {
4323
+ return null;
4324
+ }
4325
+ return Array.from(overlayNode.querySelectorAll("[data-morph-role='live']")).map((node, index) => {
4326
+ const styles = getComputedStyle(node);
4327
+ const sliceNode = node.firstElementChild;
4328
+ let sliceStyles = null;
4329
+ let sliceRect = null;
4330
+ if (sliceNode instanceof HTMLElement) {
4331
+ sliceStyles = getComputedStyle(sliceNode);
4332
+ sliceRect = sliceNode.getBoundingClientRect();
4333
+ }
4334
+ let nodeRect = null;
4335
+ nodeRect = node.getBoundingClientRect();
4336
+ let sliceScrollWidth = null;
4337
+ let sliceClientWidth = null;
4338
+ let sliceOffsetWidth = null;
4339
+ if (sliceNode instanceof HTMLElement) {
4340
+ sliceScrollWidth = sliceNode.scrollWidth;
4341
+ sliceClientWidth = sliceNode.clientWidth;
4342
+ sliceOffsetWidth = sliceNode.offsetWidth;
4343
+ }
4344
+ return {
4345
+ index,
4346
+ nodeId: getDebugDomNodeId(node),
4347
+ key: node.dataset.morphKey ?? null,
4348
+ glyph: node.dataset.morphGlyph ?? null,
4349
+ kind: node.dataset.morphKind ?? null,
4350
+ transform: styles.transform,
4351
+ inlineTransform: node.style.transform,
4352
+ opacity: styles.opacity,
4353
+ transitionProperty: styles.transitionProperty,
4354
+ transitionDuration: styles.transitionDuration,
4355
+ transitionTimingFunction: styles.transitionTimingFunction,
4356
+ nodeRect: summarizePreciseRect(nodeRect),
4357
+ sliceNodeId: getDebugDomNodeId(sliceNode),
4358
+ sliceInlineLeft: node.firstElementChild instanceof HTMLElement ? node.firstElementChild.style.left : null,
4359
+ sliceInlineTop: node.firstElementChild instanceof HTMLElement ? node.firstElementChild.style.top : null,
4360
+ sliceInlineWidth: node.firstElementChild instanceof HTMLElement ? node.firstElementChild.style.width : null,
4361
+ sliceLeft: sliceStyles?.left ?? null,
4362
+ sliceTop: sliceStyles?.top ?? null,
4363
+ sliceWidth: sliceStyles?.width ?? null,
4364
+ sliceWhiteSpace: sliceStyles?.whiteSpace ?? null,
4365
+ sliceText: sliceNode instanceof HTMLElement ? sliceNode.textContent : null,
4366
+ sliceRect: summarizePreciseRect(sliceRect),
4367
+ sliceScrollWidth: roundDebugValue(sliceScrollWidth),
4368
+ sliceClientWidth: roundDebugValue(sliceClientWidth),
4369
+ sliceOffsetWidth: roundDebugValue(sliceOffsetWidth)
4370
+ };
4371
+ });
4372
+ }
4373
+ function summarizeRootRuntimeStyles(root) {
4374
+ if (root === null) {
4375
+ return null;
4376
+ }
4377
+ const styles = getComputedStyle(root);
4378
+ const parent = root.parentElement;
4379
+ let parentStyles = null;
4380
+ let parentRect = null;
4381
+ if (parent instanceof HTMLElement) {
4382
+ parentStyles = getComputedStyle(parent);
4383
+ parentRect = parent.getBoundingClientRect();
4384
+ }
4385
+ let parentSummary = null;
4386
+ if (parent !== null) {
4387
+ parentSummary = {
4388
+ display: parentStyles?.display ?? null,
4389
+ justifyContent: parentStyles?.justifyContent ?? null,
4390
+ alignItems: parentStyles?.alignItems ?? null,
4391
+ placeItems: parentStyles?.placeItems ?? null,
4392
+ textAlign: parentStyles?.textAlign ?? null,
4393
+ rect: summarizePreciseRect(parentRect)
4394
+ };
4395
+ }
4396
+ return {
4397
+ inlineWidth: root.style.width || null,
4398
+ computedWidth: styles.width,
4399
+ inlineTransition: root.style.transition || null,
4400
+ computedTransitionProperty: styles.transitionProperty,
4401
+ computedTransitionDuration: styles.transitionDuration,
4402
+ computedTransform: styles.transform,
4403
+ offsetWidth: roundDebugValue(root.offsetWidth),
4404
+ clientWidth: roundDebugValue(root.clientWidth),
4405
+ scrollWidth: roundDebugValue(root.scrollWidth),
4406
+ parent: parentSummary
4407
+ };
4408
+ }
4409
+ function MorphOverlay({
4410
+ overlayRef,
4411
+ stage,
4412
+ plan,
4413
+ sourceSliceWhiteSpace,
4414
+ targetSliceWhiteSpace
4415
+ }) {
3759
4416
  let exitItems = [];
3760
4417
  if (stage !== "idle") {
3761
4418
  exitItems = plan.exitItems;
3762
4419
  }
3763
4420
  return /* @__PURE__ */ jsxDEV("div", {
4421
+ ref: overlayRef,
3764
4422
  "aria-hidden": "true",
3765
- style: getOverlayStyle(plan),
4423
+ style: getOverlayStyle(stage, plan),
3766
4424
  children: [
3767
4425
  exitItems.map((item) => /* @__PURE__ */ jsxDEV("span", {
4426
+ "data-morph-role": "exit",
4427
+ "data-morph-key": item.key,
4428
+ "data-morph-glyph": item.glyph,
3768
4429
  style: getExitGlyphStyle(item, stage, plan.visualBridge),
3769
- children: item.glyph
4430
+ children: /* @__PURE__ */ jsxDEV("span", {
4431
+ "data-morph-slice": "context",
4432
+ style: getContextSliceStyle(plan.layoutInlineSizeFrom, item, sourceSliceWhiteSpace),
4433
+ children: plan.sourceRenderText
4434
+ }, undefined, false, undefined, this)
3770
4435
  }, `exit-${item.key}`, false, undefined, this)),
3771
4436
  plan.liveItems.map((item) => /* @__PURE__ */ jsxDEV("span", {
4437
+ "data-morph-role": "live",
4438
+ "data-morph-key": item.key,
4439
+ "data-morph-glyph": item.glyph,
4440
+ "data-morph-kind": item.kind,
3772
4441
  style: getLiveGlyphStyle(item, stage, plan.visualBridge),
3773
- children: item.glyph
4442
+ children: /* @__PURE__ */ jsxDEV("span", {
4443
+ "data-morph-slice": "context",
4444
+ style: getContextSliceStyle(plan.layoutInlineSizeTo, item, targetSliceWhiteSpace),
4445
+ children: plan.targetRenderText
4446
+ }, undefined, false, undefined, this)
3774
4447
  }, item.key, false, undefined, this))
3775
4448
  ]
3776
4449
  }, undefined, true, undefined, this);
@@ -3779,38 +4452,55 @@ function MeasurementLayer({
3779
4452
  layerRef,
3780
4453
  layoutContext,
3781
4454
  text,
3782
- segments,
3783
4455
  useContentInlineSize
3784
4456
  }) {
3785
- let glyphs = segments;
3786
- if (text.length === 0) {
3787
- glyphs = EMPTY_SEGMENTS;
3788
- }
3789
4457
  return /* @__PURE__ */ jsxDEV("span", {
3790
4458
  ref: layerRef,
3791
4459
  "aria-hidden": "true",
3792
4460
  style: getMeasurementLayerStyle(layoutContext, useContentInlineSize),
3793
- children: glyphs.map((segment) => /* @__PURE__ */ jsxDEV("span", {
3794
- "data-morph-key": segment.key,
3795
- style: MEASUREMENT_GLYPH_STYLE,
3796
- children: segment.glyph
3797
- }, segment.key, false, undefined, this))
4461
+ children: text
3798
4462
  }, undefined, false, undefined, this);
3799
4463
  }
3800
- function useMorphTransition(text, className) {
3801
- const { ref, layoutContext } = useObservedLayoutContext([className]);
3802
- const flowTextRef = useRef(null);
3803
- const measurementLayerRef = useRef(null);
3804
- const completedDomMeasurementKeyRef = useRef(null);
3805
- const domMeasurementSnapshotCacheRef = useRef(new Map);
3806
- const sessionRef = useRef({ ...EMPTY_SESSION });
3807
- const timelineRef = useRef({ ...EMPTY_TIMELINE });
3808
- const [domMeasurementRequestKey, setDomMeasurementRequestKey] = useState(null);
3809
- const [state, setState] = useState(EMPTY_STATE);
3810
- let measurementHint = sessionRef.current.committed;
3811
- if (sessionRef.current.animating) {
3812
- measurementHint = sessionRef.current.target ?? sessionRef.current.committed;
4464
+ function isMorphOverlayTransformFinalizeEvent(event, hasMoveTransitions) {
4465
+ if (!hasMoveTransitions) {
4466
+ return false;
4467
+ }
4468
+ const target = event.target;
4469
+ if (!(target instanceof HTMLElement)) {
4470
+ return false;
4471
+ }
4472
+ if (target.dataset.morphRole !== "live") {
4473
+ return false;
4474
+ }
4475
+ return event.propertyName === "transform";
4476
+ }
4477
+ function resolveMorphFinalizeSignal(event, hasMoveTransitions) {
4478
+ if (isMorphOverlayTransformFinalizeEvent(event, hasMoveTransitions)) {
4479
+ return "live-transform";
3813
4480
  }
4481
+ return null;
4482
+ }
4483
+ function useMorphTransition(text, className) {
4484
+ const [state, setState] = useState2(EMPTY_STATE);
4485
+ const { ref, layoutContext } = useObservedLayoutContext([
4486
+ className
4487
+ ]);
4488
+ const debugInstanceIdRef = useRef2(null);
4489
+ const debugRenderOrdinalRef = useRef2(0);
4490
+ const flowTextRef = useRef2(null);
4491
+ const measurementLayerRef = useRef2(null);
4492
+ const completedDomMeasurementKeyRef = useRef2(null);
4493
+ const domMeasurementSnapshotCacheRef = useRef2(new Map);
4494
+ const sessionRef = useRef2({ ...EMPTY_SESSION });
4495
+ const timelineRef = useRef2({ ...EMPTY_TIMELINE });
4496
+ const debugDriftSignatureRef = useRef2(null);
4497
+ const [domMeasurementRequestKey, setDomMeasurementRequestKey] = useState2(null);
4498
+ debugRenderOrdinalRef.current += 1;
4499
+ const debugRenderOrdinal = debugRenderOrdinalRef.current;
4500
+ if (debugInstanceIdRef.current === null) {
4501
+ debugInstanceIdRef.current = nextTorphDebugInstanceId();
4502
+ }
4503
+ const measurementHint = selectMorphLayoutHint(sessionRef.current);
3814
4504
  const measurementRequest = useMemo(() => createMorphMeasurementRequest({
3815
4505
  text,
3816
4506
  layoutContext,
@@ -3821,10 +4511,39 @@ function useMorphTransition(text, className) {
3821
4511
  const measurementBackend = measurementRequest?.measurementBackend ?? null;
3822
4512
  const segments = measurementRequest?.segments ?? EMPTY_SEGMENTS;
3823
4513
  const domMeasurementKey = measurementRequest?.domMeasurementKey ?? null;
3824
- useLayoutEffect(() => {
4514
+ const logTransitionTrace = (event, payload = {}) => {
4515
+ const config = readTorphDebugConfig();
4516
+ if (!shouldRunTorphInstrumentation(config)) {
4517
+ return;
4518
+ }
4519
+ logTorphDebug(debugInstanceIdRef.current, event, {
4520
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
4521
+ renderOrdinal: debugRenderOrdinal,
4522
+ text,
4523
+ renderText,
4524
+ stateStage: state.stage,
4525
+ committed: summarizeDebugMeasurement(sessionRef.current.committed),
4526
+ stateMeasurement: summarizeDebugMeasurement(state.measurement),
4527
+ layoutContext: summarizeDebugLayoutContext(layoutContext),
4528
+ measurementBackend,
4529
+ useContentInlineSize,
4530
+ domMeasurementKey,
4531
+ domMeasurementRequestKey,
4532
+ completedDomMeasurementKey: completedDomMeasurementKeyRef.current,
4533
+ ...payload
4534
+ });
4535
+ };
4536
+ useLayoutEffect2(() => {
4537
+ ensureTorphTraceApi();
4538
+ }, []);
4539
+ useLayoutEffect2(() => {
3825
4540
  if (ref.current === null || layoutContext === null) {
3826
4541
  completedDomMeasurementKeyRef.current = null;
3827
4542
  if (domMeasurementRequestKey !== null) {
4543
+ logTransitionTrace("effect:dom-measurement-request-update", {
4544
+ reason: "clear-missing-root-or-layout",
4545
+ nextDomMeasurementRequestKey: null
4546
+ });
3828
4547
  setDomMeasurementRequestKey(null);
3829
4548
  }
3830
4549
  reconcileMorphChange({
@@ -3850,6 +4569,11 @@ function useMorphTransition(text, className) {
3850
4569
  if (cachedSnapshot !== null) {
3851
4570
  completedDomMeasurementKeyRef.current = domMeasurementKey;
3852
4571
  if (domMeasurementRequestKey !== null) {
4572
+ logTransitionTrace("effect:dom-measurement-request-update", {
4573
+ reason: "clear-after-cache-hit",
4574
+ nextDomMeasurementRequestKey: null,
4575
+ snapshotSource: "cache"
4576
+ });
3853
4577
  setDomMeasurementRequestKey(null);
3854
4578
  }
3855
4579
  reconcileMorphChange({
@@ -3869,13 +4593,20 @@ function useMorphTransition(text, className) {
3869
4593
  }
3870
4594
  if (completedDomMeasurementKeyRef.current !== domMeasurementKey) {
3871
4595
  if (domMeasurementRequestKey !== domMeasurementKey) {
4596
+ logTransitionTrace("effect:dom-measurement-request-update", {
4597
+ reason: "request-measurement-layer",
4598
+ nextDomMeasurementRequestKey: domMeasurementKey
4599
+ });
3872
4600
  setDomMeasurementRequestKey(domMeasurementKey);
3873
4601
  return;
3874
4602
  }
3875
4603
  if (measurementLayerRef.current === null) {
4604
+ logTransitionTrace("effect:dom-measurement-await-layer", {
4605
+ reason: "measurement-layer-not-mounted"
4606
+ });
3876
4607
  return;
3877
4608
  }
3878
- const nextMeasurement = reconcileMorphChange({
4609
+ const nextMeasurement2 = reconcileMorphChange({
3879
4610
  root: ref.current,
3880
4611
  measurementLayer: measurementLayerRef.current,
3881
4612
  measurementBackend,
@@ -3888,27 +4619,40 @@ function useMorphTransition(text, className) {
3888
4619
  timeline: timelineRef.current,
3889
4620
  setState
3890
4621
  });
3891
- if (nextMeasurement !== null) {
4622
+ if (nextMeasurement2 !== null) {
3892
4623
  if (canCacheMeasurementLayerSnapshot(measurementBackend)) {
3893
- rememberCachedMorphSnapshot(domMeasurementSnapshotCacheRef.current, domMeasurementKey, nextMeasurement.snapshot);
4624
+ rememberCachedMorphSnapshot(domMeasurementSnapshotCacheRef.current, domMeasurementKey, nextMeasurement2.snapshot);
3894
4625
  }
3895
4626
  }
3896
4627
  completedDomMeasurementKeyRef.current = domMeasurementKey;
3897
4628
  if (domMeasurementRequestKey !== null) {
4629
+ logTransitionTrace("effect:dom-measurement-request-update", {
4630
+ reason: "clear-after-live-measurement",
4631
+ nextDomMeasurementRequestKey: null,
4632
+ snapshotSource: "layer"
4633
+ });
3898
4634
  setDomMeasurementRequestKey(null);
3899
4635
  }
3900
4636
  return;
3901
4637
  }
3902
4638
  if (domMeasurementRequestKey !== null) {
4639
+ logTransitionTrace("effect:dom-measurement-request-update", {
4640
+ reason: "clear-completed-measurement",
4641
+ nextDomMeasurementRequestKey: null
4642
+ });
3903
4643
  setDomMeasurementRequestKey(null);
3904
4644
  }
3905
4645
  return;
3906
4646
  }
3907
4647
  completedDomMeasurementKeyRef.current = null;
3908
4648
  if (domMeasurementRequestKey !== null) {
4649
+ logTransitionTrace("effect:dom-measurement-request-update", {
4650
+ reason: "clear-no-dom-measurement-needed",
4651
+ nextDomMeasurementRequestKey: null
4652
+ });
3909
4653
  setDomMeasurementRequestKey(null);
3910
4654
  }
3911
- reconcileMorphChange({
4655
+ const nextMeasurement = reconcileMorphChange({
3912
4656
  root: ref.current,
3913
4657
  measurementLayer: measurementLayerRef.current,
3914
4658
  measurementBackend,
@@ -3931,68 +4675,874 @@ function useMorphTransition(text, className) {
3931
4675
  domMeasurementKey,
3932
4676
  domMeasurementRequestKey
3933
4677
  ]);
3934
- useLayoutEffect(() => {
3935
- syncCommittedRootOriginWhenIdle({
3936
- root: ref.current,
3937
- flowTextRef,
3938
- layoutContext,
3939
- state,
3940
- session: sessionRef.current
4678
+ useLayoutEffect2(() => {
4679
+ const config = readTorphDebugConfig();
4680
+ if (!shouldRunTorphInstrumentation(config)) {
4681
+ debugDriftSignatureRef.current = null;
4682
+ return;
4683
+ }
4684
+ if (state.stage !== "idle" || state.measurement === null) {
4685
+ debugDriftSignatureRef.current = null;
4686
+ return;
4687
+ }
4688
+ const root = ref.current;
4689
+ const flowTextNode = flowTextRef.current;
4690
+ if (root === null || flowTextNode === null) {
4691
+ debugDriftSignatureRef.current = null;
4692
+ return;
4693
+ }
4694
+ const liveSnapshot = measureLiveFlowSnapshot(root, flowTextNode);
4695
+ if (liveSnapshot === null) {
4696
+ debugDriftSignatureRef.current = null;
4697
+ return;
4698
+ }
4699
+ const drift = measureSnapshotDrift(state.measurement.snapshot, liveSnapshot);
4700
+ const hasDrift = drift.expectedGlyphs !== drift.actualGlyphs || Math.abs(drift.snapshotWidthDelta) > MORPH.geometryEpsilon || drift.maxAbsLeftDelta > MORPH.geometryEpsilon || drift.maxAbsTopDelta > MORPH.geometryEpsilon || drift.maxAbsWidthDelta > MORPH.geometryEpsilon || drift.maxAbsHeightDelta > MORPH.geometryEpsilon;
4701
+ if (!hasDrift) {
4702
+ debugDriftSignatureRef.current = null;
4703
+ return;
4704
+ }
4705
+ const signature = JSON.stringify({
4706
+ text,
4707
+ renderText: state.measurement.snapshot.renderText,
4708
+ drift: summarizeSnapshotDrift(drift)
3941
4709
  });
3942
- }, [layoutContext, state]);
3943
- useLayoutEffect(() => {
4710
+ if (debugDriftSignatureRef.current === signature) {
4711
+ return;
4712
+ }
4713
+ debugDriftSignatureRef.current = signature;
4714
+ logTorphDebug(debugInstanceIdRef.current, "effect:idle-flow-drift", {
4715
+ text,
4716
+ expected: summarizeDebugSnapshot(state.measurement.snapshot),
4717
+ actual: summarizeDebugSnapshot(liveSnapshot),
4718
+ drift: summarizeSnapshotDrift(drift)
4719
+ });
4720
+ }, [state, text]);
4721
+ useLayoutEffect2(() => {
3944
4722
  return () => {
3945
4723
  cancelTimeline(timelineRef.current);
3946
4724
  };
3947
4725
  }, []);
4726
+ useLayoutEffect2(() => {
4727
+ if (state.stage !== "prepare" || state.measurement === null || state.plan === null) {
4728
+ return;
4729
+ }
4730
+ const root = ref.current;
4731
+ if (root === null) {
4732
+ return;
4733
+ }
4734
+ const nextOrigin = readRootOrigin(root);
4735
+ const nextMeasurement = resolvePreparedMeasurementOrigin(state.measurement, nextOrigin);
4736
+ const nextPlan = resolvePreparedPlanVisualBridge(state.plan, nextOrigin);
4737
+ if (nextMeasurement !== state.measurement || nextPlan !== state.plan) {
4738
+ sessionRef.current.target = nextMeasurement;
4739
+ logTransitionTrace("effect:prepare-refine", {
4740
+ preparedOrigin: {
4741
+ left: roundDebugValue(nextOrigin.left),
4742
+ top: roundDebugValue(nextOrigin.top)
4743
+ },
4744
+ refinedMeasurement: summarizeDebugMeasurement(nextMeasurement),
4745
+ refinedVisualBridge: {
4746
+ offsetX: roundDebugValue(nextPlan.visualBridge.offsetX),
4747
+ offsetY: roundDebugValue(nextPlan.visualBridge.offsetY)
4748
+ }
4749
+ });
4750
+ setState((current) => {
4751
+ if (current.stage !== "prepare" || current.measurement === null || current.plan === null) {
4752
+ return current;
4753
+ }
4754
+ if (current.measurement === nextMeasurement && current.plan === nextPlan) {
4755
+ return current;
4756
+ }
4757
+ return {
4758
+ stage: "prepare",
4759
+ measurement: nextMeasurement,
4760
+ plan: nextPlan
4761
+ };
4762
+ });
4763
+ return;
4764
+ }
4765
+ timelineRef.current.prepareFrame = requestAnimationFrame(() => {
4766
+ timelineRef.current.prepareFrame = null;
4767
+ timelineRef.current.animateFrame = requestAnimationFrame(() => {
4768
+ timelineRef.current.animateFrame = null;
4769
+ logTransitionTrace("effect:prepare-animate", {
4770
+ preparedOrigin: {
4771
+ left: roundDebugValue(nextOrigin.left),
4772
+ top: roundDebugValue(nextOrigin.top)
4773
+ },
4774
+ visualBridge: {
4775
+ offsetX: roundDebugValue(nextPlan.visualBridge.offsetX),
4776
+ offsetY: roundDebugValue(nextPlan.visualBridge.offsetY)
4777
+ }
4778
+ });
4779
+ setState((current) => {
4780
+ if (current.stage !== "prepare" || current.measurement === null || current.plan === null) {
4781
+ return current;
4782
+ }
4783
+ return {
4784
+ stage: "animate",
4785
+ measurement: current.measurement,
4786
+ plan: current.plan
4787
+ };
4788
+ });
4789
+ });
4790
+ });
4791
+ return () => {
4792
+ if (timelineRef.current.prepareFrame !== null) {
4793
+ cancelAnimationFrame(timelineRef.current.prepareFrame);
4794
+ timelineRef.current.prepareFrame = null;
4795
+ }
4796
+ if (timelineRef.current.animateFrame !== null) {
4797
+ cancelAnimationFrame(timelineRef.current.animateFrame);
4798
+ timelineRef.current.animateFrame = null;
4799
+ }
4800
+ };
4801
+ }, [state.measurement, state.plan, state.stage]);
4802
+ const finalizeMorphTransition2 = (measurement, reason) => {
4803
+ logTransitionTrace("effect:finalize-trigger", {
4804
+ reason,
4805
+ measurement: summarizeDebugMeasurement(measurement)
4806
+ });
4807
+ finalizeMorphTransition({
4808
+ session: sessionRef.current,
4809
+ timeline: timelineRef.current,
4810
+ measurement,
4811
+ setState
4812
+ });
4813
+ };
3948
4814
  return {
4815
+ debugInstanceId: debugInstanceIdRef.current,
4816
+ debugRenderOrdinal,
3949
4817
  committedMeasurement: sessionRef.current.committed,
3950
4818
  domMeasurementRequestKey,
3951
4819
  flowTextRef,
3952
4820
  ref,
3953
4821
  measurementLayerRef,
4822
+ measurementBackend,
4823
+ domMeasurementKey,
3954
4824
  renderText,
3955
4825
  segments,
3956
4826
  layoutContext,
3957
4827
  state,
3958
- useContentInlineSize
4828
+ useContentInlineSize,
4829
+ finalizeMorphTransition: finalizeMorphTransition2,
4830
+ timelineRef
3959
4831
  };
3960
4832
  }
3961
4833
  function ActiveTorph({
3962
4834
  text,
3963
4835
  className
3964
4836
  }) {
4837
+ const debugRenderOrdinalRef = useRef2(0);
4838
+ const overlayRef = useRef2(null);
4839
+ const debugFinalizeSignatureRef = useRef2(null);
4840
+ const debugFrameHandleRef = useRef2(null);
4841
+ const debugFrameOrdinalRef = useRef2(0);
4842
+ const debugAnimateTailFramesRef = useRef2([]);
4843
+ const debugIdlePostFrameHandleRef = useRef2(null);
4844
+ const debugIdlePostFrameOrdinalRef = useRef2(0);
4845
+ const debugIdlePostFrameTokenRef = useRef2(0);
4846
+ const debugPendingIdlePostFramesRef = useRef2(false);
4847
+ const debugPreviousStageRef = useRef2(null);
4848
+ debugRenderOrdinalRef.current += 1;
4849
+ const debugRenderOrdinal = debugRenderOrdinalRef.current;
3965
4850
  const {
4851
+ debugInstanceId,
4852
+ debugRenderOrdinal: hookRenderOrdinal,
3966
4853
  committedMeasurement,
3967
4854
  domMeasurementRequestKey,
4855
+ domMeasurementKey,
3968
4856
  flowTextRef,
3969
4857
  ref,
3970
4858
  measurementLayerRef,
4859
+ measurementBackend,
3971
4860
  renderText,
3972
4861
  segments,
3973
4862
  layoutContext,
3974
4863
  state,
3975
- useContentInlineSize
4864
+ useContentInlineSize,
4865
+ finalizeMorphTransition: finalizeMorphTransition2,
4866
+ timelineRef
3976
4867
  } = useMorphTransition(text, className);
3977
4868
  const plan = state.plan;
3978
- const shouldRenderOverlay = state.stage !== "idle" && plan !== null;
4869
+ let visibleGlyphPlan = plan;
4870
+ if (state.stage === "idle" && state.measurement !== null) {
4871
+ visibleGlyphPlan = createSteadyGlyphPlan(state.measurement);
4872
+ }
4873
+ let sourceSliceWhiteSpace = "inherit";
4874
+ if (committedMeasurement !== null) {
4875
+ sourceSliceWhiteSpace = resolveGlyphSliceWhiteSpace(committedMeasurement.snapshot);
4876
+ }
4877
+ let targetSliceWhiteSpace = "inherit";
4878
+ if (state.measurement !== null) {
4879
+ targetSliceWhiteSpace = resolveGlyphSliceWhiteSpace(state.measurement.snapshot);
4880
+ }
4881
+ const shouldRenderGlyphLayer2 = shouldRenderGlyphLayer(state.stage, visibleGlyphPlan, state.measurement);
4882
+ const shouldHideFlowText = shouldRenderGlyphLayer2;
3979
4883
  const shouldRenderMeasurementLayer = domMeasurementRequestKey !== null;
3980
4884
  const flowText = resolveFlowText(committedMeasurement, state.measurement, text);
4885
+ useLayoutEffect2(() => {
4886
+ const config = readTorphDebugConfig();
4887
+ if (!shouldRunTorphInstrumentation(config)) {
4888
+ return;
4889
+ }
4890
+ logTorphDebug(debugInstanceId, "effect:trace-meta", {
4891
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
4892
+ includesIdlePostFrame: true,
4893
+ includesIdlePostFrameLifecycle: true,
4894
+ includesViewportAnchors: true,
4895
+ includesRootOriginRefine: true,
4896
+ includesFullGlyphLayouts: true,
4897
+ includesIdleVisibleGlyphLayer: true,
4898
+ includesPreciseGlyphGeometry: true,
4899
+ includesAnimateTailFrames: true,
4900
+ includesLiveNodeStyles: true
4901
+ });
4902
+ }, [debugInstanceId]);
4903
+ useLayoutEffect2(() => {
4904
+ const config = readTorphDebugConfig();
4905
+ if (!shouldRunTorphInstrumentation(config)) {
4906
+ debugFinalizeSignatureRef.current = null;
4907
+ return;
4908
+ }
4909
+ if (state.stage !== "animate" || state.measurement === null || plan === null) {
4910
+ debugFinalizeSignatureRef.current = null;
4911
+ }
4912
+ }, [plan, state.measurement, state.stage]);
4913
+ const logAnimateFinalizeSnapshot = (measurement, activePlan, reason, barrier, signal, event) => {
4914
+ const config = readTorphDebugConfig();
4915
+ if (!shouldRunTorphInstrumentation(config)) {
4916
+ return;
4917
+ }
4918
+ const root = ref.current;
4919
+ const overlayNode = overlayRef.current;
4920
+ const flowNode = flowTextRef.current;
4921
+ if (root === null || overlayNode === null || flowNode === null) {
4922
+ return;
4923
+ }
4924
+ const overlayLiveSnapshot = measureOverlayBoxSnapshot(root, overlayNode, "live");
4925
+ const overlayExitSnapshot = measureOverlayBoxSnapshot(root, overlayNode, "exit");
4926
+ const flowSnapshot = measureLiveFlowSnapshot(root, flowNode);
4927
+ if (overlayLiveSnapshot === null || flowSnapshot === null) {
4928
+ return;
4929
+ }
4930
+ const overlayLiveDrift = measureSnapshotDrift(measurement.snapshot, overlayLiveSnapshot);
4931
+ const flowDrift = measureSnapshotDrift(measurement.snapshot, flowSnapshot);
4932
+ const rootRect = root.getBoundingClientRect();
4933
+ const flowRect = flowNode.getBoundingClientRect();
4934
+ const overlayRect = overlayNode.getBoundingClientRect();
4935
+ const signature = JSON.stringify({
4936
+ text,
4937
+ renderText: measurement.snapshot.renderText,
4938
+ reason,
4939
+ signal,
4940
+ barrier: summarizeMorphFinalizeBarrier(barrier),
4941
+ overlayLiveDrift: summarizeSnapshotDrift(overlayLiveDrift),
4942
+ flowDrift: summarizeSnapshotDrift(flowDrift),
4943
+ overlayWidth: roundDebugValue(overlayRect.width),
4944
+ rootWidth: roundDebugValue(rootRect.width)
4945
+ });
4946
+ if (debugFinalizeSignatureRef.current === signature) {
4947
+ return;
4948
+ }
4949
+ debugFinalizeSignatureRef.current = signature;
4950
+ let morphRole = null;
4951
+ let morphKey = null;
4952
+ let morphGlyph = null;
4953
+ const target = event?.target;
4954
+ if (target instanceof HTMLElement) {
4955
+ morphRole = target.dataset.morphRole ?? null;
4956
+ morphKey = target.dataset.morphKey ?? null;
4957
+ morphGlyph = target.dataset.morphGlyph ?? null;
4958
+ }
4959
+ logTorphDebug(debugInstanceId, "effect:animate-finalize-snapshot", {
4960
+ text,
4961
+ reason,
4962
+ propertyName: event?.propertyName ?? null,
4963
+ finalizeSignal: signal,
4964
+ morphRole,
4965
+ morphKey,
4966
+ morphGlyph,
4967
+ barrier: summarizeMorphFinalizeBarrier(barrier),
4968
+ target: {
4969
+ layoutInlineSize: roundDebugValue(measurement.layoutInlineSize),
4970
+ reservedInlineSize: roundDebugValue(measurement.reservedInlineSize),
4971
+ flowInlineSize: roundDebugValue(measurement.flowInlineSize),
4972
+ rootOrigin: {
4973
+ left: roundDebugValue(measurement.rootOrigin.left),
4974
+ top: roundDebugValue(measurement.rootOrigin.top)
4975
+ },
4976
+ snapshot: summarizeDebugSnapshot(measurement.snapshot)
4977
+ },
4978
+ plan: {
4979
+ frameWidth: roundDebugValue(activePlan.frameWidth),
4980
+ frameHeight: roundDebugValue(activePlan.frameHeight),
4981
+ layoutInlineSizeFrom: roundDebugValue(activePlan.layoutInlineSizeFrom),
4982
+ layoutInlineSizeTo: roundDebugValue(activePlan.layoutInlineSizeTo),
4983
+ sourceRenderText: activePlan.sourceRenderText,
4984
+ targetRenderText: activePlan.targetRenderText
4985
+ },
4986
+ overlayLive: summarizeDebugSnapshot(overlayLiveSnapshot),
4987
+ overlayLiveGlyphs: summarizeDebugGlyphs(overlayLiveSnapshot),
4988
+ overlayLiveGlyphsPrecise: summarizePreciseGlyphs(overlayLiveSnapshot, rootRect),
4989
+ overlayLiveDrift: summarizeSnapshotDrift(overlayLiveDrift),
4990
+ overlayExit: summarizeDebugSnapshot(overlayExitSnapshot),
4991
+ overlayExitGlyphs: summarizeDebugGlyphs(overlayExitSnapshot),
4992
+ flow: summarizeDebugSnapshot(flowSnapshot),
4993
+ flowGlyphs: summarizeDebugGlyphs(flowSnapshot),
4994
+ flowGlyphsPrecise: summarizePreciseGlyphs(flowSnapshot, rootRect),
4995
+ flowDrift: summarizeSnapshotDrift(flowDrift),
4996
+ rootOriginDrift: summarizeDebugRootOriginDrift(measurement, rootRect),
4997
+ overlayLiveViewportAnchors: summarizeDebugViewportAnchors(overlayLiveSnapshot, rootRect),
4998
+ overlayExitViewportAnchors: summarizeDebugViewportAnchors(overlayExitSnapshot, rootRect),
4999
+ flowViewportAnchors: summarizeDebugViewportAnchors(flowSnapshot, rootRect),
5000
+ rootBox: {
5001
+ width: roundDebugValue(rootRect.width),
5002
+ height: roundDebugValue(rootRect.height)
5003
+ },
5004
+ overlayBox: {
5005
+ width: roundDebugValue(overlayRect.width),
5006
+ height: roundDebugValue(overlayRect.height)
5007
+ },
5008
+ flowBox: {
5009
+ width: roundDebugValue(flowRect.width),
5010
+ height: roundDebugValue(flowRect.height)
5011
+ },
5012
+ rootBoxPrecise: summarizePreciseRect(rootRect),
5013
+ overlayBoxPrecise: summarizePreciseRect(overlayRect),
5014
+ flowBoxPrecise: summarizePreciseRect(flowRect),
5015
+ rootRuntimeStyles: summarizeRootRuntimeStyles(root),
5016
+ overlayLiveNodeStyles: summarizeLiveNodeStyles(overlayNode),
5017
+ animateTailFrames: debugAnimateTailFramesRef.current
5018
+ });
5019
+ debugAnimateTailFramesRef.current = [];
5020
+ };
5021
+ useLayoutEffect2(() => {
5022
+ if (state.stage !== "animate" || state.measurement === null || plan === null) {
5023
+ return;
5024
+ }
5025
+ const root = ref.current;
5026
+ if (root === null) {
5027
+ return;
5028
+ }
5029
+ const measurement = state.measurement;
5030
+ const hasMoveTransitions = plan.liveItems.some((item) => item.kind === "move");
5031
+ let barrier = createMorphFinalizeBarrier(hasMoveTransitions);
5032
+ let finalizeScheduled = false;
5033
+ let finalizeCommitted = false;
5034
+ let finalizeFrame = null;
5035
+ const armedAt = performance.now();
5036
+ const finalizeNow = (reason, signal, barrierState, event) => {
5037
+ if (finalizeCommitted) {
5038
+ return;
5039
+ }
5040
+ finalizeCommitted = true;
5041
+ const target = event?.target;
5042
+ let morphRole = null;
5043
+ let morphKey = null;
5044
+ let morphGlyph = null;
5045
+ if (target instanceof HTMLElement) {
5046
+ morphRole = target.dataset.morphRole ?? null;
5047
+ morphKey = target.dataset.morphKey ?? null;
5048
+ morphGlyph = target.dataset.morphGlyph ?? null;
5049
+ }
5050
+ logAnimateFinalizeSnapshot(measurement, plan, reason, barrierState, signal, event);
5051
+ finalizeMorphTransition2(measurement, reason);
5052
+ const config = readTorphDebugConfig();
5053
+ if (!shouldRunTorphInstrumentation(config)) {
5054
+ return;
5055
+ }
5056
+ logTorphDebug(debugInstanceId, "effect:finalize-authority", {
5057
+ text,
5058
+ reason,
5059
+ propertyName: event?.propertyName ?? null,
5060
+ morphRole,
5061
+ morphKey,
5062
+ morphGlyph,
5063
+ elapsedMs: roundDebugValue(performance.now() - armedAt),
5064
+ hasMoveTransitions,
5065
+ finalizeSignal: signal,
5066
+ barrier: summarizeMorphFinalizeBarrier(barrierState),
5067
+ measurement: summarizeDebugMeasurement(measurement)
5068
+ });
5069
+ };
5070
+ const scheduleFinalize = (reason, signal, barrierState, event) => {
5071
+ if (finalizeCommitted || finalizeScheduled) {
5072
+ return;
5073
+ }
5074
+ finalizeScheduled = true;
5075
+ if (timelineRef.current.finalizeTimer !== null) {
5076
+ window.clearTimeout(timelineRef.current.finalizeTimer);
5077
+ timelineRef.current.finalizeTimer = null;
5078
+ }
5079
+ logTorphDebug(debugInstanceId, "effect:finalize-raf-schedule", {
5080
+ text,
5081
+ reason,
5082
+ propertyName: event?.propertyName ?? null,
5083
+ finalizeSignal: signal,
5084
+ elapsedMs: roundDebugValue(performance.now() - armedAt),
5085
+ barrier: summarizeMorphFinalizeBarrier(barrierState)
5086
+ });
5087
+ finalizeFrame = requestAnimationFrame(() => {
5088
+ finalizeFrame = null;
5089
+ finalizeNow(reason, signal, barrierState, event);
5090
+ });
5091
+ };
5092
+ const onTransitionEnd = (event) => {
5093
+ const signal = resolveMorphFinalizeSignal(event, hasMoveTransitions);
5094
+ if (signal === null) {
5095
+ return;
5096
+ }
5097
+ barrier = recordMorphFinalizeSignal(barrier, signal);
5098
+ const target = event.target;
5099
+ let morphRole = null;
5100
+ let morphKey = null;
5101
+ let morphGlyph = null;
5102
+ if (target instanceof HTMLElement) {
5103
+ morphRole = target.dataset.morphRole ?? null;
5104
+ morphKey = target.dataset.morphKey ?? null;
5105
+ morphGlyph = target.dataset.morphGlyph ?? null;
5106
+ }
5107
+ logTorphDebug(debugInstanceId, "effect:finalize-barrier-progress", {
5108
+ text,
5109
+ propertyName: event.propertyName,
5110
+ signal,
5111
+ morphRole,
5112
+ morphKey,
5113
+ morphGlyph,
5114
+ elapsedMs: roundDebugValue(performance.now() - armedAt),
5115
+ barrier: summarizeMorphFinalizeBarrier(barrier)
5116
+ });
5117
+ if (!isMorphFinalizeBarrierSatisfied(barrier)) {
5118
+ return;
5119
+ }
5120
+ scheduleFinalize("transitionend", signal, barrier, event);
5121
+ };
5122
+ root.addEventListener("transitionend", onTransitionEnd);
5123
+ if (timelineRef.current.finalizeTimer !== null) {
5124
+ window.clearTimeout(timelineRef.current.finalizeTimer);
5125
+ }
5126
+ timelineRef.current.finalizeTimer = window.setTimeout(() => {
5127
+ timelineRef.current.finalizeTimer = null;
5128
+ scheduleFinalize("watchdog-timeout", null, barrier);
5129
+ }, MORPH.durationMs + 32);
5130
+ return () => {
5131
+ root.removeEventListener("transitionend", onTransitionEnd);
5132
+ if (finalizeFrame !== null) {
5133
+ cancelAnimationFrame(finalizeFrame);
5134
+ finalizeFrame = null;
5135
+ }
5136
+ if (timelineRef.current.finalizeTimer !== null) {
5137
+ window.clearTimeout(timelineRef.current.finalizeTimer);
5138
+ timelineRef.current.finalizeTimer = null;
5139
+ }
5140
+ };
5141
+ }, [
5142
+ debugInstanceId,
5143
+ finalizeMorphTransition2,
5144
+ plan,
5145
+ state.measurement,
5146
+ state.stage,
5147
+ text,
5148
+ timelineRef
5149
+ ]);
5150
+ useLayoutEffect2(() => {
5151
+ const config = readTorphDebugConfig();
5152
+ if (!shouldRunTorphInstrumentation(config)) {
5153
+ debugPendingIdlePostFramesRef.current = false;
5154
+ debugPreviousStageRef.current = state.stage;
5155
+ return;
5156
+ }
5157
+ const previousStage = debugPreviousStageRef.current;
5158
+ if (previousStage !== state.stage) {
5159
+ if (state.stage === "idle") {
5160
+ debugPendingIdlePostFramesRef.current = true;
5161
+ debugIdlePostFrameTokenRef.current += 1;
5162
+ } else {
5163
+ debugPendingIdlePostFramesRef.current = false;
5164
+ }
5165
+ logTorphDebug(debugInstanceId, "effect:stage-transition", {
5166
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5167
+ renderOrdinal: debugRenderOrdinal,
5168
+ text,
5169
+ fromStage: previousStage,
5170
+ toStage: state.stage,
5171
+ committed: summarizeDebugMeasurement(committedMeasurement),
5172
+ stateMeasurement: summarizeDebugMeasurement(state.measurement),
5173
+ flowText,
5174
+ domMeasurementRequestKey,
5175
+ domMeasurementKey,
5176
+ measurementBackend
5177
+ });
5178
+ debugPreviousStageRef.current = state.stage;
5179
+ }
5180
+ }, [committedMeasurement, debugInstanceId, flowText, state, text]);
5181
+ useLayoutEffect2(() => {
5182
+ const config = readTorphDebugConfig();
5183
+ if (!shouldRunTorphInstrumentation(config)) {
5184
+ if (debugFrameHandleRef.current !== null) {
5185
+ cancelAnimationFrame(debugFrameHandleRef.current);
5186
+ debugFrameHandleRef.current = null;
5187
+ }
5188
+ debugAnimateTailFramesRef.current = [];
5189
+ return;
5190
+ }
5191
+ if (state.stage === "idle" || state.measurement === null) {
5192
+ if (debugFrameHandleRef.current !== null) {
5193
+ cancelAnimationFrame(debugFrameHandleRef.current);
5194
+ debugFrameHandleRef.current = null;
5195
+ }
5196
+ debugFrameOrdinalRef.current = 0;
5197
+ debugAnimateTailFramesRef.current = [];
5198
+ return;
5199
+ }
5200
+ debugFrameOrdinalRef.current = 0;
5201
+ const measurement = state.measurement;
5202
+ let cancelled = false;
5203
+ const captureFrame = () => {
5204
+ if (cancelled) {
5205
+ return;
5206
+ }
5207
+ const root = ref.current;
5208
+ const flowNode = flowTextRef.current;
5209
+ const overlayNode = overlayRef.current;
5210
+ let rootRect = null;
5211
+ if (root !== null) {
5212
+ rootRect = root.getBoundingClientRect();
5213
+ }
5214
+ let flowRect = null;
5215
+ if (flowNode !== null) {
5216
+ flowRect = flowNode.getBoundingClientRect();
5217
+ }
5218
+ let overlayRect = null;
5219
+ if (overlayNode !== null) {
5220
+ overlayRect = overlayNode.getBoundingClientRect();
5221
+ }
5222
+ let overlayLiveSnapshot = null;
5223
+ if (root !== null && overlayNode !== null) {
5224
+ overlayLiveSnapshot = measureOverlayBoxSnapshot(root, overlayNode, "live");
5225
+ }
5226
+ let overlayExitSnapshot = null;
5227
+ if (root !== null && overlayNode !== null) {
5228
+ overlayExitSnapshot = measureOverlayBoxSnapshot(root, overlayNode, "exit");
5229
+ }
5230
+ let flowSnapshot = null;
5231
+ if (root !== null && flowNode !== null) {
5232
+ flowSnapshot = measureLiveFlowSnapshot(root, flowNode);
5233
+ }
5234
+ let overlayLiveDrift = null;
5235
+ if (overlayLiveSnapshot !== null) {
5236
+ overlayLiveDrift = summarizeSnapshotDrift(measureSnapshotDrift(measurement.snapshot, overlayLiveSnapshot));
5237
+ }
5238
+ let flowDrift = null;
5239
+ if (flowSnapshot !== null) {
5240
+ flowDrift = summarizeSnapshotDrift(measureSnapshotDrift(measurement.snapshot, flowSnapshot));
5241
+ }
5242
+ debugAnimateTailFramesRef.current.push({
5243
+ frame: debugFrameOrdinalRef.current,
5244
+ stateStage: state.stage,
5245
+ rootBoxPrecise: summarizePreciseRect(rootRect),
5246
+ overlayBoxPrecise: summarizePreciseRect(overlayRect),
5247
+ flowBoxPrecise: summarizePreciseRect(flowRect),
5248
+ overlayLiveGlyphsPrecise: summarizePreciseGlyphs(overlayLiveSnapshot, rootRect),
5249
+ flowGlyphsPrecise: summarizePreciseGlyphs(flowSnapshot, rootRect),
5250
+ overlayLiveNodeStyles: summarizeLiveNodeStyles(overlayNode)
5251
+ });
5252
+ if (debugAnimateTailFramesRef.current.length > 8) {
5253
+ debugAnimateTailFramesRef.current.shift();
5254
+ }
5255
+ let planSummary = null;
5256
+ if (plan !== null) {
5257
+ planSummary = {
5258
+ frameWidth: roundDebugValue(plan.frameWidth),
5259
+ frameHeight: roundDebugValue(plan.frameHeight),
5260
+ layoutInlineSizeFrom: roundDebugValue(plan.layoutInlineSizeFrom),
5261
+ layoutInlineSizeTo: roundDebugValue(plan.layoutInlineSizeTo),
5262
+ sourceRenderText: plan.sourceRenderText,
5263
+ targetRenderText: plan.targetRenderText,
5264
+ visualBridge: {
5265
+ offsetX: roundDebugValue(plan.visualBridge.offsetX),
5266
+ offsetY: roundDebugValue(plan.visualBridge.offsetY)
5267
+ },
5268
+ liveItems: plan.liveItems.length,
5269
+ exitItems: plan.exitItems.length
5270
+ };
5271
+ }
5272
+ logTorphDebug(debugInstanceId, "effect:frame-snapshot", {
5273
+ text,
5274
+ frame: debugFrameOrdinalRef.current,
5275
+ stateStage: state.stage,
5276
+ propText: text,
5277
+ flowText,
5278
+ committed: summarizeDebugMeasurement(committedMeasurement),
5279
+ stateMeasurement: summarizeDebugMeasurement(measurement),
5280
+ plan: planSummary,
5281
+ rootBox: summarizeDebugRect(rootRect),
5282
+ overlayBox: summarizeDebugRect(overlayRect),
5283
+ flowBox: summarizeDebugRect(flowRect),
5284
+ rootOriginDrift: summarizeDebugRootOriginDrift(measurement, rootRect),
5285
+ overlayLive: summarizeDebugSnapshot(overlayLiveSnapshot),
5286
+ overlayLiveGlyphs: summarizeDebugGlyphs(overlayLiveSnapshot),
5287
+ overlayLiveViewportAnchors: summarizeDebugViewportAnchors(overlayLiveSnapshot, rootRect),
5288
+ overlayLiveDrift,
5289
+ overlayExit: summarizeDebugSnapshot(overlayExitSnapshot),
5290
+ overlayExitGlyphs: summarizeDebugGlyphs(overlayExitSnapshot),
5291
+ overlayExitViewportAnchors: summarizeDebugViewportAnchors(overlayExitSnapshot, rootRect),
5292
+ flow: summarizeDebugSnapshot(flowSnapshot),
5293
+ flowGlyphs: summarizeDebugGlyphs(flowSnapshot),
5294
+ flowViewportAnchors: summarizeDebugViewportAnchors(flowSnapshot, rootRect),
5295
+ flowDrift,
5296
+ rootRuntimeStyles: summarizeRootRuntimeStyles(root)
5297
+ });
5298
+ debugFrameOrdinalRef.current += 1;
5299
+ debugFrameHandleRef.current = requestAnimationFrame(captureFrame);
5300
+ };
5301
+ debugFrameHandleRef.current = requestAnimationFrame(captureFrame);
5302
+ return () => {
5303
+ cancelled = true;
5304
+ if (debugFrameHandleRef.current !== null) {
5305
+ cancelAnimationFrame(debugFrameHandleRef.current);
5306
+ debugFrameHandleRef.current = null;
5307
+ }
5308
+ };
5309
+ }, [committedMeasurement, debugInstanceId, flowText, plan, ref, state, text]);
5310
+ useLayoutEffect2(() => {
5311
+ const config = readTorphDebugConfig();
5312
+ if (!shouldRunTorphInstrumentation(config)) {
5313
+ debugPendingIdlePostFramesRef.current = false;
5314
+ if (debugIdlePostFrameHandleRef.current !== null) {
5315
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-cleanup", {
5316
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5317
+ reason: "instrumentation-disabled",
5318
+ renderOrdinal: debugRenderOrdinal,
5319
+ hookRenderOrdinal,
5320
+ token: debugIdlePostFrameTokenRef.current,
5321
+ handle: debugIdlePostFrameHandleRef.current,
5322
+ domMeasurementRequestKey,
5323
+ domMeasurementKey,
5324
+ measurementBackend
5325
+ });
5326
+ cancelAnimationFrame(debugIdlePostFrameHandleRef.current);
5327
+ debugIdlePostFrameHandleRef.current = null;
5328
+ }
5329
+ return;
5330
+ }
5331
+ if (!debugPendingIdlePostFramesRef.current) {
5332
+ return;
5333
+ }
5334
+ if (state.stage !== "idle" || state.measurement === null) {
5335
+ return;
5336
+ }
5337
+ debugIdlePostFrameOrdinalRef.current = 0;
5338
+ const token = debugIdlePostFrameTokenRef.current;
5339
+ const scheduledRenderOrdinal = debugRenderOrdinal;
5340
+ const scheduledHookRenderOrdinal = hookRenderOrdinal;
5341
+ const measurement = state.measurement;
5342
+ let remainingFrames = 3;
5343
+ let cancelled = false;
5344
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-arm", {
5345
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5346
+ renderOrdinal: scheduledRenderOrdinal,
5347
+ hookRenderOrdinal: scheduledHookRenderOrdinal,
5348
+ token,
5349
+ text,
5350
+ stateStage: state.stage,
5351
+ flowText,
5352
+ committed: summarizeDebugMeasurement(committedMeasurement),
5353
+ stateMeasurement: summarizeDebugMeasurement(measurement),
5354
+ domMeasurementRequestKey,
5355
+ domMeasurementKey,
5356
+ measurementBackend
5357
+ });
5358
+ const captureIdlePostFrame = () => {
5359
+ if (cancelled) {
5360
+ return;
5361
+ }
5362
+ if (debugPendingIdlePostFramesRef.current) {
5363
+ debugPendingIdlePostFramesRef.current = false;
5364
+ }
5365
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-fire", {
5366
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5367
+ renderOrdinal: debugRenderOrdinalRef.current,
5368
+ hookRenderOrdinal,
5369
+ scheduledRenderOrdinal,
5370
+ scheduledHookRenderOrdinal,
5371
+ token,
5372
+ frame: debugIdlePostFrameOrdinalRef.current,
5373
+ remainingFrames,
5374
+ text,
5375
+ stateStage: state.stage,
5376
+ flowText
5377
+ });
5378
+ const root = ref.current;
5379
+ const flowNode = flowTextRef.current;
5380
+ const overlayNode = overlayRef.current;
5381
+ if (root === null || flowNode === null || overlayNode === null) {
5382
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-skip", {
5383
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5384
+ renderOrdinal: debugRenderOrdinalRef.current,
5385
+ hookRenderOrdinal,
5386
+ scheduledRenderOrdinal,
5387
+ scheduledHookRenderOrdinal,
5388
+ token,
5389
+ text,
5390
+ stateStage: state.stage,
5391
+ frame: debugIdlePostFrameOrdinalRef.current,
5392
+ hasRoot: root !== null,
5393
+ hasFlowNode: flowNode !== null,
5394
+ hasOverlayNode: overlayNode !== null,
5395
+ flowText,
5396
+ committed: summarizeDebugMeasurement(committedMeasurement),
5397
+ stateMeasurement: summarizeDebugMeasurement(measurement)
5398
+ });
5399
+ return;
5400
+ }
5401
+ const rootRect = root.getBoundingClientRect();
5402
+ const flowRect = flowNode.getBoundingClientRect();
5403
+ const overlayRect = overlayNode.getBoundingClientRect();
5404
+ const flowSnapshot = measureLiveFlowSnapshot(root, flowNode);
5405
+ let visibleSnapshot = null;
5406
+ if (overlayNode !== null) {
5407
+ visibleSnapshot = measureOverlayBoxSnapshot(root, overlayNode, "live");
5408
+ }
5409
+ let flowDrift = null;
5410
+ if (flowSnapshot !== null) {
5411
+ flowDrift = summarizeSnapshotDrift(measureSnapshotDrift(measurement.snapshot, flowSnapshot));
5412
+ }
5413
+ let visibleDrift = null;
5414
+ if (visibleSnapshot !== null) {
5415
+ visibleDrift = summarizeSnapshotDrift(measureSnapshotDrift(measurement.snapshot, visibleSnapshot));
5416
+ }
5417
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame", {
5418
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5419
+ renderOrdinal: debugRenderOrdinalRef.current,
5420
+ hookRenderOrdinal,
5421
+ scheduledRenderOrdinal,
5422
+ scheduledHookRenderOrdinal,
5423
+ token,
5424
+ text,
5425
+ frame: debugIdlePostFrameOrdinalRef.current,
5426
+ stateStage: state.stage,
5427
+ propText: text,
5428
+ flowText,
5429
+ committed: summarizeDebugMeasurement(committedMeasurement),
5430
+ stateMeasurement: summarizeDebugMeasurement(measurement),
5431
+ rootBox: summarizeDebugRect(rootRect),
5432
+ rootBoxPrecise: summarizePreciseRect(rootRect),
5433
+ overlayBox: summarizeDebugRect(overlayRect),
5434
+ overlayBoxPrecise: summarizePreciseRect(overlayRect),
5435
+ flowBox: summarizeDebugRect(flowRect),
5436
+ flowBoxPrecise: summarizePreciseRect(flowRect),
5437
+ rootRuntimeStyles: summarizeRootRuntimeStyles(root),
5438
+ rootOriginDrift: summarizeDebugRootOriginDrift(measurement, rootRect),
5439
+ flow: summarizeDebugSnapshot(flowSnapshot),
5440
+ flowGlyphs: summarizeDebugGlyphs(flowSnapshot),
5441
+ flowGlyphsPrecise: summarizePreciseGlyphs(flowSnapshot, rootRect),
5442
+ flowViewportAnchors: summarizeDebugViewportAnchors(flowSnapshot, rootRect),
5443
+ flowDrift,
5444
+ visible: summarizeDebugSnapshot(visibleSnapshot),
5445
+ visibleGlyphs: summarizeDebugGlyphs(visibleSnapshot),
5446
+ visibleGlyphsPrecise: summarizePreciseGlyphs(visibleSnapshot, rootRect),
5447
+ visibleViewportAnchors: summarizeDebugViewportAnchors(visibleSnapshot, rootRect),
5448
+ visibleDrift,
5449
+ visibleLiveNodeStyles: summarizeLiveNodeStyles(overlayNode)
5450
+ });
5451
+ debugIdlePostFrameOrdinalRef.current += 1;
5452
+ remainingFrames -= 1;
5453
+ if (remainingFrames <= 0) {
5454
+ debugIdlePostFrameHandleRef.current = null;
5455
+ return;
5456
+ }
5457
+ debugIdlePostFrameHandleRef.current = requestAnimationFrame(captureIdlePostFrame);
5458
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-reschedule", {
5459
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5460
+ renderOrdinal: debugRenderOrdinalRef.current,
5461
+ hookRenderOrdinal,
5462
+ scheduledRenderOrdinal,
5463
+ scheduledHookRenderOrdinal,
5464
+ token,
5465
+ handle: debugIdlePostFrameHandleRef.current,
5466
+ nextFrame: debugIdlePostFrameOrdinalRef.current,
5467
+ remainingFrames,
5468
+ text,
5469
+ stateStage: state.stage,
5470
+ flowText
5471
+ });
5472
+ };
5473
+ debugIdlePostFrameHandleRef.current = requestAnimationFrame(captureIdlePostFrame);
5474
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-schedule", {
5475
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5476
+ renderOrdinal: scheduledRenderOrdinal,
5477
+ hookRenderOrdinal: scheduledHookRenderOrdinal,
5478
+ token,
5479
+ handle: debugIdlePostFrameHandleRef.current,
5480
+ remainingFrames,
5481
+ text,
5482
+ stateStage: state.stage,
5483
+ flowText,
5484
+ domMeasurementRequestKey,
5485
+ domMeasurementKey,
5486
+ measurementBackend
5487
+ });
5488
+ return () => {
5489
+ cancelled = true;
5490
+ if (debugIdlePostFrameHandleRef.current !== null) {
5491
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-cleanup", {
5492
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5493
+ reason: "effect-rerun-or-unmount",
5494
+ renderOrdinal: debugRenderOrdinalRef.current,
5495
+ hookRenderOrdinal,
5496
+ scheduledRenderOrdinal,
5497
+ scheduledHookRenderOrdinal,
5498
+ token,
5499
+ handle: debugIdlePostFrameHandleRef.current,
5500
+ completedFrames: debugIdlePostFrameOrdinalRef.current,
5501
+ remainingFrames,
5502
+ text,
5503
+ stateStage: state.stage,
5504
+ flowText,
5505
+ domMeasurementRequestKey,
5506
+ domMeasurementKey,
5507
+ measurementBackend
5508
+ });
5509
+ cancelAnimationFrame(debugIdlePostFrameHandleRef.current);
5510
+ debugIdlePostFrameHandleRef.current = null;
5511
+ }
5512
+ };
5513
+ }, [committedMeasurement, debugInstanceId, flowText, ref, state, text]);
5514
+ useLayoutEffect2(() => {
5515
+ return () => {
5516
+ if (debugIdlePostFrameHandleRef.current !== null) {
5517
+ logTorphDebug(debugInstanceId, "effect:idle-post-frame-cleanup", {
5518
+ traceSchemaVersion: TORPH_TRACE_SCHEMA_VERSION,
5519
+ reason: "component-unmount",
5520
+ renderOrdinal: debugRenderOrdinalRef.current,
5521
+ token: debugIdlePostFrameTokenRef.current,
5522
+ handle: debugIdlePostFrameHandleRef.current
5523
+ });
5524
+ cancelAnimationFrame(debugIdlePostFrameHandleRef.current);
5525
+ debugIdlePostFrameHandleRef.current = null;
5526
+ }
5527
+ };
5528
+ }, []);
3981
5529
  let measurementLayer = null;
3982
5530
  if (shouldRenderMeasurementLayer) {
3983
5531
  measurementLayer = /* @__PURE__ */ jsxDEV(MeasurementLayer, {
3984
5532
  layerRef: measurementLayerRef,
3985
5533
  layoutContext,
3986
5534
  text: renderText,
3987
- segments,
3988
5535
  useContentInlineSize
3989
5536
  }, undefined, false, undefined, this);
3990
5537
  }
3991
5538
  let overlay = null;
3992
- if (shouldRenderOverlay) {
5539
+ if (shouldRenderGlyphLayer2 && visibleGlyphPlan !== null) {
3993
5540
  overlay = /* @__PURE__ */ jsxDEV(MorphOverlay, {
5541
+ overlayRef,
3994
5542
  stage: state.stage,
3995
- plan
5543
+ plan: visibleGlyphPlan,
5544
+ sourceSliceWhiteSpace,
5545
+ targetSliceWhiteSpace
3996
5546
  }, undefined, false, undefined, this);
3997
5547
  }
3998
5548
  return /* @__PURE__ */ jsxDEV("div", {
@@ -4006,7 +5556,7 @@ function ActiveTorph({
4006
5556
  }, undefined, false, undefined, this),
4007
5557
  /* @__PURE__ */ jsxDEV("span", {
4008
5558
  "aria-hidden": "true",
4009
- style: getFallbackTextStyle(shouldRenderOverlay),
5559
+ style: getFallbackTextStyle(shouldHideFlowText),
4010
5560
  children: /* @__PURE__ */ jsxDEV("span", {
4011
5561
  ref: flowTextRef,
4012
5562
  children: flowText
@@ -4027,21 +5577,5 @@ function Torph({
4027
5577
  }, undefined, false, undefined, this);
4028
5578
  }
4029
5579
  export {
4030
- supportsIntrinsicWidthLock,
4031
- resolveMorphFrameBounds,
4032
- resolveFlowText,
4033
- resolveContentWidthLockInlineSize,
4034
- pairMorphCharacters,
4035
- needsMeasurementLayer,
4036
- measureMorphSnapshotFromLayer,
4037
- getRootStyle,
4038
- getRootDisplay,
4039
- getMeasurementLayerStyle,
4040
- getLiveTransition,
4041
- getLiveTransform,
4042
- getExitTransition,
4043
- getExitTransform,
4044
- buildMorphVisualBridge,
4045
- buildMorphPlan,
4046
5580
  Torph
4047
5581
  };