mesurer 0.0.8 → 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.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';
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,18 +1756,20 @@ var getOptionContainerLines = (params) => {
1261
1756
  };
1262
1757
  };
1263
1758
 
1264
- // selection-helpers.ts
1759
+ // core/selection-helpers.ts
1265
1760
  var getOverlayHost = (overlayNode) => {
1266
1761
  if (!overlayNode) return null;
1267
1762
  const rootNode = overlayNode.getRootNode();
1268
- return rootNode instanceof ShadowRoot ? rootNode.host : null;
1763
+ return rootNode.nodeType === 11 ? rootNode.host : null;
1269
1764
  };
1270
1765
  var getPrimarySelectedMeasurement = (selectedMeasurements, selectedMeasurement) => selectedMeasurements.length > 0 ? selectedMeasurements[selectedMeasurements.length - 1] : selectedMeasurement;
1271
1766
  var getSelectedMeasurementHit = (params) => {
1767
+ const ownerDocument = params.document ?? document;
1768
+ const HTMLElementConstructor = ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
1272
1769
  const overlayHost = getOverlayHost(params.overlayNode);
1273
1770
  const candidates = params.selectedMeasurements.map((measurement) => {
1274
1771
  const element = measurement.elementRef;
1275
- if (!element || !document.contains(element)) return null;
1772
+ if (!element || !ownerDocument.contains(element)) return null;
1276
1773
  const rect = getRectFromDom(element);
1277
1774
  return {
1278
1775
  measurement,
@@ -1282,9 +1779,9 @@ var getSelectedMeasurementHit = (params) => {
1282
1779
  };
1283
1780
  }).filter((item) => item !== null).sort((a, b) => a.area - b.area);
1284
1781
  if (candidates.length === 0) return null;
1285
- const stack = document.elementsFromPoint(params.point.x, params.point.y);
1782
+ const stack = ownerDocument.elementsFromPoint(params.point.x, params.point.y);
1286
1783
  for (const element of stack) {
1287
- if (!(element instanceof HTMLElement)) continue;
1784
+ if (!(element instanceof HTMLElementConstructor)) continue;
1288
1785
  if (params.overlayNode && params.overlayNode.contains(element)) continue;
1289
1786
  if (overlayHost && element === overlayHost) continue;
1290
1787
  for (const candidate of candidates) {
@@ -1303,6 +1800,8 @@ var getSelectedMeasurementHit = (params) => {
1303
1800
 
1304
1801
  // hooks/use-measurer-derived.ts
1305
1802
  var useMeasurerDerived = ({
1803
+ document: document2,
1804
+ window: window2,
1306
1805
  start,
1307
1806
  end,
1308
1807
  selectedMeasurements,
@@ -1387,7 +1886,9 @@ var useMeasurerDerived = ({
1387
1886
  selectedGuide,
1388
1887
  hoverGuide,
1389
1888
  hoverElement,
1390
- selectedElementRef: selectedElement
1889
+ selectedElementRef: selectedElement,
1890
+ document: document2,
1891
+ window: window2
1391
1892
  });
1392
1893
  }, [
1393
1894
  altPressed,
@@ -1404,7 +1905,9 @@ var useMeasurerDerived = ({
1404
1905
  optionPairOverlay,
1405
1906
  selectedGuideIds,
1406
1907
  selectedElement,
1407
- hoverElement
1908
+ hoverElement,
1909
+ document: document2,
1910
+ window: window2
1408
1911
  });
1409
1912
  }, [
1410
1913
  altPressed,
@@ -1418,8 +1921,11 @@ var useMeasurerDerived = ({
1418
1921
  if (!altPressed || !guidesEnabled) return null;
1419
1922
  if (selectedGuide && hoverGuide && selectedGuide.id !== hoverGuide.id) {
1420
1923
  return getDistanceOverlay(
1421
- getGuideRect(selectedGuide),
1422
- getGuideRect(hoverGuide)
1924
+ getGuideRect(selectedGuide, window2),
1925
+ getGuideRect(hoverGuide, window2),
1926
+ null,
1927
+ null,
1928
+ window2
1423
1929
  );
1424
1930
  }
1425
1931
  if (!guidePreview) return null;
@@ -1442,8 +1948,11 @@ var useMeasurerDerived = ({
1442
1948
  );
1443
1949
  if (!nearest) return null;
1444
1950
  return getDistanceOverlay(
1445
- getGuideRect(previewGuide),
1446
- getGuideRect(nearest.guide)
1951
+ getGuideRect(previewGuide, window2),
1952
+ getGuideRect(nearest.guide, window2),
1953
+ null,
1954
+ null,
1955
+ window2
1447
1956
  );
1448
1957
  }, [
1449
1958
  altPressed,
@@ -1513,7 +2022,7 @@ var useMeasurerDerived = ({
1513
2022
  };
1514
2023
 
1515
2024
  // hooks/use-measurer-history.ts
1516
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
2025
+ import { useCallback as useCallback3, useRef as useRef5 } from "react";
1517
2026
  var HISTORY_LIMIT = 50;
1518
2027
  var useMeasurerHistory = ({
1519
2028
  toggles,
@@ -1559,9 +2068,9 @@ var useMeasurerHistory = ({
1559
2068
  setSelectedElement,
1560
2069
  clearSelectionRect
1561
2070
  } = transient;
1562
- const historyRef = useRef4([]);
1563
- const futureRef = useRef4([]);
1564
- const historySignatureRef = useRef4(null);
2071
+ const historyRef = useRef5([]);
2072
+ const futureRef = useRef5([]);
2073
+ const historySignatureRef = useRef5(null);
1565
2074
  const captureSnapshot = useCallback3(() => {
1566
2075
  return {
1567
2076
  enabled,
@@ -1723,49 +2232,32 @@ var useMeasurerHistory = ({
1723
2232
  committed = true;
1724
2233
  };
1725
2234
  }, [recordSnapshot]);
1726
- useEffect4(() => {
1727
- const handleKeyDown = (event) => {
1728
- if (!(event.metaKey || event.ctrlKey)) return;
1729
- if (event.key.toLowerCase() !== "z") return;
1730
- if (event.shiftKey) {
1731
- if (futureRef.current.length === 0) return;
1732
- event.preventDefault();
1733
- redo();
1734
- return;
1735
- }
1736
- if (historyRef.current.length === 0) return;
1737
- event.preventDefault();
1738
- undo();
1739
- };
1740
- window.addEventListener("keydown", handleKeyDown);
1741
- return () => {
1742
- window.removeEventListener("keydown", handleKeyDown);
1743
- };
1744
- }, [redo, undo]);
1745
2235
  return {
1746
2236
  recordSnapshot,
1747
2237
  createActionCommit,
1748
2238
  setToolModeWithHistory,
1749
2239
  setGuideOrientationWithHistory,
1750
- setEnabledWithHistory
2240
+ setEnabledWithHistory,
2241
+ undo,
2242
+ redo
1751
2243
  };
1752
2244
  };
1753
2245
 
1754
2246
  // hooks/use-measurer-local-state.ts
1755
- import { useCallback as useCallback4, useState as useState6 } from "react";
2247
+ import { useCallback as useCallback4, useState as useState7 } from "react";
1756
2248
  var useMeasurerLocalState = ({
1757
2249
  selectedElementRef,
1758
2250
  hoverElementRef,
1759
2251
  selectionRectRef
1760
2252
  }) => {
1761
- const [selectionOriginRect, setSelectionOriginRect] = useState6(
2253
+ const [selectionOriginRect, setSelectionOriginRect] = useState7(
1762
2254
  null
1763
2255
  );
1764
- const [hoverPointer, setHoverPointer] = useState6(null);
1765
- const [hoverElement, setHoverElementState] = useState6(
2256
+ const [hoverPointer, setHoverPointer] = useState7(null);
2257
+ const [hoverElement, setHoverElementState] = useState7(
1766
2258
  null
1767
2259
  );
1768
- const [selectedElement, setSelectedElementState] = useState6(null);
2260
+ const [selectedElement, setSelectedElementState] = useState7(null);
1769
2261
  const setSelectedElement = useCallback4(
1770
2262
  (element) => {
1771
2263
  selectedElementRef.current = element;
@@ -1798,9 +2290,9 @@ var useMeasurerLocalState = ({
1798
2290
  };
1799
2291
 
1800
2292
  // hooks/use-measurer-pointer.ts
1801
- 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";
1802
2294
 
1803
- // targets.ts
2295
+ // core/targets.ts
1804
2296
  var pickMultiTargets = (selectionRect, items) => {
1805
2297
  const candidates = items.map(({ element, rect }) => {
1806
2298
  const overlapArea = intersectionArea(selectionRect, rect);
@@ -1865,28 +2357,39 @@ var pickPointTarget = (point, items) => {
1865
2357
  return scored[0].element;
1866
2358
  };
1867
2359
 
1868
- // selection.ts
2360
+ // core/selection.ts
1869
2361
  var getOverlayHost2 = (overlayNode) => {
1870
2362
  if (!overlayNode) return null;
1871
2363
  const rootNode = overlayNode.getRootNode();
1872
- return rootNode instanceof ShadowRoot ? rootNode.host : null;
2364
+ return rootNode.nodeType === 11 ? rootNode.host : null;
1873
2365
  };
1874
2366
  var isOverlayElement = (element, overlayNode, overlayHost) => {
1875
2367
  if (overlayNode && overlayNode.contains(element)) return true;
1876
2368
  if (overlayHost && element === overlayHost) return true;
1877
2369
  return false;
1878
2370
  };
1879
- var getTargetElement = (point, overlayNode) => {
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) => {
1880
2382
  const overlayHost = getOverlayHost2(overlayNode);
1881
2383
  if (overlayNode) {
1882
2384
  const previous = overlayNode.style.pointerEvents;
1883
2385
  overlayNode.style.pointerEvents = "none";
1884
- const elements2 = document.elementsFromPoint(point.x, point.y);
2386
+ const elements2 = ownerDocument.elementsFromPoint(point.x, point.y);
1885
2387
  overlayNode.style.pointerEvents = previous;
1886
- for (const element of elements2) {
1887
- if (!(element instanceof HTMLElement)) continue;
2388
+ for (const rawElement of elements2) {
2389
+ const element = getDeepestElementAt(rawElement, point);
2390
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
1888
2391
  if (isOverlayElement(element, overlayNode, overlayHost)) continue;
1889
- if (element === document.body || element === document.documentElement)
2392
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1890
2393
  continue;
1891
2394
  const rect = element.getBoundingClientRect();
1892
2395
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1894,11 +2397,12 @@ var getTargetElement = (point, overlayNode) => {
1894
2397
  }
1895
2398
  return null;
1896
2399
  }
1897
- const elements = document.elementsFromPoint(point.x, point.y);
1898
- for (const element of elements) {
1899
- if (!(element instanceof HTMLElement)) continue;
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;
1900
2404
  if (isOverlayElement(element, overlayNode, overlayHost)) continue;
1901
- if (element === document.body || element === document.documentElement)
2405
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1902
2406
  continue;
1903
2407
  const rect = element.getBoundingClientRect();
1904
2408
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1906,34 +2410,34 @@ var getTargetElement = (point, overlayNode) => {
1906
2410
  }
1907
2411
  return null;
1908
2412
  };
1909
- var getSnappedClickTarget = (point, overlayNode, snapEnabled) => {
1910
- if (!snapEnabled) return getTargetElement(point, overlayNode);
2413
+ var getSnappedClickTarget = (point, overlayNode, snapEnabled, ownerDocument = document) => {
2414
+ if (!snapEnabled) return getTargetElement(point, overlayNode, ownerDocument);
1911
2415
  const probeRect = {
1912
2416
  left: point.x - 20,
1913
2417
  top: point.y - 20,
1914
2418
  width: 40,
1915
2419
  height: 40
1916
2420
  };
1917
- const entries = getSelectionEntries(probeRect, overlayNode);
1918
- 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);
1919
2423
  };
1920
- var getSelectionEntries = (rect, overlayNode) => {
2424
+ var getSelectionEntries = (rect, overlayNode, ownerDocument = document) => {
1921
2425
  const overlayHost = getOverlayHost2(overlayNode);
1922
2426
  const frame = getFrameToken();
1923
2427
  const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(
1924
2428
  rect.width
1925
2429
  )}:${Math.round(rect.height)}`;
1926
- if (frame === cachedSelectionFrame && cachedSelectionKey === key && cachedOverlayNode === overlayNode) {
2430
+ if (frame === cachedSelectionFrame && cachedSelectionKey === key && cachedOverlayNode === overlayNode && cachedSelectionDocument === ownerDocument) {
1927
2431
  return cachedSelectionEntries;
1928
2432
  }
1929
2433
  const minLeft = rect.left - 1;
1930
2434
  const minTop = rect.top - 1;
1931
2435
  const maxRight = rect.left + rect.width + 1;
1932
2436
  const maxBottom = rect.top + rect.height + 1;
1933
- const elements = getBodyElementsCached();
2437
+ const elements = getBodyElementsCached(ownerDocument);
1934
2438
  const entries = elements.map((element) => ({ element, rect: getRectFromDomCached(element) })).filter(({ element, rect: elementRect }) => {
1935
2439
  if (isOverlayElement(element, overlayNode, overlayHost)) return false;
1936
- if (element === document.body || element === document.documentElement)
2440
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1937
2441
  return false;
1938
2442
  if (elementRect.width < MIN_MULTI_TARGET_SIZE || elementRect.height < MIN_MULTI_TARGET_SIZE) {
1939
2443
  return false;
@@ -1947,6 +2451,7 @@ var getSelectionEntries = (rect, overlayNode) => {
1947
2451
  cachedSelectionFrame = frame;
1948
2452
  cachedSelectionKey = key;
1949
2453
  cachedOverlayNode = overlayNode;
2454
+ cachedSelectionDocument = ownerDocument;
1950
2455
  cachedSelectionEntries = entries;
1951
2456
  return entries;
1952
2457
  };
@@ -1954,30 +2459,33 @@ var cachedSelectionFrame = -1;
1954
2459
  var cachedSelectionKey = "";
1955
2460
  var cachedSelectionEntries = [];
1956
2461
  var cachedOverlayNode = null;
2462
+ var cachedSelectionDocument = null;
1957
2463
  var getSelectionCacheKey = (rect) => `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(
1958
2464
  rect.width
1959
2465
  )}:${Math.round(rect.height)}`;
1960
- var getSelectionEntriesCached = (rect, overlayNode, cache) => {
2466
+ var getSelectionEntriesCached = (rect, overlayNode, cache, ownerDocument = document) => {
1961
2467
  const frame = getFrameToken();
1962
2468
  const key = getSelectionCacheKey(rect);
1963
2469
  if (cache.key === key && cache.overlayNode === overlayNode && cache.frame === frame) {
1964
2470
  return cache.entries;
1965
2471
  }
1966
- const entries = getSelectionEntries(rect, overlayNode);
2472
+ const entries = getSelectionEntries(rect, overlayNode, ownerDocument);
1967
2473
  cache.key = key;
1968
2474
  cache.overlayNode = overlayNode;
1969
2475
  cache.frame = frame;
1970
2476
  cache.entries = entries;
1971
2477
  return entries;
1972
2478
  };
1973
- var getElementsInRectCached = (rect, overlayNode, cache) => {
1974
- const entries = getSelectionEntriesCached(rect, overlayNode, cache);
2479
+ var getElementsInRectCached = (rect, overlayNode, cache, ownerDocument = document) => {
2480
+ const entries = getSelectionEntriesCached(rect, overlayNode, cache, ownerDocument);
1975
2481
  if (entries.length === 0) return [];
1976
2482
  return pickMultiTargets(rect, entries);
1977
2483
  };
1978
2484
 
1979
2485
  // hooks/use-measurer-pointer.ts
1980
2486
  var useMeasurerPointer = ({
2487
+ document: document2,
2488
+ window: window2,
1981
2489
  toolbarRef,
1982
2490
  overlayRef,
1983
2491
  selectionRectRef,
@@ -2021,46 +2529,26 @@ var useMeasurerPointer = ({
2021
2529
  setHoverPointer,
2022
2530
  clearSelectionRect
2023
2531
  }) => {
2024
- const hoverFrameRef = useRef5(null);
2025
- const hoverPointRef = useRef5(null);
2026
- const selectionCacheRef = useRef5({
2532
+ const hoverFrameRef = useRef6(null);
2533
+ const hoverPointRef = useRef6(null);
2534
+ const selectionCacheRef = useRef6({
2027
2535
  key: "",
2028
2536
  entries: [],
2029
2537
  overlayNode: null,
2030
2538
  frame: -1
2031
2539
  });
2032
- const shiftDragRef = useRef5(false);
2033
- const shiftToggleElementRef = useRef5(null);
2034
- const guidesEnabledRef = useRef5(guidesEnabled);
2035
- const guidesRef = useRef5(guides);
2036
- const snapGuidesEnabledRef = useRef5(snapGuidesEnabled);
2037
- const draggingGuideIdRef = useRef5(draggingGuideId);
2038
- const guideOrientationRef = useRef5(guideOrientation);
2039
- useEffect5(() => {
2540
+ const shiftDragRef = useRef6(false);
2541
+ const shiftToggleElementRef = useRef6(null);
2542
+ useEffect3(() => {
2040
2543
  return () => {
2041
2544
  if (hoverFrameRef.current) {
2042
- cancelAnimationFrame(hoverFrameRef.current);
2545
+ window2.cancelAnimationFrame(hoverFrameRef.current);
2043
2546
  }
2044
2547
  };
2045
2548
  }, []);
2046
- useEffect5(() => {
2047
- guidesEnabledRef.current = guidesEnabled;
2048
- }, [guidesEnabled]);
2049
- useEffect5(() => {
2050
- guidesRef.current = guides;
2051
- }, [guides]);
2052
- useEffect5(() => {
2053
- snapGuidesEnabledRef.current = snapGuidesEnabled;
2054
- }, [snapGuidesEnabled]);
2055
- useEffect5(() => {
2056
- guideOrientationRef.current = guideOrientation;
2057
- }, [guideOrientation]);
2058
- useEffect5(() => {
2059
- draggingGuideIdRef.current = draggingGuideId;
2060
- }, [draggingGuideId]);
2061
2549
  const updateHoverTarget = useCallback5(
2062
2550
  (point) => {
2063
- const target = getTargetElement(point, overlayRef.current);
2551
+ const target = getTargetElement(point, overlayRef.current, document2);
2064
2552
  if (target) {
2065
2553
  const rect = target.getBoundingClientRect();
2066
2554
  setHoverRect({
@@ -2075,14 +2563,14 @@ var useMeasurerPointer = ({
2075
2563
  setHoverElement(null);
2076
2564
  }
2077
2565
  },
2078
- [overlayRef, setHoverElement, setHoverRect]
2566
+ [document2, overlayRef, setHoverElement, setHoverRect]
2079
2567
  );
2080
2568
  const updateHoverElement = useCallback5(
2081
2569
  (point) => {
2082
- const target = getTargetElement(point, overlayRef.current);
2570
+ const target = getTargetElement(point, overlayRef.current, document2);
2083
2571
  setHoverElement(target);
2084
2572
  },
2085
- [overlayRef, setHoverElement]
2573
+ [document2, overlayRef, setHoverElement]
2086
2574
  );
2087
2575
  const handlePointerDown = useCallback5(
2088
2576
  (event) => {
@@ -2097,7 +2585,8 @@ var useMeasurerPointer = ({
2097
2585
  shiftToggleElementRef.current = event.shiftKey ? getSelectedMeasurementHit({
2098
2586
  point,
2099
2587
  selectedMeasurements,
2100
- overlayNode: overlayRef.current
2588
+ overlayNode: overlayRef.current,
2589
+ document: document2
2101
2590
  })?.elementRef ?? null : null;
2102
2591
  selectionCacheRef.current.key = "";
2103
2592
  if (altPressed && optionPairOverlay) {
@@ -2119,10 +2608,11 @@ var useMeasurerPointer = ({
2119
2608
  snapGuidesEnabled,
2120
2609
  overlayNode: overlayRef.current,
2121
2610
  guides,
2122
- draggingGuideId
2611
+ draggingGuideId,
2612
+ document: document2
2123
2613
  });
2124
2614
  const id = createId();
2125
- setSelectedGuideIds([]);
2615
+ setSelectedGuideIds([id]);
2126
2616
  setGuides((prev) => [
2127
2617
  ...prev,
2128
2618
  { id, orientation: guideOrientation, position }
@@ -2145,6 +2635,7 @@ var useMeasurerPointer = ({
2145
2635
  clearSelectionRect,
2146
2636
  createActionCommit,
2147
2637
  draggingGuideId,
2638
+ document2,
2148
2639
  enabled,
2149
2640
  guideOrientation,
2150
2641
  guides,
@@ -2186,31 +2677,32 @@ var useMeasurerPointer = ({
2186
2677
  }
2187
2678
  hoverPointRef.current = point;
2188
2679
  if (!hoverFrameRef.current) {
2189
- hoverFrameRef.current = requestAnimationFrame(() => {
2680
+ hoverFrameRef.current = window2.requestAnimationFrame(() => {
2190
2681
  const latest = hoverPointRef.current;
2191
- if (latest && !draggingGuideIdRef.current) {
2682
+ if (latest && !draggingGuideId) {
2192
2683
  if (hoverHighlightEnabled) {
2193
2684
  updateHoverTarget(latest);
2194
2685
  } else {
2195
2686
  updateHoverElement(latest);
2196
2687
  }
2197
2688
  }
2198
- if (latest && guidesRef.current.length > 0) {
2689
+ if (latest && guides.length > 0) {
2199
2690
  setHoverPointer(latest);
2200
2691
  } else {
2201
2692
  setHoverPointer(null);
2202
2693
  }
2203
- if (guidesEnabledRef.current && latest && !draggingGuideIdRef.current) {
2694
+ if (guidesEnabled && latest && !draggingGuideId) {
2204
2695
  const position = getSnapGuidePosition({
2205
- orientation: guideOrientationRef.current,
2696
+ orientation: guideOrientation,
2206
2697
  point: latest,
2207
- snapGuidesEnabled: snapGuidesEnabledRef.current,
2698
+ snapGuidesEnabled,
2208
2699
  overlayNode: overlayRef.current,
2209
- guides: guidesRef.current,
2210
- draggingGuideId: draggingGuideIdRef.current
2700
+ guides,
2701
+ draggingGuideId,
2702
+ document: document2
2211
2703
  });
2212
2704
  setGuidePreview({
2213
- orientation: guideOrientationRef.current,
2705
+ orientation: guideOrientation,
2214
2706
  position
2215
2707
  });
2216
2708
  } else {
@@ -2227,10 +2719,11 @@ var useMeasurerPointer = ({
2227
2719
  position: getSnapGuidePosition({
2228
2720
  orientation: guide.orientation,
2229
2721
  point,
2230
- snapGuidesEnabled: snapGuidesEnabledRef.current,
2722
+ snapGuidesEnabled,
2231
2723
  overlayNode: overlayRef.current,
2232
- guides: guidesRef.current,
2233
- draggingGuideId: draggingGuideIdRef.current
2724
+ guides,
2725
+ draggingGuideId,
2726
+ document: document2
2234
2727
  })
2235
2728
  } : guide
2236
2729
  )
@@ -2252,8 +2745,11 @@ var useMeasurerPointer = ({
2252
2745
  draggingGuideId,
2253
2746
  enabled,
2254
2747
  hoverHighlightEnabled,
2748
+ guides,
2749
+ guidesEnabled,
2255
2750
  isDragging,
2256
2751
  overlayRef,
2752
+ guideOrientation,
2257
2753
  setAltPressed,
2258
2754
  setEnd,
2259
2755
  setGuidePreview,
@@ -2262,6 +2758,7 @@ var useMeasurerPointer = ({
2262
2758
  setHoverPointer,
2263
2759
  setHoverRect,
2264
2760
  setIsDragging,
2761
+ snapGuidesEnabled,
2265
2762
  start,
2266
2763
  toolMode,
2267
2764
  toolbarRef,
@@ -2315,7 +2812,8 @@ var useMeasurerPointer = ({
2315
2812
  const elements = getElementsInRectCached(
2316
2813
  selectionRect,
2317
2814
  overlayRef.current,
2318
- selectionCacheRef.current
2815
+ selectionCacheRef.current,
2816
+ document2
2319
2817
  );
2320
2818
  const hasSameSelection = elements.length === selectedMeasurements.length && elements.every(
2321
2819
  (element, index) => selectedMeasurements[index]?.elementRef === element
@@ -2326,7 +2824,7 @@ var useMeasurerPointer = ({
2326
2824
  if (!hasSameSelection) {
2327
2825
  commit();
2328
2826
  const nextMeasurements = elements.map((element) => ({
2329
- ...getInspectMeasurement(element),
2827
+ ...getInspectMeasurement(element, window2),
2330
2828
  originRect: selectionRect
2331
2829
  }));
2332
2830
  setSelectedMeasurements(nextMeasurements);
@@ -2360,7 +2858,8 @@ var useMeasurerPointer = ({
2360
2858
  ) ?? null : getSelectedMeasurementHit({
2361
2859
  point,
2362
2860
  selectedMeasurements,
2363
- overlayNode: overlayRef.current
2861
+ overlayNode: overlayRef.current,
2862
+ document: document2
2364
2863
  });
2365
2864
  if (event.shiftKey && selectedHit) {
2366
2865
  commit();
@@ -2390,9 +2889,9 @@ var useMeasurerPointer = ({
2390
2889
  resetDragState();
2391
2890
  return;
2392
2891
  }
2393
- 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);
2394
2893
  if (target) {
2395
- const inspectMeasurement = getInspectMeasurement(target);
2894
+ const inspectMeasurement = getInspectMeasurement(target, window2);
2396
2895
  clearTransientMeasurements();
2397
2896
  if (event.shiftKey) {
2398
2897
  const alreadySelected = selectedMeasurements.some(
@@ -2491,41 +2990,48 @@ var useMeasurerPointer = ({
2491
2990
  };
2492
2991
 
2493
2992
  // hooks/use-overlay-refs.ts
2494
- import { useRef as useRef6 } from "react";
2993
+ import { useRef as useRef7 } from "react";
2495
2994
  var useOverlayRefs = () => {
2496
- const overlayRef = useRef6(null);
2497
- const selectedElementRef = useRef6(null);
2498
- const hoverElementRef = useRef6(null);
2995
+ const overlayRef = useRef7(null);
2996
+ const selectedElementRef = useRef7(null);
2997
+ const hoverElementRef = useRef7(null);
2499
2998
  return { overlayRef, selectedElementRef, hoverElementRef };
2500
2999
  };
2501
3000
 
2502
3001
  // hooks/use-resize-sync.ts
2503
- import { useEffect as useEffect6, useRef as useRef7 } from "react";
3002
+ import { useEffect as useEffect4, useRef as useRef8 } from "react";
2504
3003
  var useResizeSync = (params) => {
2505
- const resizeFrameRef = useRef7(null);
2506
- const viewportRef = useRef7(getViewportSize());
2507
- useEffect6(() => {
3004
+ const resizeFrameRef = useRef8(null);
3005
+ const viewportRef = useRef8(getViewportSize(params.window));
3006
+ useEffect4(() => {
2508
3007
  const handleResize = () => {
2509
3008
  if (resizeFrameRef.current) {
2510
- cancelAnimationFrame(resizeFrameRef.current);
3009
+ params.window.cancelAnimationFrame(resizeFrameRef.current);
2511
3010
  }
2512
- resizeFrameRef.current = requestAnimationFrame(() => {
2513
- const viewport = getViewportSize();
3011
+ resizeFrameRef.current = params.window.requestAnimationFrame(() => {
3012
+ const viewport = getViewportSize(params.window);
2514
3013
  const previousViewport = viewportRef.current;
2515
3014
  params.setMeasurements(
2516
3015
  (prev) => prev.map(
2517
- (measurement) => updateMeasurementForResize(measurement, viewport)
3016
+ (measurement) => updateMeasurementForResize(measurement, viewport, params.document)
2518
3017
  )
2519
3018
  );
2520
3019
  params.setActiveMeasurement(
2521
- (prev) => prev ? updateMeasurementForResize(prev, viewport) : prev
3020
+ (prev) => prev ? updateMeasurementForResize(prev, viewport, params.document) : prev
2522
3021
  );
2523
3022
  params.setHeldDistances(
2524
- (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
+ )
2525
3031
  );
2526
- if (params.selectedElementRef.current) {
3032
+ if (params.selectedElementRef.current && params.document.contains(params.selectedElementRef.current)) {
2527
3033
  params.setSelectedMeasurement(
2528
- getInspectMeasurement(params.selectedElementRef.current)
3034
+ getInspectMeasurement(params.selectedElementRef.current, params.window)
2529
3035
  );
2530
3036
  }
2531
3037
  if (previousViewport.width > 0 && previousViewport.height > 0) {
@@ -2540,27 +3046,27 @@ var useResizeSync = (params) => {
2540
3046
  viewportRef.current = viewport;
2541
3047
  });
2542
3048
  };
2543
- window.addEventListener("resize", handleResize);
3049
+ params.window.addEventListener("resize", handleResize);
2544
3050
  return () => {
2545
3051
  if (resizeFrameRef.current) {
2546
- cancelAnimationFrame(resizeFrameRef.current);
3052
+ params.window.cancelAnimationFrame(resizeFrameRef.current);
2547
3053
  }
2548
- window.removeEventListener("resize", handleResize);
3054
+ params.window.removeEventListener("resize", handleResize);
2549
3055
  };
2550
3056
  }, [params]);
2551
3057
  };
2552
3058
 
2553
3059
  // render/measurer-overlay.tsx
2554
- import { memo as memo5 } from "react";
3060
+ import { memo as memo6 } from "react";
2555
3061
 
2556
3062
  // components/measure-tag.tsx
2557
- import { jsx as jsx3 } from "react/jsx-runtime";
3063
+ import { jsx as jsx4 } from "react/jsx-runtime";
2558
3064
  function MeasureTag({
2559
3065
  children,
2560
3066
  className = "",
2561
3067
  style
2562
3068
  }) {
2563
- return /* @__PURE__ */ jsx3(
3069
+ return /* @__PURE__ */ jsx4(
2564
3070
  "div",
2565
3071
  {
2566
3072
  className: cn(
@@ -2574,31 +3080,21 @@ function MeasureTag({
2574
3080
  }
2575
3081
 
2576
3082
  // components/measurement-box.tsx
2577
- import { memo as memo2, useEffect as useEffect7, useState as useState7 } from "react";
2578
- 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";
2579
3085
  var formatValue2 = (value) => Math.round(value);
2580
- var MeasurementBox = memo2(function MeasurementBox2({
3086
+ var MeasurementBox = memo3(function MeasurementBox2({
2581
3087
  measurement,
2582
3088
  transitionMs,
2583
3089
  labelOffset,
2584
3090
  edgeVisibility
2585
3091
  }) {
2586
- const [rect, setRect] = useState7(
2587
- measurement.originRect ?? measurement.rect
2588
- );
2589
- useEffect7(() => {
2590
- if (!measurement.originRect) return;
2591
- const frame = requestAnimationFrame(() => {
2592
- setRect(measurement.rect);
2593
- });
2594
- return () => cancelAnimationFrame(frame);
2595
- }, [measurement]);
2596
3092
  const edges = edgeVisibility ?? { top: true, right: true, bottom: true, left: true };
2597
- const displayRect = measurement.originRect ? rect : measurement.rect;
3093
+ const displayRect = measurement.rect;
2598
3094
  const outlineColor = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
2599
3095
  const fillColor = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
2600
- return /* @__PURE__ */ jsxs2("div", { className: "msr:pointer-events-none", children: [
2601
- /* @__PURE__ */ jsxs2(
3096
+ return /* @__PURE__ */ jsxs3("div", { className: "msr:pointer-events-none", children: [
3097
+ /* @__PURE__ */ jsxs3(
2602
3098
  "div",
2603
3099
  {
2604
3100
  className: "msr:absolute",
@@ -2608,31 +3104,31 @@ var MeasurementBox = memo2(function MeasurementBox2({
2608
3104
  width: displayRect.width,
2609
3105
  height: displayRect.height,
2610
3106
  backgroundColor: fillColor,
2611
- 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`
2612
3108
  },
2613
3109
  children: [
2614
- edges.top ? /* @__PURE__ */ jsx4(
3110
+ edges.top ? /* @__PURE__ */ jsx5(
2615
3111
  "div",
2616
3112
  {
2617
3113
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2618
3114
  style: { backgroundColor: outlineColor }
2619
3115
  }
2620
3116
  ) : null,
2621
- edges.right ? /* @__PURE__ */ jsx4(
3117
+ edges.right ? /* @__PURE__ */ jsx5(
2622
3118
  "div",
2623
3119
  {
2624
3120
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2625
3121
  style: { backgroundColor: outlineColor }
2626
3122
  }
2627
3123
  ) : null,
2628
- edges.bottom ? /* @__PURE__ */ jsx4(
3124
+ edges.bottom ? /* @__PURE__ */ jsx5(
2629
3125
  "div",
2630
3126
  {
2631
3127
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2632
3128
  style: { backgroundColor: outlineColor }
2633
3129
  }
2634
3130
  ) : null,
2635
- edges.left ? /* @__PURE__ */ jsx4(
3131
+ edges.left ? /* @__PURE__ */ jsx5(
2636
3132
  "div",
2637
3133
  {
2638
3134
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2642,14 +3138,14 @@ var MeasurementBox = memo2(function MeasurementBox2({
2642
3138
  ]
2643
3139
  }
2644
3140
  ),
2645
- /* @__PURE__ */ jsxs2(
3141
+ /* @__PURE__ */ jsxs3(
2646
3142
  MeasureTag,
2647
3143
  {
2648
3144
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
2649
3145
  style: {
2650
3146
  left: displayRect.left + displayRect.width / 2,
2651
3147
  top: displayRect.top + displayRect.height + labelOffset,
2652
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease` : void 0
3148
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease`
2653
3149
  },
2654
3150
  children: [
2655
3151
  formatValue2(displayRect.width),
@@ -2662,31 +3158,21 @@ var MeasurementBox = memo2(function MeasurementBox2({
2662
3158
  });
2663
3159
 
2664
3160
  // components/selected-measurement-box.tsx
2665
- import { memo as memo3, useEffect as useEffect8, useState as useState8 } from "react";
2666
- 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";
2667
3163
  var formatValue3 = (value) => Math.round(value);
2668
- var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
3164
+ var SelectedMeasurementBox = memo4(function SelectedMeasurementBox2({
2669
3165
  measurement,
2670
3166
  transitionMs,
2671
3167
  labelOffset,
2672
3168
  edgeVisibility
2673
3169
  }) {
2674
- const [rect, setRect] = useState8(
2675
- measurement.originRect ?? measurement.rect
2676
- );
2677
- useEffect8(() => {
2678
- if (!measurement.originRect) return;
2679
- const frame = requestAnimationFrame(() => {
2680
- setRect(measurement.rect);
2681
- });
2682
- return () => cancelAnimationFrame(frame);
2683
- }, [measurement]);
2684
3170
  const edges = edgeVisibility ?? { top: true, right: true, bottom: true, left: true };
2685
- const displayRect = measurement.originRect ? rect : measurement.rect;
3171
+ const displayRect = measurement.rect;
2686
3172
  const outlineColor = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
2687
3173
  const fillColor = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
2688
- return /* @__PURE__ */ jsxs3("div", { className: "msr:pointer-events-none", children: [
2689
- /* @__PURE__ */ jsxs3(
3174
+ return /* @__PURE__ */ jsxs4("div", { className: "msr:pointer-events-none", children: [
3175
+ /* @__PURE__ */ jsxs4(
2690
3176
  "div",
2691
3177
  {
2692
3178
  className: "msr:absolute",
@@ -2696,31 +3182,31 @@ var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
2696
3182
  width: displayRect.width,
2697
3183
  height: displayRect.height,
2698
3184
  backgroundColor: fillColor,
2699
- 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`
2700
3186
  },
2701
3187
  children: [
2702
- edges.top ? /* @__PURE__ */ jsx5(
3188
+ edges.top ? /* @__PURE__ */ jsx6(
2703
3189
  "div",
2704
3190
  {
2705
3191
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2706
3192
  style: { backgroundColor: outlineColor }
2707
3193
  }
2708
3194
  ) : null,
2709
- edges.right ? /* @__PURE__ */ jsx5(
3195
+ edges.right ? /* @__PURE__ */ jsx6(
2710
3196
  "div",
2711
3197
  {
2712
3198
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2713
3199
  style: { backgroundColor: outlineColor }
2714
3200
  }
2715
3201
  ) : null,
2716
- edges.bottom ? /* @__PURE__ */ jsx5(
3202
+ edges.bottom ? /* @__PURE__ */ jsx6(
2717
3203
  "div",
2718
3204
  {
2719
3205
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2720
3206
  style: { backgroundColor: outlineColor }
2721
3207
  }
2722
3208
  ) : null,
2723
- edges.left ? /* @__PURE__ */ jsx5(
3209
+ edges.left ? /* @__PURE__ */ jsx6(
2724
3210
  "div",
2725
3211
  {
2726
3212
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2730,14 +3216,14 @@ var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
2730
3216
  ]
2731
3217
  }
2732
3218
  ),
2733
- /* @__PURE__ */ jsxs3(
3219
+ /* @__PURE__ */ jsxs4(
2734
3220
  MeasureTag,
2735
3221
  {
2736
3222
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
2737
3223
  style: {
2738
3224
  left: displayRect.left + displayRect.width / 2,
2739
3225
  top: displayRect.top + displayRect.height + labelOffset,
2740
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease` : void 0
3226
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease`
2741
3227
  },
2742
3228
  children: [
2743
3229
  formatValue3(displayRect.width),
@@ -2750,14 +3236,14 @@ var SelectedMeasurementBox = memo3(function SelectedMeasurementBox2({
2750
3236
  });
2751
3237
 
2752
3238
  // render/distance-overlay-item.tsx
2753
- import { memo as memo4 } from "react";
2754
- import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2755
- 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({
2756
3242
  distance,
2757
3243
  labelOffset,
2758
3244
  onRemove
2759
3245
  }) {
2760
- return /* @__PURE__ */ jsxs4(
3246
+ return /* @__PURE__ */ jsxs5(
2761
3247
  "div",
2762
3248
  {
2763
3249
  className: onRemove ? "msr:pointer-events-auto" : "msr:pointer-events-none",
@@ -2766,7 +3252,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2766
3252
  onRemove(distance.id);
2767
3253
  } : void 0,
2768
3254
  children: [
2769
- /* @__PURE__ */ jsx6(
3255
+ /* @__PURE__ */ jsx7(
2770
3256
  "div",
2771
3257
  {
2772
3258
  className: "msr:absolute msr:rounded msr:border msr:border-[#2563eb]/70",
@@ -2778,7 +3264,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2778
3264
  }
2779
3265
  }
2780
3266
  ),
2781
- /* @__PURE__ */ jsx6(
3267
+ /* @__PURE__ */ jsx7(
2782
3268
  "div",
2783
3269
  {
2784
3270
  className: "msr:absolute msr:rounded msr:border msr:border-[#2563eb]/70",
@@ -2791,7 +3277,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2791
3277
  }
2792
3278
  ),
2793
3279
  distance.connectors.map(
2794
- (connector, index) => Math.abs(connector.x1 - connector.x2) < 1 ? /* @__PURE__ */ jsx6(
3280
+ (connector, index) => Math.abs(connector.x1 - connector.x2) < 1 ? /* @__PURE__ */ jsx7(
2795
3281
  "div",
2796
3282
  {
2797
3283
  className: "msr:absolute msr:border-l msr:border-dashed msr:border-[#2563eb]/70",
@@ -2802,7 +3288,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2802
3288
  }
2803
3289
  },
2804
3290
  `${distance.id}-connector-${index}`
2805
- ) : /* @__PURE__ */ jsx6(
3291
+ ) : /* @__PURE__ */ jsx7(
2806
3292
  "div",
2807
3293
  {
2808
3294
  className: "msr:absolute msr:border-t msr:border-dashed msr:border-[#2563eb]/70",
@@ -2815,8 +3301,8 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2815
3301
  `${distance.id}-connector-${index}`
2816
3302
  )
2817
3303
  ),
2818
- distance.horizontal && distance.horizontal.value > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2819
- /* @__PURE__ */ jsx6(
3304
+ distance.horizontal && distance.horizontal.value > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
3305
+ /* @__PURE__ */ jsx7(
2820
3306
  "div",
2821
3307
  {
2822
3308
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -2827,7 +3313,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2827
3313
  }
2828
3314
  }
2829
3315
  ),
2830
- /* @__PURE__ */ jsx6(
3316
+ /* @__PURE__ */ jsx7(
2831
3317
  MeasureTag,
2832
3318
  {
2833
3319
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -2839,8 +3325,8 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2839
3325
  }
2840
3326
  )
2841
3327
  ] }) : null,
2842
- distance.vertical && distance.vertical.value > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2843
- /* @__PURE__ */ jsx6(
3328
+ distance.vertical && distance.vertical.value > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
3329
+ /* @__PURE__ */ jsx7(
2844
3330
  "div",
2845
3331
  {
2846
3332
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -2851,7 +3337,7 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2851
3337
  }
2852
3338
  }
2853
3339
  ),
2854
- /* @__PURE__ */ jsx6(
3340
+ /* @__PURE__ */ jsx7(
2855
3341
  MeasureTag,
2856
3342
  {
2857
3343
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -2869,10 +3355,11 @@ var DistanceOverlayItem = memo4(function DistanceOverlayItem2({
2869
3355
  });
2870
3356
 
2871
3357
  // render/measurer-overlay.tsx
2872
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2873
- 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({
2874
3360
  enabled,
2875
3361
  toolMode,
3362
+ guidePointerEvents,
2876
3363
  guidesEnabled,
2877
3364
  altPressed,
2878
3365
  isDragging,
@@ -2909,16 +3396,20 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2909
3396
  onGuidePointerUp,
2910
3397
  onGuidePointerCancel
2911
3398
  }) {
2912
- 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(
2913
3403
  "div",
2914
3404
  {
2915
- 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" },
2916
3407
  onPointerDown,
2917
3408
  onPointerMove,
2918
3409
  onPointerUp,
2919
3410
  onPointerLeave,
2920
3411
  children: [
2921
- !guidesEnabled ? displayedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx7(
3412
+ selectionVisible ? displayedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx8(
2922
3413
  MeasurementBox,
2923
3414
  {
2924
3415
  measurement,
@@ -2928,8 +3419,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2928
3419
  },
2929
3420
  measurement.id
2930
3421
  )) : null,
2931
- !guidesEnabled && activeRect && isDragging ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
2932
- /* @__PURE__ */ jsxs5(
3422
+ selectionVisible && activeRect && isDragging ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3423
+ /* @__PURE__ */ jsxs6(
2933
3424
  "div",
2934
3425
  {
2935
3426
  className: "msr:pointer-events-none msr:absolute",
@@ -2941,28 +3432,28 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2941
3432
  backgroundColor: fillColor
2942
3433
  },
2943
3434
  children: [
2944
- /* @__PURE__ */ jsx7(
3435
+ /* @__PURE__ */ jsx8(
2945
3436
  "div",
2946
3437
  {
2947
3438
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2948
3439
  style: { backgroundColor: outlineColor }
2949
3440
  }
2950
3441
  ),
2951
- /* @__PURE__ */ jsx7(
3442
+ /* @__PURE__ */ jsx8(
2952
3443
  "div",
2953
3444
  {
2954
3445
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2955
3446
  style: { backgroundColor: outlineColor }
2956
3447
  }
2957
3448
  ),
2958
- /* @__PURE__ */ jsx7(
3449
+ /* @__PURE__ */ jsx8(
2959
3450
  "div",
2960
3451
  {
2961
3452
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2962
3453
  style: { backgroundColor: outlineColor }
2963
3454
  }
2964
3455
  ),
2965
- /* @__PURE__ */ jsx7(
3456
+ /* @__PURE__ */ jsx8(
2966
3457
  "div",
2967
3458
  {
2968
3459
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2972,7 +3463,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2972
3463
  ]
2973
3464
  }
2974
3465
  ),
2975
- /* @__PURE__ */ jsxs5(
3466
+ /* @__PURE__ */ jsxs6(
2976
3467
  MeasureTag,
2977
3468
  {
2978
3469
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -2988,7 +3479,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
2988
3479
  }
2989
3480
  )
2990
3481
  ] }) : null,
2991
- !guidesEnabled && hoverRectToShow ? /* @__PURE__ */ jsxs5(
3482
+ selectionVisible && hoverRectToShow ? /* @__PURE__ */ jsxs6(
2992
3483
  "div",
2993
3484
  {
2994
3485
  className: "msr:pointer-events-none msr:absolute",
@@ -3000,28 +3491,28 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3000
3491
  backgroundColor: fillColor
3001
3492
  },
3002
3493
  children: [
3003
- hoverEdgeVisibility?.top ? /* @__PURE__ */ jsx7(
3494
+ hoverEdgeVisibility?.top ? /* @__PURE__ */ jsx8(
3004
3495
  "div",
3005
3496
  {
3006
3497
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
3007
3498
  style: { backgroundColor: outlineColor }
3008
3499
  }
3009
3500
  ) : null,
3010
- hoverEdgeVisibility?.right ? /* @__PURE__ */ jsx7(
3501
+ hoverEdgeVisibility?.right ? /* @__PURE__ */ jsx8(
3011
3502
  "div",
3012
3503
  {
3013
3504
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
3014
3505
  style: { backgroundColor: outlineColor }
3015
3506
  }
3016
3507
  ) : null,
3017
- hoverEdgeVisibility?.bottom ? /* @__PURE__ */ jsx7(
3508
+ hoverEdgeVisibility?.bottom ? /* @__PURE__ */ jsx8(
3018
3509
  "div",
3019
3510
  {
3020
3511
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
3021
3512
  style: { backgroundColor: outlineColor }
3022
3513
  }
3023
3514
  ) : null,
3024
- hoverEdgeVisibility?.left ? /* @__PURE__ */ jsx7(
3515
+ hoverEdgeVisibility?.left ? /* @__PURE__ */ jsx8(
3025
3516
  "div",
3026
3517
  {
3027
3518
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -3031,7 +3522,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3031
3522
  ]
3032
3523
  }
3033
3524
  ) : null,
3034
- guidesEnabled && guidePreview ? /* @__PURE__ */ jsx7(
3525
+ guidesEnabled && guidePreview ? /* @__PURE__ */ jsx8(
3035
3526
  "div",
3036
3527
  {
3037
3528
  className: "msr:pointer-events-none msr:absolute",
@@ -3046,7 +3537,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3046
3537
  height: GUIDE_HITBOX_SIZE,
3047
3538
  width: "100%"
3048
3539
  },
3049
- children: /* @__PURE__ */ jsx7(
3540
+ children: /* @__PURE__ */ jsx8(
3050
3541
  "div",
3051
3542
  {
3052
3543
  className: "msr:absolute",
@@ -3067,7 +3558,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3067
3558
  )
3068
3559
  }
3069
3560
  ) : null,
3070
- !guidesEnabled ? displayedSelectedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx7(
3561
+ selectionVisible ? displayedSelectedMeasurements.map((measurement, index) => /* @__PURE__ */ jsx8(
3071
3562
  SelectedMeasurementBox,
3072
3563
  {
3073
3564
  measurement,
@@ -3077,7 +3568,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3077
3568
  },
3078
3569
  measurement.id
3079
3570
  )) : null,
3080
- heldDistances.map((distance) => /* @__PURE__ */ jsx7(
3571
+ heldDistances.map((distance) => /* @__PURE__ */ jsx8(
3081
3572
  DistanceOverlayItem,
3082
3573
  {
3083
3574
  distance,
@@ -3086,7 +3577,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3086
3577
  },
3087
3578
  `held-${distance.id}`
3088
3579
  )),
3089
- !guidesEnabled && altPressed && optionPairOverlay ? /* @__PURE__ */ jsx7(
3580
+ selectionVisible && altPressed && optionPairOverlay ? /* @__PURE__ */ jsx8(
3090
3581
  DistanceOverlayItem,
3091
3582
  {
3092
3583
  distance: optionPairOverlay,
@@ -3094,7 +3585,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3094
3585
  },
3095
3586
  `option-${optionPairOverlay.id}`
3096
3587
  ) : null,
3097
- guidesEnabled && altPressed && guideDistanceOverlay ? /* @__PURE__ */ jsx7(
3588
+ guidesEnabled && altPressed && guideDistanceOverlay ? /* @__PURE__ */ jsx8(
3098
3589
  DistanceOverlayItem,
3099
3590
  {
3100
3591
  distance: guideDistanceOverlay,
@@ -3102,9 +3593,9 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3102
3593
  },
3103
3594
  `guide-preview-${guideDistanceOverlay.id}`
3104
3595
  ) : null,
3105
- !guidesEnabled && altPressed && optionContainerLines ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3106
- optionContainerLines.top.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3107
- /* @__PURE__ */ jsx7(
3596
+ selectionVisible && altPressed && optionContainerLines ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3597
+ optionContainerLines.top.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3598
+ /* @__PURE__ */ jsx8(
3108
3599
  "div",
3109
3600
  {
3110
3601
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -3115,7 +3606,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3115
3606
  }
3116
3607
  }
3117
3608
  ),
3118
- /* @__PURE__ */ jsx7(
3609
+ /* @__PURE__ */ jsx8(
3119
3610
  MeasureTag,
3120
3611
  {
3121
3612
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -3127,8 +3618,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3127
3618
  }
3128
3619
  )
3129
3620
  ] }) : null,
3130
- optionContainerLines.bottom.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3131
- /* @__PURE__ */ jsx7(
3621
+ optionContainerLines.bottom.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3622
+ /* @__PURE__ */ jsx8(
3132
3623
  "div",
3133
3624
  {
3134
3625
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -3139,7 +3630,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3139
3630
  }
3140
3631
  }
3141
3632
  ),
3142
- /* @__PURE__ */ jsx7(
3633
+ /* @__PURE__ */ jsx8(
3143
3634
  MeasureTag,
3144
3635
  {
3145
3636
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -3151,8 +3642,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3151
3642
  }
3152
3643
  )
3153
3644
  ] }) : null,
3154
- optionContainerLines.left.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3155
- /* @__PURE__ */ jsx7(
3645
+ optionContainerLines.left.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3646
+ /* @__PURE__ */ jsx8(
3156
3647
  "div",
3157
3648
  {
3158
3649
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -3163,7 +3654,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3163
3654
  }
3164
3655
  }
3165
3656
  ),
3166
- /* @__PURE__ */ jsx7(
3657
+ /* @__PURE__ */ jsx8(
3167
3658
  MeasureTag,
3168
3659
  {
3169
3660
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -3175,8 +3666,8 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3175
3666
  }
3176
3667
  )
3177
3668
  ] }) : null,
3178
- optionContainerLines.right.value > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
3179
- /* @__PURE__ */ jsx7(
3669
+ optionContainerLines.right.value > 0 ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
3670
+ /* @__PURE__ */ jsx8(
3180
3671
  "div",
3181
3672
  {
3182
3673
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -3187,7 +3678,7 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3187
3678
  }
3188
3679
  }
3189
3680
  ),
3190
- /* @__PURE__ */ jsx7(
3681
+ /* @__PURE__ */ jsx8(
3191
3682
  MeasureTag,
3192
3683
  {
3193
3684
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -3205,25 +3696,28 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3205
3696
  const isHovered = hoverGuide?.id === guide.id;
3206
3697
  const strokeWidth = isSelected || isHovered ? 2 : 1;
3207
3698
  const strokeColor = isSelected ? guideColorActive : isHovered ? guideColorHover : guideColorDefault;
3208
- return /* @__PURE__ */ jsx7(
3699
+ return /* @__PURE__ */ jsx8(
3209
3700
  "div",
3210
3701
  {
3211
3702
  className: "msr:absolute",
3703
+ "data-mesurer-guide": "true",
3212
3704
  style: guide.orientation === "vertical" ? {
3213
3705
  left: guide.position - GUIDE_HITBOX_SIZE / 2,
3214
3706
  top: 0,
3215
3707
  width: GUIDE_HITBOX_SIZE,
3216
- height: "100%"
3708
+ height: "100%",
3709
+ pointerEvents: guidePointerEvents ? "auto" : "none"
3217
3710
  } : {
3218
3711
  top: guide.position - GUIDE_HITBOX_SIZE / 2,
3219
3712
  left: 0,
3220
3713
  height: GUIDE_HITBOX_SIZE,
3221
- width: "100%"
3714
+ width: "100%",
3715
+ pointerEvents: guidePointerEvents ? "auto" : "none"
3222
3716
  },
3223
3717
  onPointerDown: (event) => onGuidePointerDown(guide, event),
3224
3718
  onPointerUp: (event) => onGuidePointerUp(guide, event),
3225
3719
  onPointerCancel: (event) => onGuidePointerCancel(guide, event),
3226
- children: /* @__PURE__ */ jsx7(
3720
+ children: /* @__PURE__ */ jsx8(
3227
3721
  "div",
3228
3722
  {
3229
3723
  className: "msr:absolute",
@@ -3251,60 +3745,1005 @@ var MeasurerOverlay = memo5(function MeasurerOverlay2({
3251
3745
  );
3252
3746
  });
3253
3747
 
3254
- // measurer.tsx
3255
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3256
- 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;
3257
3761
  };
3258
- var useHydrated = () => useSyncExternalStore(
3259
- subscribeHydration,
3260
- () => true,
3261
- () => false
3262
- );
3263
- var stripMeasurement = (measurement) => ({
3264
- ...measurement,
3265
- elementRef: void 0
3266
- });
3267
- var stripDistance = (distance) => ({
3268
- ...distance,
3269
- elementRefA: void 0,
3270
- elementRefB: void 0
3271
- });
3272
- function MeasurerClient({
3273
- highlightColor,
3274
- guideColor,
3275
- hoverHighlightEnabled,
3276
- persistOnReload,
3277
- portalTarget
3278
- }) {
3279
- const selectionRectRef = useRef8(null);
3280
- const toolbarRef = useRef8(null);
3281
- const selectionAnimationCleanupTimeoutRef = useRef8(null);
3282
- const persistedState = useMemo2(() => {
3283
- if (!persistOnReload) return null;
3284
- const stored = window.localStorage.getItem("mesurer-state");
3285
- if (!stored) return null;
3286
- try {
3287
- const parsed = JSON.parse(stored);
3288
- if (!parsed || parsed.version !== 1) return null;
3289
- return parsed;
3290
- } catch {
3291
- return null;
3292
- }
3293
- }, [persistOnReload]);
3294
- const { overlayRef, selectedElementRef, hoverElementRef } = useOverlayRefs();
3295
- const {
3296
- selectionOriginRect,
3297
- setSelectionOriginRect,
3298
- hoverPointer,
3299
- setHoverPointer,
3300
- hoverElement,
3301
- setHoverElement,
3302
- selectedElement,
3303
- setSelectedElement,
3304
- clearSelectionRect
3305
- } = useMeasurerLocalState({
3306
- selectedElementRef,
3307
- hoverElementRef,
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
4744
+ } = useMeasurerLocalState({
4745
+ selectedElementRef,
4746
+ hoverElementRef,
3308
4747
  selectionRectRef
3309
4748
  });
3310
4749
  const {
@@ -3316,12 +4755,15 @@ function MeasurerClient({
3316
4755
  setAltPressed,
3317
4756
  toolMode,
3318
4757
  setToolMode,
4758
+ rulersVisible,
4759
+ setRulersVisible,
3319
4760
  guidesEnabled,
3320
4761
  multiMeasureEnabled,
3321
4762
  snapGuidesEnabled
3322
4763
  } = useMeasureToggles({
3323
4764
  initialEnabled: persistedState?.enabled,
3324
- initialToolMode: persistedState?.toolMode
4765
+ initialToolMode: persistedState?.toolMode === "rulers" ? "none" : persistedState?.toolMode,
4766
+ initialRulersVisible: persistedState?.rulersVisible ?? persistedState?.toolMode === "rulers"
3325
4767
  });
3326
4768
  const { start, setStart, end, setEnd, isDragging, setIsDragging } = useDragState();
3327
4769
  const {
@@ -3353,85 +4795,170 @@ function MeasurerClient({
3353
4795
  initialGuides: persistedState?.guides ?? [],
3354
4796
  initialSelectedGuideIds: persistedState?.selectedGuideIds ?? []
3355
4797
  });
3356
- const [toolbarActive, setToolbarActive] = useState9(true);
3357
- const { clearGuideDragHold, scheduleGuideDragHold } = useGuideDragHold();
3358
- const [guidePreview, setGuidePreview] = useState9(null);
3359
- const [guideOrientation, setGuideOrientation] = useState9(persistedState?.guideOrientation ?? "vertical");
3360
- const persistPayload = useMemo2(() => {
3361
- if (!persistOnReload) return null;
3362
- return JSON.stringify({
3363
- version: 1,
3364
- enabled,
3365
- toolMode,
3366
- guideOrientation,
3367
- guides,
3368
- selectedGuideIds,
3369
- measurements: measurements.map(stripMeasurement),
3370
- activeMeasurement: activeMeasurement ? stripMeasurement(activeMeasurement) : null,
3371
- heldDistances: heldDistances.map(stripDistance)
3372
- });
3373
- }, [
3374
- activeMeasurement,
3375
- enabled,
3376
- guideOrientation,
3377
- guides,
3378
- heldDistances,
3379
- measurements,
3380
- persistOnReload,
3381
- selectedGuideIds,
3382
- toolMode
3383
- ]);
3384
- 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(() => {
3385
4812
  if (!persistOnReload) return;
3386
4813
  if (typeof window === "undefined") return;
3387
- if (!persistPayload) return;
3388
4814
  try {
3389
- 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
+ );
3390
4830
  } catch {
3391
4831
  }
3392
- const handlePageHide = () => {
3393
- try {
3394
- window.localStorage.setItem("mesurer-state", persistPayload);
3395
- } 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();
3396
4863
  }
3397
- };
3398
- window.addEventListener("pagehide", handlePageHide);
3399
- return () => {
3400
- window.removeEventListener("pagehide", handlePageHide);
3401
- };
3402
- }, [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
+ );
3403
4928
  const {
3404
4929
  recordSnapshot,
3405
4930
  createActionCommit,
3406
4931
  setToolModeWithHistory,
3407
4932
  setGuideOrientationWithHistory,
3408
- setEnabledWithHistory
4933
+ setEnabledWithHistory,
4934
+ undo: undoHistory,
4935
+ redo: redoHistory
3409
4936
  } = useMeasurerHistory({
3410
4937
  toggles: {
3411
4938
  enabled,
3412
- setEnabled,
4939
+ setEnabled: setEnabledPersisted,
3413
4940
  toolMode,
3414
- setToolMode,
4941
+ setToolMode: setToolModePersisted,
3415
4942
  guideOrientation,
3416
- setGuideOrientation
4943
+ setGuideOrientation: setGuideOrientationPersisted
3417
4944
  },
3418
4945
  measurements: {
3419
4946
  measurements,
3420
- setMeasurements,
4947
+ setMeasurements: setMeasurementsPersisted,
3421
4948
  activeMeasurement,
3422
- setActiveMeasurement,
4949
+ setActiveMeasurement: setActiveMeasurementPersisted,
3423
4950
  selectedMeasurements,
3424
4951
  setSelectedMeasurements,
3425
4952
  selectedMeasurement,
3426
4953
  setSelectedMeasurement,
3427
4954
  heldDistances,
3428
- setHeldDistances
4955
+ setHeldDistances: setHeldDistancesPersisted
3429
4956
  },
3430
4957
  guides: {
3431
4958
  guides,
3432
- setGuides,
4959
+ setGuides: setGuidesPersisted,
3433
4960
  selectedGuideIds,
3434
- setSelectedGuideIds,
4961
+ setSelectedGuideIds: setSelectedGuideIdsPersisted,
3435
4962
  draggingGuideId,
3436
4963
  setDraggingGuideId
3437
4964
  },
@@ -3446,96 +4973,254 @@ function MeasurerClient({
3446
4973
  clearSelectionRect
3447
4974
  }
3448
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]);
3449
4984
  const clearAll = useCallback6(() => {
4985
+ if (toolMode === "text-inspector") {
4986
+ textInspector.clear();
4987
+ }
3450
4988
  recordSnapshot();
3451
4989
  clearGuideDragHold();
3452
4990
  setStart(null);
3453
4991
  setEnd(null);
3454
4992
  setIsDragging(false);
3455
- setActiveMeasurement(null);
3456
- setMeasurements([]);
4993
+ setActiveMeasurementPersisted(null);
4994
+ setMeasurementsPersisted([]);
3457
4995
  setSelectedMeasurement(null);
3458
4996
  setSelectedMeasurements([]);
3459
4997
  clearSelectionRect();
3460
4998
  setSelectedElement(null);
3461
4999
  setHoverRect(null);
3462
5000
  setHoverElement(null);
3463
- setGuides([]);
3464
- setSelectedGuideIds([]);
3465
- setHeldDistances([]);
5001
+ setGuidesPersisted([]);
5002
+ setSelectedGuideIdsPersisted([]);
5003
+ setHeldDistancesPersisted([]);
3466
5004
  }, [
3467
5005
  clearGuideDragHold,
3468
5006
  clearSelectionRect,
3469
5007
  recordSnapshot,
3470
- setActiveMeasurement,
5008
+ setActiveMeasurementPersisted,
3471
5009
  setEnd,
3472
- setGuides,
3473
- setHeldDistances,
5010
+ setGuidesPersisted,
5011
+ setHeldDistancesPersisted,
3474
5012
  setHoverElement,
3475
5013
  setHoverRect,
3476
5014
  setIsDragging,
3477
- setMeasurements,
5015
+ setMeasurementsPersisted,
3478
5016
  setSelectedElement,
3479
- setSelectedGuideIds,
5017
+ setSelectedGuideIdsPersisted,
3480
5018
  setSelectedMeasurement,
3481
5019
  setSelectedMeasurements,
3482
- setStart
5020
+ setStart,
5021
+ textInspector,
5022
+ toolMode
3483
5023
  ]);
3484
5024
  const removeSelectedGuides = useCallback6(() => {
3485
5025
  if (selectedGuideIds.length === 0) return false;
3486
5026
  recordSnapshot();
3487
- setGuides(
5027
+ setGuidesPersisted(
3488
5028
  (prev) => prev.filter((guide) => !selectedGuideIds.includes(guide.id))
3489
5029
  );
3490
- setSelectedGuideIds([]);
5030
+ setSelectedGuideIdsPersisted([]);
3491
5031
  return true;
3492
- }, [recordSnapshot, selectedGuideIds, setGuides, setSelectedGuideIds]);
5032
+ }, [
5033
+ recordSnapshot,
5034
+ selectedGuideIds,
5035
+ setGuidesPersisted,
5036
+ setSelectedGuideIdsPersisted
5037
+ ]);
3493
5038
  useHotkeys({
5039
+ eventTarget: ownerWindow,
3494
5040
  clearAll,
5041
+ undo,
5042
+ redo,
3495
5043
  removeSelectedGuides,
3496
5044
  setEnabled: setEnabledWithHistory,
3497
5045
  setToolMode: setToolModeWithHistory,
5046
+ setRulersVisible: setRulersVisiblePersisted,
3498
5047
  setAltPressed,
3499
5048
  isOverlayActive: () => enabled && (toolMode !== "none" || toolbarActive),
3500
5049
  setGuideOrientation: setGuideOrientationWithHistory,
3501
5050
  onInteract: () => setToolbarActive(true)
3502
5051
  });
3503
5052
  useResizeSync({
3504
- setMeasurements,
3505
- setActiveMeasurement,
3506
- setHeldDistances,
5053
+ document: ownerDocument,
5054
+ window: ownerWindow,
5055
+ setMeasurements: setMeasurementsPersisted,
5056
+ setActiveMeasurement: setActiveMeasurementPersisted,
5057
+ setHeldDistances: setHeldDistancesPersisted,
3507
5058
  setSelectedMeasurement,
3508
- setGuides,
5059
+ setGuides: setGuidesPersisted,
3509
5060
  selectedElementRef
3510
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]);
3511
5082
  useLiveElementTracking({
5083
+ document: ownerDocument,
5084
+ window: ownerWindow,
3512
5085
  enabled,
3513
5086
  selectedElementRef,
3514
5087
  hoverElementRef,
3515
5088
  setSelectedMeasurement,
3516
5089
  setSelectedMeasurements,
3517
5090
  setHoverRect,
3518
- setMeasurements,
3519
- setActiveMeasurement,
3520
- setHeldDistances
5091
+ setMeasurements: setMeasurementsPersisted,
5092
+ setActiveMeasurement: setActiveMeasurementPersisted,
5093
+ setHeldDistances: setHeldDistancesPersisted
3521
5094
  });
3522
- useEffect9(() => {
5095
+ useEffect5(() => {
3523
5096
  if (!toolbarActive || toolMode !== "none") return;
3524
5097
  const handlePointerDown2 = (event) => {
3525
5098
  const toolbarNode = toolbarRef.current;
3526
5099
  if (toolbarNode && toolbarNode.contains(event.target)) return;
3527
5100
  setToolbarActive(false);
3528
5101
  };
3529
- window.addEventListener("pointerdown", handlePointerDown2);
5102
+ ownerWindow.addEventListener("pointerdown", handlePointerDown2);
3530
5103
  return () => {
3531
- window.removeEventListener("pointerdown", handlePointerDown2);
5104
+ ownerWindow.removeEventListener("pointerdown", handlePointerDown2);
3532
5105
  };
3533
- }, [toolbarActive, toolMode]);
3534
- useEffect9(() => {
3535
- if (hoverHighlightEnabled) return;
3536
- setHoverRect(null);
3537
- }, [hoverHighlightEnabled, setHoverRect]);
3538
- 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(() => {
3539
5224
  const hasSelectionAnimationState = !!selectionOriginRect || !!selectedMeasurement?.originRect || selectedMeasurements.some((measurement) => !!measurement.originRect);
3540
5225
  if (!hasSelectionAnimationState) {
3541
5226
  if (selectionAnimationCleanupTimeoutRef.current !== null) {
@@ -3564,6 +5249,12 @@ function MeasurerClient({
3564
5249
  return changed ? next : prev;
3565
5250
  });
3566
5251
  }, MEASURE_TRANSITION_MS);
5252
+ return () => {
5253
+ if (selectionAnimationCleanupTimeoutRef.current !== null) {
5254
+ window.clearTimeout(selectionAnimationCleanupTimeoutRef.current);
5255
+ selectionAnimationCleanupTimeoutRef.current = null;
5256
+ }
5257
+ };
3567
5258
  }, [
3568
5259
  selectionOriginRect,
3569
5260
  selectedMeasurement,
@@ -3572,13 +5263,6 @@ function MeasurerClient({
3572
5263
  setSelectedMeasurements,
3573
5264
  setSelectionOriginRect
3574
5265
  ]);
3575
- useEffect9(() => {
3576
- return () => {
3577
- if (selectionAnimationCleanupTimeoutRef.current !== null) {
3578
- window.clearTimeout(selectionAnimationCleanupTimeoutRef.current);
3579
- }
3580
- };
3581
- }, []);
3582
5266
  const displayedMeasurements = holdEnabled ? measurements : multiMeasureEnabled && measurements.length > 0 ? measurements : activeMeasurement ? [activeMeasurement] : [];
3583
5267
  const {
3584
5268
  activeRect,
@@ -3600,6 +5284,8 @@ function MeasurerClient({
3600
5284
  hoverEdgeVisibility,
3601
5285
  measurementEdgeVisibility
3602
5286
  } = useMeasurerDerived({
5287
+ document: ownerDocument,
5288
+ window: ownerWindow,
3603
5289
  start,
3604
5290
  end,
3605
5291
  selectedMeasurements,
@@ -3625,6 +5311,8 @@ function MeasurerClient({
3625
5311
  handlePointerUp,
3626
5312
  handlePointerLeave
3627
5313
  } = useMeasurerPointer({
5314
+ document: ownerDocument,
5315
+ window: ownerWindow,
3628
5316
  toolbarRef,
3629
5317
  overlayRef,
3630
5318
  selectionRectRef,
@@ -3650,15 +5338,15 @@ function MeasurerClient({
3650
5338
  optionPairOverlay,
3651
5339
  setAltPressed,
3652
5340
  setGuidePreview,
3653
- setSelectedGuideIds,
3654
- setGuides,
5341
+ setSelectedGuideIds: setSelectedGuideIdsPersisted,
5342
+ setGuides: setGuidesPersisted,
3655
5343
  setStart,
3656
5344
  setEnd,
3657
5345
  setIsDragging,
3658
- setHeldDistances,
5346
+ setHeldDistances: setHeldDistancesPersisted,
3659
5347
  setDraggingGuideId,
3660
- setActiveMeasurement,
3661
- setMeasurements,
5348
+ setActiveMeasurement: setActiveMeasurementPersisted,
5349
+ setMeasurements: setMeasurementsPersisted,
3662
5350
  setSelectedMeasurements,
3663
5351
  setSelectedMeasurement,
3664
5352
  setSelectionOriginRect,
@@ -3671,24 +5359,62 @@ function MeasurerClient({
3671
5359
  const removeHeldDistance = useCallback6(
3672
5360
  (id) => {
3673
5361
  recordSnapshot();
3674
- 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));
3675
5400
  },
3676
- [recordSnapshot, setHeldDistances]
5401
+ [setGuidesPersisted]
3677
5402
  );
3678
5403
  const handleGuidePointerDown = useCallback6(
3679
5404
  (guide, event) => {
3680
5405
  const commit = createActionCommit();
3681
5406
  if (!enabled) return;
3682
5407
  event.stopPropagation();
5408
+ event.preventDefault();
3683
5409
  if (event.shiftKey) {
3684
5410
  commit();
3685
- setSelectedGuideIds(
5411
+ setSelectedGuideIdsPersisted(
3686
5412
  (prev) => prev.includes(guide.id) ? prev.filter((id) => id !== guide.id) : [...prev, guide.id]
3687
5413
  );
3688
5414
  return;
3689
5415
  }
3690
5416
  commit();
3691
- setSelectedGuideIds([guide.id]);
5417
+ setSelectedGuideIdsPersisted([guide.id]);
3692
5418
  scheduleGuideDragHold(guide.id, setDraggingGuideId);
3693
5419
  event.currentTarget.setPointerCapture(event.pointerId);
3694
5420
  },
@@ -3697,7 +5423,7 @@ function MeasurerClient({
3697
5423
  enabled,
3698
5424
  scheduleGuideDragHold,
3699
5425
  setDraggingGuideId,
3700
- setSelectedGuideIds
5426
+ setSelectedGuideIdsPersisted
3701
5427
  ]
3702
5428
  );
3703
5429
  const handleGuidePointerUp = useCallback6(
@@ -3712,17 +5438,29 @@ function MeasurerClient({
3712
5438
  [clearGuideDragHold, setDraggingGuideId]
3713
5439
  );
3714
5440
  return createPortal(
3715
- /* @__PURE__ */ jsxs6(
5441
+ /* @__PURE__ */ jsxs7(
3716
5442
  "div",
3717
5443
  {
3718
5444
  ref: overlayRef,
3719
5445
  className: "mesurer-root msr:pointer-events-none msr:fixed msr:inset-0 msr:z-50",
3720
5446
  children: [
3721
- /* @__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(
3722
5459
  MeasurerOverlay,
3723
5460
  {
3724
5461
  enabled,
3725
5462
  toolMode,
5463
+ guidePointerEvents: enabled && (toolMode !== "none" || rulersVisible),
3726
5464
  guidesEnabled,
3727
5465
  altPressed,
3728
5466
  isDragging,
@@ -3760,13 +5498,16 @@ function MeasurerClient({
3760
5498
  onGuidePointerCancel: handleGuidePointerUp
3761
5499
  }
3762
5500
  ),
3763
- /* @__PURE__ */ jsx8(
5501
+ /* @__PURE__ */ jsx9(
3764
5502
  Toolbar,
3765
5503
  {
3766
5504
  ref: toolbarRef,
5505
+ eventTarget: ownerWindow,
3767
5506
  toolMode,
3768
5507
  setEnabled: setEnabledWithHistory,
3769
5508
  setToolMode: setToolModeWithHistory,
5509
+ rulersVisible,
5510
+ setRulersVisible: setRulersVisiblePersisted,
3770
5511
  guideOrientation,
3771
5512
  setGuideOrientation: setGuideOrientationWithHistory,
3772
5513
  onInteract: () => setToolbarActive(true)
@@ -3783,24 +5524,28 @@ function Measurer({
3783
5524
  guideColor = "oklch(0.63 0.26 29.23)",
3784
5525
  hoverHighlightEnabled = true,
3785
5526
  persistOnReload = false,
3786
- portalTarget
5527
+ portalTarget,
5528
+ persistKey
3787
5529
  }) {
3788
5530
  if (typeof document !== "undefined") {
3789
5531
  ensureMeasurerStyles(MESURER_STYLES, portalTarget);
3790
5532
  }
3791
5533
  const hydrated = useHydrated();
3792
5534
  if (!hydrated) return null;
3793
- return /* @__PURE__ */ jsx8(
5535
+ return /* @__PURE__ */ jsx9(
3794
5536
  MeasurerClient,
3795
5537
  {
3796
5538
  highlightColor,
3797
5539
  guideColor,
3798
5540
  hoverHighlightEnabled,
3799
5541
  persistOnReload,
5542
+ persistKey,
3800
5543
  portalTarget: portalTarget ?? document.body
3801
5544
  }
3802
5545
  );
3803
5546
  }
3804
5547
  export {
3805
- Measurer
5548
+ Measurer,
5549
+ TextInspector,
5550
+ createTextInspector
3806
5551
  };