mesurer 0.0.7 → 0.0.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,15 +1,15 @@
1
1
  // measurer.tsx
2
2
  import {
3
3
  useCallback as useCallback6,
4
- useEffect as useEffect9,
4
+ useEffect as useEffect5,
5
5
  useMemo as useMemo2,
6
- useRef as useRef8,
7
- useState as useState9,
6
+ useRef as useRef9,
7
+ useState as useState8,
8
8
  useSyncExternalStore
9
9
  } from "react";
10
10
  import { createPortal } from "react-dom";
11
11
 
12
- // constants.ts
12
+ // core/constants.ts
13
13
  var MEASURE_LABEL_OFFSET = 2;
14
14
  var GUIDE_SNAP_DISTANCE = 10;
15
15
  var GUIDE_HITBOX_SIZE = 14;
@@ -22,15 +22,17 @@ var ANCESTOR_KEEP_COVERAGE = 0.92;
22
22
  var MIN_SINGLE_TARGET_SIZE = 8;
23
23
  var MIN_SINGLE_ELEMENT_COVERAGE = 0.15;
24
24
 
25
- // style-inject.ts
25
+ // runtime/style-inject.ts
26
26
  var MESURER_STYLE_ID = "mesurer-styles";
27
+ var isDocument = (target) => target.nodeType === 9;
28
+ var isShadowRoot = (target) => target.nodeType === 11;
27
29
  var getStyleTarget = (target) => {
28
30
  if (typeof document === "undefined") return null;
29
31
  if (!target) return document;
30
- if (target instanceof ShadowRoot) return target;
32
+ if (isShadowRoot(target)) return target;
31
33
  const rootNode = target.getRootNode();
32
- if (rootNode instanceof ShadowRoot) return rootNode;
33
- return document;
34
+ if (rootNode.nodeType === 11) return rootNode;
35
+ return target.ownerDocument ?? document;
34
36
  };
35
37
  function ensureMeasurerStyles(cssText, target) {
36
38
  if (typeof document === "undefined") return;
@@ -38,31 +40,32 @@ function ensureMeasurerStyles(cssText, target) {
38
40
  const styleTarget = getStyleTarget(target);
39
41
  if (!styleTarget) return;
40
42
  if (styleTarget.querySelector(`#${MESURER_STYLE_ID}`)) return;
41
- const style = document.createElement("style");
43
+ const ownerDocument = isDocument(styleTarget) ? styleTarget : styleTarget.ownerDocument;
44
+ if (!ownerDocument) return;
45
+ const style = ownerDocument.createElement("style");
42
46
  style.id = MESURER_STYLE_ID;
43
47
  style.textContent = cssText;
44
- if (styleTarget instanceof ShadowRoot) {
48
+ if (isShadowRoot(styleTarget)) {
45
49
  styleTarget.appendChild(style);
46
50
  return;
47
51
  }
48
- styleTarget.head.appendChild(style);
52
+ ownerDocument.head.appendChild(style);
49
53
  }
50
54
 
51
55
  // styles.generated.ts
52
- var MESURER_STYLES = '/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --msr-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",\n "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";\n --msr-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",\n "Courier New", monospace;\n --msr-color-black: #000;\n --msr-color-white: #fff;\n --msr-spacing: 0.25rem;\n --msr-radius-md: 0.375rem;\n --msr-radius-lg: 0.5rem;\n --msr-default-transition-duration: 150ms;\n --msr-default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --msr-default-font-family: var(--msr-font-sans);\n --msr-default-mono-font-family: var(--msr-font-mono);\n --msr-color-ink-50: var(--color-ink-50);\n --msr-color-ink-200: var(--color-ink-200);\n --msr-color-ink-700: var(--color-ink-700);\n --msr-color-ink-900: var(--color-ink-900);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--msr-default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");\n font-feature-settings: var(--msr-default-font-feature-settings, normal);\n font-variation-settings: var(--msr-default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--msr-default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);\n font-feature-settings: var(--msr-default-mono-font-feature-settings, normal);\n font-variation-settings: var(--msr-default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden="until-found"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .msr\\:pointer-events-auto {\n pointer-events: auto;\n }\n .msr\\:pointer-events-none {\n pointer-events: none;\n }\n .msr\\:absolute {\n position: absolute;\n }\n .msr\\:fixed {\n position: fixed;\n }\n .msr\\:relative {\n position: relative;\n }\n .msr\\:inset-0 {\n inset: calc(var(--msr-spacing) * 0);\n }\n .msr\\:top-0 {\n top: calc(var(--msr-spacing) * 0);\n }\n .msr\\:top-full {\n top: 100%;\n }\n .msr\\:right-0 {\n right: calc(var(--msr-spacing) * 0);\n }\n .msr\\:bottom-0 {\n bottom: calc(var(--msr-spacing) * 0);\n }\n .msr\\:bottom-full {\n bottom: 100%;\n }\n .msr\\:left-0 {\n left: calc(var(--msr-spacing) * 0);\n }\n .msr\\:left-1\\/2 {\n left: calc(1 / 2 * 100%);\n }\n .msr\\:z-50 {\n z-index: 50;\n }\n .msr\\:z-\\[70\\] {\n z-index: 70;\n }\n .msr\\:z-\\[90\\] {\n z-index: 90;\n }\n .msr\\:mt-2 {\n margin-top: calc(var(--msr-spacing) * 2);\n }\n .msr\\:mb-2 {\n margin-bottom: calc(var(--msr-spacing) * 2);\n }\n .msr\\:-ml-1 {\n margin-left: calc(var(--msr-spacing) * -1);\n }\n .msr\\:flex {\n display: flex;\n }\n .msr\\:size-8 {\n width: calc(var(--msr-spacing) * 8);\n height: calc(var(--msr-spacing) * 8);\n }\n .msr\\:h-8 {\n height: calc(var(--msr-spacing) * 8);\n }\n .msr\\:h-full {\n height: 100%;\n }\n .msr\\:h-px {\n height: 1px;\n }\n .msr\\:w-4 {\n width: calc(var(--msr-spacing) * 4);\n }\n .msr\\:w-44 {\n width: calc(var(--msr-spacing) * 44);\n }\n .msr\\:w-full {\n width: 100%;\n }\n .msr\\:w-px {\n width: 1px;\n }\n .msr\\:flex-1 {\n flex: 1;\n }\n .msr\\:-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .msr\\:-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .msr\\:rotate-90 {\n rotate: 90deg;\n }\n .msr\\:rotate-\\[45deg\\] {\n rotate: 45deg;\n }\n .msr\\:rotate-\\[135deg\\] {\n rotate: 135deg;\n }\n .msr\\:cursor-crosshair {\n cursor: crosshair;\n }\n .msr\\:cursor-default {\n cursor: default;\n }\n .msr\\:flex-col {\n flex-direction: column;\n }\n .msr\\:items-center {\n align-items: center;\n }\n .msr\\:items-stretch {\n align-items: stretch;\n }\n .msr\\:justify-center {\n justify-content: center;\n }\n .msr\\:gap-1 {\n gap: calc(var(--msr-spacing) * 1);\n }\n .msr\\:gap-2 {\n gap: calc(var(--msr-spacing) * 2);\n }\n .msr\\:gap-px {\n gap: 1px;\n }\n .msr\\:rounded {\n border-radius: 0.25rem;\n }\n .msr\\:rounded-\\[6px\\] {\n border-radius: 6px;\n }\n .msr\\:rounded-\\[8px\\] {\n border-radius: 8px;\n }\n .msr\\:rounded-\\[12px\\] {\n border-radius: 12px;\n }\n .msr\\:rounded-lg {\n border-radius: var(--msr-radius-lg);\n }\n .msr\\:rounded-md {\n border-radius: var(--msr-radius-md);\n }\n .msr\\:border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .msr\\:border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .msr\\:border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .msr\\:border-dashed {\n --tw-border-style: dashed;\n border-style: dashed;\n }\n .msr\\:border-\\[\\#2563eb\\]\\/70 {\n border-color: color-mix(in oklab, #2563eb 70%, transparent);\n }\n .msr\\:border-ink-200 {\n border-color: var(--msr-color-ink-200);\n }\n .msr\\:bg-\\[\\#0d99ff\\] {\n background-color: #0d99ff;\n }\n .msr\\:bg-\\[\\#2563eb\\] {\n background-color: #2563eb;\n }\n .msr\\:bg-\\[\\#fff\\] {\n background-color: #fff;\n }\n .msr\\:bg-black {\n background-color: var(--msr-color-black);\n }\n .msr\\:bg-black\\/10 {\n background-color: var(--msr-color-black);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-black) 10%, transparent);\n }\n }\n .msr\\:bg-ink-900\\/90 {\n background-color: var(--msr-color-ink-900);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-ink-900) 90%, transparent);\n }\n }\n .msr\\:bg-transparent {\n background-color: transparent;\n }\n .msr\\:bg-white {\n background-color: var(--msr-color-white);\n }\n .msr\\:p-1 {\n padding: calc(var(--msr-spacing) * 1);\n }\n .msr\\:px-1 {\n padding-inline: calc(var(--msr-spacing) * 1);\n }\n .msr\\:px-2 {\n padding-inline: calc(var(--msr-spacing) * 2);\n }\n .msr\\:py-0\\.5 {\n padding-block: calc(var(--msr-spacing) * 0.5);\n }\n .msr\\:py-1 {\n padding-block: calc(var(--msr-spacing) * 1);\n }\n .msr\\:py-1\\.5 {\n padding-block: calc(var(--msr-spacing) * 1.5);\n }\n .msr\\:text-left {\n text-align: left;\n }\n .msr\\:text-\\[10px\\] {\n font-size: 10px;\n }\n .msr\\:text-\\[11px\\] {\n font-size: 11px;\n }\n .msr\\:text-\\[12px\\] {\n font-size: 12px;\n }\n .msr\\:whitespace-nowrap {\n white-space: nowrap;\n }\n .msr\\:text-black {\n color: var(--msr-color-black);\n }\n .msr\\:text-ink-50 {\n color: var(--msr-color-ink-50);\n }\n .msr\\:text-ink-700 {\n color: var(--msr-color-ink-700);\n }\n .msr\\:text-white {\n color: var(--msr-color-white);\n }\n .msr\\:text-white\\/60 {\n color: var(--msr-color-white);\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, var(--msr-color-white) 60%, transparent);\n }\n }\n .msr\\:tabular-nums {\n --tw-numeric-spacing: tabular-nums;\n font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);\n }\n .msr\\:opacity-0 {\n opacity: 0%;\n }\n .msr\\:opacity-100 {\n opacity: 100%;\n }\n .msr\\:outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .msr\\:outline-transparent {\n outline-color: transparent;\n }\n .msr\\:transition-opacity {\n transition-property: opacity;\n transition-timing-function: var(--tw-ease, var(--msr-default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--msr-default-transition-duration));\n }\n .msr\\:duration-150 {\n --tw-duration: 150ms;\n transition-duration: 150ms;\n }\n .msr\\:outline-none {\n --tw-outline-style: none;\n outline-style: none;\n }\n .msr\\:select-none {\n -webkit-user-select: none;\n user-select: none;\n }\n .msr\\:hover\\:bg-\\[\\#0d99ff\\] {\n &:hover {\n @media (hover: hover) {\n background-color: #0d99ff;\n }\n }\n }\n .msr\\:hover\\:bg-black\\/4 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--msr-color-black);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-black) 4%, transparent);\n }\n }\n }\n }\n .msr\\:hover\\:bg-black\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--msr-color-black);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-black) 10%, transparent);\n }\n }\n }\n }\n .msr\\:hover\\:text-white {\n &:hover {\n @media (hover: hover) {\n color: var(--msr-color-white);\n }\n }\n }\n .msr\\:focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n}\n:root, :host {\n --color-ink-50: #f8fafc;\n --color-ink-200: #e2e8f0;\n --color-ink-500: #64748b;\n --color-ink-700: #334155;\n --color-ink-900: #0f172a;\n}\n.mesurer-toolbar-surface {\n box-shadow: 0 0 0.5px rgba(0, 0, 0, 0.18), 0 3px 8px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n.mesurer-menu-surface {\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-ordinal {\n syntax: "*";\n inherits: false;\n}\n@property --tw-slashed-zero {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-figure {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-spacing {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-fraction {\n syntax: "*";\n inherits: false;\n}\n@property --tw-outline-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-duration {\n syntax: "*";\n inherits: false;\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-border-style: solid;\n --tw-ordinal: initial;\n --tw-slashed-zero: initial;\n --tw-numeric-figure: initial;\n --tw-numeric-spacing: initial;\n --tw-numeric-fraction: initial;\n --tw-outline-style: solid;\n --tw-duration: initial;\n }\n }\n}\n';
56
+ var MESURER_STYLES = '/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --msr-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",\n "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";\n --msr-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",\n "Courier New", monospace;\n --msr-color-black: #000;\n --msr-color-white: #fff;\n --msr-spacing: 0.25rem;\n --msr-radius-md: 0.375rem;\n --msr-radius-lg: 0.5rem;\n --msr-default-transition-duration: 150ms;\n --msr-default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --msr-default-font-family: var(--msr-font-sans);\n --msr-default-mono-font-family: var(--msr-font-mono);\n --msr-color-ink-50: var(--color-ink-50);\n --msr-color-ink-200: var(--color-ink-200);\n --msr-color-ink-700: var(--color-ink-700);\n --msr-color-ink-900: var(--color-ink-900);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--msr-default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");\n font-feature-settings: var(--msr-default-font-feature-settings, normal);\n font-variation-settings: var(--msr-default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--msr-default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);\n font-feature-settings: var(--msr-default-mono-font-feature-settings, normal);\n font-variation-settings: var(--msr-default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden="until-found"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .msr\\:pointer-events-auto {\n pointer-events: auto;\n }\n .msr\\:pointer-events-none {\n pointer-events: none;\n }\n .msr\\:absolute {\n position: absolute;\n }\n .msr\\:fixed {\n position: fixed;\n }\n .msr\\:relative {\n position: relative;\n }\n .msr\\:inset-0 {\n inset: calc(var(--msr-spacing) * 0);\n }\n .msr\\:top-0 {\n top: calc(var(--msr-spacing) * 0);\n }\n .msr\\:top-\\[18px\\] {\n top: 18px;\n }\n .msr\\:top-full {\n top: 100%;\n }\n .msr\\:right-0 {\n right: calc(var(--msr-spacing) * 0);\n }\n .msr\\:bottom-0 {\n bottom: calc(var(--msr-spacing) * 0);\n }\n .msr\\:bottom-full {\n bottom: 100%;\n }\n .msr\\:left-0 {\n left: calc(var(--msr-spacing) * 0);\n }\n .msr\\:left-1\\/2 {\n left: calc(1 / 2 * 100%);\n }\n .msr\\:left-\\[18px\\] {\n left: 18px;\n }\n .msr\\:z-50 {\n z-index: 50;\n }\n .msr\\:z-\\[70\\] {\n z-index: 70;\n }\n .msr\\:z-\\[90\\] {\n z-index: 90;\n }\n .msr\\:mt-2 {\n margin-top: calc(var(--msr-spacing) * 2);\n }\n .msr\\:mb-2 {\n margin-bottom: calc(var(--msr-spacing) * 2);\n }\n .msr\\:-ml-1 {\n margin-left: calc(var(--msr-spacing) * -1);\n }\n .msr\\:block {\n display: block;\n }\n .msr\\:flex {\n display: flex;\n }\n .msr\\:size-8 {\n width: calc(var(--msr-spacing) * 8);\n height: calc(var(--msr-spacing) * 8);\n }\n .msr\\:size-\\[18px\\] {\n width: 18px;\n height: 18px;\n }\n .msr\\:h-6 {\n height: calc(var(--msr-spacing) * 6);\n }\n .msr\\:h-8 {\n height: calc(var(--msr-spacing) * 8);\n }\n .msr\\:h-full {\n height: 100%;\n }\n .msr\\:h-px {\n height: 1px;\n }\n .msr\\:w-4 {\n width: calc(var(--msr-spacing) * 4);\n }\n .msr\\:w-6 {\n width: calc(var(--msr-spacing) * 6);\n }\n .msr\\:w-44 {\n width: calc(var(--msr-spacing) * 44);\n }\n .msr\\:w-\\[18px\\] {\n width: 18px;\n }\n .msr\\:w-full {\n width: 100%;\n }\n .msr\\:w-px {\n width: 1px;\n }\n .msr\\:flex-1 {\n flex: 1;\n }\n .msr\\:-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .msr\\:-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .msr\\:rotate-90 {\n rotate: 90deg;\n }\n .msr\\:rotate-\\[45deg\\] {\n rotate: 45deg;\n }\n .msr\\:rotate-\\[135deg\\] {\n rotate: 135deg;\n }\n .msr\\:cursor-crosshair {\n cursor: crosshair;\n }\n .msr\\:cursor-default {\n cursor: default;\n }\n .msr\\:flex-col {\n flex-direction: column;\n }\n .msr\\:items-center {\n align-items: center;\n }\n .msr\\:items-stretch {\n align-items: stretch;\n }\n .msr\\:justify-center {\n justify-content: center;\n }\n .msr\\:gap-1 {\n gap: calc(var(--msr-spacing) * 1);\n }\n .msr\\:gap-2 {\n gap: calc(var(--msr-spacing) * 2);\n }\n .msr\\:gap-px {\n gap: 1px;\n }\n .msr\\:overflow-hidden {\n overflow: hidden;\n }\n .msr\\:rounded {\n border-radius: 0.25rem;\n }\n .msr\\:rounded-\\[6px\\] {\n border-radius: 6px;\n }\n .msr\\:rounded-\\[8px\\] {\n border-radius: 8px;\n }\n .msr\\:rounded-\\[12px\\] {\n border-radius: 12px;\n }\n .msr\\:rounded-lg {\n border-radius: var(--msr-radius-lg);\n }\n .msr\\:rounded-md {\n border-radius: var(--msr-radius-md);\n }\n .msr\\:border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .msr\\:border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .msr\\:border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .msr\\:border-dashed {\n --tw-border-style: dashed;\n border-style: dashed;\n }\n .msr\\:border-\\[\\#2563eb\\]\\/70 {\n border-color: color-mix(in oklab, #2563eb 70%, transparent);\n }\n .msr\\:border-ink-200 {\n border-color: var(--msr-color-ink-200);\n }\n .msr\\:bg-\\[\\#0d99ff\\] {\n background-color: #0d99ff;\n }\n .msr\\:bg-\\[\\#2563eb\\] {\n background-color: #2563eb;\n }\n .msr\\:bg-\\[\\#fff\\] {\n background-color: #fff;\n }\n .msr\\:bg-black {\n background-color: var(--msr-color-black);\n }\n .msr\\:bg-black\\/10 {\n background-color: var(--msr-color-black);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-black) 10%, transparent);\n }\n }\n .msr\\:bg-ink-900\\/90 {\n background-color: var(--msr-color-ink-900);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-ink-900) 90%, transparent);\n }\n }\n .msr\\:bg-transparent {\n background-color: transparent;\n }\n .msr\\:bg-white {\n background-color: var(--msr-color-white);\n }\n .msr\\:p-1 {\n padding: calc(var(--msr-spacing) * 1);\n }\n .msr\\:px-1 {\n padding-inline: calc(var(--msr-spacing) * 1);\n }\n .msr\\:px-2 {\n padding-inline: calc(var(--msr-spacing) * 2);\n }\n .msr\\:py-0\\.5 {\n padding-block: calc(var(--msr-spacing) * 0.5);\n }\n .msr\\:py-1 {\n padding-block: calc(var(--msr-spacing) * 1);\n }\n .msr\\:py-1\\.5 {\n padding-block: calc(var(--msr-spacing) * 1.5);\n }\n .msr\\:text-left {\n text-align: left;\n }\n .msr\\:text-\\[9px\\] {\n font-size: 9px;\n }\n .msr\\:text-\\[10px\\] {\n font-size: 10px;\n }\n .msr\\:text-\\[11px\\] {\n font-size: 11px;\n }\n .msr\\:text-\\[12px\\] {\n font-size: 12px;\n }\n .msr\\:whitespace-nowrap {\n white-space: nowrap;\n }\n .msr\\:text-\\[\\#64748b\\] {\n color: #64748b;\n }\n .msr\\:text-black {\n color: var(--msr-color-black);\n }\n .msr\\:text-ink-50 {\n color: var(--msr-color-ink-50);\n }\n .msr\\:text-ink-700 {\n color: var(--msr-color-ink-700);\n }\n .msr\\:text-white {\n color: var(--msr-color-white);\n }\n .msr\\:text-white\\/60 {\n color: var(--msr-color-white);\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, var(--msr-color-white) 60%, transparent);\n }\n }\n .msr\\:tabular-nums {\n --tw-numeric-spacing: tabular-nums;\n font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);\n }\n .msr\\:opacity-0 {\n opacity: 0%;\n }\n .msr\\:opacity-100 {\n opacity: 100%;\n }\n .msr\\:outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .msr\\:outline-transparent {\n outline-color: transparent;\n }\n .msr\\:transition-opacity {\n transition-property: opacity;\n transition-timing-function: var(--tw-ease, var(--msr-default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--msr-default-transition-duration));\n }\n .msr\\:transition-none {\n transition-property: none;\n }\n .msr\\:duration-150 {\n --tw-duration: 150ms;\n transition-duration: 150ms;\n }\n .msr\\:outline-none {\n --tw-outline-style: none;\n outline-style: none;\n }\n .msr\\:select-none {\n -webkit-user-select: none;\n user-select: none;\n }\n .msr\\:hover\\:bg-\\[\\#0d99ff\\] {\n &:hover {\n @media (hover: hover) {\n background-color: #0d99ff;\n }\n }\n }\n .msr\\:hover\\:bg-black\\/4 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--msr-color-black);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-black) 4%, transparent);\n }\n }\n }\n }\n .msr\\:hover\\:bg-black\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--msr-color-black);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--msr-color-black) 10%, transparent);\n }\n }\n }\n }\n .msr\\:hover\\:text-white {\n &:hover {\n @media (hover: hover) {\n color: var(--msr-color-white);\n }\n }\n }\n .msr\\:focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n}\n:root, :host {\n --color-ink-50: #f8fafc;\n --color-ink-200: #e2e8f0;\n --color-ink-500: #64748b;\n --color-ink-700: #334155;\n --color-ink-900: #0f172a;\n}\n.mesurer-toolbar-surface {\n box-shadow: 0 0 0.5px rgba(0, 0, 0, 0.18), 0 3px 8px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n.mesurer-menu-surface {\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);\n}\n.mesurer-root {\n --msr-spacing: 4px;\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-ordinal {\n syntax: "*";\n inherits: false;\n}\n@property --tw-slashed-zero {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-figure {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-spacing {\n syntax: "*";\n inherits: false;\n}\n@property --tw-numeric-fraction {\n syntax: "*";\n inherits: false;\n}\n@property --tw-outline-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-duration {\n syntax: "*";\n inherits: false;\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-border-style: solid;\n --tw-ordinal: initial;\n --tw-slashed-zero: initial;\n --tw-numeric-figure: initial;\n --tw-numeric-spacing: initial;\n --tw-numeric-fraction: initial;\n --tw-outline-style: solid;\n --tw-duration: initial;\n }\n }\n}\n';
53
57
 
54
58
  // components/toolbar.tsx
55
59
  import {
56
60
  forwardRef,
57
61
  memo,
58
62
  useCallback,
59
- useEffect,
60
63
  useLayoutEffect,
61
64
  useRef,
62
65
  useState
63
66
  } from "react";
64
67
 
65
- // utils.ts
68
+ // core/utils.ts
66
69
  var formatValue = (value) => Math.round(value);
67
70
  var cn = (...inputs) => inputs.filter(Boolean).join(" ");
68
71
  var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
@@ -82,9 +85,57 @@ var IconBase = ({ size = 24, ...props }) => /* @__PURE__ */ jsx(
82
85
  );
83
86
  var CursorIcon = ({ size = 20, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx("path", { d: "M166.59,134.1a1.91,1.91,0,0,1-.55-1.79,2,2,0,0,1,1.08-1.42l46.25-17.76.24-.1A14,14,0,0,0,212.38,87L52.29,34.7A13.95,13.95,0,0,0,34.7,52.29L87,212.38a13.82,13.82,0,0,0,12.6,9.6c.23,0,.46,0,.69,0A13.84,13.84,0,0,0,113,213.61a2.44,2.44,0,0,0,.1-.24l17.76-46.25a2,2,0,0,1,3.21-.53l51.31,51.31a14,14,0,0,0,19.8,0l12.69-12.69a14,14,0,0,0,0-19.8Zm42.82,62.63-12.68,12.68a2,2,0,0,1-2.83,0L142.59,158.1a14,14,0,0,0-22.74,4.32,2.44,2.44,0,0,0-.1.24L102,208.91a2,2,0,0,1-3.61-.26L46.11,48.57a1.87,1.87,0,0,1,.47-2A1.92,1.92,0,0,1,47.93,46a2.22,2.22,0,0,1,.64.1L208.65,98.38a2,2,0,0,1,.26,3.61l-46.25,17.76-.24.1a14,14,0,0,0-4.32,22.74h0l51.31,51.31A2,2,0,0,1,209.41,196.73Z" }) });
84
87
  var RulerIcon = ({ size = 20, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx("path", { d: "M233.91,74.79,181.22,22.1a14,14,0,0,0-19.8,0L22.09,161.41a14,14,0,0,0,0,19.8L74.78,233.9a14,14,0,0,0,19.8,0L233.91,94.59A14,14,0,0,0,233.91,74.79ZM225.42,86.1,86.1,225.41h0a2,2,0,0,1-2.83,0L30.58,172.73a2,2,0,0,1,0-2.83L64,136.48l27.76,27.76a6,6,0,1,0,8.48-8.48L72.48,128,96,104.48l27.76,27.76a6,6,0,0,0,8.48-8.48L104.48,96,128,72.49l27.76,27.75a6,6,0,0,0,8.48-8.48L136.49,64,169.9,30.59a2,2,0,0,1,2.83,0l52.69,52.68A2,2,0,0,1,225.42,86.1Z" }) });
88
+ var XrayIcon = ({ size = 20, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx(
89
+ "path",
90
+ {
91
+ fill: "none",
92
+ stroke: "currentColor",
93
+ strokeLinecap: "round",
94
+ strokeLinejoin: "round",
95
+ strokeWidth: "12",
96
+ strokeOpacity: "0.78",
97
+ d: "M76 48h96a16 16 0 0 1 16 16v96a16 16 0 0 1-16 16H76a16 16 0 0 1-16-16V64a16 16 0 0 1 16-16Zm32 32h96a16 16 0 0 1 16 16v96a16 16 0 0 1-16 16h-96a16 16 0 0 1-16-16V96a16 16 0 0 1 16-16Z"
98
+ }
99
+ ) });
100
+ var RulersIcon = ({ size = 20, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx(
101
+ "path",
102
+ {
103
+ fill: "none",
104
+ stroke: "currentColor",
105
+ strokeLinecap: "round",
106
+ strokeLinejoin: "round",
107
+ strokeWidth: "12",
108
+ d: "M48 208V48h160M80 48v24m32-24v24m32-24v24m32-24v24M48 80h24M48 112h24m-24 32h24m-24 32h24"
109
+ }
110
+ ) });
85
111
  var CaretDownIcon = ({ size = 8, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx("path", { d: "M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z" }) });
86
112
  var CheckIcon = ({ size = 12, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx("path", { d: "M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z" }) });
87
113
  var MinusIcon = ({ size = 12, ...props }) => /* @__PURE__ */ jsx(IconBase, { size, ...props, children: /* @__PURE__ */ jsx("path", { d: "M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128Z" }) });
114
+ var TextInspectorIcon = ({ size = 20, ...props }) => /* @__PURE__ */ jsx(
115
+ "svg",
116
+ {
117
+ xmlns: "http://www.w3.org/2000/svg",
118
+ width: size,
119
+ height: size,
120
+ viewBox: "0 0 256 256",
121
+ fill: "currentColor",
122
+ ...props,
123
+ children: /* @__PURE__ */ jsx(
124
+ "text",
125
+ {
126
+ x: "50%",
127
+ y: "60%",
128
+ textAnchor: "middle",
129
+ dominantBaseline: "middle",
130
+ fill: "currentColor",
131
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
132
+ fontWeight: 300,
133
+ fontSize: 220,
134
+ children: "Aa"
135
+ }
136
+ )
137
+ }
138
+ );
88
139
 
89
140
  // components/toolbar.tsx
90
141
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
@@ -99,6 +150,7 @@ function ToolbarButton({
99
150
  shortcut,
100
151
  onClick,
101
152
  tooltipVisible,
153
+ tooltipInstant,
102
154
  tooltipSide,
103
155
  onTooltipEnter,
104
156
  onTooltipLeave,
@@ -116,6 +168,7 @@ function ToolbarButton({
116
168
  {
117
169
  type: "button",
118
170
  "aria-label": `${label} (${shortcut})`,
171
+ "aria-pressed": active,
119
172
  title: `${label} (${shortcut})`,
120
173
  className: cn(
121
174
  "msr:flex msr:size-8 msr:items-center msr:justify-center msr:rounded-[8px] msr:outline-none",
@@ -129,7 +182,10 @@ function ToolbarButton({
129
182
  "span",
130
183
  {
131
184
  className: cn(
132
- "msr:pointer-events-none msr:absolute msr:left-1/2 msr:-translate-x-1/2 msr:whitespace-nowrap msr:rounded msr:bg-black msr:px-2 msr:py-1 msr:text-[11px] msr:text-white msr:transition-opacity msr:duration-150 msr:select-none",
185
+ cn(
186
+ "msr:pointer-events-none msr:absolute msr:left-1/2 msr:-translate-x-1/2 msr:whitespace-nowrap msr:rounded msr:bg-black msr:px-2 msr:py-1 msr:text-[11px] msr:text-white msr:transition-opacity msr:duration-150 msr:select-none",
187
+ tooltipInstant && "msr:transition-none"
188
+ ),
133
189
  tooltipSide === "top" ? "msr:bottom-full msr:mb-2" : "msr:top-full msr:mt-2",
134
190
  tooltipVisible ? "msr:opacity-100" : "msr:opacity-0"
135
191
  ),
@@ -148,6 +204,7 @@ function useToolbarTooltip() {
148
204
  const [visibleTooltipId, setVisibleTooltipId] = useState(null);
149
205
  const timerRef = useRef(null);
150
206
  const instantRef = useRef(false);
207
+ const [tooltipInstant, setTooltipInstant] = useState(false);
151
208
  const clearTimer = useCallback(() => {
152
209
  if (timerRef.current === null) return;
153
210
  window.clearTimeout(timerRef.current);
@@ -157,9 +214,11 @@ function useToolbarTooltip() {
157
214
  (id) => {
158
215
  clearTimer();
159
216
  if (instantRef.current) {
217
+ setTooltipInstant(true);
160
218
  setVisibleTooltipId(id);
161
219
  return;
162
220
  }
221
+ setTooltipInstant(false);
163
222
  timerRef.current = window.setTimeout(() => {
164
223
  setVisibleTooltipId(id);
165
224
  instantRef.current = true;
@@ -179,15 +238,17 @@ function useToolbarTooltip() {
179
238
  clearTimer();
180
239
  setVisibleTooltipId(null);
181
240
  instantRef.current = false;
241
+ setTooltipInstant(false);
182
242
  }, [clearTimer]);
183
- useEffect(() => {
184
- return () => {
185
- clearTimer();
186
- };
187
- }, [clearTimer]);
188
- return { visibleTooltipId, onTooltipEnter, onTooltipLeave, onToolbarLeave };
243
+ return {
244
+ visibleTooltipId,
245
+ tooltipInstant,
246
+ onTooltipEnter,
247
+ onTooltipLeave,
248
+ onToolbarLeave
249
+ };
189
250
  }
190
- function useToolbarDrag(initialPosition) {
251
+ function useToolbarDrag(initialPosition, eventTarget) {
191
252
  const [position, setPosition] = useState(initialPosition);
192
253
  const suppressClickRef = useRef(false);
193
254
  const detachListenersRef = useRef(null);
@@ -230,8 +291,8 @@ function useToolbarDrag(initialPosition) {
230
291
  }
231
292
  if (!current.active) return;
232
293
  current.didDrag = true;
233
- const maxX = Math.max(8, window.innerWidth - current.width - 8);
234
- const maxY = Math.max(8, window.innerHeight - current.height - 8);
294
+ const maxX = Math.max(8, eventTarget.innerWidth - current.width - 8);
295
+ const maxY = Math.max(8, eventTarget.innerHeight - current.height - 8);
235
296
  setPosition({
236
297
  x: Math.min(maxX, Math.max(8, current.originX + dx)),
237
298
  y: Math.min(maxY, Math.max(8, current.originY + dy))
@@ -245,21 +306,21 @@ function useToolbarDrag(initialPosition) {
245
306
  current.active = false;
246
307
  current.didDrag = false;
247
308
  current.pointerId = -1;
248
- window.removeEventListener("pointermove", handlePointerMove);
249
- window.removeEventListener("pointerup", handlePointerEnd);
250
- window.removeEventListener("pointercancel", handlePointerEnd);
309
+ eventTarget.removeEventListener("pointermove", handlePointerMove);
310
+ eventTarget.removeEventListener("pointerup", handlePointerEnd);
311
+ eventTarget.removeEventListener("pointercancel", handlePointerEnd);
251
312
  detachListenersRef.current = null;
252
313
  };
253
- window.addEventListener("pointermove", handlePointerMove);
254
- window.addEventListener("pointerup", handlePointerEnd);
255
- window.addEventListener("pointercancel", handlePointerEnd);
314
+ eventTarget.addEventListener("pointermove", handlePointerMove);
315
+ eventTarget.addEventListener("pointerup", handlePointerEnd);
316
+ eventTarget.addEventListener("pointercancel", handlePointerEnd);
256
317
  detachListenersRef.current = () => {
257
- window.removeEventListener("pointermove", handlePointerMove);
258
- window.removeEventListener("pointerup", handlePointerEnd);
259
- window.removeEventListener("pointercancel", handlePointerEnd);
318
+ eventTarget.removeEventListener("pointermove", handlePointerMove);
319
+ eventTarget.removeEventListener("pointerup", handlePointerEnd);
320
+ eventTarget.removeEventListener("pointercancel", handlePointerEnd);
260
321
  };
261
322
  },
262
- [position.x, position.y]
323
+ [eventTarget, position.x, position.y]
263
324
  );
264
325
  const onClickCapture = useCallback(
265
326
  (event) => {
@@ -276,15 +337,24 @@ function ToolbarComponent({
276
337
  toolMode,
277
338
  setEnabled,
278
339
  setToolMode,
340
+ rulersVisible,
341
+ setRulersVisible,
279
342
  guideOrientation,
280
343
  setGuideOrientation,
281
- onInteract
344
+ onInteract,
345
+ eventTarget
282
346
  }, ref) {
283
347
  const { position, onPointerDown, onClickCapture } = useToolbarDrag({
284
348
  x: 16,
285
349
  y: 16
286
- });
287
- const { visibleTooltipId, onTooltipEnter, onTooltipLeave, onToolbarLeave } = useToolbarTooltip();
350
+ }, eventTarget);
351
+ const {
352
+ visibleTooltipId,
353
+ tooltipInstant,
354
+ onTooltipEnter,
355
+ onTooltipLeave,
356
+ onToolbarLeave
357
+ } = useToolbarTooltip();
288
358
  const [guideMenuOpen, setGuideMenuOpen] = useState(false);
289
359
  const guideMenuRef = useRef(null);
290
360
  const [activeMenuIndex, setActiveMenuIndex] = useState(0);
@@ -298,13 +368,13 @@ function ToolbarComponent({
298
368
  setMenuAlign("left");
299
369
  return;
300
370
  }
301
- if (leftAlignedRight > window.innerWidth - VIEWPORT_PADDING) {
371
+ if (leftAlignedRight > eventTarget.innerWidth - VIEWPORT_PADDING) {
302
372
  setMenuAlign("right");
303
373
  return;
304
374
  }
305
375
  setMenuAlign("right");
306
- }, []);
307
- const viewportHeight = typeof window === "undefined" ? 0 : window.innerHeight || 0;
376
+ }, [eventTarget]);
377
+ const viewportHeight = eventTarget.innerHeight || 0;
308
378
  const nearTop = position.y < 56;
309
379
  const nearBottom = viewportHeight > 0 && position.y > viewportHeight - 56;
310
380
  const tooltipSide = nearTop && !nearBottom ? "bottom" : "top";
@@ -319,6 +389,23 @@ function ToolbarComponent({
319
389
  setToolMode((prev) => prev === "guides" ? "none" : "guides");
320
390
  onInteract();
321
391
  }, [onInteract, setEnabled, setToolMode]);
392
+ const textInspectorMode = useCallback(() => {
393
+ setEnabled(true);
394
+ setToolMode(
395
+ (prev) => prev === "text-inspector" ? "none" : "text-inspector"
396
+ );
397
+ onInteract();
398
+ }, [onInteract, setEnabled, setToolMode]);
399
+ const xrayMode = useCallback(() => {
400
+ setEnabled(true);
401
+ setToolMode((prev) => prev === "xray" ? "none" : "xray");
402
+ onInteract();
403
+ }, [onInteract, setEnabled, setToolMode]);
404
+ const rulersMode = useCallback(() => {
405
+ setEnabled(true);
406
+ setRulersVisible((prev) => !prev);
407
+ onInteract();
408
+ }, [onInteract, setEnabled, setRulersVisible]);
322
409
  const selectGuideOrientation = useCallback(
323
410
  (orientation) => {
324
411
  setEnabled(true);
@@ -331,7 +418,7 @@ function ToolbarComponent({
331
418
  );
332
419
  useLayoutEffect(() => {
333
420
  if (!guideMenuOpen) return;
334
- const frame = requestAnimationFrame(() => {
421
+ const frame = eventTarget.requestAnimationFrame(() => {
335
422
  guideMenuRef.current?.querySelector("[role='menu']")?.focus();
336
423
  });
337
424
  const handlePointerDown = (event) => {
@@ -344,14 +431,14 @@ function ToolbarComponent({
344
431
  const handleResize = () => {
345
432
  updateMenuAlign();
346
433
  };
347
- window.addEventListener("pointerdown", handlePointerDown);
348
- window.addEventListener("resize", handleResize);
434
+ eventTarget.addEventListener("pointerdown", handlePointerDown);
435
+ eventTarget.addEventListener("resize", handleResize);
349
436
  return () => {
350
- cancelAnimationFrame(frame);
351
- window.removeEventListener("pointerdown", handlePointerDown);
352
- window.removeEventListener("resize", handleResize);
437
+ eventTarget.cancelAnimationFrame(frame);
438
+ eventTarget.removeEventListener("pointerdown", handlePointerDown);
439
+ eventTarget.removeEventListener("resize", handleResize);
353
440
  };
354
- }, [guideMenuOpen, guideOrientation, updateMenuAlign]);
441
+ }, [eventTarget, guideMenuOpen, guideOrientation, updateMenuAlign]);
355
442
  return /* @__PURE__ */ jsxs(
356
443
  "div",
357
444
  {
@@ -374,12 +461,61 @@ function ToolbarComponent({
374
461
  shortcut: "S",
375
462
  onClick: selectMode,
376
463
  tooltipVisible: visibleTooltipId === "select",
464
+ tooltipInstant,
377
465
  tooltipSide,
378
466
  onTooltipEnter,
379
467
  onTooltipLeave,
380
468
  children: /* @__PURE__ */ jsx2(CursorIcon, { size: 20 })
381
469
  }
382
470
  ),
471
+ /* @__PURE__ */ jsx2(
472
+ ToolbarButton,
473
+ {
474
+ id: "xray",
475
+ active: toolMode === "xray",
476
+ label: "X-ray",
477
+ shortcut: "X",
478
+ onClick: xrayMode,
479
+ tooltipVisible: visibleTooltipId === "xray",
480
+ tooltipInstant,
481
+ tooltipSide,
482
+ onTooltipEnter,
483
+ onTooltipLeave,
484
+ children: /* @__PURE__ */ jsx2(XrayIcon, { size: 20 })
485
+ }
486
+ ),
487
+ /* @__PURE__ */ jsx2(
488
+ ToolbarButton,
489
+ {
490
+ id: "rulers",
491
+ active: rulersVisible,
492
+ label: "Rulers",
493
+ shortcut: "R",
494
+ onClick: rulersMode,
495
+ tooltipVisible: visibleTooltipId === "rulers",
496
+ tooltipInstant,
497
+ tooltipSide,
498
+ onTooltipEnter,
499
+ onTooltipLeave,
500
+ children: /* @__PURE__ */ jsx2(RulersIcon, { size: 20 })
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsx2(
504
+ ToolbarButton,
505
+ {
506
+ id: "text-inspector",
507
+ active: toolMode === "text-inspector",
508
+ label: "Text inspector",
509
+ shortcut: "A",
510
+ onClick: textInspectorMode,
511
+ tooltipVisible: visibleTooltipId === "text-inspector",
512
+ tooltipInstant,
513
+ tooltipSide,
514
+ onTooltipEnter,
515
+ onTooltipLeave,
516
+ children: /* @__PURE__ */ jsx2(TextInspectorIcon, { size: 20, "aria-hidden": "true" })
517
+ }
518
+ ),
383
519
  /* @__PURE__ */ jsx2(
384
520
  ToolbarButton,
385
521
  {
@@ -389,6 +525,7 @@ function ToolbarComponent({
389
525
  shortcut: "G",
390
526
  onClick: guidesMode,
391
527
  tooltipVisible: visibleTooltipId === "guides",
528
+ tooltipInstant,
392
529
  tooltipSide,
393
530
  onTooltipEnter,
394
531
  onTooltipLeave,
@@ -548,23 +685,328 @@ function ToolbarComponent({
548
685
  }
549
686
  var Toolbar = memo(forwardRef(ToolbarComponent));
550
687
 
688
+ // components/rulers-overlay.tsx
689
+ import { memo as memo2, useRef as useRef2, useState as useState2 } from "react";
690
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
691
+ var RULER_SIZE = 18;
692
+ var RULER_LENGTH = 4e3;
693
+ var TICK_STEP = 5;
694
+ var ticks = Array.from(
695
+ { length: RULER_LENGTH / TICK_STEP + 1 },
696
+ (_, index) => index * TICK_STEP
697
+ );
698
+ var RulersOverlay = memo2(function RulersOverlay2({
699
+ onStartGuide,
700
+ onMoveGuide,
701
+ onFinishGuide,
702
+ onCancelGuide,
703
+ guides,
704
+ selectedGuideIds
705
+ }) {
706
+ const [dragPosition, setDragPosition] = useState2(null);
707
+ const [dragOrientation, setDragOrientation] = useState2(null);
708
+ const dragRef = useRef2(null);
709
+ const beginGuideDrag = (orientation, event) => {
710
+ if (event.button !== 0) return;
711
+ event.stopPropagation();
712
+ const position = orientation === "horizontal" ? event.clientY : event.clientX;
713
+ setDragPosition(position);
714
+ setDragOrientation(orientation);
715
+ dragRef.current = {
716
+ orientation,
717
+ pointerId: event.pointerId,
718
+ id: onStartGuide(orientation, position)
719
+ };
720
+ event.currentTarget.setPointerCapture(event.pointerId);
721
+ };
722
+ const moveGuide = (event) => {
723
+ const drag = dragRef.current;
724
+ if (!drag || drag.pointerId !== event.pointerId) return;
725
+ event.stopPropagation();
726
+ const position = drag.orientation === "horizontal" ? event.clientY : event.clientX;
727
+ setDragPosition(position);
728
+ onMoveGuide(drag.id, position);
729
+ };
730
+ const finishGuideDrag = (event) => {
731
+ const drag = dragRef.current;
732
+ if (!drag || drag.pointerId !== event.pointerId) return;
733
+ event.stopPropagation();
734
+ const position = drag.orientation === "horizontal" ? event.clientY : event.clientX;
735
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
736
+ event.currentTarget.releasePointerCapture(event.pointerId);
737
+ }
738
+ dragRef.current = null;
739
+ setDragPosition(null);
740
+ setDragOrientation(null);
741
+ onMoveGuide(drag.id, position);
742
+ onFinishGuide(drag.id);
743
+ };
744
+ const cancelGuideDrag = (event) => {
745
+ const drag = dragRef.current;
746
+ if (!drag || drag.pointerId !== event.pointerId) return;
747
+ dragRef.current = null;
748
+ setDragPosition(null);
749
+ setDragOrientation(null);
750
+ onCancelGuide(drag.id);
751
+ };
752
+ const selectedVerticalGuides = guides.filter(
753
+ (guide) => guide.orientation === "vertical" && selectedGuideIds.includes(guide.id)
754
+ );
755
+ const selectedHorizontalGuides = guides.filter(
756
+ (guide) => guide.orientation === "horizontal" && selectedGuideIds.includes(guide.id)
757
+ );
758
+ return /* @__PURE__ */ jsxs2(
759
+ "div",
760
+ {
761
+ "aria-hidden": "true",
762
+ className: "msr:pointer-events-none msr:absolute msr:inset-0 msr:select-none msr:text-[9px] msr:text-[#64748b]",
763
+ children: [
764
+ /* @__PURE__ */ jsx3(
765
+ "div",
766
+ {
767
+ className: "msr:absolute msr:left-[18px] msr:right-0 msr:top-0 msr:overflow-hidden msr:bg-white",
768
+ style: {
769
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.12)",
770
+ height: RULER_SIZE,
771
+ cursor: "ns-resize",
772
+ pointerEvents: "auto"
773
+ },
774
+ onPointerDown: (event) => beginGuideDrag("horizontal", event),
775
+ onPointerMove: moveGuide,
776
+ onPointerUp: finishGuideDrag,
777
+ onPointerCancel: cancelGuideDrag,
778
+ children: /* @__PURE__ */ jsxs2(
779
+ "svg",
780
+ {
781
+ className: "msr:block msr:h-6",
782
+ width: RULER_LENGTH,
783
+ height: RULER_SIZE,
784
+ viewBox: `0 0 ${RULER_LENGTH} ${RULER_SIZE}`,
785
+ children: [
786
+ /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsxs2(
787
+ "linearGradient",
788
+ {
789
+ id: "ruler-label-fade-x",
790
+ x1: "0%",
791
+ x2: "100%",
792
+ gradientUnits: "objectBoundingBox",
793
+ children: [
794
+ /* @__PURE__ */ jsx3("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }),
795
+ /* @__PURE__ */ jsx3("stop", { offset: "0.2", stopColor: "white" }),
796
+ /* @__PURE__ */ jsx3("stop", { offset: "0.8", stopColor: "white" }),
797
+ /* @__PURE__ */ jsx3("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })
798
+ ]
799
+ }
800
+ ) }),
801
+ ticks.map((x) => {
802
+ const major = x % 100 === 0;
803
+ const medium = x % 50 === 0;
804
+ return /* @__PURE__ */ jsxs2("g", { children: [
805
+ /* @__PURE__ */ jsx3(
806
+ "line",
807
+ {
808
+ x1: x,
809
+ y1: RULER_SIZE,
810
+ x2: x,
811
+ y2: major ? 8 : medium ? 13 : 17,
812
+ stroke: "currentColor",
813
+ strokeWidth: "1"
814
+ }
815
+ ),
816
+ major ? /* @__PURE__ */ jsx3("text", { x, y: "8", textAnchor: "middle", fill: "currentColor", children: x }) : null
817
+ ] }, x);
818
+ }),
819
+ selectedVerticalGuides.map((guide) => /* @__PURE__ */ jsxs2("g", { children: [
820
+ /* @__PURE__ */ jsx3(
821
+ "rect",
822
+ {
823
+ x: guide.position - RULER_SIZE - 8,
824
+ y: "-1",
825
+ width: "48",
826
+ height: "12",
827
+ fill: "url(#ruler-label-fade-x)"
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsx3(
831
+ "text",
832
+ {
833
+ x: guide.position - RULER_SIZE + 16,
834
+ y: "8",
835
+ textAnchor: "middle",
836
+ fill: "#ef4444",
837
+ fontWeight: "600",
838
+ children: Math.round(guide.position - RULER_SIZE)
839
+ }
840
+ )
841
+ ] }, guide.id)),
842
+ dragOrientation === "vertical" && dragPosition !== null ? /* @__PURE__ */ jsxs2("g", { children: [
843
+ /* @__PURE__ */ jsx3(
844
+ "rect",
845
+ {
846
+ x: dragPosition - RULER_SIZE - 8,
847
+ y: "-1",
848
+ width: "48",
849
+ height: "12",
850
+ fill: "url(#ruler-label-fade-x)"
851
+ }
852
+ ),
853
+ /* @__PURE__ */ jsx3(
854
+ "text",
855
+ {
856
+ x: dragPosition - RULER_SIZE + 16,
857
+ y: "8",
858
+ textAnchor: "middle",
859
+ fill: "#ef4444",
860
+ fontWeight: "600",
861
+ children: Math.round(dragPosition - RULER_SIZE)
862
+ }
863
+ )
864
+ ] }) : null
865
+ ]
866
+ }
867
+ )
868
+ }
869
+ ),
870
+ /* @__PURE__ */ jsx3(
871
+ "div",
872
+ {
873
+ className: "msr:absolute msr:bottom-0 msr:left-0 msr:top-[18px] msr:w-[18px] msr:overflow-hidden msr:bg-white",
874
+ style: {
875
+ boxShadow: "1px 0 3px rgba(0, 0, 0, 0.12)",
876
+ width: RULER_SIZE,
877
+ cursor: "ew-resize",
878
+ pointerEvents: "auto"
879
+ },
880
+ onPointerDown: (event) => beginGuideDrag("vertical", event),
881
+ onPointerMove: moveGuide,
882
+ onPointerUp: finishGuideDrag,
883
+ onPointerCancel: cancelGuideDrag,
884
+ children: /* @__PURE__ */ jsxs2(
885
+ "svg",
886
+ {
887
+ className: "msr:block msr:w-6",
888
+ width: RULER_SIZE,
889
+ height: RULER_LENGTH,
890
+ viewBox: `0 0 ${RULER_SIZE} ${RULER_LENGTH}`,
891
+ children: [
892
+ /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsxs2(
893
+ "linearGradient",
894
+ {
895
+ id: "ruler-label-fade-y",
896
+ y1: "0%",
897
+ y2: "100%",
898
+ gradientUnits: "objectBoundingBox",
899
+ children: [
900
+ /* @__PURE__ */ jsx3("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }),
901
+ /* @__PURE__ */ jsx3("stop", { offset: "0.2", stopColor: "white" }),
902
+ /* @__PURE__ */ jsx3("stop", { offset: "0.8", stopColor: "white" }),
903
+ /* @__PURE__ */ jsx3("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })
904
+ ]
905
+ }
906
+ ) }),
907
+ ticks.map((y) => {
908
+ const major = y % 100 === 0;
909
+ const medium = y % 50 === 0;
910
+ return /* @__PURE__ */ jsxs2("g", { children: [
911
+ /* @__PURE__ */ jsx3(
912
+ "line",
913
+ {
914
+ x1: RULER_SIZE,
915
+ y1: y,
916
+ x2: major ? 8 : medium ? 13 : 17,
917
+ y2: y,
918
+ stroke: "currentColor",
919
+ strokeWidth: "1"
920
+ }
921
+ ),
922
+ major ? /* @__PURE__ */ jsx3(
923
+ "text",
924
+ {
925
+ x: "8",
926
+ y: y + 10,
927
+ fill: "currentColor",
928
+ transform: `rotate(-90 8 ${y + 10})`,
929
+ children: y
930
+ }
931
+ ) : null
932
+ ] }, y);
933
+ }),
934
+ selectedHorizontalGuides.map((guide) => /* @__PURE__ */ jsxs2("g", { children: [
935
+ /* @__PURE__ */ jsx3(
936
+ "rect",
937
+ {
938
+ x: "-3",
939
+ y: guide.position - RULER_SIZE - 38,
940
+ width: "24",
941
+ height: "48",
942
+ fill: "url(#ruler-label-fade-y)"
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsx3(
946
+ "text",
947
+ {
948
+ x: "9",
949
+ y: guide.position - RULER_SIZE - 16,
950
+ textAnchor: "middle",
951
+ fill: "#ef4444",
952
+ fontWeight: "600",
953
+ transform: `rotate(-90 9 ${guide.position - RULER_SIZE - 16})`,
954
+ children: Math.round(guide.position - RULER_SIZE)
955
+ }
956
+ )
957
+ ] }, guide.id)),
958
+ dragOrientation === "horizontal" && dragPosition !== null ? /* @__PURE__ */ jsxs2("g", { children: [
959
+ /* @__PURE__ */ jsx3(
960
+ "rect",
961
+ {
962
+ x: "-3",
963
+ y: dragPosition - RULER_SIZE - 38,
964
+ width: "24",
965
+ height: "48",
966
+ fill: "url(#ruler-label-fade-y)"
967
+ }
968
+ ),
969
+ /* @__PURE__ */ jsx3(
970
+ "text",
971
+ {
972
+ x: "9",
973
+ y: dragPosition - RULER_SIZE - 16,
974
+ textAnchor: "middle",
975
+ fill: "#ef4444",
976
+ fontWeight: "600",
977
+ transform: `rotate(-90 9 ${dragPosition - RULER_SIZE - 16})`,
978
+ children: Math.round(dragPosition - RULER_SIZE)
979
+ }
980
+ )
981
+ ] }) : null
982
+ ]
983
+ }
984
+ )
985
+ }
986
+ ),
987
+ /* @__PURE__ */ jsx3("div", { className: "msr:absolute msr:left-0 msr:top-0 msr:size-[18px] msr:bg-white" })
988
+ ]
989
+ }
990
+ );
991
+ });
992
+
551
993
  // hooks/use-drag-state.ts
552
- import { useState as useState2 } from "react";
994
+ import { useState as useState3 } from "react";
553
995
  var useDragState = () => {
554
- const [start, setStart] = useState2(null);
555
- const [end, setEnd] = useState2(null);
556
- const [isDragging, setIsDragging] = useState2(false);
996
+ const [start, setStart] = useState3(null);
997
+ const [end, setEnd] = useState3(null);
998
+ const [isDragging, setIsDragging] = useState3(false);
557
999
  return { start, setStart, end, setEnd, isDragging, setIsDragging };
558
1000
  };
559
1001
 
560
1002
  // hooks/use-guide-drag-hold.ts
561
- import { useCallback as useCallback2, useRef as useRef2 } from "react";
562
- var useGuideDragHold = () => {
563
- const guideDragHoldTimerRef = useRef2(null);
564
- const guideDragHoldGuideIdRef = useRef2(null);
1003
+ import { useCallback as useCallback2, useRef as useRef3 } from "react";
1004
+ var useGuideDragHold = (eventTarget) => {
1005
+ const guideDragHoldTimerRef = useRef3(null);
1006
+ const guideDragHoldGuideIdRef = useRef3(null);
565
1007
  const clearGuideDragHold = useCallback2(() => {
566
1008
  if (guideDragHoldTimerRef.current !== null) {
567
- window.clearTimeout(guideDragHoldTimerRef.current);
1009
+ eventTarget.clearTimeout(guideDragHoldTimerRef.current);
568
1010
  guideDragHoldTimerRef.current = null;
569
1011
  }
570
1012
  guideDragHoldGuideIdRef.current = null;
@@ -573,7 +1015,7 @@ var useGuideDragHold = () => {
573
1015
  (id, onHold) => {
574
1016
  clearGuideDragHold();
575
1017
  guideDragHoldGuideIdRef.current = id;
576
- guideDragHoldTimerRef.current = window.setTimeout(() => {
1018
+ guideDragHoldTimerRef.current = eventTarget.setTimeout(() => {
577
1019
  if (guideDragHoldGuideIdRef.current === id) {
578
1020
  onHold(id);
579
1021
  }
@@ -588,11 +1030,11 @@ var useGuideDragHold = () => {
588
1030
  };
589
1031
 
590
1032
  // hooks/use-guide-state.ts
591
- import { useState as useState3 } from "react";
1033
+ import { useState as useState4 } from "react";
592
1034
  var useGuideState = (options = {}) => {
593
- const [guides, setGuides] = useState3(options.initialGuides ?? []);
594
- const [draggingGuideId, setDraggingGuideId] = useState3(null);
595
- const [selectedGuideIds, setSelectedGuideIds] = useState3(
1035
+ const [guides, setGuides] = useState4(options.initialGuides ?? []);
1036
+ const [draggingGuideId, setDraggingGuideId] = useState4(null);
1037
+ const [selectedGuideIds, setSelectedGuideIds] = useState4(
596
1038
  options.initialSelectedGuideIds ?? []
597
1039
  );
598
1040
  return {
@@ -606,19 +1048,37 @@ var useGuideState = (options = {}) => {
606
1048
  };
607
1049
 
608
1050
  // hooks/use-hotkeys.ts
609
- import { useEffect as useEffect2 } from "react";
1051
+ import { useEffect } from "react";
610
1052
  var useHotkeys = (options) => {
611
- useEffect2(() => {
1053
+ useEffect(() => {
1054
+ const target = options.eventTarget;
612
1055
  const handleKeyDown = (event) => {
613
1056
  if (event.key === "Escape") {
614
1057
  options.clearAll();
615
1058
  return;
616
1059
  }
1060
+ if (event.metaKey || event.ctrlKey) {
1061
+ if (event.key.toLowerCase() !== "z") return;
1062
+ if (event.shiftKey) {
1063
+ event.preventDefault();
1064
+ options.redo();
1065
+ return;
1066
+ }
1067
+ event.preventDefault();
1068
+ options.undo();
1069
+ return;
1070
+ }
617
1071
  if (event.key.toLowerCase() === "m") {
618
1072
  options.setEnabled((prev) => !prev);
619
1073
  }
620
1074
  const key = event.key.toLowerCase();
621
1075
  if (options.isOverlayActive()) {
1076
+ if (key === "a") {
1077
+ options.setToolMode(
1078
+ (prev) => prev === "text-inspector" ? "none" : "text-inspector"
1079
+ );
1080
+ options.onInteract();
1081
+ }
622
1082
  if (key === "s") {
623
1083
  options.setToolMode((prev) => prev === "select" ? "none" : "select");
624
1084
  options.onInteract();
@@ -627,6 +1087,14 @@ var useHotkeys = (options) => {
627
1087
  options.setToolMode((prev) => prev === "guides" ? "none" : "guides");
628
1088
  options.onInteract();
629
1089
  }
1090
+ if (key === "x") {
1091
+ options.setToolMode((prev) => prev === "xray" ? "none" : "xray");
1092
+ options.onInteract();
1093
+ }
1094
+ if (key === "r") {
1095
+ options.setRulersVisible((prev) => !prev);
1096
+ options.onInteract();
1097
+ }
630
1098
  if (key === "h") {
631
1099
  options.setGuideOrientation("horizontal");
632
1100
  options.onInteract();
@@ -651,22 +1119,22 @@ var useHotkeys = (options) => {
651
1119
  options.setAltPressed(false);
652
1120
  }
653
1121
  };
654
- window.addEventListener("keydown", handleKeyDown);
655
- window.addEventListener("keyup", handleKeyUp);
1122
+ target.addEventListener("keydown", handleKeyDown);
1123
+ target.addEventListener("keyup", handleKeyUp);
656
1124
  return () => {
657
- window.removeEventListener("keydown", handleKeyDown);
658
- window.removeEventListener("keyup", handleKeyUp);
1125
+ target.removeEventListener("keydown", handleKeyDown);
1126
+ target.removeEventListener("keyup", handleKeyUp);
659
1127
  };
660
1128
  }, [options]);
661
1129
  };
662
1130
 
663
1131
  // hooks/use-live-element-tracking.ts
664
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
1132
+ import { useEffect as useEffect2, useRef as useRef4 } from "react";
665
1133
 
666
- // geometry.ts
667
- var getViewportSize = () => ({
668
- width: typeof window === "undefined" ? 1 : window.innerWidth || 1,
669
- height: typeof window === "undefined" ? 1 : window.innerHeight || 1
1134
+ // core/geometry.ts
1135
+ var getViewportSize = (ownerWindow) => ({
1136
+ width: ownerWindow?.innerWidth || (typeof window === "undefined" ? 1 : window.innerWidth) || 1,
1137
+ height: ownerWindow?.innerHeight || (typeof window === "undefined" ? 1 : window.innerHeight) || 1
670
1138
  });
671
1139
  var normalizeRect = (rect, viewport = getViewportSize()) => ({
672
1140
  left: rect.left / viewport.width,
@@ -708,9 +1176,9 @@ var getDistanceToRect = (point, rect) => {
708
1176
  return Math.hypot(dx, dy);
709
1177
  };
710
1178
 
711
- // distances.ts
712
- var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB) => {
713
- const viewport = getViewportSize();
1179
+ // core/distances.ts
1180
+ var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB, ownerWindow = window) => {
1181
+ const viewport = getViewportSize(ownerWindow);
714
1182
  const normalizedRectA = normalizeRect(rectA, viewport);
715
1183
  const normalizedRectB = normalizeRect(rectB, viewport);
716
1184
  const rightA = rectA.left + rectA.width;
@@ -751,10 +1219,10 @@ var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB) => {
751
1219
  }
752
1220
  }
753
1221
  const normalizedConnectors = connectors.map((segment) => ({
754
- x1: clamp(segment.x1, 0, window.innerWidth),
755
- y1: clamp(segment.y1, 0, window.innerHeight),
756
- x2: clamp(segment.x2, 0, window.innerWidth),
757
- y2: clamp(segment.y2, 0, window.innerHeight)
1222
+ x1: clamp(segment.x1, 0, ownerWindow.innerWidth),
1223
+ y1: clamp(segment.y1, 0, ownerWindow.innerHeight),
1224
+ x2: clamp(segment.x2, 0, ownerWindow.innerWidth),
1225
+ y2: clamp(segment.y2, 0, ownerWindow.innerHeight)
758
1226
  })).filter(
759
1227
  (segment) => Math.abs(segment.x1 - segment.x2) > 0.5 || Math.abs(segment.y1 - segment.y2) > 0.5
760
1228
  );
@@ -771,17 +1239,17 @@ var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB) => {
771
1239
  connectors: normalizedConnectors
772
1240
  };
773
1241
  };
774
- var updateDistanceForResize = (distance, viewport = getViewportSize()) => {
775
- const normalizedRectA = distance.normalizedRectA ?? normalizeRect(distance.rectA);
776
- const normalizedRectB = distance.normalizedRectB ?? normalizeRect(distance.rectB);
1242
+ var updateDistanceForResize = (distance, viewport = getViewportSize(), ownerDocument = document, ownerWindow = window) => {
1243
+ const normalizedRectA = distance.normalizedRectA ?? normalizeRect(distance.rectA, viewport);
1244
+ const normalizedRectB = distance.normalizedRectB ?? normalizeRect(distance.rectB, viewport);
777
1245
  let rectA = distance.rectA;
778
1246
  let rectB = distance.rectB;
779
- if (distance.elementRefA && document.contains(distance.elementRefA)) {
1247
+ if (distance.elementRefA && ownerDocument.contains(distance.elementRefA)) {
780
1248
  rectA = distance.elementRefA.getBoundingClientRect();
781
1249
  } else {
782
1250
  rectA = denormalizeRect(normalizedRectA, viewport);
783
1251
  }
784
- if (distance.elementRefB && document.contains(distance.elementRefB)) {
1252
+ if (distance.elementRefB && ownerDocument.contains(distance.elementRefB)) {
785
1253
  rectB = distance.elementRefB.getBoundingClientRect();
786
1254
  } else {
787
1255
  rectB = denormalizeRect(normalizedRectB, viewport);
@@ -790,7 +1258,8 @@ var updateDistanceForResize = (distance, viewport = getViewportSize()) => {
790
1258
  rectA,
791
1259
  rectB,
792
1260
  distance.elementRefA,
793
- distance.elementRefB
1261
+ distance.elementRefB,
1262
+ ownerWindow
794
1263
  );
795
1264
  return {
796
1265
  ...updated,
@@ -798,7 +1267,7 @@ var updateDistanceForResize = (distance, viewport = getViewportSize()) => {
798
1267
  };
799
1268
  };
800
1269
 
801
- // dom.ts
1270
+ // core/dom.ts
802
1271
  var getElementLabel = (element) => {
803
1272
  const tag = element.tagName.toLowerCase();
804
1273
  const id = element.id ? `#${element.id}` : "";
@@ -831,24 +1300,38 @@ var getRectFromDomCached = (element) => {
831
1300
  };
832
1301
  var cachedElements = [];
833
1302
  var cachedFrame = -1;
1303
+ var cachedDocument = null;
834
1304
  var getFrameToken = () => {
835
1305
  if (typeof performance === "undefined") return 0;
836
1306
  return Math.floor(performance.now() / 16);
837
1307
  };
838
- var getBodyElementsCached = () => {
1308
+ var getBodyElementsCached = (ownerDocument = document) => {
839
1309
  const frame = getFrameToken();
840
- if (frame === cachedFrame && cachedElements.length > 0) {
1310
+ if (frame === cachedFrame && cachedDocument === ownerDocument && cachedElements.length > 0) {
841
1311
  return cachedElements;
842
1312
  }
843
1313
  cachedFrame = frame;
844
- cachedElements = Array.from(document.querySelectorAll("body *")).filter(
845
- (element) => element instanceof HTMLElement
846
- );
1314
+ cachedDocument = ownerDocument;
1315
+ const elements = [];
1316
+ const HTMLElementConstructor = ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
1317
+ const visit = (root) => {
1318
+ const walker = ownerDocument.createTreeWalker(root, 1);
1319
+ let node = walker.nextNode();
1320
+ while (node) {
1321
+ if (node instanceof HTMLElementConstructor) {
1322
+ elements.push(node);
1323
+ if (node.shadowRoot) visit(node.shadowRoot);
1324
+ }
1325
+ node = walker.nextNode();
1326
+ }
1327
+ };
1328
+ if (ownerDocument.body) visit(ownerDocument.body);
1329
+ cachedElements = elements;
847
1330
  return cachedElements;
848
1331
  };
849
- var getInspectMeasurement = (element) => {
1332
+ var getInspectMeasurement = (element, ownerWindow = window) => {
850
1333
  const rect = element.getBoundingClientRect();
851
- const style = window.getComputedStyle(element);
1334
+ const style = ownerWindow.getComputedStyle(element);
852
1335
  const padding = {
853
1336
  top: parseEdge(style.paddingTop),
854
1337
  right: parseEdge(style.paddingRight),
@@ -889,9 +1372,9 @@ var getInspectMeasurement = (element) => {
889
1372
  elementRef: element
890
1373
  };
891
1374
  };
892
- var updateMeasurementForResize = (measurement, viewport = getViewportSize()) => {
1375
+ var updateMeasurementForResize = (measurement, viewport = getViewportSize(), ownerDocument = document) => {
893
1376
  let rect = measurement.rect;
894
- if (measurement.elementRef && document.contains(measurement.elementRef)) {
1377
+ if (measurement.elementRef && ownerDocument.contains(measurement.elementRef)) {
895
1378
  rect = getRectFromDom(measurement.elementRef);
896
1379
  } else if (measurement.normalizedRect) {
897
1380
  rect = denormalizeRect(measurement.normalizedRect, viewport);
@@ -906,11 +1389,11 @@ var updateMeasurementForResize = (measurement, viewport = getViewportSize()) =>
906
1389
 
907
1390
  // hooks/use-live-element-tracking.ts
908
1391
  var useLiveElementTracking = (params) => {
909
- const frameRef = useRef3(null);
910
- useEffect3(() => {
1392
+ const frameRef = useRef4(null);
1393
+ useEffect2(() => {
911
1394
  if (!params.enabled) {
912
1395
  if (frameRef.current) {
913
- cancelAnimationFrame(frameRef.current);
1396
+ params.window.cancelAnimationFrame(frameRef.current);
914
1397
  }
915
1398
  frameRef.current = null;
916
1399
  return;
@@ -918,7 +1401,7 @@ var useLiveElementTracking = (params) => {
918
1401
  const tick = () => {
919
1402
  params.setMeasurements(
920
1403
  (prev) => prev.map((measurement) => {
921
- if (!measurement.elementRef || !document.contains(measurement.elementRef)) {
1404
+ if (!measurement.elementRef || !params.document.contains(measurement.elementRef)) {
922
1405
  return measurement;
923
1406
  }
924
1407
  const rect = getRectFromDom(measurement.elementRef);
@@ -932,7 +1415,7 @@ var useLiveElementTracking = (params) => {
932
1415
  })
933
1416
  );
934
1417
  params.setActiveMeasurement((prev) => {
935
- if (!prev?.elementRef || !document.contains(prev.elementRef))
1418
+ if (!prev?.elementRef || !params.document.contains(prev.elementRef))
936
1419
  return prev;
937
1420
  const rect = getRectFromDom(prev.elementRef);
938
1421
  if (rectAlmostEqual(rect, prev.rect)) return prev;
@@ -945,8 +1428,8 @@ var useLiveElementTracking = (params) => {
945
1428
  });
946
1429
  params.setHeldDistances(
947
1430
  (prev) => prev.map((distance) => {
948
- const canTrackA = distance.elementRefA && document.contains(distance.elementRefA);
949
- const canTrackB = distance.elementRefB && document.contains(distance.elementRefB);
1431
+ const canTrackA = distance.elementRefA && params.document.contains(distance.elementRefA);
1432
+ const canTrackB = distance.elementRefB && params.document.contains(distance.elementRefB);
950
1433
  if (!canTrackA && !canTrackB) return distance;
951
1434
  const rectA = canTrackA ? getRectFromDom(distance.elementRefA) : distance.rectA;
952
1435
  const rectB = canTrackB ? getRectFromDom(distance.elementRefB) : distance.rectB;
@@ -957,7 +1440,8 @@ var useLiveElementTracking = (params) => {
957
1440
  rectA,
958
1441
  rectB,
959
1442
  distance.elementRefA,
960
- distance.elementRefB
1443
+ distance.elementRefB,
1444
+ params.window
961
1445
  );
962
1446
  return {
963
1447
  ...updated,
@@ -966,19 +1450,19 @@ var useLiveElementTracking = (params) => {
966
1450
  })
967
1451
  );
968
1452
  const selected = params.selectedElementRef.current;
969
- if (selected && document.contains(selected)) {
1453
+ if (selected && params.document.contains(selected)) {
970
1454
  params.setSelectedMeasurement((prev) => {
971
- const next = getInspectMeasurement(selected);
1455
+ const next = getInspectMeasurement(selected, params.window);
972
1456
  if (prev && rectAlmostEqual(prev.rect, next.rect)) return prev;
973
1457
  return next;
974
1458
  });
975
1459
  }
976
1460
  params.setSelectedMeasurements(
977
1461
  (prev) => prev.map((measurement) => {
978
- if (!measurement.elementRef || !document.contains(measurement.elementRef)) {
1462
+ if (!measurement.elementRef || !params.document.contains(measurement.elementRef)) {
979
1463
  return measurement;
980
1464
  }
981
- const next = getInspectMeasurement(measurement.elementRef);
1465
+ const next = getInspectMeasurement(measurement.elementRef, params.window);
982
1466
  if (rectAlmostEqual(next.rect, measurement.rect)) return measurement;
983
1467
  return {
984
1468
  ...next,
@@ -987,18 +1471,18 @@ var useLiveElementTracking = (params) => {
987
1471
  })
988
1472
  );
989
1473
  const hover = params.hoverElementRef.current;
990
- if (hover && document.contains(hover)) {
1474
+ if (hover && params.document.contains(hover)) {
991
1475
  const rect = getRectFromDom(hover);
992
1476
  params.setHoverRect(
993
1477
  (prev) => prev && rectAlmostEqual(prev, rect) ? prev : rect
994
1478
  );
995
1479
  }
996
- frameRef.current = requestAnimationFrame(tick);
1480
+ frameRef.current = params.window.requestAnimationFrame(tick);
997
1481
  };
998
- frameRef.current = requestAnimationFrame(tick);
1482
+ frameRef.current = params.window.requestAnimationFrame(tick);
999
1483
  return () => {
1000
1484
  if (frameRef.current) {
1001
- cancelAnimationFrame(frameRef.current);
1485
+ params.window.cancelAnimationFrame(frameRef.current);
1002
1486
  }
1003
1487
  frameRef.current = null;
1004
1488
  };
@@ -1006,13 +1490,16 @@ var useLiveElementTracking = (params) => {
1006
1490
  };
1007
1491
 
1008
1492
  // hooks/use-measure-toggles.ts
1009
- import { useState as useState4 } from "react";
1493
+ import { useState as useState5 } from "react";
1010
1494
  var useMeasureToggles = (options = {}) => {
1011
- const [enabled, setEnabled] = useState4(options.initialEnabled ?? true);
1012
- const [altPressed, setAltPressed] = useState4(false);
1013
- const [toolMode, setToolMode] = useState4(
1495
+ const [enabled, setEnabled] = useState5(options.initialEnabled ?? true);
1496
+ const [altPressed, setAltPressed] = useState5(false);
1497
+ const [toolMode, setToolMode] = useState5(
1014
1498
  options.initialToolMode ?? "none"
1015
1499
  );
1500
+ const [rulersVisible, setRulersVisible] = useState5(
1501
+ options.initialRulersVisible ?? false
1502
+ );
1016
1503
  const holdEnabled = false;
1017
1504
  const multiMeasureEnabled = false;
1018
1505
  const snapGuidesEnabled = true;
@@ -1026,6 +1513,8 @@ var useMeasureToggles = (options = {}) => {
1026
1513
  setAltPressed,
1027
1514
  toolMode,
1028
1515
  setToolMode,
1516
+ rulersVisible,
1517
+ setRulersVisible,
1029
1518
  guidesEnabled,
1030
1519
  multiMeasureEnabled,
1031
1520
  snapGuidesEnabled,
@@ -1034,16 +1523,16 @@ var useMeasureToggles = (options = {}) => {
1034
1523
  };
1035
1524
 
1036
1525
  // hooks/use-measurement-state.ts
1037
- import { useState as useState5 } from "react";
1526
+ import { useState as useState6 } from "react";
1038
1527
  var useMeasurementState = (options = {}) => {
1039
- const [activeMeasurement, setActiveMeasurement] = useState5(options.initialActiveMeasurement ?? null);
1040
- const [measurements, setMeasurements] = useState5(
1528
+ const [activeMeasurement, setActiveMeasurement] = useState6(options.initialActiveMeasurement ?? null);
1529
+ const [measurements, setMeasurements] = useState6(
1041
1530
  options.initialMeasurements ?? []
1042
1531
  );
1043
- const [selectedMeasurement, setSelectedMeasurement] = useState5(null);
1044
- const [selectedMeasurements, setSelectedMeasurements] = useState5([]);
1045
- const [hoverRect, setHoverRect] = useState5(null);
1046
- const [heldDistances, setHeldDistances] = useState5(
1532
+ const [selectedMeasurement, setSelectedMeasurement] = useState6(null);
1533
+ const [selectedMeasurements, setSelectedMeasurements] = useState6([]);
1534
+ const [hoverRect, setHoverRect] = useState6(null);
1535
+ const [heldDistances, setHeldDistances] = useState6(
1047
1536
  options.initialHeldDistances ?? []
1048
1537
  );
1049
1538
  return {
@@ -1065,7 +1554,7 @@ var useMeasurementState = (options = {}) => {
1065
1554
  // hooks/use-measurer-derived.ts
1066
1555
  import { useMemo } from "react";
1067
1556
 
1068
- // edge-visibility.ts
1557
+ // core/edge-visibility.ts
1069
1558
  var rangesOverlap = (a1, a2, b1, b2) => Math.min(a2, b2) - Math.max(a1, b1) > 0;
1070
1559
  var getEdgeVisibilityForRects = (rects, epsilon = 1) => {
1071
1560
  const visibility = rects.map(() => ({
@@ -1105,24 +1594,25 @@ var getEdgeVisibilityForRects = (rects, epsilon = 1) => {
1105
1594
  return visibility;
1106
1595
  };
1107
1596
 
1108
- // guides.ts
1109
- var getGuideRect = (guide) => {
1110
- const viewport = getViewportSize();
1597
+ // core/guides.ts
1598
+ var getGuideRect = (guide, ownerWindow = window) => {
1599
+ const viewport = getViewportSize(ownerWindow);
1111
1600
  return guide.orientation === "vertical" ? { left: guide.position, top: 0, width: 1, height: viewport.height } : { left: 0, top: guide.position, width: viewport.width, height: 1 };
1112
1601
  };
1113
1602
  var getGuideDistance = (guide, point) => guide.orientation === "vertical" ? Math.abs(guide.position - point.x) : Math.abs(guide.position - point.y);
1114
1603
  var getSnapGuidePosition = (params) => {
1604
+ const ownerDocument = params.document ?? document;
1115
1605
  const { orientation, point, snapGuidesEnabled, overlayNode, guides } = params;
1116
1606
  if (!snapGuidesEnabled) {
1117
1607
  return orientation === "vertical" ? point.x : point.y;
1118
1608
  }
1119
- const elements = getBodyElementsCached();
1609
+ const elements = getBodyElementsCached(ownerDocument);
1120
1610
  let bestValue = orientation === "vertical" ? point.x : point.y;
1121
1611
  let bestDistance = GUIDE_SNAP_DISTANCE + 1;
1122
1612
  for (const element of elements) {
1123
- if (!(element instanceof HTMLElement)) continue;
1613
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
1124
1614
  if (overlayNode && overlayNode.contains(element)) continue;
1125
- if (element === document.body || element === document.documentElement)
1615
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1126
1616
  continue;
1127
1617
  const rect = getRectFromDomCached(element);
1128
1618
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1158,7 +1648,7 @@ var getSnapGuidePosition = (params) => {
1158
1648
  return bestDistance <= GUIDE_SNAP_DISTANCE ? bestValue : orientation === "vertical" ? point.x : point.y;
1159
1649
  };
1160
1650
 
1161
- // option-measurements.ts
1651
+ // core/option-measurements.ts
1162
1652
  var getHoveredGuide = (point, guides) => {
1163
1653
  if (!point || guides.length === 0) return null;
1164
1654
  let closest = null;
@@ -1179,6 +1669,8 @@ var getSelectedGuide = (guides, selectedGuideIds) => {
1179
1669
  return guides.find((guide) => guide.id === lastId) ?? null;
1180
1670
  };
1181
1671
  var getOptionPairOverlay = (params) => {
1672
+ const ownerDocument = params.document ?? document;
1673
+ const ownerWindow = params.window ?? window;
1182
1674
  if (!params.altPressed) return null;
1183
1675
  if (!params.selectedGuide && !params.primarySelectedMeasurement) return null;
1184
1676
  const primary = params.primarySelectedMeasurement;
@@ -1207,10 +1699,13 @@ var getOptionPairOverlay = (params) => {
1207
1699
  selectedTarget.rect,
1208
1700
  hoverTarget.rect,
1209
1701
  selectedTarget.element ?? null,
1210
- hoverTarget.element ?? null
1702
+ hoverTarget.element ?? null,
1703
+ ownerWindow
1211
1704
  );
1212
1705
  };
1213
1706
  var getOptionContainerLines = (params) => {
1707
+ const ownerDocument = params.document ?? document;
1708
+ const ownerWindow = params.window ?? window;
1214
1709
  if (!params.altPressed || !params.primarySelectedMeasurement || params.optionPairOverlay)
1215
1710
  return null;
1216
1711
  if (params.selectedGuideIds.length > 0) return null;
@@ -1218,11 +1713,11 @@ var getOptionContainerLines = (params) => {
1218
1713
  if (params.selectedElement && params.hoverElement && params.hoverElement !== params.selectedElement && params.hoverElement.contains(params.selectedElement)) {
1219
1714
  containerElement = params.hoverElement;
1220
1715
  }
1221
- const containerRect = containerElement && containerElement !== document.body && containerElement !== document.documentElement ? getRectFromDom(containerElement) : {
1716
+ const containerRect = containerElement && containerElement !== ownerDocument.body && containerElement !== ownerDocument.documentElement ? getRectFromDom(containerElement) : {
1222
1717
  left: 0,
1223
1718
  top: 0,
1224
- width: window.innerWidth,
1225
- height: window.innerHeight
1719
+ width: ownerWindow.innerWidth,
1720
+ height: ownerWindow.innerHeight
1226
1721
  };
1227
1722
  const rect = params.primarySelectedMeasurement.rect;
1228
1723
  const right = rect.left + rect.width;
@@ -1261,12 +1756,20 @@ var getOptionContainerLines = (params) => {
1261
1756
  };
1262
1757
  };
1263
1758
 
1264
- // selection-helpers.ts
1759
+ // core/selection-helpers.ts
1760
+ var getOverlayHost = (overlayNode) => {
1761
+ if (!overlayNode) return null;
1762
+ const rootNode = overlayNode.getRootNode();
1763
+ return rootNode.nodeType === 11 ? rootNode.host : null;
1764
+ };
1265
1765
  var getPrimarySelectedMeasurement = (selectedMeasurements, selectedMeasurement) => selectedMeasurements.length > 0 ? selectedMeasurements[selectedMeasurements.length - 1] : selectedMeasurement;
1266
1766
  var getSelectedMeasurementHit = (params) => {
1767
+ const ownerDocument = params.document ?? document;
1768
+ const HTMLElementConstructor = ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
1769
+ const overlayHost = getOverlayHost(params.overlayNode);
1267
1770
  const candidates = params.selectedMeasurements.map((measurement) => {
1268
1771
  const element = measurement.elementRef;
1269
- if (!element || !document.contains(element)) return null;
1772
+ if (!element || !ownerDocument.contains(element)) return null;
1270
1773
  const rect = getRectFromDom(element);
1271
1774
  return {
1272
1775
  measurement,
@@ -1276,10 +1779,11 @@ var getSelectedMeasurementHit = (params) => {
1276
1779
  };
1277
1780
  }).filter((item) => item !== null).sort((a, b) => a.area - b.area);
1278
1781
  if (candidates.length === 0) return null;
1279
- const stack = document.elementsFromPoint(params.point.x, params.point.y);
1782
+ const stack = ownerDocument.elementsFromPoint(params.point.x, params.point.y);
1280
1783
  for (const element of stack) {
1281
- if (!(element instanceof HTMLElement)) continue;
1784
+ if (!(element instanceof HTMLElementConstructor)) continue;
1282
1785
  if (params.overlayNode && params.overlayNode.contains(element)) continue;
1786
+ if (overlayHost && element === overlayHost) continue;
1283
1787
  for (const candidate of candidates) {
1284
1788
  if (candidate.element === element || candidate.element.contains(element)) {
1285
1789
  return candidate.measurement;
@@ -1296,6 +1800,8 @@ var getSelectedMeasurementHit = (params) => {
1296
1800
 
1297
1801
  // hooks/use-measurer-derived.ts
1298
1802
  var useMeasurerDerived = ({
1803
+ document: document2,
1804
+ window: window2,
1299
1805
  start,
1300
1806
  end,
1301
1807
  selectedMeasurements,
@@ -1380,7 +1886,9 @@ var useMeasurerDerived = ({
1380
1886
  selectedGuide,
1381
1887
  hoverGuide,
1382
1888
  hoverElement,
1383
- selectedElementRef: selectedElement
1889
+ selectedElementRef: selectedElement,
1890
+ document: document2,
1891
+ window: window2
1384
1892
  });
1385
1893
  }, [
1386
1894
  altPressed,
@@ -1397,7 +1905,9 @@ var useMeasurerDerived = ({
1397
1905
  optionPairOverlay,
1398
1906
  selectedGuideIds,
1399
1907
  selectedElement,
1400
- hoverElement
1908
+ hoverElement,
1909
+ document: document2,
1910
+ window: window2
1401
1911
  });
1402
1912
  }, [
1403
1913
  altPressed,
@@ -1411,8 +1921,11 @@ var useMeasurerDerived = ({
1411
1921
  if (!altPressed || !guidesEnabled) return null;
1412
1922
  if (selectedGuide && hoverGuide && selectedGuide.id !== hoverGuide.id) {
1413
1923
  return getDistanceOverlay(
1414
- getGuideRect(selectedGuide),
1415
- getGuideRect(hoverGuide)
1924
+ getGuideRect(selectedGuide, window2),
1925
+ getGuideRect(hoverGuide, window2),
1926
+ null,
1927
+ null,
1928
+ window2
1416
1929
  );
1417
1930
  }
1418
1931
  if (!guidePreview) return null;
@@ -1435,8 +1948,11 @@ var useMeasurerDerived = ({
1435
1948
  );
1436
1949
  if (!nearest) return null;
1437
1950
  return getDistanceOverlay(
1438
- getGuideRect(previewGuide),
1439
- getGuideRect(nearest.guide)
1951
+ getGuideRect(previewGuide, window2),
1952
+ getGuideRect(nearest.guide, window2),
1953
+ null,
1954
+ null,
1955
+ window2
1440
1956
  );
1441
1957
  }, [
1442
1958
  altPressed,
@@ -1506,7 +2022,7 @@ var useMeasurerDerived = ({
1506
2022
  };
1507
2023
 
1508
2024
  // hooks/use-measurer-history.ts
1509
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
2025
+ import { useCallback as useCallback3, useRef as useRef5 } from "react";
1510
2026
  var HISTORY_LIMIT = 50;
1511
2027
  var useMeasurerHistory = ({
1512
2028
  toggles,
@@ -1552,9 +2068,9 @@ var useMeasurerHistory = ({
1552
2068
  setSelectedElement,
1553
2069
  clearSelectionRect
1554
2070
  } = transient;
1555
- const historyRef = useRef4([]);
1556
- const futureRef = useRef4([]);
1557
- const historySignatureRef = useRef4(null);
2071
+ const historyRef = useRef5([]);
2072
+ const futureRef = useRef5([]);
2073
+ const historySignatureRef = useRef5(null);
1558
2074
  const captureSnapshot = useCallback3(() => {
1559
2075
  return {
1560
2076
  enabled,
@@ -1716,49 +2232,32 @@ var useMeasurerHistory = ({
1716
2232
  committed = true;
1717
2233
  };
1718
2234
  }, [recordSnapshot]);
1719
- useEffect4(() => {
1720
- const handleKeyDown = (event) => {
1721
- if (!(event.metaKey || event.ctrlKey)) return;
1722
- if (event.key.toLowerCase() !== "z") return;
1723
- if (event.shiftKey) {
1724
- if (futureRef.current.length === 0) return;
1725
- event.preventDefault();
1726
- redo();
1727
- return;
1728
- }
1729
- if (historyRef.current.length === 0) return;
1730
- event.preventDefault();
1731
- undo();
1732
- };
1733
- window.addEventListener("keydown", handleKeyDown);
1734
- return () => {
1735
- window.removeEventListener("keydown", handleKeyDown);
1736
- };
1737
- }, [redo, undo]);
1738
2235
  return {
1739
2236
  recordSnapshot,
1740
2237
  createActionCommit,
1741
2238
  setToolModeWithHistory,
1742
2239
  setGuideOrientationWithHistory,
1743
- setEnabledWithHistory
2240
+ setEnabledWithHistory,
2241
+ undo,
2242
+ redo
1744
2243
  };
1745
2244
  };
1746
2245
 
1747
2246
  // hooks/use-measurer-local-state.ts
1748
- import { useCallback as useCallback4, useState as useState6 } from "react";
2247
+ import { useCallback as useCallback4, useState as useState7 } from "react";
1749
2248
  var useMeasurerLocalState = ({
1750
2249
  selectedElementRef,
1751
2250
  hoverElementRef,
1752
2251
  selectionRectRef
1753
2252
  }) => {
1754
- const [selectionOriginRect, setSelectionOriginRect] = useState6(
2253
+ const [selectionOriginRect, setSelectionOriginRect] = useState7(
1755
2254
  null
1756
2255
  );
1757
- const [hoverPointer, setHoverPointer] = useState6(null);
1758
- const [hoverElement, setHoverElementState] = useState6(
2256
+ const [hoverPointer, setHoverPointer] = useState7(null);
2257
+ const [hoverElement, setHoverElementState] = useState7(
1759
2258
  null
1760
2259
  );
1761
- const [selectedElement, setSelectedElementState] = useState6(null);
2260
+ const [selectedElement, setSelectedElementState] = useState7(null);
1762
2261
  const setSelectedElement = useCallback4(
1763
2262
  (element) => {
1764
2263
  selectedElementRef.current = element;
@@ -1791,9 +2290,9 @@ var useMeasurerLocalState = ({
1791
2290
  };
1792
2291
 
1793
2292
  // hooks/use-measurer-pointer.ts
1794
- import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef5 } from "react";
2293
+ import { useCallback as useCallback5, useEffect as useEffect3, useRef as useRef6 } from "react";
1795
2294
 
1796
- // targets.ts
2295
+ // core/targets.ts
1797
2296
  var pickMultiTargets = (selectionRect, items) => {
1798
2297
  const candidates = items.map(({ element, rect }) => {
1799
2298
  const overlapArea = intersectionArea(selectionRect, rect);
@@ -1858,17 +2357,39 @@ var pickPointTarget = (point, items) => {
1858
2357
  return scored[0].element;
1859
2358
  };
1860
2359
 
1861
- // selection.ts
1862
- var getTargetElement = (point, overlayNode) => {
2360
+ // core/selection.ts
2361
+ var getOverlayHost2 = (overlayNode) => {
2362
+ if (!overlayNode) return null;
2363
+ const rootNode = overlayNode.getRootNode();
2364
+ return rootNode.nodeType === 11 ? rootNode.host : null;
2365
+ };
2366
+ var isOverlayElement = (element, overlayNode, overlayHost) => {
2367
+ if (overlayNode && overlayNode.contains(element)) return true;
2368
+ if (overlayHost && element === overlayHost) return true;
2369
+ return false;
2370
+ };
2371
+ var getDeepestElementAt = (element, point) => {
2372
+ let current = element;
2373
+ const HTMLElementConstructor = element.ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
2374
+ while (current instanceof HTMLElementConstructor && current.shadowRoot) {
2375
+ const nested = current.shadowRoot.elementFromPoint(point.x, point.y);
2376
+ if (!nested || nested === current) break;
2377
+ current = nested;
2378
+ }
2379
+ return current;
2380
+ };
2381
+ var getTargetElement = (point, overlayNode, ownerDocument = document) => {
2382
+ const overlayHost = getOverlayHost2(overlayNode);
1863
2383
  if (overlayNode) {
1864
2384
  const previous = overlayNode.style.pointerEvents;
1865
2385
  overlayNode.style.pointerEvents = "none";
1866
- const elements2 = document.elementsFromPoint(point.x, point.y);
2386
+ const elements2 = ownerDocument.elementsFromPoint(point.x, point.y);
1867
2387
  overlayNode.style.pointerEvents = previous;
1868
- for (const element of elements2) {
1869
- if (!(element instanceof HTMLElement)) continue;
1870
- if (overlayNode.contains(element)) continue;
1871
- if (element === document.body || element === document.documentElement)
2388
+ for (const rawElement of elements2) {
2389
+ const element = getDeepestElementAt(rawElement, point);
2390
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
2391
+ if (isOverlayElement(element, overlayNode, overlayHost)) continue;
2392
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1872
2393
  continue;
1873
2394
  const rect = element.getBoundingClientRect();
1874
2395
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1876,10 +2397,12 @@ var getTargetElement = (point, overlayNode) => {
1876
2397
  }
1877
2398
  return null;
1878
2399
  }
1879
- const elements = document.elementsFromPoint(point.x, point.y);
1880
- for (const element of elements) {
1881
- if (!(element instanceof HTMLElement)) continue;
1882
- if (element === document.body || element === document.documentElement)
2400
+ const elements = ownerDocument.elementsFromPoint(point.x, point.y);
2401
+ for (const rawElement of elements) {
2402
+ const element = getDeepestElementAt(rawElement, point);
2403
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
2404
+ if (isOverlayElement(element, overlayNode, overlayHost)) continue;
2405
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1883
2406
  continue;
1884
2407
  const rect = element.getBoundingClientRect();
1885
2408
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1887,33 +2410,34 @@ var getTargetElement = (point, overlayNode) => {
1887
2410
  }
1888
2411
  return null;
1889
2412
  };
1890
- var getSnappedClickTarget = (point, overlayNode, snapEnabled) => {
1891
- if (!snapEnabled) return getTargetElement(point, overlayNode);
2413
+ var getSnappedClickTarget = (point, overlayNode, snapEnabled, ownerDocument = document) => {
2414
+ if (!snapEnabled) return getTargetElement(point, overlayNode, ownerDocument);
1892
2415
  const probeRect = {
1893
2416
  left: point.x - 20,
1894
2417
  top: point.y - 20,
1895
2418
  width: 40,
1896
2419
  height: 40
1897
2420
  };
1898
- const entries = getSelectionEntries(probeRect, overlayNode);
1899
- return pickPointTarget(point, entries) ?? pickSingleTarget(probeRect, point, entries) ?? getTargetElement(point, overlayNode);
2421
+ const entries = getSelectionEntries(probeRect, overlayNode, ownerDocument);
2422
+ return pickPointTarget(point, entries) ?? pickSingleTarget(probeRect, point, entries) ?? getTargetElement(point, overlayNode, ownerDocument);
1900
2423
  };
1901
- var getSelectionEntries = (rect, overlayNode) => {
2424
+ var getSelectionEntries = (rect, overlayNode, ownerDocument = document) => {
2425
+ const overlayHost = getOverlayHost2(overlayNode);
1902
2426
  const frame = getFrameToken();
1903
2427
  const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(
1904
2428
  rect.width
1905
2429
  )}:${Math.round(rect.height)}`;
1906
- if (frame === cachedSelectionFrame && cachedSelectionKey === key && cachedOverlayNode === overlayNode) {
2430
+ if (frame === cachedSelectionFrame && cachedSelectionKey === key && cachedOverlayNode === overlayNode && cachedSelectionDocument === ownerDocument) {
1907
2431
  return cachedSelectionEntries;
1908
2432
  }
1909
2433
  const minLeft = rect.left - 1;
1910
2434
  const minTop = rect.top - 1;
1911
2435
  const maxRight = rect.left + rect.width + 1;
1912
2436
  const maxBottom = rect.top + rect.height + 1;
1913
- const elements = getBodyElementsCached();
2437
+ const elements = getBodyElementsCached(ownerDocument);
1914
2438
  const entries = elements.map((element) => ({ element, rect: getRectFromDomCached(element) })).filter(({ element, rect: elementRect }) => {
1915
- if (overlayNode && overlayNode.contains(element)) return false;
1916
- if (element === document.body || element === document.documentElement)
2439
+ if (isOverlayElement(element, overlayNode, overlayHost)) return false;
2440
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1917
2441
  return false;
1918
2442
  if (elementRect.width < MIN_MULTI_TARGET_SIZE || elementRect.height < MIN_MULTI_TARGET_SIZE) {
1919
2443
  return false;
@@ -1927,6 +2451,7 @@ var getSelectionEntries = (rect, overlayNode) => {
1927
2451
  cachedSelectionFrame = frame;
1928
2452
  cachedSelectionKey = key;
1929
2453
  cachedOverlayNode = overlayNode;
2454
+ cachedSelectionDocument = ownerDocument;
1930
2455
  cachedSelectionEntries = entries;
1931
2456
  return entries;
1932
2457
  };
@@ -1934,30 +2459,33 @@ var cachedSelectionFrame = -1;
1934
2459
  var cachedSelectionKey = "";
1935
2460
  var cachedSelectionEntries = [];
1936
2461
  var cachedOverlayNode = null;
2462
+ var cachedSelectionDocument = null;
1937
2463
  var getSelectionCacheKey = (rect) => `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(
1938
2464
  rect.width
1939
2465
  )}:${Math.round(rect.height)}`;
1940
- var getSelectionEntriesCached = (rect, overlayNode, cache) => {
2466
+ var getSelectionEntriesCached = (rect, overlayNode, cache, ownerDocument = document) => {
1941
2467
  const frame = getFrameToken();
1942
2468
  const key = getSelectionCacheKey(rect);
1943
2469
  if (cache.key === key && cache.overlayNode === overlayNode && cache.frame === frame) {
1944
2470
  return cache.entries;
1945
2471
  }
1946
- const entries = getSelectionEntries(rect, overlayNode);
2472
+ const entries = getSelectionEntries(rect, overlayNode, ownerDocument);
1947
2473
  cache.key = key;
1948
2474
  cache.overlayNode = overlayNode;
1949
2475
  cache.frame = frame;
1950
2476
  cache.entries = entries;
1951
2477
  return entries;
1952
2478
  };
1953
- var getElementsInRectCached = (rect, overlayNode, cache) => {
1954
- const entries = getSelectionEntriesCached(rect, overlayNode, cache);
2479
+ var getElementsInRectCached = (rect, overlayNode, cache, ownerDocument = document) => {
2480
+ const entries = getSelectionEntriesCached(rect, overlayNode, cache, ownerDocument);
1955
2481
  if (entries.length === 0) return [];
1956
2482
  return pickMultiTargets(rect, entries);
1957
2483
  };
1958
2484
 
1959
2485
  // hooks/use-measurer-pointer.ts
1960
2486
  var useMeasurerPointer = ({
2487
+ document: document2,
2488
+ window: window2,
1961
2489
  toolbarRef,
1962
2490
  overlayRef,
1963
2491
  selectionRectRef,
@@ -2001,46 +2529,26 @@ var useMeasurerPointer = ({
2001
2529
  setHoverPointer,
2002
2530
  clearSelectionRect
2003
2531
  }) => {
2004
- const hoverFrameRef = useRef5(null);
2005
- const hoverPointRef = useRef5(null);
2006
- const selectionCacheRef = useRef5({
2532
+ const hoverFrameRef = useRef6(null);
2533
+ const hoverPointRef = useRef6(null);
2534
+ const selectionCacheRef = useRef6({
2007
2535
  key: "",
2008
2536
  entries: [],
2009
2537
  overlayNode: null,
2010
2538
  frame: -1
2011
2539
  });
2012
- const shiftDragRef = useRef5(false);
2013
- const shiftToggleElementRef = useRef5(null);
2014
- const guidesEnabledRef = useRef5(guidesEnabled);
2015
- const guidesRef = useRef5(guides);
2016
- const snapGuidesEnabledRef = useRef5(snapGuidesEnabled);
2017
- const draggingGuideIdRef = useRef5(draggingGuideId);
2018
- const guideOrientationRef = useRef5(guideOrientation);
2019
- useEffect5(() => {
2540
+ const shiftDragRef = useRef6(false);
2541
+ const shiftToggleElementRef = useRef6(null);
2542
+ useEffect3(() => {
2020
2543
  return () => {
2021
2544
  if (hoverFrameRef.current) {
2022
- cancelAnimationFrame(hoverFrameRef.current);
2545
+ window2.cancelAnimationFrame(hoverFrameRef.current);
2023
2546
  }
2024
2547
  };
2025
2548
  }, []);
2026
- useEffect5(() => {
2027
- guidesEnabledRef.current = guidesEnabled;
2028
- }, [guidesEnabled]);
2029
- useEffect5(() => {
2030
- guidesRef.current = guides;
2031
- }, [guides]);
2032
- useEffect5(() => {
2033
- snapGuidesEnabledRef.current = snapGuidesEnabled;
2034
- }, [snapGuidesEnabled]);
2035
- useEffect5(() => {
2036
- guideOrientationRef.current = guideOrientation;
2037
- }, [guideOrientation]);
2038
- useEffect5(() => {
2039
- draggingGuideIdRef.current = draggingGuideId;
2040
- }, [draggingGuideId]);
2041
2549
  const updateHoverTarget = useCallback5(
2042
2550
  (point) => {
2043
- const target = getTargetElement(point, overlayRef.current);
2551
+ const target = getTargetElement(point, overlayRef.current, document2);
2044
2552
  if (target) {
2045
2553
  const rect = target.getBoundingClientRect();
2046
2554
  setHoverRect({
@@ -2055,14 +2563,14 @@ var useMeasurerPointer = ({
2055
2563
  setHoverElement(null);
2056
2564
  }
2057
2565
  },
2058
- [overlayRef, setHoverElement, setHoverRect]
2566
+ [document2, overlayRef, setHoverElement, setHoverRect]
2059
2567
  );
2060
2568
  const updateHoverElement = useCallback5(
2061
2569
  (point) => {
2062
- const target = getTargetElement(point, overlayRef.current);
2570
+ const target = getTargetElement(point, overlayRef.current, document2);
2063
2571
  setHoverElement(target);
2064
2572
  },
2065
- [overlayRef, setHoverElement]
2573
+ [document2, overlayRef, setHoverElement]
2066
2574
  );
2067
2575
  const handlePointerDown = useCallback5(
2068
2576
  (event) => {
@@ -2077,7 +2585,8 @@ var useMeasurerPointer = ({
2077
2585
  shiftToggleElementRef.current = event.shiftKey ? getSelectedMeasurementHit({
2078
2586
  point,
2079
2587
  selectedMeasurements,
2080
- overlayNode: overlayRef.current
2588
+ overlayNode: overlayRef.current,
2589
+ document: document2
2081
2590
  })?.elementRef ?? null : null;
2082
2591
  selectionCacheRef.current.key = "";
2083
2592
  if (altPressed && optionPairOverlay) {
@@ -2099,10 +2608,11 @@ var useMeasurerPointer = ({
2099
2608
  snapGuidesEnabled,
2100
2609
  overlayNode: overlayRef.current,
2101
2610
  guides,
2102
- draggingGuideId
2611
+ draggingGuideId,
2612
+ document: document2
2103
2613
  });
2104
2614
  const id = createId();
2105
- setSelectedGuideIds([]);
2615
+ setSelectedGuideIds([id]);
2106
2616
  setGuides((prev) => [
2107
2617
  ...prev,
2108
2618
  { id, orientation: guideOrientation, position }
@@ -2125,6 +2635,7 @@ var useMeasurerPointer = ({
2125
2635
  clearSelectionRect,
2126
2636
  createActionCommit,
2127
2637
  draggingGuideId,
2638
+ document2,
2128
2639
  enabled,
2129
2640
  guideOrientation,
2130
2641
  guides,
@@ -2166,31 +2677,32 @@ var useMeasurerPointer = ({
2166
2677
  }
2167
2678
  hoverPointRef.current = point;
2168
2679
  if (!hoverFrameRef.current) {
2169
- hoverFrameRef.current = requestAnimationFrame(() => {
2680
+ hoverFrameRef.current = window2.requestAnimationFrame(() => {
2170
2681
  const latest = hoverPointRef.current;
2171
- if (latest && !draggingGuideIdRef.current) {
2682
+ if (latest && !draggingGuideId) {
2172
2683
  if (hoverHighlightEnabled) {
2173
2684
  updateHoverTarget(latest);
2174
2685
  } else {
2175
2686
  updateHoverElement(latest);
2176
2687
  }
2177
2688
  }
2178
- if (latest && guidesRef.current.length > 0) {
2689
+ if (latest && guides.length > 0) {
2179
2690
  setHoverPointer(latest);
2180
2691
  } else {
2181
2692
  setHoverPointer(null);
2182
2693
  }
2183
- if (guidesEnabledRef.current && latest && !draggingGuideIdRef.current) {
2694
+ if (guidesEnabled && latest && !draggingGuideId) {
2184
2695
  const position = getSnapGuidePosition({
2185
- orientation: guideOrientationRef.current,
2696
+ orientation: guideOrientation,
2186
2697
  point: latest,
2187
- snapGuidesEnabled: snapGuidesEnabledRef.current,
2698
+ snapGuidesEnabled,
2188
2699
  overlayNode: overlayRef.current,
2189
- guides: guidesRef.current,
2190
- draggingGuideId: draggingGuideIdRef.current
2700
+ guides,
2701
+ draggingGuideId,
2702
+ document: document2
2191
2703
  });
2192
2704
  setGuidePreview({
2193
- orientation: guideOrientationRef.current,
2705
+ orientation: guideOrientation,
2194
2706
  position
2195
2707
  });
2196
2708
  } else {
@@ -2207,10 +2719,11 @@ var useMeasurerPointer = ({
2207
2719
  position: getSnapGuidePosition({
2208
2720
  orientation: guide.orientation,
2209
2721
  point,
2210
- snapGuidesEnabled: snapGuidesEnabledRef.current,
2722
+ snapGuidesEnabled,
2211
2723
  overlayNode: overlayRef.current,
2212
- guides: guidesRef.current,
2213
- draggingGuideId: draggingGuideIdRef.current
2724
+ guides,
2725
+ draggingGuideId,
2726
+ document: document2
2214
2727
  })
2215
2728
  } : guide
2216
2729
  )
@@ -2232,8 +2745,11 @@ var useMeasurerPointer = ({
2232
2745
  draggingGuideId,
2233
2746
  enabled,
2234
2747
  hoverHighlightEnabled,
2748
+ guides,
2749
+ guidesEnabled,
2235
2750
  isDragging,
2236
2751
  overlayRef,
2752
+ guideOrientation,
2237
2753
  setAltPressed,
2238
2754
  setEnd,
2239
2755
  setGuidePreview,
@@ -2242,6 +2758,7 @@ var useMeasurerPointer = ({
2242
2758
  setHoverPointer,
2243
2759
  setHoverRect,
2244
2760
  setIsDragging,
2761
+ snapGuidesEnabled,
2245
2762
  start,
2246
2763
  toolMode,
2247
2764
  toolbarRef,
@@ -2295,7 +2812,8 @@ var useMeasurerPointer = ({
2295
2812
  const elements = getElementsInRectCached(
2296
2813
  selectionRect,
2297
2814
  overlayRef.current,
2298
- selectionCacheRef.current
2815
+ selectionCacheRef.current,
2816
+ document2
2299
2817
  );
2300
2818
  const hasSameSelection = elements.length === selectedMeasurements.length && elements.every(
2301
2819
  (element, index) => selectedMeasurements[index]?.elementRef === element
@@ -2306,7 +2824,7 @@ var useMeasurerPointer = ({
2306
2824
  if (!hasSameSelection) {
2307
2825
  commit();
2308
2826
  const nextMeasurements = elements.map((element) => ({
2309
- ...getInspectMeasurement(element),
2827
+ ...getInspectMeasurement(element, window2),
2310
2828
  originRect: selectionRect
2311
2829
  }));
2312
2830
  setSelectedMeasurements(nextMeasurements);
@@ -2340,7 +2858,8 @@ var useMeasurerPointer = ({
2340
2858
  ) ?? null : getSelectedMeasurementHit({
2341
2859
  point,
2342
2860
  selectedMeasurements,
2343
- overlayNode: overlayRef.current
2861
+ overlayNode: overlayRef.current,
2862
+ document: document2
2344
2863
  });
2345
2864
  if (event.shiftKey && selectedHit) {
2346
2865
  commit();
@@ -2370,9 +2889,9 @@ var useMeasurerPointer = ({
2370
2889
  resetDragState();
2371
2890
  return;
2372
2891
  }
2373
- const target = event.shiftKey ? getTargetElement(point, overlayRef.current) ?? getSnappedClickTarget(point, overlayRef.current, snapEnabled) : getSnappedClickTarget(point, overlayRef.current, snapEnabled);
2892
+ const target = event.shiftKey ? getTargetElement(point, overlayRef.current, document2) ?? getSnappedClickTarget(point, overlayRef.current, snapEnabled, document2) : getSnappedClickTarget(point, overlayRef.current, snapEnabled, document2);
2374
2893
  if (target) {
2375
- const inspectMeasurement = getInspectMeasurement(target);
2894
+ const inspectMeasurement = getInspectMeasurement(target, window2);
2376
2895
  clearTransientMeasurements();
2377
2896
  if (event.shiftKey) {
2378
2897
  const alreadySelected = selectedMeasurements.some(
@@ -2471,41 +2990,48 @@ var useMeasurerPointer = ({
2471
2990
  };
2472
2991
 
2473
2992
  // hooks/use-overlay-refs.ts
2474
- import { useRef as useRef6 } from "react";
2993
+ import { useRef as useRef7 } from "react";
2475
2994
  var useOverlayRefs = () => {
2476
- const overlayRef = useRef6(null);
2477
- const selectedElementRef = useRef6(null);
2478
- const hoverElementRef = useRef6(null);
2995
+ const overlayRef = useRef7(null);
2996
+ const selectedElementRef = useRef7(null);
2997
+ const hoverElementRef = useRef7(null);
2479
2998
  return { overlayRef, selectedElementRef, hoverElementRef };
2480
2999
  };
2481
3000
 
2482
3001
  // hooks/use-resize-sync.ts
2483
- import { useEffect as useEffect6, useRef as useRef7 } from "react";
3002
+ import { useEffect as useEffect4, useRef as useRef8 } from "react";
2484
3003
  var useResizeSync = (params) => {
2485
- const resizeFrameRef = useRef7(null);
2486
- const viewportRef = useRef7(getViewportSize());
2487
- useEffect6(() => {
3004
+ const resizeFrameRef = useRef8(null);
3005
+ const viewportRef = useRef8(getViewportSize(params.window));
3006
+ useEffect4(() => {
2488
3007
  const handleResize = () => {
2489
3008
  if (resizeFrameRef.current) {
2490
- cancelAnimationFrame(resizeFrameRef.current);
3009
+ params.window.cancelAnimationFrame(resizeFrameRef.current);
2491
3010
  }
2492
- resizeFrameRef.current = requestAnimationFrame(() => {
2493
- const viewport = getViewportSize();
3011
+ resizeFrameRef.current = params.window.requestAnimationFrame(() => {
3012
+ const viewport = getViewportSize(params.window);
2494
3013
  const previousViewport = viewportRef.current;
2495
3014
  params.setMeasurements(
2496
3015
  (prev) => prev.map(
2497
- (measurement) => updateMeasurementForResize(measurement, viewport)
3016
+ (measurement) => updateMeasurementForResize(measurement, viewport, params.document)
2498
3017
  )
2499
3018
  );
2500
3019
  params.setActiveMeasurement(
2501
- (prev) => prev ? updateMeasurementForResize(prev, viewport) : prev
3020
+ (prev) => prev ? updateMeasurementForResize(prev, viewport, params.document) : prev
2502
3021
  );
2503
3022
  params.setHeldDistances(
2504
- (prev) => prev.map((distance) => updateDistanceForResize(distance, viewport))
3023
+ (prev) => prev.map(
3024
+ (distance) => updateDistanceForResize(
3025
+ distance,
3026
+ viewport,
3027
+ params.document,
3028
+ params.window
3029
+ )
3030
+ )
2505
3031
  );
2506
- if (params.selectedElementRef.current) {
3032
+ if (params.selectedElementRef.current && params.document.contains(params.selectedElementRef.current)) {
2507
3033
  params.setSelectedMeasurement(
2508
- getInspectMeasurement(params.selectedElementRef.current)
3034
+ getInspectMeasurement(params.selectedElementRef.current, params.window)
2509
3035
  );
2510
3036
  }
2511
3037
  if (previousViewport.width > 0 && previousViewport.height > 0) {
@@ -2520,27 +3046,27 @@ var useResizeSync = (params) => {
2520
3046
  viewportRef.current = viewport;
2521
3047
  });
2522
3048
  };
2523
- window.addEventListener("resize", handleResize);
3049
+ params.window.addEventListener("resize", handleResize);
2524
3050
  return () => {
2525
3051
  if (resizeFrameRef.current) {
2526
- cancelAnimationFrame(resizeFrameRef.current);
3052
+ params.window.cancelAnimationFrame(resizeFrameRef.current);
2527
3053
  }
2528
- window.removeEventListener("resize", handleResize);
3054
+ params.window.removeEventListener("resize", handleResize);
2529
3055
  };
2530
3056
  }, [params]);
2531
3057
  };
2532
3058
 
2533
3059
  // render/measurer-overlay.tsx
2534
- import { memo as memo5 } from "react";
3060
+ import { memo as memo6 } from "react";
2535
3061
 
2536
3062
  // components/measure-tag.tsx
2537
- import { jsx as jsx3 } from "react/jsx-runtime";
3063
+ import { jsx as jsx4 } from "react/jsx-runtime";
2538
3064
  function MeasureTag({
2539
3065
  children,
2540
3066
  className = "",
2541
3067
  style
2542
3068
  }) {
2543
- return /* @__PURE__ */ jsx3(
3069
+ return /* @__PURE__ */ jsx4(
2544
3070
  "div",
2545
3071
  {
2546
3072
  className: cn(
@@ -2554,31 +3080,21 @@ function MeasureTag({
2554
3080
  }
2555
3081
 
2556
3082
  // components/measurement-box.tsx
2557
- import { memo as memo2, useEffect as useEffect7, useState as useState7 } from "react";
2558
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
3083
+ import { memo as memo3 } from "react";
3084
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
2559
3085
  var formatValue2 = (value) => Math.round(value);
2560
- var MeasurementBox = memo2(function MeasurementBox2({
3086
+ var MeasurementBox = memo3(function MeasurementBox2({
2561
3087
  measurement,
2562
3088
  transitionMs,
2563
3089
  labelOffset,
2564
3090
  edgeVisibility
2565
3091
  }) {
2566
- const [rect, setRect] = useState7(
2567
- measurement.originRect ?? measurement.rect
2568
- );
2569
- useEffect7(() => {
2570
- if (!measurement.originRect) return;
2571
- const frame = requestAnimationFrame(() => {
2572
- setRect(measurement.rect);
2573
- });
2574
- return () => cancelAnimationFrame(frame);
2575
- }, [measurement]);
2576
3092
  const edges = edgeVisibility ?? { top: true, right: true, bottom: true, left: true };
2577
- const displayRect = measurement.originRect ? rect : measurement.rect;
3093
+ const displayRect = measurement.rect;
2578
3094
  const outlineColor = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
2579
3095
  const fillColor = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
2580
- return /* @__PURE__ */ jsxs2("div", { className: "msr:pointer-events-none", children: [
2581
- /* @__PURE__ */ jsxs2(
3096
+ return /* @__PURE__ */ jsxs3("div", { className: "msr:pointer-events-none", children: [
3097
+ /* @__PURE__ */ jsxs3(
2582
3098
  "div",
2583
3099
  {
2584
3100
  className: "msr:absolute",
@@ -2588,31 +3104,31 @@ var MeasurementBox = memo2(function MeasurementBox2({
2588
3104
  width: displayRect.width,
2589
3105
  height: displayRect.height,
2590
3106
  backgroundColor: fillColor,
2591
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease` : void 0
3107
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease`
2592
3108
  },
2593
3109
  children: [
2594
- edges.top ? /* @__PURE__ */ jsx4(
3110
+ edges.top ? /* @__PURE__ */ jsx5(
2595
3111
  "div",
2596
3112
  {
2597
3113
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2598
3114
  style: { backgroundColor: outlineColor }
2599
3115
  }
2600
3116
  ) : null,
2601
- edges.right ? /* @__PURE__ */ jsx4(
3117
+ edges.right ? /* @__PURE__ */ jsx5(
2602
3118
  "div",
2603
3119
  {
2604
3120
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2605
3121
  style: { backgroundColor: outlineColor }
2606
3122
  }
2607
3123
  ) : null,
2608
- edges.bottom ? /* @__PURE__ */ jsx4(
3124
+ edges.bottom ? /* @__PURE__ */ jsx5(
2609
3125
  "div",
2610
3126
  {
2611
3127
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2612
3128
  style: { backgroundColor: outlineColor }
2613
3129
  }
2614
3130
  ) : null,
2615
- edges.left ? /* @__PURE__ */ jsx4(
3131
+ edges.left ? /* @__PURE__ */ jsx5(
2616
3132
  "div",
2617
3133
  {
2618
3134
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2622,14 +3138,14 @@ var MeasurementBox = memo2(function MeasurementBox2({
2622
3138
  ]
2623
3139
  }
2624
3140
  ),
2625
- /* @__PURE__ */ jsxs2(
3141
+ /* @__PURE__ */ jsxs3(
2626
3142
  MeasureTag,
2627
3143
  {
2628
3144
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
2629
3145
  style: {
2630
3146
  left: displayRect.left + displayRect.width / 2,
2631
3147
  top: displayRect.top + displayRect.height + labelOffset,
2632
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease` : void 0
3148
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease`
2633
3149
  },
2634
3150
  children: [
2635
3151
  formatValue2(displayRect.width),
@@ -2642,31 +3158,21 @@ var MeasurementBox = memo2(function MeasurementBox2({
2642
3158
  });
2643
3159
 
2644
3160
  // components/selected-measurement-box.tsx
2645
- import { memo as memo3, useEffect as useEffect8, useState as useState8 } from "react";
2646
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
3161
+ import { memo as memo4 } from "react";
3162
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2647
3163
  var formatValue3 = (value) => Math.round(value);
2648
- var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
3164
+ var SelectedMeasurementBox = memo4(function SelectedMeasurementBox2({
2649
3165
  measurement,
2650
3166
  transitionMs,
2651
3167
  labelOffset,
2652
3168
  edgeVisibility
2653
3169
  }) {
2654
- const [rect, setRect] = useState8(
2655
- measurement.originRect ?? measurement.rect
2656
- );
2657
- useEffect8(() => {
2658
- if (!measurement.originRect) return;
2659
- const frame = requestAnimationFrame(() => {
2660
- setRect(measurement.rect);
2661
- });
2662
- return () => cancelAnimationFrame(frame);
2663
- }, [measurement]);
2664
3170
  const edges = edgeVisibility ?? { top: true, right: true, bottom: true, left: true };
2665
- const displayRect = measurement.originRect ? rect : measurement.rect;
3171
+ const displayRect = measurement.rect;
2666
3172
  const outlineColor = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
2667
3173
  const fillColor = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
2668
- return /* @__PURE__ */ jsxs3("div", { className: "msr:pointer-events-none", children: [
2669
- /* @__PURE__ */ jsxs3(
3174
+ return /* @__PURE__ */ jsxs4("div", { className: "msr:pointer-events-none", children: [
3175
+ /* @__PURE__ */ jsxs4(
2670
3176
  "div",
2671
3177
  {
2672
3178
  className: "msr:absolute",
@@ -2676,31 +3182,31 @@ var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
2676
3182
  width: displayRect.width,
2677
3183
  height: displayRect.height,
2678
3184
  backgroundColor: fillColor,
2679
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease` : void 0
3185
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease`
2680
3186
  },
2681
3187
  children: [
2682
- edges.top ? /* @__PURE__ */ jsx5(
3188
+ edges.top ? /* @__PURE__ */ jsx6(
2683
3189
  "div",
2684
3190
  {
2685
3191
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2686
3192
  style: { backgroundColor: outlineColor }
2687
3193
  }
2688
3194
  ) : null,
2689
- edges.right ? /* @__PURE__ */ jsx5(
3195
+ edges.right ? /* @__PURE__ */ jsx6(
2690
3196
  "div",
2691
3197
  {
2692
3198
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2693
3199
  style: { backgroundColor: outlineColor }
2694
3200
  }
2695
3201
  ) : null,
2696
- edges.bottom ? /* @__PURE__ */ jsx5(
3202
+ edges.bottom ? /* @__PURE__ */ jsx6(
2697
3203
  "div",
2698
3204
  {
2699
3205
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2700
3206
  style: { backgroundColor: outlineColor }
2701
3207
  }
2702
3208
  ) : null,
2703
- edges.left ? /* @__PURE__ */ jsx5(
3209
+ edges.left ? /* @__PURE__ */ jsx6(
2704
3210
  "div",
2705
3211
  {
2706
3212
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2710,14 +3216,14 @@ var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
2710
3216
  ]
2711
3217
  }
2712
3218
  ),
2713
- /* @__PURE__ */ jsxs3(
3219
+ /* @__PURE__ */ jsxs4(
2714
3220
  MeasureTag,
2715
3221
  {
2716
3222
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
2717
3223
  style: {
2718
3224
  left: displayRect.left + displayRect.width / 2,
2719
3225
  top: displayRect.top + displayRect.height + labelOffset,
2720
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease` : void 0
3226
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease`
2721
3227
  },
2722
3228
  children: [
2723
3229
  formatValue3(displayRect.width),
@@ -2730,14 +3236,14 @@ var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
2730
3236
  });
2731
3237
 
2732
3238
  // render/distance-overlay-item.tsx
2733
- import { memo as memo4 } from "react";
2734
- import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2735
- var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
3239
+ import { memo as memo5 } from "react";
3240
+ import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
3241
+ var DistanceOverlayItem = memo5(function DistanceOverlayItem2({
2736
3242
  distance,
2737
3243
  labelOffset,
2738
3244
  onRemove
2739
3245
  }) {
2740
- return /* @__PURE__ */ jsxs4(
3246
+ return /* @__PURE__ */ jsxs5(
2741
3247
  "div",
2742
3248
  {
2743
3249
  className: onRemove ? "msr:pointer-events-auto" : "msr:pointer-events-none",
@@ -2746,7 +3252,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2746
3252
  onRemove(distance.id);
2747
3253
  } : void 0,
2748
3254
  children: [
2749
- /* @__PURE__ */ jsx6(
3255
+ /* @__PURE__ */ jsx7(
2750
3256
  "div",
2751
3257
  {
2752
3258
  className: "msr:absolute msr:rounded msr:border msr:border-[#2563eb]/70",
@@ -2758,7 +3264,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2758
3264
  }
2759
3265
  }
2760
3266
  ),
2761
- /* @__PURE__ */ jsx6(
3267
+ /* @__PURE__ */ jsx7(
2762
3268
  "div",
2763
3269
  {
2764
3270
  className: "msr:absolute msr:rounded msr:border msr:border-[#2563eb]/70",
@@ -2771,7 +3277,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2771
3277
  }
2772
3278
  ),
2773
3279
  distance.connectors.map(
2774
- (connector, index) => Math.abs(connector.x1 - connector.x2) < 1 ? /* @__PURE__ */ jsx6(
3280
+ (connector, index) => Math.abs(connector.x1 - connector.x2) < 1 ? /* @__PURE__ */ jsx7(
2775
3281
  "div",
2776
3282
  {
2777
3283
  className: "msr:absolute msr:border-l msr:border-dashed msr:border-[#2563eb]/70",
@@ -2782,7 +3288,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2782
3288
  }
2783
3289
  },
2784
3290
  `${distance.id}-connector-${index}`
2785
- ) : /* @__PURE__ */ jsx6(
3291
+ ) : /* @__PURE__ */ jsx7(
2786
3292
  "div",
2787
3293
  {
2788
3294
  className: "msr:absolute msr:border-t msr:border-dashed msr:border-[#2563eb]/70",
@@ -2795,8 +3301,8 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2795
3301
  `${distance.id}-connector-${index}`
2796
3302
  )
2797
3303
  ),
2798
- distance.horizontal && distance.horizontal.value > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2799
- /* @__PURE__ */ jsx6(
3304
+ distance.horizontal && distance.horizontal.value > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
3305
+ /* @__PURE__ */ jsx7(
2800
3306
  "div",
2801
3307
  {
2802
3308
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -2807,7 +3313,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2807
3313
  }
2808
3314
  }
2809
3315
  ),
2810
- /* @__PURE__ */ jsx6(
3316
+ /* @__PURE__ */ jsx7(
2811
3317
  MeasureTag,
2812
3318
  {
2813
3319
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -2819,8 +3325,8 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2819
3325
  }
2820
3326
  )
2821
3327
  ] }) : null,
2822
- distance.vertical && distance.vertical.value > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2823
- /* @__PURE__ */ jsx6(
3328
+ distance.vertical && distance.vertical.value > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
3329
+ /* @__PURE__ */ jsx7(
2824
3330
  "div",
2825
3331
  {
2826
3332
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -2831,7 +3337,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2831
3337
  }
2832
3338
  }
2833
3339
  ),
2834
- /* @__PURE__ */ jsx6(
3340
+ /* @__PURE__ */ jsx7(
2835
3341
  MeasureTag,
2836
3342
  {
2837
3343
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -2849,10 +3355,11 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2849
3355
  });
2850
3356
 
2851
3357
  // render/measurer-overlay.tsx
2852
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2853
- var MeasurerOverlay = memo5(function MeasurerOverlay2({
3358
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3359
+ var MeasurerOverlay = memo6(function MeasurerOverlay2({
2854
3360
  enabled,
2855
3361
  toolMode,
3362
+ guidePointerEvents,
2856
3363
  guidesEnabled,
2857
3364
  altPressed,
2858
3365
  isDragging,
@@ -2889,16 +3396,20 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2889
3396
  onGuidePointerUp,
2890
3397
  onGuidePointerCancel
2891
3398
  }) {
2892
- return /* @__PURE__ */ jsxs5(
3399
+ const overlayVisible = enabled;
3400
+ const overlayInteractive = overlayVisible && toolMode !== "none" && toolMode !== "text-inspector" && toolMode !== "xray" && toolMode !== "rulers";
3401
+ const selectionVisible = toolMode === "select";
3402
+ return /* @__PURE__ */ jsxs6(
2893
3403
  "div",
2894
3404
  {
2895
- className: `msr:absolute msr:inset-0 ${enabled && toolMode !== "none" ? `msr:pointer-events-auto ${guidesEnabled ? hoverGuide || draggingGuideId ? "msr:cursor-default" : "msr:cursor-crosshair" : "msr:cursor-default"} msr:opacity-100` : "msr:pointer-events-none msr:opacity-0"}`,
3405
+ className: `msr:absolute msr:inset-0 ${overlayVisible ? `msr:pointer-events-auto ${guidesEnabled ? hoverGuide || draggingGuideId ? "msr:cursor-default" : "msr:cursor-crosshair" : "msr:cursor-default"} msr:opacity-100` : "msr:pointer-events-none msr:opacity-0"}`,
3406
+ style: { pointerEvents: overlayInteractive ? "auto" : "none" },
2896
3407
  onPointerDown,
2897
3408
  onPointerMove,
2898
3409
  onPointerUp,
2899
3410
  onPointerLeave,
2900
3411
  children: [
2901
- !guidesEnabled ? displayedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx7(
3412
+ selectionVisible ? displayedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx8(
2902
3413
  MeasurementBox,
2903
3414
  {
2904
3415
  measurement,
@@ -2908,8 +3419,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2908
3419
  },
2909
3420
  measurement.id
2910
3421
  )) : null,
2911
- !guidesEnabled && activeRect && isDragging ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
2912
- /* @__PURE__ */ jsxs5(
3422
+ selectionVisible && activeRect && isDragging ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3423
+ /* @__PURE__ */ jsxs6(
2913
3424
  "div",
2914
3425
  {
2915
3426
  className: "msr:pointer-events-none msr:absolute",
@@ -2921,28 +3432,28 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2921
3432
  backgroundColor: fillColor
2922
3433
  },
2923
3434
  children: [
2924
- /* @__PURE__ */ jsx7(
3435
+ /* @__PURE__ */ jsx8(
2925
3436
  "div",
2926
3437
  {
2927
3438
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2928
3439
  style: { backgroundColor: outlineColor }
2929
3440
  }
2930
3441
  ),
2931
- /* @__PURE__ */ jsx7(
3442
+ /* @__PURE__ */ jsx8(
2932
3443
  "div",
2933
3444
  {
2934
3445
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2935
3446
  style: { backgroundColor: outlineColor }
2936
3447
  }
2937
3448
  ),
2938
- /* @__PURE__ */ jsx7(
3449
+ /* @__PURE__ */ jsx8(
2939
3450
  "div",
2940
3451
  {
2941
3452
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2942
3453
  style: { backgroundColor: outlineColor }
2943
3454
  }
2944
3455
  ),
2945
- /* @__PURE__ */ jsx7(
3456
+ /* @__PURE__ */ jsx8(
2946
3457
  "div",
2947
3458
  {
2948
3459
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2952,7 +3463,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2952
3463
  ]
2953
3464
  }
2954
3465
  ),
2955
- /* @__PURE__ */ jsxs5(
3466
+ /* @__PURE__ */ jsxs6(
2956
3467
  MeasureTag,
2957
3468
  {
2958
3469
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -2968,7 +3479,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2968
3479
  }
2969
3480
  )
2970
3481
  ] }) : null,
2971
- !guidesEnabled && hoverRectToShow ? /* @__PURE__ */ jsxs5(
3482
+ selectionVisible && hoverRectToShow ? /* @__PURE__ */ jsxs6(
2972
3483
  "div",
2973
3484
  {
2974
3485
  className: "msr:pointer-events-none msr:absolute",
@@ -2980,28 +3491,28 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2980
3491
  backgroundColor: fillColor
2981
3492
  },
2982
3493
  children: [
2983
- hoverEdgeVisibility?.top ? /* @__PURE__ */ jsx7(
3494
+ hoverEdgeVisibility?.top ? /* @__PURE__ */ jsx8(
2984
3495
  "div",
2985
3496
  {
2986
3497
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2987
3498
  style: { backgroundColor: outlineColor }
2988
3499
  }
2989
3500
  ) : null,
2990
- hoverEdgeVisibility?.right ? /* @__PURE__ */ jsx7(
3501
+ hoverEdgeVisibility?.right ? /* @__PURE__ */ jsx8(
2991
3502
  "div",
2992
3503
  {
2993
3504
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2994
3505
  style: { backgroundColor: outlineColor }
2995
3506
  }
2996
3507
  ) : null,
2997
- hoverEdgeVisibility?.bottom ? /* @__PURE__ */ jsx7(
3508
+ hoverEdgeVisibility?.bottom ? /* @__PURE__ */ jsx8(
2998
3509
  "div",
2999
3510
  {
3000
3511
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
3001
3512
  style: { backgroundColor: outlineColor }
3002
3513
  }
3003
3514
  ) : null,
3004
- hoverEdgeVisibility?.left ? /* @__PURE__ */ jsx7(
3515
+ hoverEdgeVisibility?.left ? /* @__PURE__ */ jsx8(
3005
3516
  "div",
3006
3517
  {
3007
3518
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -3011,7 +3522,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3011
3522
  ]
3012
3523
  }
3013
3524
  ) : null,
3014
- guidesEnabled && guidePreview ? /* @__PURE__ */ jsx7(
3525
+ guidesEnabled && guidePreview ? /* @__PURE__ */ jsx8(
3015
3526
  "div",
3016
3527
  {
3017
3528
  className: "msr:pointer-events-none msr:absolute",
@@ -3026,7 +3537,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3026
3537
  height: GUIDE_HITBOX_SIZE,
3027
3538
  width: "100%"
3028
3539
  },
3029
- children: /* @__PURE__ */ jsx7(
3540
+ children: /* @__PURE__ */ jsx8(
3030
3541
  "div",
3031
3542
  {
3032
3543
  className: "msr:absolute",
@@ -3047,7 +3558,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3047
3558
  )
3048
3559
  }
3049
3560
  ) : null,
3050
- !guidesEnabled ? displayedSelectedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx7(
3561
+ selectionVisible ? displayedSelectedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx8(
3051
3562
  SelectedMeasurementBox,
3052
3563
  {
3053
3564
  measurement,
@@ -3057,7 +3568,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3057
3568
  },
3058
3569
  measurement.id
3059
3570
  )) : null,
3060
- heldDistances.map((distance) => /* @__PURE__ */ jsx7(
3571
+ heldDistances.map((distance) => /* @__PURE__ */ jsx8(
3061
3572
  DistanceOverlayItem,
3062
3573
  {
3063
3574
  distance,
@@ -3066,7 +3577,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3066
3577
  },
3067
3578
  `held-${distance.id}`
3068
3579
  )),
3069
- !guidesEnabled && altPressed && optionPairOverlay ? /* @__PURE__ */ jsx7(
3580
+ selectionVisible && altPressed && optionPairOverlay ? /* @__PURE__ */ jsx8(
3070
3581
  DistanceOverlayItem,
3071
3582
  {
3072
3583
  distance: optionPairOverlay,
@@ -3074,7 +3585,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3074
3585
  },
3075
3586
  `option-${optionPairOverlay.id}`
3076
3587
  ) : null,
3077
- guidesEnabled && altPressed && guideDistanceOverlay ? /* @__PURE__ */ jsx7(
3588
+ guidesEnabled && altPressed && guideDistanceOverlay ? /* @__PURE__ */ jsx8(
3078
3589
  DistanceOverlayItem,
3079
3590
  {
3080
3591
  distance: guideDistanceOverlay,
@@ -3082,9 +3593,9 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3082
3593
  },
3083
3594
  `guide-preview-${guideDistanceOverlay.id}`
3084
3595
  ) : null,
3085
- !guidesEnabled && altPressed && optionContainerLines ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3086
- optionContainerLines.top.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3087
- /* @__PURE__ */ jsx7(
3596
+ selectionVisible && altPressed && optionContainerLines ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3597
+ optionContainerLines.top.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3598
+ /* @__PURE__ */ jsx8(
3088
3599
  "div",
3089
3600
  {
3090
3601
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -3095,7 +3606,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3095
3606
  }
3096
3607
  }
3097
3608
  ),
3098
- /* @__PURE__ */ jsx7(
3609
+ /* @__PURE__ */ jsx8(
3099
3610
  MeasureTag,
3100
3611
  {
3101
3612
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -3107,8 +3618,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3107
3618
  }
3108
3619
  )
3109
3620
  ] }) : null,
3110
- optionContainerLines.bottom.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3111
- /* @__PURE__ */ jsx7(
3621
+ optionContainerLines.bottom.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3622
+ /* @__PURE__ */ jsx8(
3112
3623
  "div",
3113
3624
  {
3114
3625
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -3119,7 +3630,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3119
3630
  }
3120
3631
  }
3121
3632
  ),
3122
- /* @__PURE__ */ jsx7(
3633
+ /* @__PURE__ */ jsx8(
3123
3634
  MeasureTag,
3124
3635
  {
3125
3636
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -3131,8 +3642,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3131
3642
  }
3132
3643
  )
3133
3644
  ] }) : null,
3134
- optionContainerLines.left.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3135
- /* @__PURE__ */ jsx7(
3645
+ optionContainerLines.left.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3646
+ /* @__PURE__ */ jsx8(
3136
3647
  "div",
3137
3648
  {
3138
3649
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -3143,7 +3654,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3143
3654
  }
3144
3655
  }
3145
3656
  ),
3146
- /* @__PURE__ */ jsx7(
3657
+ /* @__PURE__ */ jsx8(
3147
3658
  MeasureTag,
3148
3659
  {
3149
3660
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -3155,8 +3666,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3155
3666
  }
3156
3667
  )
3157
3668
  ] }) : null,
3158
- optionContainerLines.right.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3159
- /* @__PURE__ */ jsx7(
3669
+ optionContainerLines.right.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3670
+ /* @__PURE__ */ jsx8(
3160
3671
  "div",
3161
3672
  {
3162
3673
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -3167,7 +3678,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3167
3678
  }
3168
3679
  }
3169
3680
  ),
3170
- /* @__PURE__ */ jsx7(
3681
+ /* @__PURE__ */ jsx8(
3171
3682
  MeasureTag,
3172
3683
  {
3173
3684
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -3185,25 +3696,28 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3185
3696
  const isHovered = hoverGuide?.id === guide.id;
3186
3697
  const strokeWidth = isSelected || isHovered ? 2 : 1;
3187
3698
  const strokeColor = isSelected ? guideColorActive : isHovered ? guideColorHover : guideColorDefault;
3188
- return /* @__PURE__ */ jsx7(
3699
+ return /* @__PURE__ */ jsx8(
3189
3700
  "div",
3190
3701
  {
3191
3702
  className: "msr:absolute",
3703
+ "data-mesurer-guide": "true",
3192
3704
  style: guide.orientation === "vertical" ? {
3193
3705
  left: guide.position - GUIDE_HITBOX_SIZE / 2,
3194
3706
  top: 0,
3195
3707
  width: GUIDE_HITBOX_SIZE,
3196
- height: "100%"
3708
+ height: "100%",
3709
+ pointerEvents: guidePointerEvents ? "auto" : "none"
3197
3710
  } : {
3198
3711
  top: guide.position - GUIDE_HITBOX_SIZE / 2,
3199
3712
  left: 0,
3200
3713
  height: GUIDE_HITBOX_SIZE,
3201
- width: "100%"
3714
+ width: "100%",
3715
+ pointerEvents: guidePointerEvents ? "auto" : "none"
3202
3716
  },
3203
3717
  onPointerDown: (event) => onGuidePointerDown(guide, event),
3204
3718
  onPointerUp: (event) => onGuidePointerUp(guide, event),
3205
3719
  onPointerCancel: (event) => onGuidePointerCancel(guide, event),
3206
- children: /* @__PURE__ */ jsx7(
3720
+ children: /* @__PURE__ */ jsx8(
3207
3721
  "div",
3208
3722
  {
3209
3723
  className: "msr:absolute",
@@ -3231,57 +3745,1002 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3231
3745
  );
3232
3746
  });
3233
3747
 
3234
- // measurer.tsx
3235
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3236
- var subscribeHydration = () => () => {
3748
+ // runtime/text-inspector-dom.ts
3749
+ var INK_50 = "#f8fafc";
3750
+ var INK_200 = "#e2e8f0";
3751
+ var INK_500 = "#64748b";
3752
+ var INK_900 = "#0f172a";
3753
+ var makeEdge = (document2, side, color) => {
3754
+ const edge = document2.createElement("div");
3755
+ Object.assign(edge.style, {
3756
+ position: "absolute",
3757
+ backgroundColor: color,
3758
+ ...side === "top" || side === "bottom" ? { left: "0", [side]: "0", width: "100%", height: "1px" } : { top: "0", [side]: "0", width: "1px", height: "100%" }
3759
+ });
3760
+ return edge;
3237
3761
  };
3238
- var useHydrated = () => useSyncExternalStore(
3239
- subscribeHydration,
3240
- () => true,
3241
- () => false
3242
- );
3243
- var stripMeasurement = (measurement) => ({
3244
- ...measurement,
3245
- elementRef: void 0
3246
- });
3247
- var stripDistance = (distance) => ({
3248
- ...distance,
3249
- elementRefA: void 0,
3250
- elementRefB: void 0
3251
- });
3252
- function MeasurerClient({
3253
- highlightColor,
3254
- guideColor,
3255
- hoverHighlightEnabled,
3256
- persistOnReload,
3257
- portalTarget
3258
- }) {
3259
- const selectionRectRef = useRef8(null);
3260
- const toolbarRef = useRef8(null);
3261
- const selectionAnimationCleanupTimeoutRef = useRef8(null);
3262
- const persistedState = useMemo2(() => {
3263
- if (!persistOnReload) return null;
3264
- const stored = window.localStorage.getItem("mesurer-state");
3265
- if (!stored) return null;
3266
- try {
3267
- const parsed = JSON.parse(stored);
3268
- if (!parsed || parsed.version !== 1) return null;
3269
- return parsed;
3270
- } catch {
3271
- return null;
3272
- }
3273
- }, [persistOnReload]);
3274
- const { overlayRef, selectedElementRef, hoverElementRef } = useOverlayRefs();
3275
- const {
3276
- selectionOriginRect,
3277
- setSelectionOriginRect,
3278
- hoverPointer,
3279
- setHoverPointer,
3280
- hoverElement,
3281
- setHoverElement,
3282
- selectedElement,
3283
- setSelectedElement,
3284
- clearSelectionRect
3762
+ var makeBox = (document2, fill, outline) => {
3763
+ const box = document2.createElement("div");
3764
+ Object.assign(box.style, {
3765
+ position: "fixed",
3766
+ zIndex: "0",
3767
+ pointerEvents: "none",
3768
+ backgroundColor: fill,
3769
+ boxSizing: "border-box"
3770
+ });
3771
+ box.className = "mesurer-ti-box";
3772
+ box.dataset.state = "hidden";
3773
+ for (const side of ["top", "right", "bottom", "left"]) {
3774
+ box.appendChild(makeEdge(document2, side, outline));
3775
+ }
3776
+ return box;
3777
+ };
3778
+ var positionBox = (box, rect) => {
3779
+ Object.assign(box.style, {
3780
+ left: `${rect.left}px`,
3781
+ top: `${rect.top}px`,
3782
+ width: `${rect.width}px`,
3783
+ height: `${rect.height}px`
3784
+ });
3785
+ };
3786
+ var makeCard = (document2, pinned) => {
3787
+ const card = document2.createElement("div");
3788
+ Object.assign(card.style, {
3789
+ position: "fixed",
3790
+ zIndex: "1",
3791
+ pointerEvents: pinned ? "auto" : "none",
3792
+ background: "#ffffff",
3793
+ color: INK_900,
3794
+ borderRadius: "13px",
3795
+ padding: "10px 12px",
3796
+ fontSize: "11px",
3797
+ lineHeight: "1.5",
3798
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
3799
+ fontVariantNumeric: "tabular-nums",
3800
+ userSelect: "none",
3801
+ whiteSpace: "nowrap",
3802
+ width: "320px",
3803
+ minWidth: "320px",
3804
+ maxWidth: "320px",
3805
+ boxSizing: "border-box",
3806
+ boxShadow: "0px 0px 0.5px rgba(0, 0, 0, 0.18), 0px 3px 8px rgba(0, 0, 0, 0.1), 0px 1px 3px rgba(0, 0, 0, 0.1)"
3807
+ });
3808
+ card.className = pinned ? "mesurer-ti-card mesurer-ti-card--pinned" : "mesurer-ti-card";
3809
+ card.dataset.state = "hidden";
3810
+ return card;
3811
+ };
3812
+ var populateCard = (document2, card, info, pinned) => {
3813
+ card.replaceChildren();
3814
+ const header = document2.createElement("div");
3815
+ Object.assign(header.style, {
3816
+ display: "flex",
3817
+ alignItems: "center",
3818
+ gap: "8px",
3819
+ marginBottom: "8px",
3820
+ paddingBottom: "6px"
3821
+ });
3822
+ const tag = document2.createElement("span");
3823
+ Object.assign(tag.style, {
3824
+ color: INK_50,
3825
+ background: INK_900,
3826
+ borderRadius: "4px",
3827
+ padding: "1px 5px",
3828
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3829
+ fontSize: "10px",
3830
+ fontWeight: "500"
3831
+ });
3832
+ tag.textContent = info.tagName;
3833
+ header.appendChild(tag);
3834
+ if (info.textSnippet) {
3835
+ const snippet = document2.createElement("span");
3836
+ Object.assign(snippet.style, {
3837
+ color: INK_500,
3838
+ overflow: "hidden",
3839
+ textOverflow: "ellipsis",
3840
+ whiteSpace: "nowrap",
3841
+ flex: "1",
3842
+ minWidth: "0",
3843
+ fontSize: "10px"
3844
+ });
3845
+ snippet.textContent = info.textSnippet;
3846
+ header.appendChild(snippet);
3847
+ }
3848
+ if (pinned) {
3849
+ const close = document2.createElement("button");
3850
+ close.type = "button";
3851
+ close.className = "mesurer-ti-close";
3852
+ close.setAttribute("aria-label", "Close");
3853
+ close.textContent = "\xD7";
3854
+ Object.assign(close.style, {
3855
+ all: "unset",
3856
+ marginLeft: info.textSnippet ? "0" : "auto",
3857
+ flex: "0 0 auto",
3858
+ width: "16px",
3859
+ height: "16px",
3860
+ lineHeight: "14px",
3861
+ textAlign: "center",
3862
+ borderRadius: "4px",
3863
+ color: INK_500,
3864
+ fontSize: "14px"
3865
+ });
3866
+ header.appendChild(close);
3867
+ }
3868
+ card.appendChild(header);
3869
+ const grid = document2.createElement("div");
3870
+ Object.assign(grid.style, {
3871
+ display: "grid",
3872
+ gridTemplateColumns: "auto 1fr",
3873
+ columnGap: "12px",
3874
+ rowGap: "3px",
3875
+ alignItems: "baseline"
3876
+ });
3877
+ for (const row of info.rows) {
3878
+ const label = document2.createElement("span");
3879
+ label.style.color = INK_500;
3880
+ label.style.fontSize = "11px";
3881
+ label.textContent = row.label;
3882
+ const value = document2.createElement("span");
3883
+ Object.assign(value.style, {
3884
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3885
+ fontSize: "11px",
3886
+ color: INK_900,
3887
+ overflow: "hidden",
3888
+ textOverflow: "ellipsis",
3889
+ whiteSpace: "nowrap"
3890
+ });
3891
+ if (!row.varName) {
3892
+ value.textContent = row.value;
3893
+ } else {
3894
+ const variable = document2.createElement("span");
3895
+ variable.textContent = row.value;
3896
+ const separator = document2.createElement("span");
3897
+ separator.style.color = INK_200;
3898
+ separator.style.margin = "0 6px";
3899
+ separator.textContent = "\xB7";
3900
+ const name = document2.createElement("span");
3901
+ name.style.color = "#0369a1";
3902
+ name.textContent = row.varName;
3903
+ value.append(variable, separator, name);
3904
+ }
3905
+ grid.append(label, value);
3906
+ }
3907
+ card.appendChild(grid);
3908
+ };
3909
+ var positionCard = (window2, card, rect, offsetX = 0, offsetY = 0) => {
3910
+ const center = rect.left + rect.width / 2 + offsetX;
3911
+ card.style.left = `${center}px`;
3912
+ const size = card.getBoundingClientRect();
3913
+ let top = rect.bottom + 4 + offsetY;
3914
+ if (top + size.height > window2.innerHeight - 8) {
3915
+ top = rect.top - size.height - 4 + offsetY;
3916
+ }
3917
+ card.style.top = `${Math.max(8, top)}px`;
3918
+ const half = size.width / 2;
3919
+ card.style.left = `${Math.min(
3920
+ Math.max(center, 8 + half),
3921
+ window2.innerWidth - 8 - half
3922
+ )}px`;
3923
+ };
3924
+
3925
+ // runtime/text-inspector-typography.ts
3926
+ var FONT_WEIGHT_KEYWORD = {
3927
+ "100": "thin",
3928
+ "200": "extralight",
3929
+ "300": "light",
3930
+ "400": "normal",
3931
+ "500": "medium",
3932
+ "600": "semibold",
3933
+ "700": "bold",
3934
+ "800": "extrabold",
3935
+ "900": "black"
3936
+ };
3937
+ var TYPO_PROPS = [
3938
+ "font-family",
3939
+ "font-size",
3940
+ "font-weight",
3941
+ "line-height",
3942
+ "letter-spacing"
3943
+ ];
3944
+ var TYPO_LABELS = {
3945
+ Family: "font-family",
3946
+ Size: "font-size",
3947
+ Weight: "font-weight",
3948
+ Line: "line-height",
3949
+ Tracking: "letter-spacing"
3950
+ };
3951
+ var formatPx = (raw) => {
3952
+ if (!raw || raw === "normal") return raw || "normal";
3953
+ const match = /^(-?[\d.]+)px$/.exec(raw);
3954
+ if (!match) return raw;
3955
+ return `${Math.round(Number(match[1]) * 10) / 10}px`;
3956
+ };
3957
+ var firstFontFamily = (families) => (families.split(",")[0] ?? "").trim().replace(/^['"]|['"]$/g, "");
3958
+ var weightWithKeyword = (weight) => {
3959
+ const keyword = FONT_WEIGHT_KEYWORD[weight];
3960
+ return keyword ? `${weight} / ${keyword}` : weight;
3961
+ };
3962
+ var extractVarName = (value) => {
3963
+ if (!value) return null;
3964
+ return /var\(\s*(--[a-zA-Z0-9_-]+)/.exec(value)?.[1] ?? null;
3965
+ };
3966
+ var selectorSpecificity = (selector) => {
3967
+ const ids = (selector.match(/#[\w-]+/g) ?? []).length;
3968
+ const classes = (selector.match(/\.[\w-]+|\[[^\]]+\]|:[\w-]+/g) ?? []).length;
3969
+ const elements = (selector.match(/(?:^|[ >+~])([a-zA-Z][\w-]*)/g) ?? []).length;
3970
+ return ids * 1e4 + classes * 100 + elements;
3971
+ };
3972
+ var wins = (next, previous) => {
3973
+ if (!previous) return true;
3974
+ if (next.important !== previous.important) return next.important;
3975
+ if (next.specificity !== previous.specificity) {
3976
+ return next.specificity > previous.specificity;
3977
+ }
3978
+ return next.order > previous.order;
3979
+ };
3980
+ var TypographyInspector = class {
3981
+ constructor(document2, window2) {
3982
+ this.document = document2;
3983
+ this.window = window2;
3984
+ }
3985
+ collectRules(rules, output, mediaOk, order) {
3986
+ for (let i = 0; i < rules.length; i++) {
3987
+ const rule = rules[i];
3988
+ const currentOrder = order.value++;
3989
+ if (rule.type === 4) {
3990
+ const mediaRule = rule;
3991
+ let matches = mediaOk;
3992
+ try {
3993
+ matches = mediaOk && this.window.matchMedia(mediaRule.media.mediaText).matches;
3994
+ } catch {
3995
+ matches = false;
3996
+ }
3997
+ this.collectRules(mediaRule.cssRules, output, matches, order);
3998
+ } else if (rule.type === 12) {
3999
+ this.collectRules(rule.cssRules, output, mediaOk, order);
4000
+ } else if (rule.type === 1) {
4001
+ output.push({
4002
+ rule,
4003
+ mediaOk,
4004
+ order: currentOrder
4005
+ });
4006
+ }
4007
+ }
4008
+ }
4009
+ getRules() {
4010
+ const rules = [];
4011
+ const order = { value: 0 };
4012
+ for (const sheet of Array.from(this.document.styleSheets)) {
4013
+ try {
4014
+ this.collectRules(sheet.cssRules, rules, true, order);
4015
+ } catch {
4016
+ continue;
4017
+ }
4018
+ }
4019
+ return rules;
4020
+ }
4021
+ findVarReferences(el) {
4022
+ const result = Object.fromEntries(
4023
+ TYPO_PROPS.map((prop) => [prop, null])
4024
+ );
4025
+ const rules = this.getRules();
4026
+ for (const prop of TYPO_PROPS) {
4027
+ let node = el;
4028
+ while (node && result[prop] === null) {
4029
+ let winner;
4030
+ const inlineValue = node.style.getPropertyValue(prop);
4031
+ const inlineName = extractVarName(inlineValue);
4032
+ if (inlineName) {
4033
+ winner = {
4034
+ name: inlineName,
4035
+ specificity: Number.MAX_SAFE_INTEGER,
4036
+ order: Number.MAX_SAFE_INTEGER,
4037
+ important: node.style.getPropertyPriority(prop) === "important"
4038
+ };
4039
+ }
4040
+ for (const { rule, mediaOk, order } of rules) {
4041
+ if (!mediaOk) continue;
4042
+ let matches = false;
4043
+ try {
4044
+ matches = node.matches(rule.selectorText);
4045
+ } catch {
4046
+ continue;
4047
+ }
4048
+ if (!matches) continue;
4049
+ const name = extractVarName(rule.style.getPropertyValue(prop));
4050
+ if (!name) continue;
4051
+ const candidate = {
4052
+ name,
4053
+ specificity: selectorSpecificity(rule.selectorText),
4054
+ order,
4055
+ important: rule.style.getPropertyPriority(prop) === "important"
4056
+ };
4057
+ if (wins(candidate, winner)) winner = candidate;
4058
+ }
4059
+ if (winner) result[prop] = winner.name;
4060
+ node = node.parentElement;
4061
+ }
4062
+ }
4063
+ return result;
4064
+ }
4065
+ getFast(el) {
4066
+ const styles = this.window.getComputedStyle(el);
4067
+ const rows = [
4068
+ { label: "Family", value: firstFontFamily(styles.fontFamily), varName: null },
4069
+ { label: "Size", value: formatPx(styles.fontSize), varName: null },
4070
+ {
4071
+ label: "Weight",
4072
+ value: weightWithKeyword(styles.fontWeight),
4073
+ varName: null
4074
+ },
4075
+ {
4076
+ label: "Line",
4077
+ value: styles.lineHeight === "normal" ? "normal" : formatPx(styles.lineHeight),
4078
+ varName: null
4079
+ },
4080
+ {
4081
+ label: "Tracking",
4082
+ value: styles.letterSpacing === "normal" ? "normal" : formatPx(styles.letterSpacing),
4083
+ varName: null
4084
+ }
4085
+ ];
4086
+ const directText = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.nodeValue?.trim() ?? "").filter(Boolean).join(" ").replace(/\s+/g, " ");
4087
+ return {
4088
+ rows,
4089
+ tagName: el.tagName.toLowerCase(),
4090
+ textSnippet: directText.length > 40 ? `${directText.slice(0, 40)}\u2026` : directText
4091
+ };
4092
+ }
4093
+ getFull(el, base = this.getFast(el)) {
4094
+ const vars = this.findVarReferences(el);
4095
+ return {
4096
+ ...base,
4097
+ rows: base.rows.map((row) => ({
4098
+ ...row,
4099
+ varName: vars[TYPO_LABELS[row.label]]
4100
+ }))
4101
+ };
4102
+ }
4103
+ };
4104
+
4105
+ // runtime/text-inspector.ts
4106
+ var DEFAULT_EXTENSION_HOST_ID = "mesurer-extension-host";
4107
+ var DEFAULT_OVERLAY_ID = "mesurer-text-inspector-overlay";
4108
+ var DEFAULT_STYLE_ID = "mesurer-text-inspector-styles";
4109
+ var DEFAULT_BODY_MODE_CLASS = "mesurer-text-inspect-mode";
4110
+ var inspectorInstanceCount = 0;
4111
+ var FILL_HOVER = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
4112
+ var OUTLINE_HOVER = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
4113
+ var FILL_PINNED = "color-mix(in oklch, oklch(0.62 0.18 255) 4%, transparent)";
4114
+ var OUTLINE_PINNED = "color-mix(in oklch, oklch(0.62 0.18 255) 35%, transparent)";
4115
+ var DEFAULT_SKIP_TAGS = [
4116
+ "HTML",
4117
+ "BODY",
4118
+ "SCRIPT",
4119
+ "STYLE",
4120
+ "META",
4121
+ "LINK",
4122
+ "NOSCRIPT",
4123
+ "IMG",
4124
+ "VIDEO",
4125
+ "AUDIO",
4126
+ "IFRAME"
4127
+ ];
4128
+ var createInspectorStyles = (extensionHostId, overlayId, bodyModeClass) => `
4129
+ .${bodyModeClass},
4130
+ .${bodyModeClass} * {
4131
+ cursor: help !important;
4132
+ }
4133
+ .${bodyModeClass} #${extensionHostId},
4134
+ .${bodyModeClass} #${extensionHostId} * {
4135
+ cursor: auto !important;
4136
+ }
4137
+ #${overlayId} .mesurer-ti-card--pinned {
4138
+ cursor: grab;
4139
+ }
4140
+ #${overlayId} .mesurer-ti-card--pinned:active {
4141
+ cursor: grabbing;
4142
+ }
4143
+ #${overlayId} .mesurer-ti-close {
4144
+ cursor: pointer;
4145
+ }
4146
+ #${overlayId} .mesurer-ti-close:hover {
4147
+ background: rgba(15, 23, 42, 0.06);
4148
+ color: #0f172a;
4149
+ }
4150
+ #${overlayId} .mesurer-ti-box,
4151
+ #${overlayId} .mesurer-ti-card {
4152
+ opacity: 1;
4153
+ }
4154
+ #${overlayId} .mesurer-ti-box[data-state="hidden"],
4155
+ #${overlayId} .mesurer-ti-card[data-state="hidden"] {
4156
+ opacity: 0;
4157
+ }
4158
+ #${overlayId} .mesurer-ti-card {
4159
+ transform: translateX(-50%);
4160
+ }
4161
+ `;
4162
+ var createIdleScheduler = (window2) => {
4163
+ const browser = window2;
4164
+ if (browser.requestIdleCallback) {
4165
+ return {
4166
+ request: (callback) => browser.requestIdleCallback(
4167
+ () => callback(),
4168
+ { timeout: 250 }
4169
+ ),
4170
+ cancel: (id) => browser.cancelIdleCallback?.(id)
4171
+ };
4172
+ }
4173
+ return {
4174
+ request: (callback) => window2.setTimeout(callback, 32),
4175
+ cancel: (id) => window2.clearTimeout(id)
4176
+ };
4177
+ };
4178
+ var createTextInspector = (options = {}, legacy = false) => {
4179
+ const portalDocument = options.portalTarget?.ownerDocument;
4180
+ const availableDocument = portalDocument ?? (typeof globalThis.document === "undefined" ? null : globalThis.document);
4181
+ const availableWindow = availableDocument?.defaultView ?? (typeof globalThis.window === "undefined" ? null : globalThis.window);
4182
+ const document2 = availableDocument;
4183
+ const window2 = availableWindow;
4184
+ const realm = availableWindow;
4185
+ const HTMLElementConstructor = realm?.HTMLElement ?? globalThis.HTMLElement;
4186
+ const SVGElementConstructor = realm?.SVGElement ?? globalThis.SVGElement;
4187
+ const extensionHostId = DEFAULT_EXTENSION_HOST_ID;
4188
+ const instanceId = (options.id ?? (legacy ? DEFAULT_OVERLAY_ID : `mesurer-text-inspector-${++inspectorInstanceCount}`)).replace(
4189
+ /[^a-zA-Z0-9_-]/g,
4190
+ "-"
4191
+ );
4192
+ const overlayId = legacy ? DEFAULT_OVERLAY_ID : `${instanceId}-overlay`;
4193
+ const styleId = legacy ? DEFAULT_STYLE_ID : `${instanceId}-styles`;
4194
+ const bodyModeClass = legacy ? DEFAULT_BODY_MODE_CLASS : `${instanceId}-mode`;
4195
+ const skipTags = new Set(
4196
+ (options.ignoredTags ?? DEFAULT_SKIP_TAGS).map((tag) => tag.toUpperCase())
4197
+ );
4198
+ const maxPinned = Number.isFinite(options.maxPinned) ? Math.max(1, Math.floor(options.maxPinned)) : Infinity;
4199
+ const inspectorStyles = createInspectorStyles(
4200
+ extensionHostId,
4201
+ overlayId,
4202
+ bodyModeClass
4203
+ );
4204
+ const EXTENSION_HOST_ID = extensionHostId;
4205
+ const OVERLAY_ID = overlayId;
4206
+ const STYLE_ID = styleId;
4207
+ const BODY_MODE_CLASS = bodyModeClass;
4208
+ const SKIP_TAGS = skipTags;
4209
+ const INSPECTOR_STYLES = inspectorStyles;
4210
+ const portalIsShadowRoot = options.portalTarget?.nodeType === 11;
4211
+ const typography = new TypographyInspector(document2, window2);
4212
+ const pinned = [];
4213
+ const history = [];
4214
+ const future = [];
4215
+ let enabled = false;
4216
+ let overlay = null;
4217
+ let hoverBox = null;
4218
+ let hoverCard = null;
4219
+ let hoveredEl = null;
4220
+ let pointerX = 0;
4221
+ let pointerY = 0;
4222
+ let frameScheduled = false;
4223
+ let pendingElement = null;
4224
+ let pendingIdleId = -1;
4225
+ let idleScheduler = null;
4226
+ const getIdleScheduler = () => {
4227
+ idleScheduler ?? (idleScheduler = createIdleScheduler(window2));
4228
+ return idleScheduler;
4229
+ };
4230
+ const hasDirectText = (el) => Array.from(el.childNodes).some(
4231
+ (node) => node.nodeType === Node.TEXT_NODE && !!node.nodeValue?.trim()
4232
+ );
4233
+ const isInspectable = (el) => !!el && el instanceof HTMLElementConstructor && !SKIP_TAGS.has(el.tagName) && !(el instanceof SVGElementConstructor) && hasDirectText(el);
4234
+ const isOverlayNode = (node) => {
4235
+ for (let current = node; current; current = current.parentNode) {
4236
+ if (current instanceof HTMLElementConstructor) {
4237
+ const element = current;
4238
+ if (element.id === OVERLAY_ID || element.id === EXTENSION_HOST_ID) {
4239
+ return true;
4240
+ }
4241
+ }
4242
+ }
4243
+ return false;
4244
+ };
4245
+ const pickElementAt = (x, y) => {
4246
+ return document2.elementsFromPoint(x, y).find(
4247
+ (el) => !isOverlayNode(el) && isInspectable(el)
4248
+ ) ?? null;
4249
+ };
4250
+ const pointerOverToolbar = (x, y) => {
4251
+ const shadow = document2.getElementById(EXTENSION_HOST_ID)?.shadowRoot;
4252
+ const element = shadow?.elementFromPoint(x, y);
4253
+ if (!element) return false;
4254
+ for (let node = element; node; node = node.parentElement) {
4255
+ if (node.classList.contains("mesurer-toolbar-surface")) return true;
4256
+ }
4257
+ return false;
4258
+ };
4259
+ const ensureStyles = () => {
4260
+ const append = (root) => {
4261
+ if (root.querySelector(`#${STYLE_ID}`)) return;
4262
+ const style = document2.createElement("style");
4263
+ style.id = STYLE_ID;
4264
+ style.textContent = INSPECTOR_STYLES;
4265
+ root.nodeType === 9 ? root.head.appendChild(style) : root.appendChild(style);
4266
+ };
4267
+ append(document2);
4268
+ if (portalIsShadowRoot) append(options.portalTarget);
4269
+ };
4270
+ const ensureOverlay = () => {
4271
+ if (overlay?.isConnected) return overlay;
4272
+ overlay = document2.createElement("div");
4273
+ overlay.id = OVERLAY_ID;
4274
+ Object.assign(overlay.style, {
4275
+ position: "fixed",
4276
+ inset: "0",
4277
+ pointerEvents: "none",
4278
+ zIndex: "2147483646"
4279
+ });
4280
+ (options.portalTarget ?? document2.body).appendChild(overlay);
4281
+ return overlay;
4282
+ };
4283
+ const setState = (element, visible) => {
4284
+ element.dataset.state = visible ? "visible" : "hidden";
4285
+ };
4286
+ const ensureHover = () => {
4287
+ const root = ensureOverlay();
4288
+ hoverBox ?? (hoverBox = makeBox(document2, FILL_HOVER, OUTLINE_HOVER));
4289
+ hoverCard ?? (hoverCard = makeCard(document2, false));
4290
+ if (!hoverBox.parentNode) root.appendChild(hoverBox);
4291
+ if (!hoverCard.parentNode) root.appendChild(hoverCard);
4292
+ };
4293
+ const scheduleEnrichment = (element, fast) => {
4294
+ if (pendingElement === element) return;
4295
+ if (pendingIdleId !== -1) getIdleScheduler().cancel(pendingIdleId);
4296
+ pendingElement = element;
4297
+ pendingIdleId = getIdleScheduler().request(() => {
4298
+ pendingIdleId = -1;
4299
+ pendingElement = null;
4300
+ if (!enabled) return;
4301
+ const full = typography.getFull(element, fast);
4302
+ if (hoveredEl !== element || !hoverCard) return;
4303
+ populateCard(document2, hoverCard, full, false);
4304
+ positionCard(window2, hoverCard, element.getBoundingClientRect());
4305
+ options.onInspect?.(element, full);
4306
+ });
4307
+ };
4308
+ const inspect = (element) => {
4309
+ if (!enabled || !isInspectable(element)) return false;
4310
+ ensureHover();
4311
+ const rect = element.getBoundingClientRect();
4312
+ hoveredEl = element;
4313
+ const fast = typography.getFast(element);
4314
+ populateCard(document2, hoverCard, fast, false);
4315
+ scheduleEnrichment(element, fast);
4316
+ positionBox(hoverBox, rect);
4317
+ positionCard(window2, hoverCard, rect);
4318
+ setState(hoverBox, true);
4319
+ setState(hoverCard, true);
4320
+ return true;
4321
+ };
4322
+ const updateHover = () => {
4323
+ if (!enabled || pointerOverToolbar(pointerX, pointerY)) {
4324
+ hideHover();
4325
+ return;
4326
+ }
4327
+ const element = pickElementAt(pointerX, pointerY);
4328
+ if (!element) {
4329
+ hideHover();
4330
+ return;
4331
+ }
4332
+ if (element !== hoveredEl) inspect(element);
4333
+ else {
4334
+ positionBox(hoverBox, element.getBoundingClientRect());
4335
+ positionCard(window2, hoverCard, element.getBoundingClientRect());
4336
+ }
4337
+ };
4338
+ const hideHover = () => {
4339
+ hoveredEl = null;
4340
+ if (hoverBox) setState(hoverBox, false);
4341
+ if (hoverCard) setState(hoverCard, false);
4342
+ };
4343
+ const snapshotPins = () => pinned.map((pin) => {
4344
+ const rect = pin.card.getBoundingClientRect();
4345
+ return {
4346
+ sourceEl: pin.sourceEl,
4347
+ left: pin.userPlaced ? rect.left + rect.width / 2 : 0,
4348
+ top: pin.userPlaced ? rect.top : 0,
4349
+ userPlaced: pin.userPlaced
4350
+ };
4351
+ });
4352
+ const recordPinState = () => {
4353
+ history.push(snapshotPins());
4354
+ future.length = 0;
4355
+ };
4356
+ const removePin = (pin, record = true, notify = record) => {
4357
+ const index = pinned.indexOf(pin);
4358
+ if (index === -1) return;
4359
+ if (record) recordPinState();
4360
+ pinned.splice(index, 1);
4361
+ pin.detach();
4362
+ pin.box.remove();
4363
+ pin.card.remove();
4364
+ if (notify) options.onUnpin?.(pin.sourceEl);
4365
+ };
4366
+ const clearPins = (notify = false) => {
4367
+ while (pinned.length) {
4368
+ removePin(pinned[pinned.length - 1], false, notify);
4369
+ }
4370
+ };
4371
+ const attachDrag = (pin) => {
4372
+ let pointerId = -1;
4373
+ let startX = 0;
4374
+ let startY = 0;
4375
+ let originLeft = 0;
4376
+ let originTop = 0;
4377
+ let active = false;
4378
+ let dragged = false;
4379
+ let historyRecorded = false;
4380
+ const slop = 6;
4381
+ const onMove = (event) => {
4382
+ if (event.pointerId !== pointerId) return;
4383
+ const dx = event.clientX - startX;
4384
+ const dy = event.clientY - startY;
4385
+ if (!active) active = Math.abs(dx) > slop || Math.abs(dy) > slop;
4386
+ if (!active) return;
4387
+ if (!historyRecorded) {
4388
+ recordPinState();
4389
+ historyRecorded = true;
4390
+ }
4391
+ dragged = true;
4392
+ pin.card.style.left = `${Math.min(
4393
+ window2.innerWidth - 8,
4394
+ Math.max(8, originLeft + dx)
4395
+ )}px`;
4396
+ pin.card.style.top = `${Math.min(
4397
+ window2.innerHeight - 8,
4398
+ Math.max(8, originTop + dy)
4399
+ )}px`;
4400
+ pin.userPlaced = true;
4401
+ };
4402
+ const onEnd = (event) => {
4403
+ if (event.pointerId !== pointerId && pointerId !== -1) return;
4404
+ window2.removeEventListener("pointermove", onMove);
4405
+ window2.removeEventListener("pointerup", onEnd);
4406
+ window2.removeEventListener("pointercancel", onEnd);
4407
+ pointerId = -1;
4408
+ active = false;
4409
+ historyRecorded = false;
4410
+ if (dragged) {
4411
+ const swallow = (click) => {
4412
+ click.preventDefault();
4413
+ click.stopPropagation();
4414
+ window2.removeEventListener("click", swallow, true);
4415
+ };
4416
+ window2.addEventListener("click", swallow, true);
4417
+ }
4418
+ dragged = false;
4419
+ };
4420
+ const onDown = (event) => {
4421
+ if (event.button !== 0) return;
4422
+ if (event.target?.classList.contains("mesurer-ti-close")) {
4423
+ return;
4424
+ }
4425
+ const rect = pin.card.getBoundingClientRect();
4426
+ originLeft = rect.left + rect.width / 2;
4427
+ originTop = rect.top;
4428
+ startX = event.clientX;
4429
+ startY = event.clientY;
4430
+ pointerId = event.pointerId;
4431
+ active = false;
4432
+ dragged = false;
4433
+ window2.addEventListener("pointermove", onMove);
4434
+ window2.addEventListener("pointerup", onEnd);
4435
+ window2.addEventListener("pointercancel", onEnd);
4436
+ };
4437
+ pin.card.addEventListener("pointerdown", onDown);
4438
+ return () => {
4439
+ pin.card.removeEventListener("pointerdown", onDown);
4440
+ window2.removeEventListener("pointermove", onMove);
4441
+ window2.removeEventListener("pointerup", onEnd);
4442
+ window2.removeEventListener("pointercancel", onEnd);
4443
+ };
4444
+ };
4445
+ const createPin = (sourceEl, state, record = true, notify = record) => {
4446
+ const existing = pinned.find((pin2) => pin2.sourceEl === sourceEl);
4447
+ if (existing) {
4448
+ const root2 = ensureOverlay();
4449
+ root2.append(existing.box, existing.card);
4450
+ return;
4451
+ }
4452
+ if (record) {
4453
+ recordPinState();
4454
+ if (pinned.length >= maxPinned) removePin(pinned[0], false, true);
4455
+ }
4456
+ const root = ensureOverlay();
4457
+ const box = makeBox(document2, FILL_PINNED, OUTLINE_PINNED);
4458
+ const card = makeCard(document2, true);
4459
+ const info = typography.getFull(sourceEl);
4460
+ populateCard(document2, card, info, true);
4461
+ root.append(box, card);
4462
+ const pin = {
4463
+ sourceEl,
4464
+ box,
4465
+ card,
4466
+ userPlaced: state?.userPlaced ?? false,
4467
+ detach: () => {
4468
+ }
4469
+ };
4470
+ const rect = sourceEl.getBoundingClientRect();
4471
+ positionBox(box, rect);
4472
+ positionCard(window2, card, rect);
4473
+ if (state?.userPlaced) {
4474
+ card.style.left = `${state.left}px`;
4475
+ card.style.top = `${state.top}px`;
4476
+ }
4477
+ setState(box, true);
4478
+ setState(card, true);
4479
+ pinned.push(pin);
4480
+ card.querySelector(".mesurer-ti-close")?.addEventListener(
4481
+ "click",
4482
+ (event) => {
4483
+ event.preventDefault();
4484
+ event.stopPropagation();
4485
+ removePin(pin);
4486
+ }
4487
+ );
4488
+ pin.detach = attachDrag(pin);
4489
+ if (notify) options.onPin?.(sourceEl, info);
4490
+ };
4491
+ const restorePins = (states) => {
4492
+ clearPins(false);
4493
+ for (const state of states) {
4494
+ if (state.sourceEl.isConnected) createPin(state.sourceEl, state, false, false);
4495
+ }
4496
+ };
4497
+ const undo = () => {
4498
+ const previous = history.pop();
4499
+ if (!previous) return false;
4500
+ future.push(snapshotPins());
4501
+ restorePins(previous);
4502
+ return true;
4503
+ };
4504
+ const redo = () => {
4505
+ const next = future.pop();
4506
+ if (!next) return false;
4507
+ history.push(snapshotPins());
4508
+ restorePins(next);
4509
+ return true;
4510
+ };
4511
+ const clear = () => {
4512
+ if (!pinned.length) return;
4513
+ recordPinState();
4514
+ clearPins(true);
4515
+ };
4516
+ const syncPins = () => {
4517
+ for (const pin of [...pinned]) {
4518
+ if (!pin.sourceEl.isConnected) {
4519
+ removePin(pin, false);
4520
+ continue;
4521
+ }
4522
+ const rect = pin.sourceEl.getBoundingClientRect();
4523
+ positionBox(pin.box, rect);
4524
+ if (!pin.userPlaced) positionCard(window2, pin.card, rect);
4525
+ const visible = rect.bottom >= 0 && rect.right >= 0 && rect.left <= window2.innerWidth && rect.top <= window2.innerHeight;
4526
+ setState(pin.box, visible);
4527
+ setState(pin.card, visible);
4528
+ pin.card.style.pointerEvents = visible ? "auto" : "none";
4529
+ }
4530
+ };
4531
+ const scheduleFrame = () => {
4532
+ if (frameScheduled) return;
4533
+ frameScheduled = true;
4534
+ requestAnimationFrame(() => {
4535
+ frameScheduled = false;
4536
+ if (!enabled) return;
4537
+ updateHover();
4538
+ syncPins();
4539
+ });
4540
+ };
4541
+ const onMouseMove = (event) => {
4542
+ pointerX = event.clientX;
4543
+ pointerY = event.clientY;
4544
+ scheduleFrame();
4545
+ };
4546
+ const onMouseOut = (event) => {
4547
+ if (!event.relatedTarget) hideHover();
4548
+ };
4549
+ const onScrollOrResize = () => scheduleFrame();
4550
+ const isInspectorUIEvent = (event) => event.composedPath().some(
4551
+ (node) => node instanceof HTMLElementConstructor && (() => {
4552
+ const element = node;
4553
+ return element.id === OVERLAY_ID || element.classList.contains("mesurer-ti-card") || element.classList.contains("mesurer-ti-box") || element.classList.contains("mesurer-ti-close") || element.classList.contains("mesurer-toolbar-surface");
4554
+ })()
4555
+ );
4556
+ const onClick = (event) => {
4557
+ if (isInspectorUIEvent(event) || pointerOverToolbar(event.clientX, event.clientY)) {
4558
+ return;
4559
+ }
4560
+ event.preventDefault();
4561
+ event.stopImmediatePropagation();
4562
+ if (event.button === 0) {
4563
+ const source = pickElementAt(event.clientX, event.clientY);
4564
+ if (source) createPin(source);
4565
+ }
4566
+ };
4567
+ const onAuxClick = (event) => {
4568
+ if (isInspectorUIEvent(event) || pointerOverToolbar(event.clientX, event.clientY)) {
4569
+ return;
4570
+ }
4571
+ event.preventDefault();
4572
+ event.stopImmediatePropagation();
4573
+ };
4574
+ const enable = () => {
4575
+ if (enabled || !availableDocument || !availableWindow) return;
4576
+ enabled = true;
4577
+ ensureStyles();
4578
+ ensureOverlay();
4579
+ document2.body.classList.add(BODY_MODE_CLASS);
4580
+ window2.addEventListener("mousemove", onMouseMove, true);
4581
+ window2.addEventListener("mouseout", onMouseOut, true);
4582
+ window2.addEventListener("click", onClick, true);
4583
+ window2.addEventListener("auxclick", onAuxClick, true);
4584
+ window2.addEventListener("scroll", onScrollOrResize, true);
4585
+ window2.addEventListener("resize", onScrollOrResize, true);
4586
+ };
4587
+ const disable = () => {
4588
+ if (!enabled) return;
4589
+ enabled = false;
4590
+ if (pendingIdleId !== -1) getIdleScheduler().cancel(pendingIdleId);
4591
+ pendingIdleId = -1;
4592
+ pendingElement = null;
4593
+ history.length = 0;
4594
+ future.length = 0;
4595
+ window2.removeEventListener("mousemove", onMouseMove, true);
4596
+ window2.removeEventListener("mouseout", onMouseOut, true);
4597
+ window2.removeEventListener("click", onClick, true);
4598
+ window2.removeEventListener("auxclick", onAuxClick, true);
4599
+ window2.removeEventListener("scroll", onScrollOrResize, true);
4600
+ window2.removeEventListener("resize", onScrollOrResize, true);
4601
+ hideHover();
4602
+ hoverBox?.remove();
4603
+ hoverCard?.remove();
4604
+ hoverBox = null;
4605
+ hoverCard = null;
4606
+ clearPins();
4607
+ document2.body.classList.remove(BODY_MODE_CLASS);
4608
+ };
4609
+ const cleanup = () => {
4610
+ disable();
4611
+ overlay?.remove();
4612
+ overlay = null;
4613
+ if (typeof document2 !== "undefined") {
4614
+ document2.getElementById(STYLE_ID)?.remove();
4615
+ if (portalIsShadowRoot) {
4616
+ options.portalTarget.querySelector(`#${STYLE_ID}`)?.remove();
4617
+ }
4618
+ }
4619
+ };
4620
+ return {
4621
+ enable,
4622
+ disable,
4623
+ undo,
4624
+ redo,
4625
+ canUndo: () => history.length > 0,
4626
+ canRedo: () => future.length > 0,
4627
+ clear,
4628
+ inspect,
4629
+ isEnabled: () => enabled,
4630
+ cleanup,
4631
+ destroy: cleanup
4632
+ };
4633
+ };
4634
+ var TextInspector = createTextInspector({}, true);
4635
+
4636
+ // measurer.tsx
4637
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4638
+ var measurerInstanceCount = 0;
4639
+ var XRAY_STYLE_ID = "mesurer-xray-styles";
4640
+ var XRAY_STYLES = `
4641
+ .xray-mode * {
4642
+ outline: solid 1px blue !important;
4643
+ }
4644
+ .xray-mode #mesurer-extension-host,
4645
+ .xray-mode #mesurer-extension-host *,
4646
+ .xray-mode .mesurer-root,
4647
+ .xray-mode .mesurer-root *,
4648
+ .xray-mode .mesurer-toolbar-surface,
4649
+ .xray-mode .mesurer-toolbar-surface * {
4650
+ outline: none !important;
4651
+ }
4652
+ `;
4653
+ var subscribeHydration = () => () => {
4654
+ };
4655
+ var useHydrated = () => useSyncExternalStore(
4656
+ subscribeHydration,
4657
+ () => true,
4658
+ () => false
4659
+ );
4660
+ var stripMeasurement = (measurement) => ({
4661
+ ...measurement,
4662
+ elementRef: void 0
4663
+ });
4664
+ var stripDistance = (distance) => ({
4665
+ ...distance,
4666
+ elementRefA: void 0,
4667
+ elementRefB: void 0
4668
+ });
4669
+ function MeasurerClient({
4670
+ highlightColor,
4671
+ guideColor,
4672
+ hoverHighlightEnabled,
4673
+ persistOnReload,
4674
+ portalTarget,
4675
+ persistKey
4676
+ }) {
4677
+ const instanceIdRef = useRef9(null);
4678
+ if (instanceIdRef.current === null) {
4679
+ instanceIdRef.current = ++measurerInstanceCount;
4680
+ }
4681
+ const storageKey = persistKey ?? (instanceIdRef.current === 1 ? "mesurer-state" : `mesurer-state-${instanceIdRef.current}`);
4682
+ const ownerDocument = portalTarget.ownerDocument ?? document;
4683
+ const ownerWindow = ownerDocument.defaultView ?? window;
4684
+ const guideScrollRef = useRef9({
4685
+ x: ownerWindow.scrollX,
4686
+ y: ownerWindow.scrollY
4687
+ });
4688
+ const textInspectorRef = useRef9(null);
4689
+ if (!textInspectorRef.current) {
4690
+ textInspectorRef.current = createTextInspector({ portalTarget });
4691
+ }
4692
+ const textInspector = textInspectorRef.current;
4693
+ const selectionRectRef = useRef9(null);
4694
+ const toolbarRef = useRef9(null);
4695
+ const selectionAnimationCleanupTimeoutRef = useRef9(null);
4696
+ const guideDragRef = useRef9(null);
4697
+ const guideUserSelectRef = useRef9(null);
4698
+ const persistedState = useMemo2(() => {
4699
+ if (!persistOnReload) return null;
4700
+ try {
4701
+ const stored = window.localStorage.getItem(storageKey);
4702
+ if (!stored) return null;
4703
+ const parsed = JSON.parse(stored);
4704
+ if (!parsed || parsed.version !== 1) return null;
4705
+ return parsed;
4706
+ } catch {
4707
+ return null;
4708
+ }
4709
+ }, [persistOnReload, storageKey]);
4710
+ const enabledRef = useRef9(false);
4711
+ const toolModeRef = useRef9(
4712
+ persistedState?.toolMode === "rulers" ? "none" : persistedState?.toolMode ?? "none"
4713
+ );
4714
+ const rulersVisibleRef = useRef9(
4715
+ persistedState?.rulersVisible ?? persistedState?.toolMode === "rulers"
4716
+ );
4717
+ const guideOrientationRef = useRef9(
4718
+ persistedState?.guideOrientation ?? "vertical"
4719
+ );
4720
+ const measurementsRef = useRef9(
4721
+ persistedState?.measurements ?? []
4722
+ );
4723
+ const activeMeasurementRef = useRef9(
4724
+ persistedState?.activeMeasurement ?? null
4725
+ );
4726
+ const heldDistancesRef = useRef9(
4727
+ persistedState?.heldDistances ?? []
4728
+ );
4729
+ const guidesRef = useRef9(persistedState?.guides ?? []);
4730
+ const selectedGuideIdsRef = useRef9(
4731
+ persistedState?.selectedGuideIds ?? []
4732
+ );
4733
+ const { overlayRef, selectedElementRef, hoverElementRef } = useOverlayRefs();
4734
+ const {
4735
+ selectionOriginRect,
4736
+ setSelectionOriginRect,
4737
+ hoverPointer,
4738
+ setHoverPointer,
4739
+ hoverElement,
4740
+ setHoverElement,
4741
+ selectedElement,
4742
+ setSelectedElement,
4743
+ clearSelectionRect
3285
4744
  } = useMeasurerLocalState({
3286
4745
  selectedElementRef,
3287
4746
  hoverElementRef,
@@ -3296,12 +4755,15 @@ function MeasurerClient({
3296
4755
  setAltPressed,
3297
4756
  toolMode,
3298
4757
  setToolMode,
4758
+ rulersVisible,
4759
+ setRulersVisible,
3299
4760
  guidesEnabled,
3300
4761
  multiMeasureEnabled,
3301
4762
  snapGuidesEnabled
3302
4763
  } = useMeasureToggles({
3303
4764
  initialEnabled: persistedState?.enabled,
3304
- initialToolMode: persistedState?.toolMode
4765
+ initialToolMode: persistedState?.toolMode === "rulers" ? "none" : persistedState?.toolMode,
4766
+ initialRulersVisible: persistedState?.rulersVisible ?? persistedState?.toolMode === "rulers"
3305
4767
  });
3306
4768
  const { start, setStart, end, setEnd, isDragging, setIsDragging } = useDragState();
3307
4769
  const {
@@ -3333,85 +4795,170 @@ function MeasurerClient({
3333
4795
  initialGuides: persistedState?.guides ?? [],
3334
4796
  initialSelectedGuideIds: persistedState?.selectedGuideIds ?? []
3335
4797
  });
3336
- const [toolbarActive, setToolbarActive] = useState9(true);
3337
- const { clearGuideDragHold, scheduleGuideDragHold } = useGuideDragHold();
3338
- const [guidePreview, setGuidePreview] = useState9(null);
3339
- const [guideOrientation, setGuideOrientation] = useState9(persistedState?.guideOrientation ?? "vertical");
3340
- const persistPayload = useMemo2(() => {
3341
- if (!persistOnReload) return null;
3342
- return JSON.stringify({
3343
- version: 1,
3344
- enabled,
3345
- toolMode,
3346
- guideOrientation,
3347
- guides,
3348
- selectedGuideIds,
3349
- measurements: measurements.map(stripMeasurement),
3350
- activeMeasurement: activeMeasurement ? stripMeasurement(activeMeasurement) : null,
3351
- heldDistances: heldDistances.map(stripDistance)
3352
- });
3353
- }, [
3354
- activeMeasurement,
3355
- enabled,
3356
- guideOrientation,
3357
- guides,
3358
- heldDistances,
3359
- measurements,
3360
- persistOnReload,
3361
- selectedGuideIds,
3362
- toolMode
3363
- ]);
3364
- useEffect9(() => {
4798
+ const [toolbarActive, setToolbarActive] = useState8(true);
4799
+ const { clearGuideDragHold, scheduleGuideDragHold } = useGuideDragHold(ownerWindow);
4800
+ const [guidePreview, setGuidePreview] = useState8(null);
4801
+ const [guideOrientation, setGuideOrientation] = useState8(persistedState?.guideOrientation ?? "vertical");
4802
+ enabledRef.current = enabled;
4803
+ toolModeRef.current = toolMode;
4804
+ rulersVisibleRef.current = rulersVisible;
4805
+ guideOrientationRef.current = guideOrientation;
4806
+ measurementsRef.current = measurements;
4807
+ activeMeasurementRef.current = activeMeasurement;
4808
+ heldDistancesRef.current = heldDistances;
4809
+ guidesRef.current = guides;
4810
+ selectedGuideIdsRef.current = selectedGuideIds;
4811
+ const persistState = useCallback6(() => {
3365
4812
  if (!persistOnReload) return;
3366
4813
  if (typeof window === "undefined") return;
3367
- if (!persistPayload) return;
3368
4814
  try {
3369
- window.localStorage.setItem("mesurer-state", persistPayload);
4815
+ window.localStorage.setItem(
4816
+ storageKey,
4817
+ JSON.stringify({
4818
+ version: 1,
4819
+ enabled: enabledRef.current,
4820
+ toolMode: toolModeRef.current,
4821
+ rulersVisible: rulersVisibleRef.current,
4822
+ guideOrientation: guideOrientationRef.current,
4823
+ guides: guidesRef.current,
4824
+ selectedGuideIds: selectedGuideIdsRef.current,
4825
+ measurements: measurementsRef.current.map(stripMeasurement),
4826
+ activeMeasurement: activeMeasurementRef.current ? stripMeasurement(activeMeasurementRef.current) : null,
4827
+ heldDistances: heldDistancesRef.current.map(stripDistance)
4828
+ })
4829
+ );
3370
4830
  } catch {
3371
4831
  }
3372
- const handlePageHide = () => {
3373
- try {
3374
- window.localStorage.setItem("mesurer-state", persistPayload);
3375
- } catch {
4832
+ }, [persistOnReload, storageKey]);
4833
+ const setEnabledPersisted = useCallback6(
4834
+ (value) => {
4835
+ const next = typeof value === "function" ? value(enabledRef.current) : value;
4836
+ if (Object.is(next, enabledRef.current)) return;
4837
+ enabledRef.current = next;
4838
+ setEnabled(next);
4839
+ persistState();
4840
+ },
4841
+ [persistState, setEnabled]
4842
+ );
4843
+ const setRulersVisiblePersisted = useCallback6(
4844
+ (value) => {
4845
+ const next = typeof value === "function" ? value(rulersVisibleRef.current) : value;
4846
+ if (Object.is(next, rulersVisibleRef.current)) return;
4847
+ rulersVisibleRef.current = next;
4848
+ setRulersVisible(next);
4849
+ persistState();
4850
+ },
4851
+ [persistState, setRulersVisible]
4852
+ );
4853
+ const setToolModePersisted = useCallback6(
4854
+ (value) => {
4855
+ const next = typeof value === "function" ? value(toolModeRef.current) : value;
4856
+ if (Object.is(next, toolModeRef.current)) return;
4857
+ toolModeRef.current = next;
4858
+ setToolMode(next);
4859
+ if (next === "text-inspector") {
4860
+ textInspector.enable();
4861
+ } else {
4862
+ textInspector.disable();
3376
4863
  }
3377
- };
3378
- window.addEventListener("pagehide", handlePageHide);
3379
- return () => {
3380
- window.removeEventListener("pagehide", handlePageHide);
3381
- };
3382
- }, [persistOnReload, persistPayload]);
4864
+ persistState();
4865
+ },
4866
+ [persistState, setToolMode, textInspector]
4867
+ );
4868
+ const setGuideOrientationPersisted = useCallback6(
4869
+ (value) => {
4870
+ const next = typeof value === "function" ? value(guideOrientationRef.current) : value;
4871
+ if (Object.is(next, guideOrientationRef.current)) return;
4872
+ guideOrientationRef.current = next;
4873
+ setGuideOrientation(next);
4874
+ persistState();
4875
+ },
4876
+ [persistState, setGuideOrientation]
4877
+ );
4878
+ const setMeasurementsPersisted = useCallback6(
4879
+ (value) => {
4880
+ const next = typeof value === "function" ? value(measurementsRef.current) : value;
4881
+ if (Object.is(next, measurementsRef.current)) return;
4882
+ measurementsRef.current = next;
4883
+ setMeasurements(next);
4884
+ persistState();
4885
+ },
4886
+ [persistState, setMeasurements]
4887
+ );
4888
+ const setActiveMeasurementPersisted = useCallback6(
4889
+ (value) => {
4890
+ const next = typeof value === "function" ? value(activeMeasurementRef.current) : value;
4891
+ if (Object.is(next, activeMeasurementRef.current)) return;
4892
+ activeMeasurementRef.current = next;
4893
+ setActiveMeasurement(next);
4894
+ persistState();
4895
+ },
4896
+ [persistState, setActiveMeasurement]
4897
+ );
4898
+ const setHeldDistancesPersisted = useCallback6(
4899
+ (value) => {
4900
+ const next = typeof value === "function" ? value(heldDistancesRef.current) : value;
4901
+ if (Object.is(next, heldDistancesRef.current)) return;
4902
+ heldDistancesRef.current = next;
4903
+ setHeldDistances(next);
4904
+ persistState();
4905
+ },
4906
+ [persistState, setHeldDistances]
4907
+ );
4908
+ const setGuidesPersisted = useCallback6(
4909
+ (value) => {
4910
+ const next = typeof value === "function" ? value(guidesRef.current) : value;
4911
+ if (Object.is(next, guidesRef.current)) return;
4912
+ guidesRef.current = next;
4913
+ setGuides(next);
4914
+ persistState();
4915
+ },
4916
+ [persistState, setGuides]
4917
+ );
4918
+ const setSelectedGuideIdsPersisted = useCallback6(
4919
+ (value) => {
4920
+ const next = typeof value === "function" ? value(selectedGuideIdsRef.current) : value;
4921
+ if (Object.is(next, selectedGuideIdsRef.current)) return;
4922
+ selectedGuideIdsRef.current = next;
4923
+ setSelectedGuideIds(next);
4924
+ persistState();
4925
+ },
4926
+ [persistState, setSelectedGuideIds]
4927
+ );
3383
4928
  const {
3384
4929
  recordSnapshot,
3385
4930
  createActionCommit,
3386
4931
  setToolModeWithHistory,
3387
4932
  setGuideOrientationWithHistory,
3388
- setEnabledWithHistory
4933
+ setEnabledWithHistory,
4934
+ undo: undoHistory,
4935
+ redo: redoHistory
3389
4936
  } = useMeasurerHistory({
3390
4937
  toggles: {
3391
4938
  enabled,
3392
- setEnabled,
4939
+ setEnabled: setEnabledPersisted,
3393
4940
  toolMode,
3394
- setToolMode,
4941
+ setToolMode: setToolModePersisted,
3395
4942
  guideOrientation,
3396
- setGuideOrientation
4943
+ setGuideOrientation: setGuideOrientationPersisted
3397
4944
  },
3398
4945
  measurements: {
3399
4946
  measurements,
3400
- setMeasurements,
4947
+ setMeasurements: setMeasurementsPersisted,
3401
4948
  activeMeasurement,
3402
- setActiveMeasurement,
4949
+ setActiveMeasurement: setActiveMeasurementPersisted,
3403
4950
  selectedMeasurements,
3404
4951
  setSelectedMeasurements,
3405
4952
  selectedMeasurement,
3406
4953
  setSelectedMeasurement,
3407
4954
  heldDistances,
3408
- setHeldDistances
4955
+ setHeldDistances: setHeldDistancesPersisted
3409
4956
  },
3410
4957
  guides: {
3411
4958
  guides,
3412
- setGuides,
4959
+ setGuides: setGuidesPersisted,
3413
4960
  selectedGuideIds,
3414
- setSelectedGuideIds,
4961
+ setSelectedGuideIds: setSelectedGuideIdsPersisted,
3415
4962
  draggingGuideId,
3416
4963
  setDraggingGuideId
3417
4964
  },
@@ -3426,96 +4973,254 @@ function MeasurerClient({
3426
4973
  clearSelectionRect
3427
4974
  }
3428
4975
  });
4976
+ const undo = useCallback6(() => {
4977
+ if (toolMode === "text-inspector" && textInspector.undo()) return;
4978
+ undoHistory();
4979
+ }, [textInspector, toolMode, undoHistory]);
4980
+ const redo = useCallback6(() => {
4981
+ if (toolMode === "text-inspector" && textInspector.redo()) return;
4982
+ redoHistory();
4983
+ }, [redoHistory, textInspector, toolMode]);
3429
4984
  const clearAll = useCallback6(() => {
4985
+ if (toolMode === "text-inspector") {
4986
+ textInspector.clear();
4987
+ }
3430
4988
  recordSnapshot();
3431
4989
  clearGuideDragHold();
3432
4990
  setStart(null);
3433
4991
  setEnd(null);
3434
4992
  setIsDragging(false);
3435
- setActiveMeasurement(null);
3436
- setMeasurements([]);
4993
+ setActiveMeasurementPersisted(null);
4994
+ setMeasurementsPersisted([]);
3437
4995
  setSelectedMeasurement(null);
3438
4996
  setSelectedMeasurements([]);
3439
4997
  clearSelectionRect();
3440
4998
  setSelectedElement(null);
3441
4999
  setHoverRect(null);
3442
5000
  setHoverElement(null);
3443
- setGuides([]);
3444
- setSelectedGuideIds([]);
3445
- setHeldDistances([]);
5001
+ setGuidesPersisted([]);
5002
+ setSelectedGuideIdsPersisted([]);
5003
+ setHeldDistancesPersisted([]);
3446
5004
  }, [
3447
5005
  clearGuideDragHold,
3448
5006
  clearSelectionRect,
3449
5007
  recordSnapshot,
3450
- setActiveMeasurement,
5008
+ setActiveMeasurementPersisted,
3451
5009
  setEnd,
3452
- setGuides,
3453
- setHeldDistances,
5010
+ setGuidesPersisted,
5011
+ setHeldDistancesPersisted,
3454
5012
  setHoverElement,
3455
5013
  setHoverRect,
3456
5014
  setIsDragging,
3457
- setMeasurements,
5015
+ setMeasurementsPersisted,
3458
5016
  setSelectedElement,
3459
- setSelectedGuideIds,
5017
+ setSelectedGuideIdsPersisted,
3460
5018
  setSelectedMeasurement,
3461
5019
  setSelectedMeasurements,
3462
- setStart
5020
+ setStart,
5021
+ textInspector,
5022
+ toolMode
3463
5023
  ]);
3464
5024
  const removeSelectedGuides = useCallback6(() => {
3465
5025
  if (selectedGuideIds.length === 0) return false;
3466
5026
  recordSnapshot();
3467
- setGuides(
5027
+ setGuidesPersisted(
3468
5028
  (prev) => prev.filter((guide) => !selectedGuideIds.includes(guide.id))
3469
5029
  );
3470
- setSelectedGuideIds([]);
5030
+ setSelectedGuideIdsPersisted([]);
3471
5031
  return true;
3472
- }, [recordSnapshot, selectedGuideIds, setGuides, setSelectedGuideIds]);
5032
+ }, [
5033
+ recordSnapshot,
5034
+ selectedGuideIds,
5035
+ setGuidesPersisted,
5036
+ setSelectedGuideIdsPersisted
5037
+ ]);
3473
5038
  useHotkeys({
5039
+ eventTarget: ownerWindow,
3474
5040
  clearAll,
5041
+ undo,
5042
+ redo,
3475
5043
  removeSelectedGuides,
3476
5044
  setEnabled: setEnabledWithHistory,
3477
5045
  setToolMode: setToolModeWithHistory,
5046
+ setRulersVisible: setRulersVisiblePersisted,
3478
5047
  setAltPressed,
3479
5048
  isOverlayActive: () => enabled && (toolMode !== "none" || toolbarActive),
3480
5049
  setGuideOrientation: setGuideOrientationWithHistory,
3481
5050
  onInteract: () => setToolbarActive(true)
3482
5051
  });
3483
5052
  useResizeSync({
3484
- setMeasurements,
3485
- setActiveMeasurement,
3486
- setHeldDistances,
5053
+ document: ownerDocument,
5054
+ window: ownerWindow,
5055
+ setMeasurements: setMeasurementsPersisted,
5056
+ setActiveMeasurement: setActiveMeasurementPersisted,
5057
+ setHeldDistances: setHeldDistancesPersisted,
3487
5058
  setSelectedMeasurement,
3488
- setGuides,
5059
+ setGuides: setGuidesPersisted,
3489
5060
  selectedElementRef
3490
5061
  });
5062
+ useEffect5(() => {
5063
+ const handleScroll = () => {
5064
+ const next = {
5065
+ x: ownerWindow.scrollX,
5066
+ y: ownerWindow.scrollY
5067
+ };
5068
+ const deltaX = next.x - guideScrollRef.current.x;
5069
+ const deltaY = next.y - guideScrollRef.current.y;
5070
+ guideScrollRef.current = next;
5071
+ if (deltaX === 0 && deltaY === 0) return;
5072
+ setGuidesPersisted(
5073
+ (prev) => prev.map((guide) => ({
5074
+ ...guide,
5075
+ position: guide.position - (guide.orientation === "vertical" ? deltaX : deltaY)
5076
+ }))
5077
+ );
5078
+ };
5079
+ ownerWindow.addEventListener("scroll", handleScroll, true);
5080
+ return () => ownerWindow.removeEventListener("scroll", handleScroll, true);
5081
+ }, [ownerWindow, setGuidesPersisted]);
3491
5082
  useLiveElementTracking({
5083
+ document: ownerDocument,
5084
+ window: ownerWindow,
3492
5085
  enabled,
3493
5086
  selectedElementRef,
3494
5087
  hoverElementRef,
3495
5088
  setSelectedMeasurement,
3496
5089
  setSelectedMeasurements,
3497
5090
  setHoverRect,
3498
- setMeasurements,
3499
- setActiveMeasurement,
3500
- setHeldDistances
5091
+ setMeasurements: setMeasurementsPersisted,
5092
+ setActiveMeasurement: setActiveMeasurementPersisted,
5093
+ setHeldDistances: setHeldDistancesPersisted
3501
5094
  });
3502
- useEffect9(() => {
5095
+ useEffect5(() => {
3503
5096
  if (!toolbarActive || toolMode !== "none") return;
3504
5097
  const handlePointerDown2 = (event) => {
3505
5098
  const toolbarNode = toolbarRef.current;
3506
5099
  if (toolbarNode && toolbarNode.contains(event.target)) return;
3507
5100
  setToolbarActive(false);
3508
5101
  };
3509
- window.addEventListener("pointerdown", handlePointerDown2);
5102
+ ownerWindow.addEventListener("pointerdown", handlePointerDown2);
3510
5103
  return () => {
3511
- window.removeEventListener("pointerdown", handlePointerDown2);
5104
+ ownerWindow.removeEventListener("pointerdown", handlePointerDown2);
3512
5105
  };
3513
- }, [toolbarActive, toolMode]);
3514
- useEffect9(() => {
3515
- if (hoverHighlightEnabled) return;
3516
- setHoverRect(null);
3517
- }, [hoverHighlightEnabled, setHoverRect]);
3518
- useEffect9(() => {
5106
+ }, [ownerWindow, toolbarActive, toolMode]);
5107
+ useEffect5(() => {
5108
+ if (!enabled) return;
5109
+ const handleGuidePointerDown2 = (event) => {
5110
+ if (toolbarRef.current?.contains(event.target)) return;
5111
+ const guideTarget = event.composedPath().some(
5112
+ (target) => target instanceof ownerWindow.Element && target.hasAttribute("data-mesurer-guide")
5113
+ );
5114
+ if (guideTarget && toolMode !== "none") return;
5115
+ const point = { x: event.clientX, y: event.clientY };
5116
+ const guide = guides.find((candidate) => {
5117
+ const distance = candidate.orientation === "vertical" ? Math.abs(candidate.position - point.x) : Math.abs(candidate.position - point.y);
5118
+ return distance <= GUIDE_HITBOX_SIZE / 2;
5119
+ });
5120
+ if (!guide) return;
5121
+ if (event.button === 0 && !event.shiftKey && toolMode === "none") {
5122
+ guideDragRef.current = {
5123
+ id: guide.id,
5124
+ orientation: guide.orientation,
5125
+ pointerId: event.pointerId,
5126
+ commit: createActionCommit(),
5127
+ committed: false
5128
+ };
5129
+ }
5130
+ setSelectedGuideIdsPersisted(
5131
+ (prev) => event.shiftKey ? prev.includes(guide.id) ? prev.filter((id) => id !== guide.id) : [...prev, guide.id] : [guide.id]
5132
+ );
5133
+ };
5134
+ const handleGuidePointerMove = (event) => {
5135
+ const drag = guideDragRef.current;
5136
+ if (!drag || drag.pointerId !== event.pointerId) return;
5137
+ const position = drag.orientation === "vertical" ? event.clientX : event.clientY;
5138
+ if (!drag.committed) {
5139
+ event.preventDefault();
5140
+ if (guideUserSelectRef.current === null) {
5141
+ guideUserSelectRef.current = ownerDocument.documentElement.style.userSelect;
5142
+ ownerDocument.documentElement.style.userSelect = "none";
5143
+ }
5144
+ ownerWindow.getSelection()?.removeAllRanges();
5145
+ drag.commit();
5146
+ drag.committed = true;
5147
+ }
5148
+ setGuidesPersisted(
5149
+ (prev) => prev.map(
5150
+ (guide) => guide.id === drag.id ? { ...guide, position } : guide
5151
+ )
5152
+ );
5153
+ };
5154
+ const handleGuidePointerEnd = (event) => {
5155
+ if (guideDragRef.current?.pointerId === event.pointerId) {
5156
+ guideDragRef.current = null;
5157
+ if (guideUserSelectRef.current !== null) {
5158
+ ownerDocument.documentElement.style.userSelect = guideUserSelectRef.current;
5159
+ guideUserSelectRef.current = null;
5160
+ }
5161
+ }
5162
+ };
5163
+ ownerWindow.addEventListener("pointerdown", handleGuidePointerDown2, true);
5164
+ ownerWindow.addEventListener("pointermove", handleGuidePointerMove, true);
5165
+ ownerWindow.addEventListener("pointerup", handleGuidePointerEnd, true);
5166
+ ownerWindow.addEventListener("pointercancel", handleGuidePointerEnd, true);
5167
+ return () => {
5168
+ ownerWindow.removeEventListener(
5169
+ "pointerdown",
5170
+ handleGuidePointerDown2,
5171
+ true
5172
+ );
5173
+ ownerWindow.removeEventListener("pointermove", handleGuidePointerMove, true);
5174
+ ownerWindow.removeEventListener("pointerup", handleGuidePointerEnd, true);
5175
+ ownerWindow.removeEventListener(
5176
+ "pointercancel",
5177
+ handleGuidePointerEnd,
5178
+ true
5179
+ );
5180
+ if (guideUserSelectRef.current !== null) {
5181
+ ownerDocument.documentElement.style.userSelect = guideUserSelectRef.current;
5182
+ guideUserSelectRef.current = null;
5183
+ }
5184
+ };
5185
+ }, [
5186
+ createActionCommit,
5187
+ enabled,
5188
+ guides,
5189
+ ownerWindow,
5190
+ setGuidesPersisted,
5191
+ setSelectedGuideIdsPersisted,
5192
+ toolMode
5193
+ ]);
5194
+ useEffect5(() => {
5195
+ if (toolMode === "text-inspector") {
5196
+ textInspector.enable();
5197
+ } else {
5198
+ textInspector.disable();
5199
+ }
5200
+ }, [textInspector, toolMode]);
5201
+ useEffect5(() => {
5202
+ let style = ownerDocument.getElementById(XRAY_STYLE_ID);
5203
+ if (!style) {
5204
+ style = ownerDocument.createElement("style");
5205
+ style.id = XRAY_STYLE_ID;
5206
+ style.textContent = XRAY_STYLES;
5207
+ ownerDocument.head.appendChild(style);
5208
+ }
5209
+ if (toolMode === "xray") {
5210
+ ownerDocument.body.classList.add("xray-mode");
5211
+ } else {
5212
+ ownerDocument.body.classList.remove("xray-mode");
5213
+ }
5214
+ return () => {
5215
+ ownerDocument.body.classList.remove("xray-mode");
5216
+ };
5217
+ }, [ownerDocument, toolMode]);
5218
+ useEffect5(() => {
5219
+ return () => {
5220
+ textInspector.destroy();
5221
+ };
5222
+ }, [textInspector]);
5223
+ useEffect5(() => {
3519
5224
  const hasSelectionAnimationState = !!selectionOriginRect || !!selectedMeasurement?.originRect || selectedMeasurements.some((measurement) => !!measurement.originRect);
3520
5225
  if (!hasSelectionAnimationState) {
3521
5226
  if (selectionAnimationCleanupTimeoutRef.current !== null) {
@@ -3544,6 +5249,12 @@ function MeasurerClient({
3544
5249
  return changed ? next : prev;
3545
5250
  });
3546
5251
  }, MEASURE_TRANSITION_MS);
5252
+ return () => {
5253
+ if (selectionAnimationCleanupTimeoutRef.current !== null) {
5254
+ window.clearTimeout(selectionAnimationCleanupTimeoutRef.current);
5255
+ selectionAnimationCleanupTimeoutRef.current = null;
5256
+ }
5257
+ };
3547
5258
  }, [
3548
5259
  selectionOriginRect,
3549
5260
  selectedMeasurement,
@@ -3552,13 +5263,6 @@ function MeasurerClient({
3552
5263
  setSelectedMeasurements,
3553
5264
  setSelectionOriginRect
3554
5265
  ]);
3555
- useEffect9(() => {
3556
- return () => {
3557
- if (selectionAnimationCleanupTimeoutRef.current !== null) {
3558
- window.clearTimeout(selectionAnimationCleanupTimeoutRef.current);
3559
- }
3560
- };
3561
- }, []);
3562
5266
  const displayedMeasurements = holdEnabled ? measurements : multiMeasureEnabled && measurements.length > 0 ? measurements : activeMeasurement ? [activeMeasurement] : [];
3563
5267
  const {
3564
5268
  activeRect,
@@ -3580,6 +5284,8 @@ function MeasurerClient({
3580
5284
  hoverEdgeVisibility,
3581
5285
  measurementEdgeVisibility
3582
5286
  } = useMeasurerDerived({
5287
+ document: ownerDocument,
5288
+ window: ownerWindow,
3583
5289
  start,
3584
5290
  end,
3585
5291
  selectedMeasurements,
@@ -3605,6 +5311,8 @@ function MeasurerClient({
3605
5311
  handlePointerUp,
3606
5312
  handlePointerLeave
3607
5313
  } = useMeasurerPointer({
5314
+ document: ownerDocument,
5315
+ window: ownerWindow,
3608
5316
  toolbarRef,
3609
5317
  overlayRef,
3610
5318
  selectionRectRef,
@@ -3630,15 +5338,15 @@ function MeasurerClient({
3630
5338
  optionPairOverlay,
3631
5339
  setAltPressed,
3632
5340
  setGuidePreview,
3633
- setSelectedGuideIds,
3634
- setGuides,
5341
+ setSelectedGuideIds: setSelectedGuideIdsPersisted,
5342
+ setGuides: setGuidesPersisted,
3635
5343
  setStart,
3636
5344
  setEnd,
3637
5345
  setIsDragging,
3638
- setHeldDistances,
5346
+ setHeldDistances: setHeldDistancesPersisted,
3639
5347
  setDraggingGuideId,
3640
- setActiveMeasurement,
3641
- setMeasurements,
5348
+ setActiveMeasurement: setActiveMeasurementPersisted,
5349
+ setMeasurements: setMeasurementsPersisted,
3642
5350
  setSelectedMeasurements,
3643
5351
  setSelectedMeasurement,
3644
5352
  setSelectionOriginRect,
@@ -3651,24 +5359,62 @@ function MeasurerClient({
3651
5359
  const removeHeldDistance = useCallback6(
3652
5360
  (id) => {
3653
5361
  recordSnapshot();
3654
- setHeldDistances((prev) => prev.filter((distance) => distance.id !== id));
5362
+ setHeldDistancesPersisted(
5363
+ (prev) => prev.filter((distance) => distance.id !== id)
5364
+ );
5365
+ },
5366
+ [recordSnapshot, setHeldDistancesPersisted]
5367
+ );
5368
+ const startGuideFromRuler = useCallback6(
5369
+ (orientation, position) => {
5370
+ const id = createId();
5371
+ const commit = createActionCommit();
5372
+ commit();
5373
+ setSelectedGuideIdsPersisted([]);
5374
+ setGuidesPersisted((prev) => [...prev, { id, orientation, position }]);
5375
+ return id;
5376
+ },
5377
+ [
5378
+ createActionCommit,
5379
+ setGuidesPersisted,
5380
+ setSelectedGuideIdsPersisted
5381
+ ]
5382
+ );
5383
+ const moveGuideFromRuler = useCallback6(
5384
+ (id, position) => {
5385
+ setGuidesPersisted(
5386
+ (prev) => prev.map((guide) => guide.id === id ? { ...guide, position } : guide)
5387
+ );
5388
+ },
5389
+ [setGuidesPersisted]
5390
+ );
5391
+ const finishGuideFromRuler = useCallback6(
5392
+ (id) => {
5393
+ setSelectedGuideIdsPersisted([id]);
5394
+ },
5395
+ [setSelectedGuideIdsPersisted]
5396
+ );
5397
+ const cancelGuideFromRuler = useCallback6(
5398
+ (id) => {
5399
+ setGuidesPersisted((prev) => prev.filter((guide) => guide.id !== id));
3655
5400
  },
3656
- [recordSnapshot, setHeldDistances]
5401
+ [setGuidesPersisted]
3657
5402
  );
3658
5403
  const handleGuidePointerDown = useCallback6(
3659
5404
  (guide, event) => {
3660
5405
  const commit = createActionCommit();
3661
5406
  if (!enabled) return;
3662
5407
  event.stopPropagation();
5408
+ event.preventDefault();
3663
5409
  if (event.shiftKey) {
3664
5410
  commit();
3665
- setSelectedGuideIds(
5411
+ setSelectedGuideIdsPersisted(
3666
5412
  (prev) => prev.includes(guide.id) ? prev.filter((id) => id !== guide.id) : [...prev, guide.id]
3667
5413
  );
3668
5414
  return;
3669
5415
  }
3670
5416
  commit();
3671
- setSelectedGuideIds([guide.id]);
5417
+ setSelectedGuideIdsPersisted([guide.id]);
3672
5418
  scheduleGuideDragHold(guide.id, setDraggingGuideId);
3673
5419
  event.currentTarget.setPointerCapture(event.pointerId);
3674
5420
  },
@@ -3677,7 +5423,7 @@ function MeasurerClient({
3677
5423
  enabled,
3678
5424
  scheduleGuideDragHold,
3679
5425
  setDraggingGuideId,
3680
- setSelectedGuideIds
5426
+ setSelectedGuideIdsPersisted
3681
5427
  ]
3682
5428
  );
3683
5429
  const handleGuidePointerUp = useCallback6(
@@ -3692,17 +5438,29 @@ function MeasurerClient({
3692
5438
  [clearGuideDragHold, setDraggingGuideId]
3693
5439
  );
3694
5440
  return createPortal(
3695
- /* @__PURE__ */ jsxs6(
5441
+ /* @__PURE__ */ jsxs7(
3696
5442
  "div",
3697
5443
  {
3698
5444
  ref: overlayRef,
3699
5445
  className: "mesurer-root msr:pointer-events-none msr:fixed msr:inset-0 msr:z-50",
3700
5446
  children: [
3701
- /* @__PURE__ */ jsx8(
5447
+ enabled && rulersVisible ? /* @__PURE__ */ jsx9(
5448
+ RulersOverlay,
5449
+ {
5450
+ onStartGuide: startGuideFromRuler,
5451
+ onMoveGuide: moveGuideFromRuler,
5452
+ onFinishGuide: finishGuideFromRuler,
5453
+ onCancelGuide: cancelGuideFromRuler,
5454
+ guides,
5455
+ selectedGuideIds
5456
+ }
5457
+ ) : null,
5458
+ /* @__PURE__ */ jsx9(
3702
5459
  MeasurerOverlay,
3703
5460
  {
3704
5461
  enabled,
3705
5462
  toolMode,
5463
+ guidePointerEvents: enabled && (toolMode !== "none" || rulersVisible),
3706
5464
  guidesEnabled,
3707
5465
  altPressed,
3708
5466
  isDragging,
@@ -3740,13 +5498,16 @@ function MeasurerClient({
3740
5498
  onGuidePointerCancel: handleGuidePointerUp
3741
5499
  }
3742
5500
  ),
3743
- /* @__PURE__ */ jsx8(
5501
+ /* @__PURE__ */ jsx9(
3744
5502
  Toolbar,
3745
5503
  {
3746
5504
  ref: toolbarRef,
5505
+ eventTarget: ownerWindow,
3747
5506
  toolMode,
3748
5507
  setEnabled: setEnabledWithHistory,
3749
5508
  setToolMode: setToolModeWithHistory,
5509
+ rulersVisible,
5510
+ setRulersVisible: setRulersVisiblePersisted,
3750
5511
  guideOrientation,
3751
5512
  setGuideOrientation: setGuideOrientationWithHistory,
3752
5513
  onInteract: () => setToolbarActive(true)
@@ -3763,24 +5524,28 @@ function Measurer({
3763
5524
  guideColor = "oklch(0.63 0.26 29.23)",
3764
5525
  hoverHighlightEnabled = true,
3765
5526
  persistOnReload = false,
3766
- portalTarget
5527
+ portalTarget,
5528
+ persistKey
3767
5529
  }) {
3768
5530
  if (typeof document !== "undefined") {
3769
5531
  ensureMeasurerStyles(MESURER_STYLES, portalTarget);
3770
5532
  }
3771
5533
  const hydrated = useHydrated();
3772
5534
  if (!hydrated) return null;
3773
- return /* @__PURE__ */ jsx8(
5535
+ return /* @__PURE__ */ jsx9(
3774
5536
  MeasurerClient,
3775
5537
  {
3776
5538
  highlightColor,
3777
5539
  guideColor,
3778
5540
  hoverHighlightEnabled,
3779
5541
  persistOnReload,
5542
+ persistKey,
3780
5543
  portalTarget: portalTarget ?? document.body
3781
5544
  }
3782
5545
  );
3783
5546
  }
3784
5547
  export {
3785
- Measurer
5548
+ Measurer,
5549
+ TextInspector,
5550
+ createTextInspector
3786
5551
  };