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.cjs CHANGED
@@ -20,15 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- Measurer: () => Measurer
23
+ Measurer: () => Measurer,
24
+ TextInspector: () => TextInspector,
25
+ createTextInspector: () => createTextInspector
24
26
  });
25
27
  module.exports = __toCommonJS(index_exports);
26
28
 
27
29
  // measurer.tsx
28
- var import_react19 = require("react");
30
+ var import_react20 = require("react");
29
31
  var import_react_dom = require("react-dom");
30
32
 
31
- // constants.ts
33
+ // core/constants.ts
32
34
  var MEASURE_LABEL_OFFSET = 2;
33
35
  var GUIDE_SNAP_DISTANCE = 10;
34
36
  var GUIDE_HITBOX_SIZE = 14;
@@ -41,15 +43,17 @@ var ANCESTOR_KEEP_COVERAGE = 0.92;
41
43
  var MIN_SINGLE_TARGET_SIZE = 8;
42
44
  var MIN_SINGLE_ELEMENT_COVERAGE = 0.15;
43
45
 
44
- // style-inject.ts
46
+ // runtime/style-inject.ts
45
47
  var MESURER_STYLE_ID = "mesurer-styles";
48
+ var isDocument = (target) => target.nodeType === 9;
49
+ var isShadowRoot = (target) => target.nodeType === 11;
46
50
  var getStyleTarget = (target) => {
47
51
  if (typeof document === "undefined") return null;
48
52
  if (!target) return document;
49
- if (target instanceof ShadowRoot) return target;
53
+ if (isShadowRoot(target)) return target;
50
54
  const rootNode = target.getRootNode();
51
- if (rootNode instanceof ShadowRoot) return rootNode;
52
- return document;
55
+ if (rootNode.nodeType === 11) return rootNode;
56
+ return target.ownerDocument ?? document;
53
57
  };
54
58
  function ensureMeasurerStyles(cssText, target) {
55
59
  if (typeof document === "undefined") return;
@@ -57,23 +61,25 @@ function ensureMeasurerStyles(cssText, target) {
57
61
  const styleTarget = getStyleTarget(target);
58
62
  if (!styleTarget) return;
59
63
  if (styleTarget.querySelector(`#${MESURER_STYLE_ID}`)) return;
60
- const style = document.createElement("style");
64
+ const ownerDocument = isDocument(styleTarget) ? styleTarget : styleTarget.ownerDocument;
65
+ if (!ownerDocument) return;
66
+ const style = ownerDocument.createElement("style");
61
67
  style.id = MESURER_STYLE_ID;
62
68
  style.textContent = cssText;
63
- if (styleTarget instanceof ShadowRoot) {
69
+ if (isShadowRoot(styleTarget)) {
64
70
  styleTarget.appendChild(style);
65
71
  return;
66
72
  }
67
- styleTarget.head.appendChild(style);
73
+ ownerDocument.head.appendChild(style);
68
74
  }
69
75
 
70
76
  // styles.generated.ts
71
- 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';
77
+ 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';
72
78
 
73
79
  // components/toolbar.tsx
74
80
  var import_react = require("react");
75
81
 
76
- // utils.ts
82
+ // core/utils.ts
77
83
  var formatValue = (value) => Math.round(value);
78
84
  var cn = (...inputs) => inputs.filter(Boolean).join(" ");
79
85
  var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
@@ -93,9 +99,57 @@ var IconBase = ({ size = 24, ...props }) => /* @__PURE__ */ (0, import_jsx_runti
93
99
  );
94
100
  var CursorIcon = ({ size = 20, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.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" }) });
95
101
  var RulerIcon = ({ size = 20, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.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" }) });
102
+ var XrayIcon = ({ size = 20, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
103
+ "path",
104
+ {
105
+ fill: "none",
106
+ stroke: "currentColor",
107
+ strokeLinecap: "round",
108
+ strokeLinejoin: "round",
109
+ strokeWidth: "12",
110
+ strokeOpacity: "0.78",
111
+ 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"
112
+ }
113
+ ) });
114
+ var RulersIcon = ({ size = 20, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
115
+ "path",
116
+ {
117
+ fill: "none",
118
+ stroke: "currentColor",
119
+ strokeLinecap: "round",
120
+ strokeLinejoin: "round",
121
+ strokeWidth: "12",
122
+ d: "M48 208V48h160M80 48v24m32-24v24m32-24v24m32-24v24M48 80h24M48 112h24m-24 32h24m-24 32h24"
123
+ }
124
+ ) });
96
125
  var CaretDownIcon = ({ size = 8, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.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" }) });
97
126
  var CheckIcon = ({ size = 12, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.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" }) });
98
127
  var MinusIcon = ({ size = 12, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBase, { size, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128Z" }) });
128
+ var TextInspectorIcon = ({ size = 20, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
129
+ "svg",
130
+ {
131
+ xmlns: "http://www.w3.org/2000/svg",
132
+ width: size,
133
+ height: size,
134
+ viewBox: "0 0 256 256",
135
+ fill: "currentColor",
136
+ ...props,
137
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
138
+ "text",
139
+ {
140
+ x: "50%",
141
+ y: "60%",
142
+ textAnchor: "middle",
143
+ dominantBaseline: "middle",
144
+ fill: "currentColor",
145
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
146
+ fontWeight: 300,
147
+ fontSize: 220,
148
+ children: "Aa"
149
+ }
150
+ )
151
+ }
152
+ );
99
153
 
100
154
  // components/toolbar.tsx
101
155
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -110,6 +164,7 @@ function ToolbarButton({
110
164
  shortcut,
111
165
  onClick,
112
166
  tooltipVisible,
167
+ tooltipInstant,
113
168
  tooltipSide,
114
169
  onTooltipEnter,
115
170
  onTooltipLeave,
@@ -127,6 +182,7 @@ function ToolbarButton({
127
182
  {
128
183
  type: "button",
129
184
  "aria-label": `${label} (${shortcut})`,
185
+ "aria-pressed": active,
130
186
  title: `${label} (${shortcut})`,
131
187
  className: cn(
132
188
  "msr:flex msr:size-8 msr:items-center msr:justify-center msr:rounded-[8px] msr:outline-none",
@@ -140,7 +196,10 @@ function ToolbarButton({
140
196
  "span",
141
197
  {
142
198
  className: cn(
143
- "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",
199
+ cn(
200
+ "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",
201
+ tooltipInstant && "msr:transition-none"
202
+ ),
144
203
  tooltipSide === "top" ? "msr:bottom-full msr:mb-2" : "msr:top-full msr:mt-2",
145
204
  tooltipVisible ? "msr:opacity-100" : "msr:opacity-0"
146
205
  ),
@@ -159,6 +218,7 @@ function useToolbarTooltip() {
159
218
  const [visibleTooltipId, setVisibleTooltipId] = (0, import_react.useState)(null);
160
219
  const timerRef = (0, import_react.useRef)(null);
161
220
  const instantRef = (0, import_react.useRef)(false);
221
+ const [tooltipInstant, setTooltipInstant] = (0, import_react.useState)(false);
162
222
  const clearTimer = (0, import_react.useCallback)(() => {
163
223
  if (timerRef.current === null) return;
164
224
  window.clearTimeout(timerRef.current);
@@ -168,9 +228,11 @@ function useToolbarTooltip() {
168
228
  (id) => {
169
229
  clearTimer();
170
230
  if (instantRef.current) {
231
+ setTooltipInstant(true);
171
232
  setVisibleTooltipId(id);
172
233
  return;
173
234
  }
235
+ setTooltipInstant(false);
174
236
  timerRef.current = window.setTimeout(() => {
175
237
  setVisibleTooltipId(id);
176
238
  instantRef.current = true;
@@ -190,15 +252,17 @@ function useToolbarTooltip() {
190
252
  clearTimer();
191
253
  setVisibleTooltipId(null);
192
254
  instantRef.current = false;
255
+ setTooltipInstant(false);
193
256
  }, [clearTimer]);
194
- (0, import_react.useEffect)(() => {
195
- return () => {
196
- clearTimer();
197
- };
198
- }, [clearTimer]);
199
- return { visibleTooltipId, onTooltipEnter, onTooltipLeave, onToolbarLeave };
257
+ return {
258
+ visibleTooltipId,
259
+ tooltipInstant,
260
+ onTooltipEnter,
261
+ onTooltipLeave,
262
+ onToolbarLeave
263
+ };
200
264
  }
201
- function useToolbarDrag(initialPosition) {
265
+ function useToolbarDrag(initialPosition, eventTarget) {
202
266
  const [position, setPosition] = (0, import_react.useState)(initialPosition);
203
267
  const suppressClickRef = (0, import_react.useRef)(false);
204
268
  const detachListenersRef = (0, import_react.useRef)(null);
@@ -241,8 +305,8 @@ function useToolbarDrag(initialPosition) {
241
305
  }
242
306
  if (!current.active) return;
243
307
  current.didDrag = true;
244
- const maxX = Math.max(8, window.innerWidth - current.width - 8);
245
- const maxY = Math.max(8, window.innerHeight - current.height - 8);
308
+ const maxX = Math.max(8, eventTarget.innerWidth - current.width - 8);
309
+ const maxY = Math.max(8, eventTarget.innerHeight - current.height - 8);
246
310
  setPosition({
247
311
  x: Math.min(maxX, Math.max(8, current.originX + dx)),
248
312
  y: Math.min(maxY, Math.max(8, current.originY + dy))
@@ -256,21 +320,21 @@ function useToolbarDrag(initialPosition) {
256
320
  current.active = false;
257
321
  current.didDrag = false;
258
322
  current.pointerId = -1;
259
- window.removeEventListener("pointermove", handlePointerMove);
260
- window.removeEventListener("pointerup", handlePointerEnd);
261
- window.removeEventListener("pointercancel", handlePointerEnd);
323
+ eventTarget.removeEventListener("pointermove", handlePointerMove);
324
+ eventTarget.removeEventListener("pointerup", handlePointerEnd);
325
+ eventTarget.removeEventListener("pointercancel", handlePointerEnd);
262
326
  detachListenersRef.current = null;
263
327
  };
264
- window.addEventListener("pointermove", handlePointerMove);
265
- window.addEventListener("pointerup", handlePointerEnd);
266
- window.addEventListener("pointercancel", handlePointerEnd);
328
+ eventTarget.addEventListener("pointermove", handlePointerMove);
329
+ eventTarget.addEventListener("pointerup", handlePointerEnd);
330
+ eventTarget.addEventListener("pointercancel", handlePointerEnd);
267
331
  detachListenersRef.current = () => {
268
- window.removeEventListener("pointermove", handlePointerMove);
269
- window.removeEventListener("pointerup", handlePointerEnd);
270
- window.removeEventListener("pointercancel", handlePointerEnd);
332
+ eventTarget.removeEventListener("pointermove", handlePointerMove);
333
+ eventTarget.removeEventListener("pointerup", handlePointerEnd);
334
+ eventTarget.removeEventListener("pointercancel", handlePointerEnd);
271
335
  };
272
336
  },
273
- [position.x, position.y]
337
+ [eventTarget, position.x, position.y]
274
338
  );
275
339
  const onClickCapture = (0, import_react.useCallback)(
276
340
  (event) => {
@@ -287,15 +351,24 @@ function ToolbarComponent({
287
351
  toolMode,
288
352
  setEnabled,
289
353
  setToolMode,
354
+ rulersVisible,
355
+ setRulersVisible,
290
356
  guideOrientation,
291
357
  setGuideOrientation,
292
- onInteract
358
+ onInteract,
359
+ eventTarget
293
360
  }, ref) {
294
361
  const { position, onPointerDown, onClickCapture } = useToolbarDrag({
295
362
  x: 16,
296
363
  y: 16
297
- });
298
- const { visibleTooltipId, onTooltipEnter, onTooltipLeave, onToolbarLeave } = useToolbarTooltip();
364
+ }, eventTarget);
365
+ const {
366
+ visibleTooltipId,
367
+ tooltipInstant,
368
+ onTooltipEnter,
369
+ onTooltipLeave,
370
+ onToolbarLeave
371
+ } = useToolbarTooltip();
299
372
  const [guideMenuOpen, setGuideMenuOpen] = (0, import_react.useState)(false);
300
373
  const guideMenuRef = (0, import_react.useRef)(null);
301
374
  const [activeMenuIndex, setActiveMenuIndex] = (0, import_react.useState)(0);
@@ -309,13 +382,13 @@ function ToolbarComponent({
309
382
  setMenuAlign("left");
310
383
  return;
311
384
  }
312
- if (leftAlignedRight > window.innerWidth - VIEWPORT_PADDING) {
385
+ if (leftAlignedRight > eventTarget.innerWidth - VIEWPORT_PADDING) {
313
386
  setMenuAlign("right");
314
387
  return;
315
388
  }
316
389
  setMenuAlign("right");
317
- }, []);
318
- const viewportHeight = typeof window === "undefined" ? 0 : window.innerHeight || 0;
390
+ }, [eventTarget]);
391
+ const viewportHeight = eventTarget.innerHeight || 0;
319
392
  const nearTop = position.y < 56;
320
393
  const nearBottom = viewportHeight > 0 && position.y > viewportHeight - 56;
321
394
  const tooltipSide = nearTop && !nearBottom ? "bottom" : "top";
@@ -330,6 +403,23 @@ function ToolbarComponent({
330
403
  setToolMode((prev) => prev === "guides" ? "none" : "guides");
331
404
  onInteract();
332
405
  }, [onInteract, setEnabled, setToolMode]);
406
+ const textInspectorMode = (0, import_react.useCallback)(() => {
407
+ setEnabled(true);
408
+ setToolMode(
409
+ (prev) => prev === "text-inspector" ? "none" : "text-inspector"
410
+ );
411
+ onInteract();
412
+ }, [onInteract, setEnabled, setToolMode]);
413
+ const xrayMode = (0, import_react.useCallback)(() => {
414
+ setEnabled(true);
415
+ setToolMode((prev) => prev === "xray" ? "none" : "xray");
416
+ onInteract();
417
+ }, [onInteract, setEnabled, setToolMode]);
418
+ const rulersMode = (0, import_react.useCallback)(() => {
419
+ setEnabled(true);
420
+ setRulersVisible((prev) => !prev);
421
+ onInteract();
422
+ }, [onInteract, setEnabled, setRulersVisible]);
333
423
  const selectGuideOrientation = (0, import_react.useCallback)(
334
424
  (orientation) => {
335
425
  setEnabled(true);
@@ -342,7 +432,7 @@ function ToolbarComponent({
342
432
  );
343
433
  (0, import_react.useLayoutEffect)(() => {
344
434
  if (!guideMenuOpen) return;
345
- const frame = requestAnimationFrame(() => {
435
+ const frame = eventTarget.requestAnimationFrame(() => {
346
436
  guideMenuRef.current?.querySelector("[role='menu']")?.focus();
347
437
  });
348
438
  const handlePointerDown = (event) => {
@@ -355,14 +445,14 @@ function ToolbarComponent({
355
445
  const handleResize = () => {
356
446
  updateMenuAlign();
357
447
  };
358
- window.addEventListener("pointerdown", handlePointerDown);
359
- window.addEventListener("resize", handleResize);
448
+ eventTarget.addEventListener("pointerdown", handlePointerDown);
449
+ eventTarget.addEventListener("resize", handleResize);
360
450
  return () => {
361
- cancelAnimationFrame(frame);
362
- window.removeEventListener("pointerdown", handlePointerDown);
363
- window.removeEventListener("resize", handleResize);
451
+ eventTarget.cancelAnimationFrame(frame);
452
+ eventTarget.removeEventListener("pointerdown", handlePointerDown);
453
+ eventTarget.removeEventListener("resize", handleResize);
364
454
  };
365
- }, [guideMenuOpen, guideOrientation, updateMenuAlign]);
455
+ }, [eventTarget, guideMenuOpen, guideOrientation, updateMenuAlign]);
366
456
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
367
457
  "div",
368
458
  {
@@ -385,12 +475,61 @@ function ToolbarComponent({
385
475
  shortcut: "S",
386
476
  onClick: selectMode,
387
477
  tooltipVisible: visibleTooltipId === "select",
478
+ tooltipInstant,
388
479
  tooltipSide,
389
480
  onTooltipEnter,
390
481
  onTooltipLeave,
391
482
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CursorIcon, { size: 20 })
392
483
  }
393
484
  ),
485
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
486
+ ToolbarButton,
487
+ {
488
+ id: "xray",
489
+ active: toolMode === "xray",
490
+ label: "X-ray",
491
+ shortcut: "X",
492
+ onClick: xrayMode,
493
+ tooltipVisible: visibleTooltipId === "xray",
494
+ tooltipInstant,
495
+ tooltipSide,
496
+ onTooltipEnter,
497
+ onTooltipLeave,
498
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(XrayIcon, { size: 20 })
499
+ }
500
+ ),
501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
502
+ ToolbarButton,
503
+ {
504
+ id: "rulers",
505
+ active: rulersVisible,
506
+ label: "Rulers",
507
+ shortcut: "R",
508
+ onClick: rulersMode,
509
+ tooltipVisible: visibleTooltipId === "rulers",
510
+ tooltipInstant,
511
+ tooltipSide,
512
+ onTooltipEnter,
513
+ onTooltipLeave,
514
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RulersIcon, { size: 20 })
515
+ }
516
+ ),
517
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
518
+ ToolbarButton,
519
+ {
520
+ id: "text-inspector",
521
+ active: toolMode === "text-inspector",
522
+ label: "Text inspector",
523
+ shortcut: "A",
524
+ onClick: textInspectorMode,
525
+ tooltipVisible: visibleTooltipId === "text-inspector",
526
+ tooltipInstant,
527
+ tooltipSide,
528
+ onTooltipEnter,
529
+ onTooltipLeave,
530
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TextInspectorIcon, { size: 20, "aria-hidden": "true" })
531
+ }
532
+ ),
394
533
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
395
534
  ToolbarButton,
396
535
  {
@@ -400,6 +539,7 @@ function ToolbarComponent({
400
539
  shortcut: "G",
401
540
  onClick: guidesMode,
402
541
  tooltipVisible: visibleTooltipId === "guides",
542
+ tooltipInstant,
403
543
  tooltipSide,
404
544
  onTooltipEnter,
405
545
  onTooltipLeave,
@@ -559,32 +699,337 @@ function ToolbarComponent({
559
699
  }
560
700
  var Toolbar = (0, import_react.memo)((0, import_react.forwardRef)(ToolbarComponent));
561
701
 
562
- // hooks/use-drag-state.ts
702
+ // components/rulers-overlay.tsx
563
703
  var import_react2 = require("react");
704
+ var import_jsx_runtime3 = require("react/jsx-runtime");
705
+ var RULER_SIZE = 18;
706
+ var RULER_LENGTH = 4e3;
707
+ var TICK_STEP = 5;
708
+ var ticks = Array.from(
709
+ { length: RULER_LENGTH / TICK_STEP + 1 },
710
+ (_, index) => index * TICK_STEP
711
+ );
712
+ var RulersOverlay = (0, import_react2.memo)(function RulersOverlay2({
713
+ onStartGuide,
714
+ onMoveGuide,
715
+ onFinishGuide,
716
+ onCancelGuide,
717
+ guides,
718
+ selectedGuideIds
719
+ }) {
720
+ const [dragPosition, setDragPosition] = (0, import_react2.useState)(null);
721
+ const [dragOrientation, setDragOrientation] = (0, import_react2.useState)(null);
722
+ const dragRef = (0, import_react2.useRef)(null);
723
+ const beginGuideDrag = (orientation, event) => {
724
+ if (event.button !== 0) return;
725
+ event.stopPropagation();
726
+ const position = orientation === "horizontal" ? event.clientY : event.clientX;
727
+ setDragPosition(position);
728
+ setDragOrientation(orientation);
729
+ dragRef.current = {
730
+ orientation,
731
+ pointerId: event.pointerId,
732
+ id: onStartGuide(orientation, position)
733
+ };
734
+ event.currentTarget.setPointerCapture(event.pointerId);
735
+ };
736
+ const moveGuide = (event) => {
737
+ const drag = dragRef.current;
738
+ if (!drag || drag.pointerId !== event.pointerId) return;
739
+ event.stopPropagation();
740
+ const position = drag.orientation === "horizontal" ? event.clientY : event.clientX;
741
+ setDragPosition(position);
742
+ onMoveGuide(drag.id, position);
743
+ };
744
+ const finishGuideDrag = (event) => {
745
+ const drag = dragRef.current;
746
+ if (!drag || drag.pointerId !== event.pointerId) return;
747
+ event.stopPropagation();
748
+ const position = drag.orientation === "horizontal" ? event.clientY : event.clientX;
749
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
750
+ event.currentTarget.releasePointerCapture(event.pointerId);
751
+ }
752
+ dragRef.current = null;
753
+ setDragPosition(null);
754
+ setDragOrientation(null);
755
+ onMoveGuide(drag.id, position);
756
+ onFinishGuide(drag.id);
757
+ };
758
+ const cancelGuideDrag = (event) => {
759
+ const drag = dragRef.current;
760
+ if (!drag || drag.pointerId !== event.pointerId) return;
761
+ dragRef.current = null;
762
+ setDragPosition(null);
763
+ setDragOrientation(null);
764
+ onCancelGuide(drag.id);
765
+ };
766
+ const selectedVerticalGuides = guides.filter(
767
+ (guide) => guide.orientation === "vertical" && selectedGuideIds.includes(guide.id)
768
+ );
769
+ const selectedHorizontalGuides = guides.filter(
770
+ (guide) => guide.orientation === "horizontal" && selectedGuideIds.includes(guide.id)
771
+ );
772
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
773
+ "div",
774
+ {
775
+ "aria-hidden": "true",
776
+ className: "msr:pointer-events-none msr:absolute msr:inset-0 msr:select-none msr:text-[9px] msr:text-[#64748b]",
777
+ children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
779
+ "div",
780
+ {
781
+ className: "msr:absolute msr:left-[18px] msr:right-0 msr:top-0 msr:overflow-hidden msr:bg-white",
782
+ style: {
783
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.12)",
784
+ height: RULER_SIZE,
785
+ cursor: "ns-resize",
786
+ pointerEvents: "auto"
787
+ },
788
+ onPointerDown: (event) => beginGuideDrag("horizontal", event),
789
+ onPointerMove: moveGuide,
790
+ onPointerUp: finishGuideDrag,
791
+ onPointerCancel: cancelGuideDrag,
792
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
793
+ "svg",
794
+ {
795
+ className: "msr:block msr:h-6",
796
+ width: RULER_LENGTH,
797
+ height: RULER_SIZE,
798
+ viewBox: `0 0 ${RULER_LENGTH} ${RULER_SIZE}`,
799
+ children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
801
+ "linearGradient",
802
+ {
803
+ id: "ruler-label-fade-x",
804
+ x1: "0%",
805
+ x2: "100%",
806
+ gradientUnits: "objectBoundingBox",
807
+ children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0.2", stopColor: "white" }),
810
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0.8", stopColor: "white" }),
811
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })
812
+ ]
813
+ }
814
+ ) }),
815
+ ticks.map((x) => {
816
+ const major = x % 100 === 0;
817
+ const medium = x % 50 === 0;
818
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
820
+ "line",
821
+ {
822
+ x1: x,
823
+ y1: RULER_SIZE,
824
+ x2: x,
825
+ y2: major ? 8 : medium ? 13 : 17,
826
+ stroke: "currentColor",
827
+ strokeWidth: "1"
828
+ }
829
+ ),
830
+ major ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x, y: "8", textAnchor: "middle", fill: "currentColor", children: x }) : null
831
+ ] }, x);
832
+ }),
833
+ selectedVerticalGuides.map((guide) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
834
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
835
+ "rect",
836
+ {
837
+ x: guide.position - RULER_SIZE - 8,
838
+ y: "-1",
839
+ width: "48",
840
+ height: "12",
841
+ fill: "url(#ruler-label-fade-x)"
842
+ }
843
+ ),
844
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
845
+ "text",
846
+ {
847
+ x: guide.position - RULER_SIZE + 16,
848
+ y: "8",
849
+ textAnchor: "middle",
850
+ fill: "#ef4444",
851
+ fontWeight: "600",
852
+ children: Math.round(guide.position - RULER_SIZE)
853
+ }
854
+ )
855
+ ] }, guide.id)),
856
+ dragOrientation === "vertical" && dragPosition !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
857
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
858
+ "rect",
859
+ {
860
+ x: dragPosition - RULER_SIZE - 8,
861
+ y: "-1",
862
+ width: "48",
863
+ height: "12",
864
+ fill: "url(#ruler-label-fade-x)"
865
+ }
866
+ ),
867
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
868
+ "text",
869
+ {
870
+ x: dragPosition - RULER_SIZE + 16,
871
+ y: "8",
872
+ textAnchor: "middle",
873
+ fill: "#ef4444",
874
+ fontWeight: "600",
875
+ children: Math.round(dragPosition - RULER_SIZE)
876
+ }
877
+ )
878
+ ] }) : null
879
+ ]
880
+ }
881
+ )
882
+ }
883
+ ),
884
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
885
+ "div",
886
+ {
887
+ className: "msr:absolute msr:bottom-0 msr:left-0 msr:top-[18px] msr:w-[18px] msr:overflow-hidden msr:bg-white",
888
+ style: {
889
+ boxShadow: "1px 0 3px rgba(0, 0, 0, 0.12)",
890
+ width: RULER_SIZE,
891
+ cursor: "ew-resize",
892
+ pointerEvents: "auto"
893
+ },
894
+ onPointerDown: (event) => beginGuideDrag("vertical", event),
895
+ onPointerMove: moveGuide,
896
+ onPointerUp: finishGuideDrag,
897
+ onPointerCancel: cancelGuideDrag,
898
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
899
+ "svg",
900
+ {
901
+ className: "msr:block msr:w-6",
902
+ width: RULER_SIZE,
903
+ height: RULER_LENGTH,
904
+ viewBox: `0 0 ${RULER_SIZE} ${RULER_LENGTH}`,
905
+ children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
907
+ "linearGradient",
908
+ {
909
+ id: "ruler-label-fade-y",
910
+ y1: "0%",
911
+ y2: "100%",
912
+ gradientUnits: "objectBoundingBox",
913
+ children: [
914
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }),
915
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0.2", stopColor: "white" }),
916
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0.8", stopColor: "white" }),
917
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })
918
+ ]
919
+ }
920
+ ) }),
921
+ ticks.map((y) => {
922
+ const major = y % 100 === 0;
923
+ const medium = y % 50 === 0;
924
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
925
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
926
+ "line",
927
+ {
928
+ x1: RULER_SIZE,
929
+ y1: y,
930
+ x2: major ? 8 : medium ? 13 : 17,
931
+ y2: y,
932
+ stroke: "currentColor",
933
+ strokeWidth: "1"
934
+ }
935
+ ),
936
+ major ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
937
+ "text",
938
+ {
939
+ x: "8",
940
+ y: y + 10,
941
+ fill: "currentColor",
942
+ transform: `rotate(-90 8 ${y + 10})`,
943
+ children: y
944
+ }
945
+ ) : null
946
+ ] }, y);
947
+ }),
948
+ selectedHorizontalGuides.map((guide) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
950
+ "rect",
951
+ {
952
+ x: "-3",
953
+ y: guide.position - RULER_SIZE - 38,
954
+ width: "24",
955
+ height: "48",
956
+ fill: "url(#ruler-label-fade-y)"
957
+ }
958
+ ),
959
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
960
+ "text",
961
+ {
962
+ x: "9",
963
+ y: guide.position - RULER_SIZE - 16,
964
+ textAnchor: "middle",
965
+ fill: "#ef4444",
966
+ fontWeight: "600",
967
+ transform: `rotate(-90 9 ${guide.position - RULER_SIZE - 16})`,
968
+ children: Math.round(guide.position - RULER_SIZE)
969
+ }
970
+ )
971
+ ] }, guide.id)),
972
+ dragOrientation === "horizontal" && dragPosition !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
973
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
974
+ "rect",
975
+ {
976
+ x: "-3",
977
+ y: dragPosition - RULER_SIZE - 38,
978
+ width: "24",
979
+ height: "48",
980
+ fill: "url(#ruler-label-fade-y)"
981
+ }
982
+ ),
983
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
984
+ "text",
985
+ {
986
+ x: "9",
987
+ y: dragPosition - RULER_SIZE - 16,
988
+ textAnchor: "middle",
989
+ fill: "#ef4444",
990
+ fontWeight: "600",
991
+ transform: `rotate(-90 9 ${dragPosition - RULER_SIZE - 16})`,
992
+ children: Math.round(dragPosition - RULER_SIZE)
993
+ }
994
+ )
995
+ ] }) : null
996
+ ]
997
+ }
998
+ )
999
+ }
1000
+ ),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "msr:absolute msr:left-0 msr:top-0 msr:size-[18px] msr:bg-white" })
1002
+ ]
1003
+ }
1004
+ );
1005
+ });
1006
+
1007
+ // hooks/use-drag-state.ts
1008
+ var import_react3 = require("react");
564
1009
  var useDragState = () => {
565
- const [start, setStart] = (0, import_react2.useState)(null);
566
- const [end, setEnd] = (0, import_react2.useState)(null);
567
- const [isDragging, setIsDragging] = (0, import_react2.useState)(false);
1010
+ const [start, setStart] = (0, import_react3.useState)(null);
1011
+ const [end, setEnd] = (0, import_react3.useState)(null);
1012
+ const [isDragging, setIsDragging] = (0, import_react3.useState)(false);
568
1013
  return { start, setStart, end, setEnd, isDragging, setIsDragging };
569
1014
  };
570
1015
 
571
1016
  // hooks/use-guide-drag-hold.ts
572
- var import_react3 = require("react");
573
- var useGuideDragHold = () => {
574
- const guideDragHoldTimerRef = (0, import_react3.useRef)(null);
575
- const guideDragHoldGuideIdRef = (0, import_react3.useRef)(null);
576
- const clearGuideDragHold = (0, import_react3.useCallback)(() => {
1017
+ var import_react4 = require("react");
1018
+ var useGuideDragHold = (eventTarget) => {
1019
+ const guideDragHoldTimerRef = (0, import_react4.useRef)(null);
1020
+ const guideDragHoldGuideIdRef = (0, import_react4.useRef)(null);
1021
+ const clearGuideDragHold = (0, import_react4.useCallback)(() => {
577
1022
  if (guideDragHoldTimerRef.current !== null) {
578
- window.clearTimeout(guideDragHoldTimerRef.current);
1023
+ eventTarget.clearTimeout(guideDragHoldTimerRef.current);
579
1024
  guideDragHoldTimerRef.current = null;
580
1025
  }
581
1026
  guideDragHoldGuideIdRef.current = null;
582
1027
  }, []);
583
- const scheduleGuideDragHold = (0, import_react3.useCallback)(
1028
+ const scheduleGuideDragHold = (0, import_react4.useCallback)(
584
1029
  (id, onHold) => {
585
1030
  clearGuideDragHold();
586
1031
  guideDragHoldGuideIdRef.current = id;
587
- guideDragHoldTimerRef.current = window.setTimeout(() => {
1032
+ guideDragHoldTimerRef.current = eventTarget.setTimeout(() => {
588
1033
  if (guideDragHoldGuideIdRef.current === id) {
589
1034
  onHold(id);
590
1035
  }
@@ -599,11 +1044,11 @@ var useGuideDragHold = () => {
599
1044
  };
600
1045
 
601
1046
  // hooks/use-guide-state.ts
602
- var import_react4 = require("react");
1047
+ var import_react5 = require("react");
603
1048
  var useGuideState = (options = {}) => {
604
- const [guides, setGuides] = (0, import_react4.useState)(options.initialGuides ?? []);
605
- const [draggingGuideId, setDraggingGuideId] = (0, import_react4.useState)(null);
606
- const [selectedGuideIds, setSelectedGuideIds] = (0, import_react4.useState)(
1049
+ const [guides, setGuides] = (0, import_react5.useState)(options.initialGuides ?? []);
1050
+ const [draggingGuideId, setDraggingGuideId] = (0, import_react5.useState)(null);
1051
+ const [selectedGuideIds, setSelectedGuideIds] = (0, import_react5.useState)(
607
1052
  options.initialSelectedGuideIds ?? []
608
1053
  );
609
1054
  return {
@@ -617,19 +1062,37 @@ var useGuideState = (options = {}) => {
617
1062
  };
618
1063
 
619
1064
  // hooks/use-hotkeys.ts
620
- var import_react5 = require("react");
1065
+ var import_react6 = require("react");
621
1066
  var useHotkeys = (options) => {
622
- (0, import_react5.useEffect)(() => {
1067
+ (0, import_react6.useEffect)(() => {
1068
+ const target = options.eventTarget;
623
1069
  const handleKeyDown = (event) => {
624
1070
  if (event.key === "Escape") {
625
1071
  options.clearAll();
626
1072
  return;
627
1073
  }
1074
+ if (event.metaKey || event.ctrlKey) {
1075
+ if (event.key.toLowerCase() !== "z") return;
1076
+ if (event.shiftKey) {
1077
+ event.preventDefault();
1078
+ options.redo();
1079
+ return;
1080
+ }
1081
+ event.preventDefault();
1082
+ options.undo();
1083
+ return;
1084
+ }
628
1085
  if (event.key.toLowerCase() === "m") {
629
1086
  options.setEnabled((prev) => !prev);
630
1087
  }
631
1088
  const key = event.key.toLowerCase();
632
1089
  if (options.isOverlayActive()) {
1090
+ if (key === "a") {
1091
+ options.setToolMode(
1092
+ (prev) => prev === "text-inspector" ? "none" : "text-inspector"
1093
+ );
1094
+ options.onInteract();
1095
+ }
633
1096
  if (key === "s") {
634
1097
  options.setToolMode((prev) => prev === "select" ? "none" : "select");
635
1098
  options.onInteract();
@@ -638,6 +1101,14 @@ var useHotkeys = (options) => {
638
1101
  options.setToolMode((prev) => prev === "guides" ? "none" : "guides");
639
1102
  options.onInteract();
640
1103
  }
1104
+ if (key === "x") {
1105
+ options.setToolMode((prev) => prev === "xray" ? "none" : "xray");
1106
+ options.onInteract();
1107
+ }
1108
+ if (key === "r") {
1109
+ options.setRulersVisible((prev) => !prev);
1110
+ options.onInteract();
1111
+ }
641
1112
  if (key === "h") {
642
1113
  options.setGuideOrientation("horizontal");
643
1114
  options.onInteract();
@@ -662,22 +1133,22 @@ var useHotkeys = (options) => {
662
1133
  options.setAltPressed(false);
663
1134
  }
664
1135
  };
665
- window.addEventListener("keydown", handleKeyDown);
666
- window.addEventListener("keyup", handleKeyUp);
1136
+ target.addEventListener("keydown", handleKeyDown);
1137
+ target.addEventListener("keyup", handleKeyUp);
667
1138
  return () => {
668
- window.removeEventListener("keydown", handleKeyDown);
669
- window.removeEventListener("keyup", handleKeyUp);
1139
+ target.removeEventListener("keydown", handleKeyDown);
1140
+ target.removeEventListener("keyup", handleKeyUp);
670
1141
  };
671
1142
  }, [options]);
672
1143
  };
673
1144
 
674
1145
  // hooks/use-live-element-tracking.ts
675
- var import_react6 = require("react");
1146
+ var import_react7 = require("react");
676
1147
 
677
- // geometry.ts
678
- var getViewportSize = () => ({
679
- width: typeof window === "undefined" ? 1 : window.innerWidth || 1,
680
- height: typeof window === "undefined" ? 1 : window.innerHeight || 1
1148
+ // core/geometry.ts
1149
+ var getViewportSize = (ownerWindow) => ({
1150
+ width: ownerWindow?.innerWidth || (typeof window === "undefined" ? 1 : window.innerWidth) || 1,
1151
+ height: ownerWindow?.innerHeight || (typeof window === "undefined" ? 1 : window.innerHeight) || 1
681
1152
  });
682
1153
  var normalizeRect = (rect, viewport = getViewportSize()) => ({
683
1154
  left: rect.left / viewport.width,
@@ -719,9 +1190,9 @@ var getDistanceToRect = (point, rect) => {
719
1190
  return Math.hypot(dx, dy);
720
1191
  };
721
1192
 
722
- // distances.ts
723
- var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB) => {
724
- const viewport = getViewportSize();
1193
+ // core/distances.ts
1194
+ var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB, ownerWindow = window) => {
1195
+ const viewport = getViewportSize(ownerWindow);
725
1196
  const normalizedRectA = normalizeRect(rectA, viewport);
726
1197
  const normalizedRectB = normalizeRect(rectB, viewport);
727
1198
  const rightA = rectA.left + rectA.width;
@@ -762,10 +1233,10 @@ var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB) => {
762
1233
  }
763
1234
  }
764
1235
  const normalizedConnectors = connectors.map((segment) => ({
765
- x1: clamp(segment.x1, 0, window.innerWidth),
766
- y1: clamp(segment.y1, 0, window.innerHeight),
767
- x2: clamp(segment.x2, 0, window.innerWidth),
768
- y2: clamp(segment.y2, 0, window.innerHeight)
1236
+ x1: clamp(segment.x1, 0, ownerWindow.innerWidth),
1237
+ y1: clamp(segment.y1, 0, ownerWindow.innerHeight),
1238
+ x2: clamp(segment.x2, 0, ownerWindow.innerWidth),
1239
+ y2: clamp(segment.y2, 0, ownerWindow.innerHeight)
769
1240
  })).filter(
770
1241
  (segment) => Math.abs(segment.x1 - segment.x2) > 0.5 || Math.abs(segment.y1 - segment.y2) > 0.5
771
1242
  );
@@ -782,17 +1253,17 @@ var getDistanceOverlay = (rectA, rectB, elementRefA, elementRefB) => {
782
1253
  connectors: normalizedConnectors
783
1254
  };
784
1255
  };
785
- var updateDistanceForResize = (distance, viewport = getViewportSize()) => {
786
- const normalizedRectA = distance.normalizedRectA ?? normalizeRect(distance.rectA);
787
- const normalizedRectB = distance.normalizedRectB ?? normalizeRect(distance.rectB);
1256
+ var updateDistanceForResize = (distance, viewport = getViewportSize(), ownerDocument = document, ownerWindow = window) => {
1257
+ const normalizedRectA = distance.normalizedRectA ?? normalizeRect(distance.rectA, viewport);
1258
+ const normalizedRectB = distance.normalizedRectB ?? normalizeRect(distance.rectB, viewport);
788
1259
  let rectA = distance.rectA;
789
1260
  let rectB = distance.rectB;
790
- if (distance.elementRefA && document.contains(distance.elementRefA)) {
1261
+ if (distance.elementRefA && ownerDocument.contains(distance.elementRefA)) {
791
1262
  rectA = distance.elementRefA.getBoundingClientRect();
792
1263
  } else {
793
1264
  rectA = denormalizeRect(normalizedRectA, viewport);
794
1265
  }
795
- if (distance.elementRefB && document.contains(distance.elementRefB)) {
1266
+ if (distance.elementRefB && ownerDocument.contains(distance.elementRefB)) {
796
1267
  rectB = distance.elementRefB.getBoundingClientRect();
797
1268
  } else {
798
1269
  rectB = denormalizeRect(normalizedRectB, viewport);
@@ -801,7 +1272,8 @@ var updateDistanceForResize = (distance, viewport = getViewportSize()) => {
801
1272
  rectA,
802
1273
  rectB,
803
1274
  distance.elementRefA,
804
- distance.elementRefB
1275
+ distance.elementRefB,
1276
+ ownerWindow
805
1277
  );
806
1278
  return {
807
1279
  ...updated,
@@ -809,7 +1281,7 @@ var updateDistanceForResize = (distance, viewport = getViewportSize()) => {
809
1281
  };
810
1282
  };
811
1283
 
812
- // dom.ts
1284
+ // core/dom.ts
813
1285
  var getElementLabel = (element) => {
814
1286
  const tag = element.tagName.toLowerCase();
815
1287
  const id = element.id ? `#${element.id}` : "";
@@ -842,24 +1314,38 @@ var getRectFromDomCached = (element) => {
842
1314
  };
843
1315
  var cachedElements = [];
844
1316
  var cachedFrame = -1;
1317
+ var cachedDocument = null;
845
1318
  var getFrameToken = () => {
846
1319
  if (typeof performance === "undefined") return 0;
847
1320
  return Math.floor(performance.now() / 16);
848
1321
  };
849
- var getBodyElementsCached = () => {
1322
+ var getBodyElementsCached = (ownerDocument = document) => {
850
1323
  const frame = getFrameToken();
851
- if (frame === cachedFrame && cachedElements.length > 0) {
1324
+ if (frame === cachedFrame && cachedDocument === ownerDocument && cachedElements.length > 0) {
852
1325
  return cachedElements;
853
1326
  }
854
1327
  cachedFrame = frame;
855
- cachedElements = Array.from(document.querySelectorAll("body *")).filter(
856
- (element) => element instanceof HTMLElement
857
- );
1328
+ cachedDocument = ownerDocument;
1329
+ const elements = [];
1330
+ const HTMLElementConstructor = ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
1331
+ const visit = (root) => {
1332
+ const walker = ownerDocument.createTreeWalker(root, 1);
1333
+ let node = walker.nextNode();
1334
+ while (node) {
1335
+ if (node instanceof HTMLElementConstructor) {
1336
+ elements.push(node);
1337
+ if (node.shadowRoot) visit(node.shadowRoot);
1338
+ }
1339
+ node = walker.nextNode();
1340
+ }
1341
+ };
1342
+ if (ownerDocument.body) visit(ownerDocument.body);
1343
+ cachedElements = elements;
858
1344
  return cachedElements;
859
1345
  };
860
- var getInspectMeasurement = (element) => {
1346
+ var getInspectMeasurement = (element, ownerWindow = window) => {
861
1347
  const rect = element.getBoundingClientRect();
862
- const style = window.getComputedStyle(element);
1348
+ const style = ownerWindow.getComputedStyle(element);
863
1349
  const padding = {
864
1350
  top: parseEdge(style.paddingTop),
865
1351
  right: parseEdge(style.paddingRight),
@@ -900,9 +1386,9 @@ var getInspectMeasurement = (element) => {
900
1386
  elementRef: element
901
1387
  };
902
1388
  };
903
- var updateMeasurementForResize = (measurement, viewport = getViewportSize()) => {
1389
+ var updateMeasurementForResize = (measurement, viewport = getViewportSize(), ownerDocument = document) => {
904
1390
  let rect = measurement.rect;
905
- if (measurement.elementRef && document.contains(measurement.elementRef)) {
1391
+ if (measurement.elementRef && ownerDocument.contains(measurement.elementRef)) {
906
1392
  rect = getRectFromDom(measurement.elementRef);
907
1393
  } else if (measurement.normalizedRect) {
908
1394
  rect = denormalizeRect(measurement.normalizedRect, viewport);
@@ -917,11 +1403,11 @@ var updateMeasurementForResize = (measurement, viewport = getViewportSize()) =>
917
1403
 
918
1404
  // hooks/use-live-element-tracking.ts
919
1405
  var useLiveElementTracking = (params) => {
920
- const frameRef = (0, import_react6.useRef)(null);
921
- (0, import_react6.useEffect)(() => {
1406
+ const frameRef = (0, import_react7.useRef)(null);
1407
+ (0, import_react7.useEffect)(() => {
922
1408
  if (!params.enabled) {
923
1409
  if (frameRef.current) {
924
- cancelAnimationFrame(frameRef.current);
1410
+ params.window.cancelAnimationFrame(frameRef.current);
925
1411
  }
926
1412
  frameRef.current = null;
927
1413
  return;
@@ -929,7 +1415,7 @@ var useLiveElementTracking = (params) => {
929
1415
  const tick = () => {
930
1416
  params.setMeasurements(
931
1417
  (prev) => prev.map((measurement) => {
932
- if (!measurement.elementRef || !document.contains(measurement.elementRef)) {
1418
+ if (!measurement.elementRef || !params.document.contains(measurement.elementRef)) {
933
1419
  return measurement;
934
1420
  }
935
1421
  const rect = getRectFromDom(measurement.elementRef);
@@ -943,7 +1429,7 @@ var useLiveElementTracking = (params) => {
943
1429
  })
944
1430
  );
945
1431
  params.setActiveMeasurement((prev) => {
946
- if (!prev?.elementRef || !document.contains(prev.elementRef))
1432
+ if (!prev?.elementRef || !params.document.contains(prev.elementRef))
947
1433
  return prev;
948
1434
  const rect = getRectFromDom(prev.elementRef);
949
1435
  if (rectAlmostEqual(rect, prev.rect)) return prev;
@@ -956,8 +1442,8 @@ var useLiveElementTracking = (params) => {
956
1442
  });
957
1443
  params.setHeldDistances(
958
1444
  (prev) => prev.map((distance) => {
959
- const canTrackA = distance.elementRefA && document.contains(distance.elementRefA);
960
- const canTrackB = distance.elementRefB && document.contains(distance.elementRefB);
1445
+ const canTrackA = distance.elementRefA && params.document.contains(distance.elementRefA);
1446
+ const canTrackB = distance.elementRefB && params.document.contains(distance.elementRefB);
961
1447
  if (!canTrackA && !canTrackB) return distance;
962
1448
  const rectA = canTrackA ? getRectFromDom(distance.elementRefA) : distance.rectA;
963
1449
  const rectB = canTrackB ? getRectFromDom(distance.elementRefB) : distance.rectB;
@@ -968,7 +1454,8 @@ var useLiveElementTracking = (params) => {
968
1454
  rectA,
969
1455
  rectB,
970
1456
  distance.elementRefA,
971
- distance.elementRefB
1457
+ distance.elementRefB,
1458
+ params.window
972
1459
  );
973
1460
  return {
974
1461
  ...updated,
@@ -977,19 +1464,19 @@ var useLiveElementTracking = (params) => {
977
1464
  })
978
1465
  );
979
1466
  const selected = params.selectedElementRef.current;
980
- if (selected && document.contains(selected)) {
1467
+ if (selected && params.document.contains(selected)) {
981
1468
  params.setSelectedMeasurement((prev) => {
982
- const next = getInspectMeasurement(selected);
1469
+ const next = getInspectMeasurement(selected, params.window);
983
1470
  if (prev && rectAlmostEqual(prev.rect, next.rect)) return prev;
984
1471
  return next;
985
1472
  });
986
1473
  }
987
1474
  params.setSelectedMeasurements(
988
1475
  (prev) => prev.map((measurement) => {
989
- if (!measurement.elementRef || !document.contains(measurement.elementRef)) {
1476
+ if (!measurement.elementRef || !params.document.contains(measurement.elementRef)) {
990
1477
  return measurement;
991
1478
  }
992
- const next = getInspectMeasurement(measurement.elementRef);
1479
+ const next = getInspectMeasurement(measurement.elementRef, params.window);
993
1480
  if (rectAlmostEqual(next.rect, measurement.rect)) return measurement;
994
1481
  return {
995
1482
  ...next,
@@ -998,18 +1485,18 @@ var useLiveElementTracking = (params) => {
998
1485
  })
999
1486
  );
1000
1487
  const hover = params.hoverElementRef.current;
1001
- if (hover && document.contains(hover)) {
1488
+ if (hover && params.document.contains(hover)) {
1002
1489
  const rect = getRectFromDom(hover);
1003
1490
  params.setHoverRect(
1004
1491
  (prev) => prev && rectAlmostEqual(prev, rect) ? prev : rect
1005
1492
  );
1006
1493
  }
1007
- frameRef.current = requestAnimationFrame(tick);
1494
+ frameRef.current = params.window.requestAnimationFrame(tick);
1008
1495
  };
1009
- frameRef.current = requestAnimationFrame(tick);
1496
+ frameRef.current = params.window.requestAnimationFrame(tick);
1010
1497
  return () => {
1011
1498
  if (frameRef.current) {
1012
- cancelAnimationFrame(frameRef.current);
1499
+ params.window.cancelAnimationFrame(frameRef.current);
1013
1500
  }
1014
1501
  frameRef.current = null;
1015
1502
  };
@@ -1017,13 +1504,16 @@ var useLiveElementTracking = (params) => {
1017
1504
  };
1018
1505
 
1019
1506
  // hooks/use-measure-toggles.ts
1020
- var import_react7 = require("react");
1507
+ var import_react8 = require("react");
1021
1508
  var useMeasureToggles = (options = {}) => {
1022
- const [enabled, setEnabled] = (0, import_react7.useState)(options.initialEnabled ?? true);
1023
- const [altPressed, setAltPressed] = (0, import_react7.useState)(false);
1024
- const [toolMode, setToolMode] = (0, import_react7.useState)(
1509
+ const [enabled, setEnabled] = (0, import_react8.useState)(options.initialEnabled ?? true);
1510
+ const [altPressed, setAltPressed] = (0, import_react8.useState)(false);
1511
+ const [toolMode, setToolMode] = (0, import_react8.useState)(
1025
1512
  options.initialToolMode ?? "none"
1026
1513
  );
1514
+ const [rulersVisible, setRulersVisible] = (0, import_react8.useState)(
1515
+ options.initialRulersVisible ?? false
1516
+ );
1027
1517
  const holdEnabled = false;
1028
1518
  const multiMeasureEnabled = false;
1029
1519
  const snapGuidesEnabled = true;
@@ -1037,6 +1527,8 @@ var useMeasureToggles = (options = {}) => {
1037
1527
  setAltPressed,
1038
1528
  toolMode,
1039
1529
  setToolMode,
1530
+ rulersVisible,
1531
+ setRulersVisible,
1040
1532
  guidesEnabled,
1041
1533
  multiMeasureEnabled,
1042
1534
  snapGuidesEnabled,
@@ -1045,16 +1537,16 @@ var useMeasureToggles = (options = {}) => {
1045
1537
  };
1046
1538
 
1047
1539
  // hooks/use-measurement-state.ts
1048
- var import_react8 = require("react");
1540
+ var import_react9 = require("react");
1049
1541
  var useMeasurementState = (options = {}) => {
1050
- const [activeMeasurement, setActiveMeasurement] = (0, import_react8.useState)(options.initialActiveMeasurement ?? null);
1051
- const [measurements, setMeasurements] = (0, import_react8.useState)(
1542
+ const [activeMeasurement, setActiveMeasurement] = (0, import_react9.useState)(options.initialActiveMeasurement ?? null);
1543
+ const [measurements, setMeasurements] = (0, import_react9.useState)(
1052
1544
  options.initialMeasurements ?? []
1053
1545
  );
1054
- const [selectedMeasurement, setSelectedMeasurement] = (0, import_react8.useState)(null);
1055
- const [selectedMeasurements, setSelectedMeasurements] = (0, import_react8.useState)([]);
1056
- const [hoverRect, setHoverRect] = (0, import_react8.useState)(null);
1057
- const [heldDistances, setHeldDistances] = (0, import_react8.useState)(
1546
+ const [selectedMeasurement, setSelectedMeasurement] = (0, import_react9.useState)(null);
1547
+ const [selectedMeasurements, setSelectedMeasurements] = (0, import_react9.useState)([]);
1548
+ const [hoverRect, setHoverRect] = (0, import_react9.useState)(null);
1549
+ const [heldDistances, setHeldDistances] = (0, import_react9.useState)(
1058
1550
  options.initialHeldDistances ?? []
1059
1551
  );
1060
1552
  return {
@@ -1074,9 +1566,9 @@ var useMeasurementState = (options = {}) => {
1074
1566
  };
1075
1567
 
1076
1568
  // hooks/use-measurer-derived.ts
1077
- var import_react9 = require("react");
1569
+ var import_react10 = require("react");
1078
1570
 
1079
- // edge-visibility.ts
1571
+ // core/edge-visibility.ts
1080
1572
  var rangesOverlap = (a1, a2, b1, b2) => Math.min(a2, b2) - Math.max(a1, b1) > 0;
1081
1573
  var getEdgeVisibilityForRects = (rects, epsilon = 1) => {
1082
1574
  const visibility = rects.map(() => ({
@@ -1116,24 +1608,25 @@ var getEdgeVisibilityForRects = (rects, epsilon = 1) => {
1116
1608
  return visibility;
1117
1609
  };
1118
1610
 
1119
- // guides.ts
1120
- var getGuideRect = (guide) => {
1121
- const viewport = getViewportSize();
1611
+ // core/guides.ts
1612
+ var getGuideRect = (guide, ownerWindow = window) => {
1613
+ const viewport = getViewportSize(ownerWindow);
1122
1614
  return guide.orientation === "vertical" ? { left: guide.position, top: 0, width: 1, height: viewport.height } : { left: 0, top: guide.position, width: viewport.width, height: 1 };
1123
1615
  };
1124
1616
  var getGuideDistance = (guide, point) => guide.orientation === "vertical" ? Math.abs(guide.position - point.x) : Math.abs(guide.position - point.y);
1125
1617
  var getSnapGuidePosition = (params) => {
1618
+ const ownerDocument = params.document ?? document;
1126
1619
  const { orientation, point, snapGuidesEnabled, overlayNode, guides } = params;
1127
1620
  if (!snapGuidesEnabled) {
1128
1621
  return orientation === "vertical" ? point.x : point.y;
1129
1622
  }
1130
- const elements = getBodyElementsCached();
1623
+ const elements = getBodyElementsCached(ownerDocument);
1131
1624
  let bestValue = orientation === "vertical" ? point.x : point.y;
1132
1625
  let bestDistance = GUIDE_SNAP_DISTANCE + 1;
1133
1626
  for (const element of elements) {
1134
- if (!(element instanceof HTMLElement)) continue;
1627
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
1135
1628
  if (overlayNode && overlayNode.contains(element)) continue;
1136
- if (element === document.body || element === document.documentElement)
1629
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1137
1630
  continue;
1138
1631
  const rect = getRectFromDomCached(element);
1139
1632
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1169,7 +1662,7 @@ var getSnapGuidePosition = (params) => {
1169
1662
  return bestDistance <= GUIDE_SNAP_DISTANCE ? bestValue : orientation === "vertical" ? point.x : point.y;
1170
1663
  };
1171
1664
 
1172
- // option-measurements.ts
1665
+ // core/option-measurements.ts
1173
1666
  var getHoveredGuide = (point, guides) => {
1174
1667
  if (!point || guides.length === 0) return null;
1175
1668
  let closest = null;
@@ -1190,6 +1683,8 @@ var getSelectedGuide = (guides, selectedGuideIds) => {
1190
1683
  return guides.find((guide) => guide.id === lastId) ?? null;
1191
1684
  };
1192
1685
  var getOptionPairOverlay = (params) => {
1686
+ const ownerDocument = params.document ?? document;
1687
+ const ownerWindow = params.window ?? window;
1193
1688
  if (!params.altPressed) return null;
1194
1689
  if (!params.selectedGuide && !params.primarySelectedMeasurement) return null;
1195
1690
  const primary = params.primarySelectedMeasurement;
@@ -1218,10 +1713,13 @@ var getOptionPairOverlay = (params) => {
1218
1713
  selectedTarget.rect,
1219
1714
  hoverTarget.rect,
1220
1715
  selectedTarget.element ?? null,
1221
- hoverTarget.element ?? null
1716
+ hoverTarget.element ?? null,
1717
+ ownerWindow
1222
1718
  );
1223
1719
  };
1224
1720
  var getOptionContainerLines = (params) => {
1721
+ const ownerDocument = params.document ?? document;
1722
+ const ownerWindow = params.window ?? window;
1225
1723
  if (!params.altPressed || !params.primarySelectedMeasurement || params.optionPairOverlay)
1226
1724
  return null;
1227
1725
  if (params.selectedGuideIds.length > 0) return null;
@@ -1229,11 +1727,11 @@ var getOptionContainerLines = (params) => {
1229
1727
  if (params.selectedElement && params.hoverElement && params.hoverElement !== params.selectedElement && params.hoverElement.contains(params.selectedElement)) {
1230
1728
  containerElement = params.hoverElement;
1231
1729
  }
1232
- const containerRect = containerElement && containerElement !== document.body && containerElement !== document.documentElement ? getRectFromDom(containerElement) : {
1730
+ const containerRect = containerElement && containerElement !== ownerDocument.body && containerElement !== ownerDocument.documentElement ? getRectFromDom(containerElement) : {
1233
1731
  left: 0,
1234
1732
  top: 0,
1235
- width: window.innerWidth,
1236
- height: window.innerHeight
1733
+ width: ownerWindow.innerWidth,
1734
+ height: ownerWindow.innerHeight
1237
1735
  };
1238
1736
  const rect = params.primarySelectedMeasurement.rect;
1239
1737
  const right = rect.left + rect.width;
@@ -1272,18 +1770,20 @@ var getOptionContainerLines = (params) => {
1272
1770
  };
1273
1771
  };
1274
1772
 
1275
- // selection-helpers.ts
1773
+ // core/selection-helpers.ts
1276
1774
  var getOverlayHost = (overlayNode) => {
1277
1775
  if (!overlayNode) return null;
1278
1776
  const rootNode = overlayNode.getRootNode();
1279
- return rootNode instanceof ShadowRoot ? rootNode.host : null;
1777
+ return rootNode.nodeType === 11 ? rootNode.host : null;
1280
1778
  };
1281
1779
  var getPrimarySelectedMeasurement = (selectedMeasurements, selectedMeasurement) => selectedMeasurements.length > 0 ? selectedMeasurements[selectedMeasurements.length - 1] : selectedMeasurement;
1282
1780
  var getSelectedMeasurementHit = (params) => {
1781
+ const ownerDocument = params.document ?? document;
1782
+ const HTMLElementConstructor = ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
1283
1783
  const overlayHost = getOverlayHost(params.overlayNode);
1284
1784
  const candidates = params.selectedMeasurements.map((measurement) => {
1285
1785
  const element = measurement.elementRef;
1286
- if (!element || !document.contains(element)) return null;
1786
+ if (!element || !ownerDocument.contains(element)) return null;
1287
1787
  const rect = getRectFromDom(element);
1288
1788
  return {
1289
1789
  measurement,
@@ -1293,9 +1793,9 @@ var getSelectedMeasurementHit = (params) => {
1293
1793
  };
1294
1794
  }).filter((item) => item !== null).sort((a, b) => a.area - b.area);
1295
1795
  if (candidates.length === 0) return null;
1296
- const stack = document.elementsFromPoint(params.point.x, params.point.y);
1796
+ const stack = ownerDocument.elementsFromPoint(params.point.x, params.point.y);
1297
1797
  for (const element of stack) {
1298
- if (!(element instanceof HTMLElement)) continue;
1798
+ if (!(element instanceof HTMLElementConstructor)) continue;
1299
1799
  if (params.overlayNode && params.overlayNode.contains(element)) continue;
1300
1800
  if (overlayHost && element === overlayHost) continue;
1301
1801
  for (const candidate of candidates) {
@@ -1314,6 +1814,8 @@ var getSelectedMeasurementHit = (params) => {
1314
1814
 
1315
1815
  // hooks/use-measurer-derived.ts
1316
1816
  var useMeasurerDerived = ({
1817
+ document: document2,
1818
+ window: window2,
1317
1819
  start,
1318
1820
  end,
1319
1821
  selectedMeasurements,
@@ -1333,7 +1835,7 @@ var useMeasurerDerived = ({
1333
1835
  highlightColor,
1334
1836
  guideColor
1335
1837
  }) => {
1336
- const activeRect = (0, import_react9.useMemo)(() => {
1838
+ const activeRect = (0, import_react10.useMemo)(() => {
1337
1839
  if (!start || !end) return null;
1338
1840
  return {
1339
1841
  left: Math.min(start.x, end.x),
@@ -1344,7 +1846,7 @@ var useMeasurerDerived = ({
1344
1846
  }, [end, start]);
1345
1847
  const activeWidth = activeRect ? formatValue(activeRect.width) : 0;
1346
1848
  const activeHeight = activeRect ? formatValue(activeRect.height) : 0;
1347
- const groupedSelection = (0, import_react9.useMemo)(() => {
1849
+ const groupedSelection = (0, import_react10.useMemo)(() => {
1348
1850
  if (selectedMeasurements.length <= 1) return null;
1349
1851
  const selectionKey = selectedMeasurements.map((measurement) => measurement.id).join("|");
1350
1852
  const rects = selectedMeasurements.map((measurement) => measurement.rect);
@@ -1374,31 +1876,33 @@ var useMeasurerDerived = ({
1374
1876
  originRect: selectionOriginRect ?? void 0
1375
1877
  };
1376
1878
  }, [selectedMeasurements, selectionOriginRect]);
1377
- const displayedSelectedMeasurements = (0, import_react9.useMemo)(
1879
+ const displayedSelectedMeasurements = (0, import_react10.useMemo)(
1378
1880
  () => groupedSelection ? [groupedSelection] : selectedMeasurements.length > 0 ? selectedMeasurements : selectedMeasurement ? [selectedMeasurement] : [],
1379
1881
  [groupedSelection, selectedMeasurement, selectedMeasurements]
1380
1882
  );
1381
- const selectedGuide = (0, import_react9.useMemo)(
1883
+ const selectedGuide = (0, import_react10.useMemo)(
1382
1884
  () => getSelectedGuide(guides, selectedGuideIds),
1383
1885
  [guides, selectedGuideIds]
1384
1886
  );
1385
- const hoverGuide = (0, import_react9.useMemo)(
1887
+ const hoverGuide = (0, import_react10.useMemo)(
1386
1888
  () => getHoveredGuide(hoverPointer, guides),
1387
1889
  [guides, hoverPointer]
1388
1890
  );
1389
- const primarySelectedMeasurement = (0, import_react9.useMemo)(
1891
+ const primarySelectedMeasurement = (0, import_react10.useMemo)(
1390
1892
  () => getPrimarySelectedMeasurement(selectedMeasurements, selectedMeasurement),
1391
1893
  [selectedMeasurement, selectedMeasurements]
1392
1894
  );
1393
1895
  const effectivePrimarySelected = groupedSelection ?? primarySelectedMeasurement;
1394
- const optionPairOverlay = (0, import_react9.useMemo)(() => {
1896
+ const optionPairOverlay = (0, import_react10.useMemo)(() => {
1395
1897
  return getOptionPairOverlay({
1396
1898
  altPressed,
1397
1899
  primarySelectedMeasurement: effectivePrimarySelected,
1398
1900
  selectedGuide,
1399
1901
  hoverGuide,
1400
1902
  hoverElement,
1401
- selectedElementRef: selectedElement
1903
+ selectedElementRef: selectedElement,
1904
+ document: document2,
1905
+ window: window2
1402
1906
  });
1403
1907
  }, [
1404
1908
  altPressed,
@@ -1408,14 +1912,16 @@ var useMeasurerDerived = ({
1408
1912
  selectedElement,
1409
1913
  selectedGuide
1410
1914
  ]);
1411
- const optionContainerLines = (0, import_react9.useMemo)(() => {
1915
+ const optionContainerLines = (0, import_react10.useMemo)(() => {
1412
1916
  return getOptionContainerLines({
1413
1917
  altPressed,
1414
1918
  primarySelectedMeasurement: effectivePrimarySelected,
1415
1919
  optionPairOverlay,
1416
1920
  selectedGuideIds,
1417
1921
  selectedElement,
1418
- hoverElement
1922
+ hoverElement,
1923
+ document: document2,
1924
+ window: window2
1419
1925
  });
1420
1926
  }, [
1421
1927
  altPressed,
@@ -1425,12 +1931,15 @@ var useMeasurerDerived = ({
1425
1931
  selectedElement,
1426
1932
  selectedGuideIds
1427
1933
  ]);
1428
- const guideDistanceOverlay = (0, import_react9.useMemo)(() => {
1934
+ const guideDistanceOverlay = (0, import_react10.useMemo)(() => {
1429
1935
  if (!altPressed || !guidesEnabled) return null;
1430
1936
  if (selectedGuide && hoverGuide && selectedGuide.id !== hoverGuide.id) {
1431
1937
  return getDistanceOverlay(
1432
- getGuideRect(selectedGuide),
1433
- getGuideRect(hoverGuide)
1938
+ getGuideRect(selectedGuide, window2),
1939
+ getGuideRect(hoverGuide, window2),
1940
+ null,
1941
+ null,
1942
+ window2
1434
1943
  );
1435
1944
  }
1436
1945
  if (!guidePreview) return null;
@@ -1453,8 +1962,11 @@ var useMeasurerDerived = ({
1453
1962
  );
1454
1963
  if (!nearest) return null;
1455
1964
  return getDistanceOverlay(
1456
- getGuideRect(previewGuide),
1457
- getGuideRect(nearest.guide)
1965
+ getGuideRect(previewGuide, window2),
1966
+ getGuideRect(nearest.guide, window2),
1967
+ null,
1968
+ null,
1969
+ window2
1458
1970
  );
1459
1971
  }, [
1460
1972
  altPressed,
@@ -1471,7 +1983,7 @@ var useMeasurerDerived = ({
1471
1983
  guideColorHover,
1472
1984
  guideColorDefault,
1473
1985
  guideColorPreview
1474
- } = (0, import_react9.useMemo)(
1986
+ } = (0, import_react10.useMemo)(
1475
1987
  () => ({
1476
1988
  outlineColor: `color-mix(in oklch, ${highlightColor} 80%, transparent)`,
1477
1989
  fillColor: `color-mix(in oklch, ${highlightColor} 8%, transparent)`,
@@ -1482,12 +1994,12 @@ var useMeasurerDerived = ({
1482
1994
  }),
1483
1995
  [guideColor, highlightColor]
1484
1996
  );
1485
- const selectedRects = (0, import_react9.useMemo)(
1997
+ const selectedRects = (0, import_react10.useMemo)(
1486
1998
  () => displayedSelectedMeasurements.map((measurement) => measurement.rect),
1487
1999
  [displayedSelectedMeasurements]
1488
2000
  );
1489
2001
  const hoverRectToShow = hoverHighlightEnabled && hoverRect && selectedMeasurements.length <= 1 ? hoverRect : null;
1490
- const selectionEdgeVisibility = (0, import_react9.useMemo)(
2002
+ const selectionEdgeVisibility = (0, import_react10.useMemo)(
1491
2003
  () => getEdgeVisibilityForRects(selectedRects),
1492
2004
  [selectedRects]
1493
2005
  );
@@ -1496,7 +2008,7 @@ var useMeasurerDerived = ({
1496
2008
  selectedRects.length
1497
2009
  );
1498
2010
  const hoverEdgeVisibility = hoverRectToShow ? { top: true, right: true, bottom: true, left: true } : null;
1499
- const measurementEdgeVisibility = (0, import_react9.useMemo)(
2011
+ const measurementEdgeVisibility = (0, import_react10.useMemo)(
1500
2012
  () => getEdgeVisibilityForRects(displayedMeasurements.map((m) => m.rect)),
1501
2013
  [displayedMeasurements]
1502
2014
  );
@@ -1524,7 +2036,7 @@ var useMeasurerDerived = ({
1524
2036
  };
1525
2037
 
1526
2038
  // hooks/use-measurer-history.ts
1527
- var import_react10 = require("react");
2039
+ var import_react11 = require("react");
1528
2040
  var HISTORY_LIMIT = 50;
1529
2041
  var useMeasurerHistory = ({
1530
2042
  toggles,
@@ -1570,10 +2082,10 @@ var useMeasurerHistory = ({
1570
2082
  setSelectedElement,
1571
2083
  clearSelectionRect
1572
2084
  } = transient;
1573
- const historyRef = (0, import_react10.useRef)([]);
1574
- const futureRef = (0, import_react10.useRef)([]);
1575
- const historySignatureRef = (0, import_react10.useRef)(null);
1576
- const captureSnapshot = (0, import_react10.useCallback)(() => {
2085
+ const historyRef = (0, import_react11.useRef)([]);
2086
+ const futureRef = (0, import_react11.useRef)([]);
2087
+ const historySignatureRef = (0, import_react11.useRef)(null);
2088
+ const captureSnapshot = (0, import_react11.useCallback)(() => {
1577
2089
  return {
1578
2090
  enabled,
1579
2091
  toolMode,
@@ -1600,7 +2112,7 @@ var useMeasurerHistory = ({
1600
2112
  selectedMeasurements,
1601
2113
  toolMode
1602
2114
  ]);
1603
- const getSnapshotSignature = (0, import_react10.useCallback)((snapshot) => {
2115
+ const getSnapshotSignature = (0, import_react11.useCallback)((snapshot) => {
1604
2116
  const serializeRect = (rect) => `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`;
1605
2117
  return [
1606
2118
  snapshot.enabled ? "1" : "0",
@@ -1616,7 +2128,7 @@ var useMeasurerHistory = ({
1616
2128
  snapshot.draggingGuideId ?? ""
1617
2129
  ].join("|");
1618
2130
  }, []);
1619
- const recordSnapshot = (0, import_react10.useCallback)(() => {
2131
+ const recordSnapshot = (0, import_react11.useCallback)(() => {
1620
2132
  const snapshot = captureSnapshot();
1621
2133
  const signature = getSnapshotSignature(snapshot);
1622
2134
  if (historySignatureRef.current === signature) return;
@@ -1627,7 +2139,7 @@ var useMeasurerHistory = ({
1627
2139
  historyRef.current.shift();
1628
2140
  }
1629
2141
  }, [captureSnapshot, getSnapshotSignature]);
1630
- const restoreSnapshot = (0, import_react10.useCallback)(
2142
+ const restoreSnapshot = (0, import_react11.useCallback)(
1631
2143
  (snapshot) => {
1632
2144
  setEnabled(snapshot.enabled);
1633
2145
  setToolMode(snapshot.toolMode);
@@ -1672,7 +2184,7 @@ var useMeasurerHistory = ({
1672
2184
  setHoverElement
1673
2185
  ]
1674
2186
  );
1675
- const undo = (0, import_react10.useCallback)(() => {
2187
+ const undo = (0, import_react11.useCallback)(() => {
1676
2188
  const previousSnapshot = historyRef.current.pop();
1677
2189
  if (!previousSnapshot) return;
1678
2190
  const currentSnapshot = captureSnapshot();
@@ -1683,7 +2195,7 @@ var useMeasurerHistory = ({
1683
2195
  historySignatureRef.current = null;
1684
2196
  restoreSnapshot(previousSnapshot);
1685
2197
  }, [captureSnapshot, restoreSnapshot]);
1686
- const redo = (0, import_react10.useCallback)(() => {
2198
+ const redo = (0, import_react11.useCallback)(() => {
1687
2199
  const nextSnapshot = futureRef.current.pop();
1688
2200
  if (!nextSnapshot) return;
1689
2201
  historyRef.current.push(captureSnapshot());
@@ -1693,7 +2205,7 @@ var useMeasurerHistory = ({
1693
2205
  historySignatureRef.current = null;
1694
2206
  restoreSnapshot(nextSnapshot);
1695
2207
  }, [captureSnapshot, restoreSnapshot]);
1696
- const setToolModeWithHistory = (0, import_react10.useCallback)(
2208
+ const setToolModeWithHistory = (0, import_react11.useCallback)(
1697
2209
  (value) => {
1698
2210
  setToolMode((prev) => {
1699
2211
  const next = typeof value === "function" ? value(prev) : value;
@@ -1704,7 +2216,7 @@ var useMeasurerHistory = ({
1704
2216
  },
1705
2217
  [recordSnapshot, setToolMode]
1706
2218
  );
1707
- const setGuideOrientationWithHistory = (0, import_react10.useCallback)(
2219
+ const setGuideOrientationWithHistory = (0, import_react11.useCallback)(
1708
2220
  (value) => {
1709
2221
  setGuideOrientation((prev) => {
1710
2222
  const next = typeof value === "function" ? value(prev) : value;
@@ -1715,7 +2227,7 @@ var useMeasurerHistory = ({
1715
2227
  },
1716
2228
  [recordSnapshot, setGuideOrientation]
1717
2229
  );
1718
- const setEnabledWithHistory = (0, import_react10.useCallback)(
2230
+ const setEnabledWithHistory = (0, import_react11.useCallback)(
1719
2231
  (value) => {
1720
2232
  setEnabled((prev) => {
1721
2233
  const next = typeof value === "function" ? value(prev) : value;
@@ -1726,7 +2238,7 @@ var useMeasurerHistory = ({
1726
2238
  },
1727
2239
  [recordSnapshot, setEnabled]
1728
2240
  );
1729
- const createActionCommit = (0, import_react10.useCallback)(() => {
2241
+ const createActionCommit = (0, import_react11.useCallback)(() => {
1730
2242
  let committed = false;
1731
2243
  return () => {
1732
2244
  if (committed) return;
@@ -1734,64 +2246,47 @@ var useMeasurerHistory = ({
1734
2246
  committed = true;
1735
2247
  };
1736
2248
  }, [recordSnapshot]);
1737
- (0, import_react10.useEffect)(() => {
1738
- const handleKeyDown = (event) => {
1739
- if (!(event.metaKey || event.ctrlKey)) return;
1740
- if (event.key.toLowerCase() !== "z") return;
1741
- if (event.shiftKey) {
1742
- if (futureRef.current.length === 0) return;
1743
- event.preventDefault();
1744
- redo();
1745
- return;
1746
- }
1747
- if (historyRef.current.length === 0) return;
1748
- event.preventDefault();
1749
- undo();
1750
- };
1751
- window.addEventListener("keydown", handleKeyDown);
1752
- return () => {
1753
- window.removeEventListener("keydown", handleKeyDown);
1754
- };
1755
- }, [redo, undo]);
1756
2249
  return {
1757
2250
  recordSnapshot,
1758
2251
  createActionCommit,
1759
2252
  setToolModeWithHistory,
1760
2253
  setGuideOrientationWithHistory,
1761
- setEnabledWithHistory
2254
+ setEnabledWithHistory,
2255
+ undo,
2256
+ redo
1762
2257
  };
1763
2258
  };
1764
2259
 
1765
2260
  // hooks/use-measurer-local-state.ts
1766
- var import_react11 = require("react");
2261
+ var import_react12 = require("react");
1767
2262
  var useMeasurerLocalState = ({
1768
2263
  selectedElementRef,
1769
2264
  hoverElementRef,
1770
2265
  selectionRectRef
1771
2266
  }) => {
1772
- const [selectionOriginRect, setSelectionOriginRect] = (0, import_react11.useState)(
2267
+ const [selectionOriginRect, setSelectionOriginRect] = (0, import_react12.useState)(
1773
2268
  null
1774
2269
  );
1775
- const [hoverPointer, setHoverPointer] = (0, import_react11.useState)(null);
1776
- const [hoverElement, setHoverElementState] = (0, import_react11.useState)(
2270
+ const [hoverPointer, setHoverPointer] = (0, import_react12.useState)(null);
2271
+ const [hoverElement, setHoverElementState] = (0, import_react12.useState)(
1777
2272
  null
1778
2273
  );
1779
- const [selectedElement, setSelectedElementState] = (0, import_react11.useState)(null);
1780
- const setSelectedElement = (0, import_react11.useCallback)(
2274
+ const [selectedElement, setSelectedElementState] = (0, import_react12.useState)(null);
2275
+ const setSelectedElement = (0, import_react12.useCallback)(
1781
2276
  (element) => {
1782
2277
  selectedElementRef.current = element;
1783
2278
  setSelectedElementState(element);
1784
2279
  },
1785
2280
  [selectedElementRef]
1786
2281
  );
1787
- const setHoverElement = (0, import_react11.useCallback)(
2282
+ const setHoverElement = (0, import_react12.useCallback)(
1788
2283
  (element) => {
1789
2284
  hoverElementRef.current = element;
1790
2285
  setHoverElementState(element);
1791
2286
  },
1792
2287
  [hoverElementRef]
1793
2288
  );
1794
- const clearSelectionRect = (0, import_react11.useCallback)(() => {
2289
+ const clearSelectionRect = (0, import_react12.useCallback)(() => {
1795
2290
  selectionRectRef.current = null;
1796
2291
  setSelectionOriginRect(null);
1797
2292
  }, [selectionRectRef]);
@@ -1809,9 +2304,9 @@ var useMeasurerLocalState = ({
1809
2304
  };
1810
2305
 
1811
2306
  // hooks/use-measurer-pointer.ts
1812
- var import_react12 = require("react");
2307
+ var import_react13 = require("react");
1813
2308
 
1814
- // targets.ts
2309
+ // core/targets.ts
1815
2310
  var pickMultiTargets = (selectionRect, items) => {
1816
2311
  const candidates = items.map(({ element, rect }) => {
1817
2312
  const overlapArea = intersectionArea(selectionRect, rect);
@@ -1876,28 +2371,39 @@ var pickPointTarget = (point, items) => {
1876
2371
  return scored[0].element;
1877
2372
  };
1878
2373
 
1879
- // selection.ts
2374
+ // core/selection.ts
1880
2375
  var getOverlayHost2 = (overlayNode) => {
1881
2376
  if (!overlayNode) return null;
1882
2377
  const rootNode = overlayNode.getRootNode();
1883
- return rootNode instanceof ShadowRoot ? rootNode.host : null;
2378
+ return rootNode.nodeType === 11 ? rootNode.host : null;
1884
2379
  };
1885
2380
  var isOverlayElement = (element, overlayNode, overlayHost) => {
1886
2381
  if (overlayNode && overlayNode.contains(element)) return true;
1887
2382
  if (overlayHost && element === overlayHost) return true;
1888
2383
  return false;
1889
2384
  };
1890
- var getTargetElement = (point, overlayNode) => {
2385
+ var getDeepestElementAt = (element, point) => {
2386
+ let current = element;
2387
+ const HTMLElementConstructor = element.ownerDocument.defaultView?.HTMLElement ?? HTMLElement;
2388
+ while (current instanceof HTMLElementConstructor && current.shadowRoot) {
2389
+ const nested = current.shadowRoot.elementFromPoint(point.x, point.y);
2390
+ if (!nested || nested === current) break;
2391
+ current = nested;
2392
+ }
2393
+ return current;
2394
+ };
2395
+ var getTargetElement = (point, overlayNode, ownerDocument = document) => {
1891
2396
  const overlayHost = getOverlayHost2(overlayNode);
1892
2397
  if (overlayNode) {
1893
2398
  const previous = overlayNode.style.pointerEvents;
1894
2399
  overlayNode.style.pointerEvents = "none";
1895
- const elements2 = document.elementsFromPoint(point.x, point.y);
2400
+ const elements2 = ownerDocument.elementsFromPoint(point.x, point.y);
1896
2401
  overlayNode.style.pointerEvents = previous;
1897
- for (const element of elements2) {
1898
- if (!(element instanceof HTMLElement)) continue;
2402
+ for (const rawElement of elements2) {
2403
+ const element = getDeepestElementAt(rawElement, point);
2404
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
1899
2405
  if (isOverlayElement(element, overlayNode, overlayHost)) continue;
1900
- if (element === document.body || element === document.documentElement)
2406
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1901
2407
  continue;
1902
2408
  const rect = element.getBoundingClientRect();
1903
2409
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1905,11 +2411,12 @@ var getTargetElement = (point, overlayNode) => {
1905
2411
  }
1906
2412
  return null;
1907
2413
  }
1908
- const elements = document.elementsFromPoint(point.x, point.y);
1909
- for (const element of elements) {
1910
- if (!(element instanceof HTMLElement)) continue;
2414
+ const elements = ownerDocument.elementsFromPoint(point.x, point.y);
2415
+ for (const rawElement of elements) {
2416
+ const element = getDeepestElementAt(rawElement, point);
2417
+ if (!(element instanceof (ownerDocument.defaultView?.HTMLElement ?? HTMLElement))) continue;
1911
2418
  if (isOverlayElement(element, overlayNode, overlayHost)) continue;
1912
- if (element === document.body || element === document.documentElement)
2419
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1913
2420
  continue;
1914
2421
  const rect = element.getBoundingClientRect();
1915
2422
  if (rect.width <= 2 || rect.height <= 2) continue;
@@ -1917,34 +2424,34 @@ var getTargetElement = (point, overlayNode) => {
1917
2424
  }
1918
2425
  return null;
1919
2426
  };
1920
- var getSnappedClickTarget = (point, overlayNode, snapEnabled) => {
1921
- if (!snapEnabled) return getTargetElement(point, overlayNode);
2427
+ var getSnappedClickTarget = (point, overlayNode, snapEnabled, ownerDocument = document) => {
2428
+ if (!snapEnabled) return getTargetElement(point, overlayNode, ownerDocument);
1922
2429
  const probeRect = {
1923
2430
  left: point.x - 20,
1924
2431
  top: point.y - 20,
1925
2432
  width: 40,
1926
2433
  height: 40
1927
2434
  };
1928
- const entries = getSelectionEntries(probeRect, overlayNode);
1929
- return pickPointTarget(point, entries) ?? pickSingleTarget(probeRect, point, entries) ?? getTargetElement(point, overlayNode);
2435
+ const entries = getSelectionEntries(probeRect, overlayNode, ownerDocument);
2436
+ return pickPointTarget(point, entries) ?? pickSingleTarget(probeRect, point, entries) ?? getTargetElement(point, overlayNode, ownerDocument);
1930
2437
  };
1931
- var getSelectionEntries = (rect, overlayNode) => {
2438
+ var getSelectionEntries = (rect, overlayNode, ownerDocument = document) => {
1932
2439
  const overlayHost = getOverlayHost2(overlayNode);
1933
2440
  const frame = getFrameToken();
1934
2441
  const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(
1935
2442
  rect.width
1936
2443
  )}:${Math.round(rect.height)}`;
1937
- if (frame === cachedSelectionFrame && cachedSelectionKey === key && cachedOverlayNode === overlayNode) {
2444
+ if (frame === cachedSelectionFrame && cachedSelectionKey === key && cachedOverlayNode === overlayNode && cachedSelectionDocument === ownerDocument) {
1938
2445
  return cachedSelectionEntries;
1939
2446
  }
1940
2447
  const minLeft = rect.left - 1;
1941
2448
  const minTop = rect.top - 1;
1942
2449
  const maxRight = rect.left + rect.width + 1;
1943
2450
  const maxBottom = rect.top + rect.height + 1;
1944
- const elements = getBodyElementsCached();
2451
+ const elements = getBodyElementsCached(ownerDocument);
1945
2452
  const entries = elements.map((element) => ({ element, rect: getRectFromDomCached(element) })).filter(({ element, rect: elementRect }) => {
1946
2453
  if (isOverlayElement(element, overlayNode, overlayHost)) return false;
1947
- if (element === document.body || element === document.documentElement)
2454
+ if (element === ownerDocument.body || element === ownerDocument.documentElement)
1948
2455
  return false;
1949
2456
  if (elementRect.width < MIN_MULTI_TARGET_SIZE || elementRect.height < MIN_MULTI_TARGET_SIZE) {
1950
2457
  return false;
@@ -1958,6 +2465,7 @@ var getSelectionEntries = (rect, overlayNode) => {
1958
2465
  cachedSelectionFrame = frame;
1959
2466
  cachedSelectionKey = key;
1960
2467
  cachedOverlayNode = overlayNode;
2468
+ cachedSelectionDocument = ownerDocument;
1961
2469
  cachedSelectionEntries = entries;
1962
2470
  return entries;
1963
2471
  };
@@ -1965,30 +2473,33 @@ var cachedSelectionFrame = -1;
1965
2473
  var cachedSelectionKey = "";
1966
2474
  var cachedSelectionEntries = [];
1967
2475
  var cachedOverlayNode = null;
2476
+ var cachedSelectionDocument = null;
1968
2477
  var getSelectionCacheKey = (rect) => `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(
1969
2478
  rect.width
1970
2479
  )}:${Math.round(rect.height)}`;
1971
- var getSelectionEntriesCached = (rect, overlayNode, cache) => {
2480
+ var getSelectionEntriesCached = (rect, overlayNode, cache, ownerDocument = document) => {
1972
2481
  const frame = getFrameToken();
1973
2482
  const key = getSelectionCacheKey(rect);
1974
2483
  if (cache.key === key && cache.overlayNode === overlayNode && cache.frame === frame) {
1975
2484
  return cache.entries;
1976
2485
  }
1977
- const entries = getSelectionEntries(rect, overlayNode);
2486
+ const entries = getSelectionEntries(rect, overlayNode, ownerDocument);
1978
2487
  cache.key = key;
1979
2488
  cache.overlayNode = overlayNode;
1980
2489
  cache.frame = frame;
1981
2490
  cache.entries = entries;
1982
2491
  return entries;
1983
2492
  };
1984
- var getElementsInRectCached = (rect, overlayNode, cache) => {
1985
- const entries = getSelectionEntriesCached(rect, overlayNode, cache);
2493
+ var getElementsInRectCached = (rect, overlayNode, cache, ownerDocument = document) => {
2494
+ const entries = getSelectionEntriesCached(rect, overlayNode, cache, ownerDocument);
1986
2495
  if (entries.length === 0) return [];
1987
2496
  return pickMultiTargets(rect, entries);
1988
2497
  };
1989
2498
 
1990
2499
  // hooks/use-measurer-pointer.ts
1991
2500
  var useMeasurerPointer = ({
2501
+ document: document2,
2502
+ window: window2,
1992
2503
  toolbarRef,
1993
2504
  overlayRef,
1994
2505
  selectionRectRef,
@@ -2032,46 +2543,26 @@ var useMeasurerPointer = ({
2032
2543
  setHoverPointer,
2033
2544
  clearSelectionRect
2034
2545
  }) => {
2035
- const hoverFrameRef = (0, import_react12.useRef)(null);
2036
- const hoverPointRef = (0, import_react12.useRef)(null);
2037
- const selectionCacheRef = (0, import_react12.useRef)({
2546
+ const hoverFrameRef = (0, import_react13.useRef)(null);
2547
+ const hoverPointRef = (0, import_react13.useRef)(null);
2548
+ const selectionCacheRef = (0, import_react13.useRef)({
2038
2549
  key: "",
2039
2550
  entries: [],
2040
2551
  overlayNode: null,
2041
2552
  frame: -1
2042
2553
  });
2043
- const shiftDragRef = (0, import_react12.useRef)(false);
2044
- const shiftToggleElementRef = (0, import_react12.useRef)(null);
2045
- const guidesEnabledRef = (0, import_react12.useRef)(guidesEnabled);
2046
- const guidesRef = (0, import_react12.useRef)(guides);
2047
- const snapGuidesEnabledRef = (0, import_react12.useRef)(snapGuidesEnabled);
2048
- const draggingGuideIdRef = (0, import_react12.useRef)(draggingGuideId);
2049
- const guideOrientationRef = (0, import_react12.useRef)(guideOrientation);
2050
- (0, import_react12.useEffect)(() => {
2554
+ const shiftDragRef = (0, import_react13.useRef)(false);
2555
+ const shiftToggleElementRef = (0, import_react13.useRef)(null);
2556
+ (0, import_react13.useEffect)(() => {
2051
2557
  return () => {
2052
2558
  if (hoverFrameRef.current) {
2053
- cancelAnimationFrame(hoverFrameRef.current);
2559
+ window2.cancelAnimationFrame(hoverFrameRef.current);
2054
2560
  }
2055
2561
  };
2056
2562
  }, []);
2057
- (0, import_react12.useEffect)(() => {
2058
- guidesEnabledRef.current = guidesEnabled;
2059
- }, [guidesEnabled]);
2060
- (0, import_react12.useEffect)(() => {
2061
- guidesRef.current = guides;
2062
- }, [guides]);
2063
- (0, import_react12.useEffect)(() => {
2064
- snapGuidesEnabledRef.current = snapGuidesEnabled;
2065
- }, [snapGuidesEnabled]);
2066
- (0, import_react12.useEffect)(() => {
2067
- guideOrientationRef.current = guideOrientation;
2068
- }, [guideOrientation]);
2069
- (0, import_react12.useEffect)(() => {
2070
- draggingGuideIdRef.current = draggingGuideId;
2071
- }, [draggingGuideId]);
2072
- const updateHoverTarget = (0, import_react12.useCallback)(
2563
+ const updateHoverTarget = (0, import_react13.useCallback)(
2073
2564
  (point) => {
2074
- const target = getTargetElement(point, overlayRef.current);
2565
+ const target = getTargetElement(point, overlayRef.current, document2);
2075
2566
  if (target) {
2076
2567
  const rect = target.getBoundingClientRect();
2077
2568
  setHoverRect({
@@ -2086,16 +2577,16 @@ var useMeasurerPointer = ({
2086
2577
  setHoverElement(null);
2087
2578
  }
2088
2579
  },
2089
- [overlayRef, setHoverElement, setHoverRect]
2580
+ [document2, overlayRef, setHoverElement, setHoverRect]
2090
2581
  );
2091
- const updateHoverElement = (0, import_react12.useCallback)(
2582
+ const updateHoverElement = (0, import_react13.useCallback)(
2092
2583
  (point) => {
2093
- const target = getTargetElement(point, overlayRef.current);
2584
+ const target = getTargetElement(point, overlayRef.current, document2);
2094
2585
  setHoverElement(target);
2095
2586
  },
2096
- [overlayRef, setHoverElement]
2587
+ [document2, overlayRef, setHoverElement]
2097
2588
  );
2098
- const handlePointerDown = (0, import_react12.useCallback)(
2589
+ const handlePointerDown = (0, import_react13.useCallback)(
2099
2590
  (event) => {
2100
2591
  const commit = createActionCommit();
2101
2592
  const toolbarNode = toolbarRef.current;
@@ -2108,7 +2599,8 @@ var useMeasurerPointer = ({
2108
2599
  shiftToggleElementRef.current = event.shiftKey ? getSelectedMeasurementHit({
2109
2600
  point,
2110
2601
  selectedMeasurements,
2111
- overlayNode: overlayRef.current
2602
+ overlayNode: overlayRef.current,
2603
+ document: document2
2112
2604
  })?.elementRef ?? null : null;
2113
2605
  selectionCacheRef.current.key = "";
2114
2606
  if (altPressed && optionPairOverlay) {
@@ -2130,10 +2622,11 @@ var useMeasurerPointer = ({
2130
2622
  snapGuidesEnabled,
2131
2623
  overlayNode: overlayRef.current,
2132
2624
  guides,
2133
- draggingGuideId
2625
+ draggingGuideId,
2626
+ document: document2
2134
2627
  });
2135
2628
  const id = createId();
2136
- setSelectedGuideIds([]);
2629
+ setSelectedGuideIds([id]);
2137
2630
  setGuides((prev) => [
2138
2631
  ...prev,
2139
2632
  { id, orientation: guideOrientation, position }
@@ -2156,6 +2649,7 @@ var useMeasurerPointer = ({
2156
2649
  clearSelectionRect,
2157
2650
  createActionCommit,
2158
2651
  draggingGuideId,
2652
+ document2,
2159
2653
  enabled,
2160
2654
  guideOrientation,
2161
2655
  guides,
@@ -2177,7 +2671,7 @@ var useMeasurerPointer = ({
2177
2671
  toolbarRef
2178
2672
  ]
2179
2673
  );
2180
- const handlePointerMove = (0, import_react12.useCallback)(
2674
+ const handlePointerMove = (0, import_react13.useCallback)(
2181
2675
  (event) => {
2182
2676
  const toolbarNode = toolbarRef.current;
2183
2677
  if (toolbarNode && toolbarNode.contains(event.target)) return;
@@ -2197,31 +2691,32 @@ var useMeasurerPointer = ({
2197
2691
  }
2198
2692
  hoverPointRef.current = point;
2199
2693
  if (!hoverFrameRef.current) {
2200
- hoverFrameRef.current = requestAnimationFrame(() => {
2694
+ hoverFrameRef.current = window2.requestAnimationFrame(() => {
2201
2695
  const latest = hoverPointRef.current;
2202
- if (latest && !draggingGuideIdRef.current) {
2696
+ if (latest && !draggingGuideId) {
2203
2697
  if (hoverHighlightEnabled) {
2204
2698
  updateHoverTarget(latest);
2205
2699
  } else {
2206
2700
  updateHoverElement(latest);
2207
2701
  }
2208
2702
  }
2209
- if (latest && guidesRef.current.length > 0) {
2703
+ if (latest && guides.length > 0) {
2210
2704
  setHoverPointer(latest);
2211
2705
  } else {
2212
2706
  setHoverPointer(null);
2213
2707
  }
2214
- if (guidesEnabledRef.current && latest && !draggingGuideIdRef.current) {
2708
+ if (guidesEnabled && latest && !draggingGuideId) {
2215
2709
  const position = getSnapGuidePosition({
2216
- orientation: guideOrientationRef.current,
2710
+ orientation: guideOrientation,
2217
2711
  point: latest,
2218
- snapGuidesEnabled: snapGuidesEnabledRef.current,
2712
+ snapGuidesEnabled,
2219
2713
  overlayNode: overlayRef.current,
2220
- guides: guidesRef.current,
2221
- draggingGuideId: draggingGuideIdRef.current
2714
+ guides,
2715
+ draggingGuideId,
2716
+ document: document2
2222
2717
  });
2223
2718
  setGuidePreview({
2224
- orientation: guideOrientationRef.current,
2719
+ orientation: guideOrientation,
2225
2720
  position
2226
2721
  });
2227
2722
  } else {
@@ -2238,10 +2733,11 @@ var useMeasurerPointer = ({
2238
2733
  position: getSnapGuidePosition({
2239
2734
  orientation: guide.orientation,
2240
2735
  point,
2241
- snapGuidesEnabled: snapGuidesEnabledRef.current,
2736
+ snapGuidesEnabled,
2242
2737
  overlayNode: overlayRef.current,
2243
- guides: guidesRef.current,
2244
- draggingGuideId: draggingGuideIdRef.current
2738
+ guides,
2739
+ draggingGuideId,
2740
+ document: document2
2245
2741
  })
2246
2742
  } : guide
2247
2743
  )
@@ -2263,8 +2759,11 @@ var useMeasurerPointer = ({
2263
2759
  draggingGuideId,
2264
2760
  enabled,
2265
2761
  hoverHighlightEnabled,
2762
+ guides,
2763
+ guidesEnabled,
2266
2764
  isDragging,
2267
2765
  overlayRef,
2766
+ guideOrientation,
2268
2767
  setAltPressed,
2269
2768
  setEnd,
2270
2769
  setGuidePreview,
@@ -2273,6 +2772,7 @@ var useMeasurerPointer = ({
2273
2772
  setHoverPointer,
2274
2773
  setHoverRect,
2275
2774
  setIsDragging,
2775
+ snapGuidesEnabled,
2276
2776
  start,
2277
2777
  toolMode,
2278
2778
  toolbarRef,
@@ -2280,7 +2780,7 @@ var useMeasurerPointer = ({
2280
2780
  updateHoverTarget
2281
2781
  ]
2282
2782
  );
2283
- const handlePointerUp = (0, import_react12.useCallback)(
2783
+ const handlePointerUp = (0, import_react13.useCallback)(
2284
2784
  (event) => {
2285
2785
  const commit = createActionCommit();
2286
2786
  const toolbarNode = toolbarRef.current;
@@ -2326,7 +2826,8 @@ var useMeasurerPointer = ({
2326
2826
  const elements = getElementsInRectCached(
2327
2827
  selectionRect,
2328
2828
  overlayRef.current,
2329
- selectionCacheRef.current
2829
+ selectionCacheRef.current,
2830
+ document2
2330
2831
  );
2331
2832
  const hasSameSelection = elements.length === selectedMeasurements.length && elements.every(
2332
2833
  (element, index) => selectedMeasurements[index]?.elementRef === element
@@ -2337,7 +2838,7 @@ var useMeasurerPointer = ({
2337
2838
  if (!hasSameSelection) {
2338
2839
  commit();
2339
2840
  const nextMeasurements = elements.map((element) => ({
2340
- ...getInspectMeasurement(element),
2841
+ ...getInspectMeasurement(element, window2),
2341
2842
  originRect: selectionRect
2342
2843
  }));
2343
2844
  setSelectedMeasurements(nextMeasurements);
@@ -2371,7 +2872,8 @@ var useMeasurerPointer = ({
2371
2872
  ) ?? null : getSelectedMeasurementHit({
2372
2873
  point,
2373
2874
  selectedMeasurements,
2374
- overlayNode: overlayRef.current
2875
+ overlayNode: overlayRef.current,
2876
+ document: document2
2375
2877
  });
2376
2878
  if (event.shiftKey && selectedHit) {
2377
2879
  commit();
@@ -2401,9 +2903,9 @@ var useMeasurerPointer = ({
2401
2903
  resetDragState();
2402
2904
  return;
2403
2905
  }
2404
- const target = event.shiftKey ? getTargetElement(point, overlayRef.current) ?? getSnappedClickTarget(point, overlayRef.current, snapEnabled) : getSnappedClickTarget(point, overlayRef.current, snapEnabled);
2906
+ const target = event.shiftKey ? getTargetElement(point, overlayRef.current, document2) ?? getSnappedClickTarget(point, overlayRef.current, snapEnabled, document2) : getSnappedClickTarget(point, overlayRef.current, snapEnabled, document2);
2405
2907
  if (target) {
2406
- const inspectMeasurement = getInspectMeasurement(target);
2908
+ const inspectMeasurement = getInspectMeasurement(target, window2);
2407
2909
  clearTransientMeasurements();
2408
2910
  if (event.shiftKey) {
2409
2911
  const alreadySelected = selectedMeasurements.some(
@@ -2478,7 +2980,7 @@ var useMeasurerPointer = ({
2478
2980
  toolbarRef
2479
2981
  ]
2480
2982
  );
2481
- const handlePointerLeave = (0, import_react12.useCallback)(() => {
2983
+ const handlePointerLeave = (0, import_react13.useCallback)(() => {
2482
2984
  clearGuideDragHold();
2483
2985
  setStart(null);
2484
2986
  setEnd(null);
@@ -2502,41 +3004,48 @@ var useMeasurerPointer = ({
2502
3004
  };
2503
3005
 
2504
3006
  // hooks/use-overlay-refs.ts
2505
- var import_react13 = require("react");
3007
+ var import_react14 = require("react");
2506
3008
  var useOverlayRefs = () => {
2507
- const overlayRef = (0, import_react13.useRef)(null);
2508
- const selectedElementRef = (0, import_react13.useRef)(null);
2509
- const hoverElementRef = (0, import_react13.useRef)(null);
3009
+ const overlayRef = (0, import_react14.useRef)(null);
3010
+ const selectedElementRef = (0, import_react14.useRef)(null);
3011
+ const hoverElementRef = (0, import_react14.useRef)(null);
2510
3012
  return { overlayRef, selectedElementRef, hoverElementRef };
2511
3013
  };
2512
3014
 
2513
3015
  // hooks/use-resize-sync.ts
2514
- var import_react14 = require("react");
3016
+ var import_react15 = require("react");
2515
3017
  var useResizeSync = (params) => {
2516
- const resizeFrameRef = (0, import_react14.useRef)(null);
2517
- const viewportRef = (0, import_react14.useRef)(getViewportSize());
2518
- (0, import_react14.useEffect)(() => {
3018
+ const resizeFrameRef = (0, import_react15.useRef)(null);
3019
+ const viewportRef = (0, import_react15.useRef)(getViewportSize(params.window));
3020
+ (0, import_react15.useEffect)(() => {
2519
3021
  const handleResize = () => {
2520
3022
  if (resizeFrameRef.current) {
2521
- cancelAnimationFrame(resizeFrameRef.current);
3023
+ params.window.cancelAnimationFrame(resizeFrameRef.current);
2522
3024
  }
2523
- resizeFrameRef.current = requestAnimationFrame(() => {
2524
- const viewport = getViewportSize();
3025
+ resizeFrameRef.current = params.window.requestAnimationFrame(() => {
3026
+ const viewport = getViewportSize(params.window);
2525
3027
  const previousViewport = viewportRef.current;
2526
3028
  params.setMeasurements(
2527
3029
  (prev) => prev.map(
2528
- (measurement) => updateMeasurementForResize(measurement, viewport)
3030
+ (measurement) => updateMeasurementForResize(measurement, viewport, params.document)
2529
3031
  )
2530
3032
  );
2531
3033
  params.setActiveMeasurement(
2532
- (prev) => prev ? updateMeasurementForResize(prev, viewport) : prev
3034
+ (prev) => prev ? updateMeasurementForResize(prev, viewport, params.document) : prev
2533
3035
  );
2534
3036
  params.setHeldDistances(
2535
- (prev) => prev.map((distance) => updateDistanceForResize(distance, viewport))
3037
+ (prev) => prev.map(
3038
+ (distance) => updateDistanceForResize(
3039
+ distance,
3040
+ viewport,
3041
+ params.document,
3042
+ params.window
3043
+ )
3044
+ )
2536
3045
  );
2537
- if (params.selectedElementRef.current) {
3046
+ if (params.selectedElementRef.current && params.document.contains(params.selectedElementRef.current)) {
2538
3047
  params.setSelectedMeasurement(
2539
- getInspectMeasurement(params.selectedElementRef.current)
3048
+ getInspectMeasurement(params.selectedElementRef.current, params.window)
2540
3049
  );
2541
3050
  }
2542
3051
  if (previousViewport.width > 0 && previousViewport.height > 0) {
@@ -2551,27 +3060,27 @@ var useResizeSync = (params) => {
2551
3060
  viewportRef.current = viewport;
2552
3061
  });
2553
3062
  };
2554
- window.addEventListener("resize", handleResize);
3063
+ params.window.addEventListener("resize", handleResize);
2555
3064
  return () => {
2556
3065
  if (resizeFrameRef.current) {
2557
- cancelAnimationFrame(resizeFrameRef.current);
3066
+ params.window.cancelAnimationFrame(resizeFrameRef.current);
2558
3067
  }
2559
- window.removeEventListener("resize", handleResize);
3068
+ params.window.removeEventListener("resize", handleResize);
2560
3069
  };
2561
3070
  }, [params]);
2562
3071
  };
2563
3072
 
2564
3073
  // render/measurer-overlay.tsx
2565
- var import_react18 = require("react");
3074
+ var import_react19 = require("react");
2566
3075
 
2567
3076
  // components/measure-tag.tsx
2568
- var import_jsx_runtime3 = require("react/jsx-runtime");
3077
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2569
3078
  function MeasureTag({
2570
3079
  children,
2571
3080
  className = "",
2572
3081
  style
2573
3082
  }) {
2574
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3083
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2575
3084
  "div",
2576
3085
  {
2577
3086
  className: cn(
@@ -2585,31 +3094,21 @@ function MeasureTag({
2585
3094
  }
2586
3095
 
2587
3096
  // components/measurement-box.tsx
2588
- var import_react15 = require("react");
2589
- var import_jsx_runtime4 = require("react/jsx-runtime");
3097
+ var import_react16 = require("react");
3098
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2590
3099
  var formatValue2 = (value) => Math.round(value);
2591
- var MeasurementBox = (0, import_react15.memo)(function MeasurementBox2({
3100
+ var MeasurementBox = (0, import_react16.memo)(function MeasurementBox2({
2592
3101
  measurement,
2593
3102
  transitionMs,
2594
3103
  labelOffset,
2595
3104
  edgeVisibility
2596
3105
  }) {
2597
- const [rect, setRect] = (0, import_react15.useState)(
2598
- measurement.originRect ?? measurement.rect
2599
- );
2600
- (0, import_react15.useEffect)(() => {
2601
- if (!measurement.originRect) return;
2602
- const frame = requestAnimationFrame(() => {
2603
- setRect(measurement.rect);
2604
- });
2605
- return () => cancelAnimationFrame(frame);
2606
- }, [measurement]);
2607
3106
  const edges = edgeVisibility ?? { top: true, right: true, bottom: true, left: true };
2608
- const displayRect = measurement.originRect ? rect : measurement.rect;
3107
+ const displayRect = measurement.rect;
2609
3108
  const outlineColor = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
2610
3109
  const fillColor = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
2611
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "msr:pointer-events-none", children: [
2612
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3110
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "msr:pointer-events-none", children: [
3111
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2613
3112
  "div",
2614
3113
  {
2615
3114
  className: "msr:absolute",
@@ -2619,31 +3118,31 @@ var MeasurementBox = (0, import_react15.memo)(function MeasurementBox2({
2619
3118
  width: displayRect.width,
2620
3119
  height: displayRect.height,
2621
3120
  backgroundColor: fillColor,
2622
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease` : void 0
3121
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease`
2623
3122
  },
2624
3123
  children: [
2625
- edges.top ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3124
+ edges.top ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2626
3125
  "div",
2627
3126
  {
2628
3127
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2629
3128
  style: { backgroundColor: outlineColor }
2630
3129
  }
2631
3130
  ) : null,
2632
- edges.right ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3131
+ edges.right ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2633
3132
  "div",
2634
3133
  {
2635
3134
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2636
3135
  style: { backgroundColor: outlineColor }
2637
3136
  }
2638
3137
  ) : null,
2639
- edges.bottom ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3138
+ edges.bottom ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2640
3139
  "div",
2641
3140
  {
2642
3141
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2643
3142
  style: { backgroundColor: outlineColor }
2644
3143
  }
2645
3144
  ) : null,
2646
- edges.left ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3145
+ edges.left ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2647
3146
  "div",
2648
3147
  {
2649
3148
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2653,14 +3152,14 @@ var MeasurementBox = (0, import_react15.memo)(function MeasurementBox2({
2653
3152
  ]
2654
3153
  }
2655
3154
  ),
2656
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3155
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2657
3156
  MeasureTag,
2658
3157
  {
2659
3158
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
2660
3159
  style: {
2661
3160
  left: displayRect.left + displayRect.width / 2,
2662
3161
  top: displayRect.top + displayRect.height + labelOffset,
2663
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease` : void 0
3162
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease`
2664
3163
  },
2665
3164
  children: [
2666
3165
  formatValue2(displayRect.width),
@@ -2673,31 +3172,21 @@ var MeasurementBox = (0, import_react15.memo)(function MeasurementBox2({
2673
3172
  });
2674
3173
 
2675
3174
  // components/selected-measurement-box.tsx
2676
- var import_react16 = require("react");
2677
- var import_jsx_runtime5 = require("react/jsx-runtime");
3175
+ var import_react17 = require("react");
3176
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2678
3177
  var formatValue3 = (value) => Math.round(value);
2679
- var SelectedMeasurementBox = (0, import_react16.memo)(function SelectedMeasurementBox2({
3178
+ var SelectedMeasurementBox = (0, import_react17.memo)(function SelectedMeasurementBox2({
2680
3179
  measurement,
2681
3180
  transitionMs,
2682
3181
  labelOffset,
2683
3182
  edgeVisibility
2684
3183
  }) {
2685
- const [rect, setRect] = (0, import_react16.useState)(
2686
- measurement.originRect ?? measurement.rect
2687
- );
2688
- (0, import_react16.useEffect)(() => {
2689
- if (!measurement.originRect) return;
2690
- const frame = requestAnimationFrame(() => {
2691
- setRect(measurement.rect);
2692
- });
2693
- return () => cancelAnimationFrame(frame);
2694
- }, [measurement]);
2695
3184
  const edges = edgeVisibility ?? { top: true, right: true, bottom: true, left: true };
2696
- const displayRect = measurement.originRect ? rect : measurement.rect;
3185
+ const displayRect = measurement.rect;
2697
3186
  const outlineColor = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
2698
3187
  const fillColor = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
2699
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "msr:pointer-events-none", children: [
2700
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3188
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "msr:pointer-events-none", children: [
3189
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2701
3190
  "div",
2702
3191
  {
2703
3192
  className: "msr:absolute",
@@ -2707,31 +3196,31 @@ var SelectedMeasurementBox = (0, import_react16.memo)(function SelectedMeasureme
2707
3196
  width: displayRect.width,
2708
3197
  height: displayRect.height,
2709
3198
  backgroundColor: fillColor,
2710
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease` : void 0
3199
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease, width ${transitionMs}ms ease, height ${transitionMs}ms ease`
2711
3200
  },
2712
3201
  children: [
2713
- edges.top ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3202
+ edges.top ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2714
3203
  "div",
2715
3204
  {
2716
3205
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2717
3206
  style: { backgroundColor: outlineColor }
2718
3207
  }
2719
3208
  ) : null,
2720
- edges.right ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3209
+ edges.right ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2721
3210
  "div",
2722
3211
  {
2723
3212
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2724
3213
  style: { backgroundColor: outlineColor }
2725
3214
  }
2726
3215
  ) : null,
2727
- edges.bottom ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3216
+ edges.bottom ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2728
3217
  "div",
2729
3218
  {
2730
3219
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2731
3220
  style: { backgroundColor: outlineColor }
2732
3221
  }
2733
3222
  ) : null,
2734
- edges.left ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3223
+ edges.left ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2735
3224
  "div",
2736
3225
  {
2737
3226
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2741,14 +3230,14 @@ var SelectedMeasurementBox = (0, import_react16.memo)(function SelectedMeasureme
2741
3230
  ]
2742
3231
  }
2743
3232
  ),
2744
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3233
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2745
3234
  MeasureTag,
2746
3235
  {
2747
3236
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
2748
3237
  style: {
2749
3238
  left: displayRect.left + displayRect.width / 2,
2750
3239
  top: displayRect.top + displayRect.height + labelOffset,
2751
- transition: measurement.originRect ? `left ${transitionMs}ms ease, top ${transitionMs}ms ease` : void 0
3240
+ transition: `left ${transitionMs}ms ease, top ${transitionMs}ms ease`
2752
3241
  },
2753
3242
  children: [
2754
3243
  formatValue3(displayRect.width),
@@ -2761,14 +3250,14 @@ var SelectedMeasurementBox = (0, import_react16.memo)(function SelectedMeasureme
2761
3250
  });
2762
3251
 
2763
3252
  // render/distance-overlay-item.tsx
2764
- var import_react17 = require("react");
2765
- var import_jsx_runtime6 = require("react/jsx-runtime");
2766
- var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2({
3253
+ var import_react18 = require("react");
3254
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3255
+ var DistanceOverlayItem = (0, import_react18.memo)(function DistanceOverlayItem2({
2767
3256
  distance,
2768
3257
  labelOffset,
2769
3258
  onRemove
2770
3259
  }) {
2771
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3260
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2772
3261
  "div",
2773
3262
  {
2774
3263
  className: onRemove ? "msr:pointer-events-auto" : "msr:pointer-events-none",
@@ -2777,7 +3266,7 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2777
3266
  onRemove(distance.id);
2778
3267
  } : void 0,
2779
3268
  children: [
2780
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3269
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2781
3270
  "div",
2782
3271
  {
2783
3272
  className: "msr:absolute msr:rounded msr:border msr:border-[#2563eb]/70",
@@ -2789,7 +3278,7 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2789
3278
  }
2790
3279
  }
2791
3280
  ),
2792
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3281
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2793
3282
  "div",
2794
3283
  {
2795
3284
  className: "msr:absolute msr:rounded msr:border msr:border-[#2563eb]/70",
@@ -2802,7 +3291,7 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2802
3291
  }
2803
3292
  ),
2804
3293
  distance.connectors.map(
2805
- (connector, index) => Math.abs(connector.x1 - connector.x2) < 1 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3294
+ (connector, index) => Math.abs(connector.x1 - connector.x2) < 1 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2806
3295
  "div",
2807
3296
  {
2808
3297
  className: "msr:absolute msr:border-l msr:border-dashed msr:border-[#2563eb]/70",
@@ -2813,7 +3302,7 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2813
3302
  }
2814
3303
  },
2815
3304
  `${distance.id}-connector-${index}`
2816
- ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3305
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2817
3306
  "div",
2818
3307
  {
2819
3308
  className: "msr:absolute msr:border-t msr:border-dashed msr:border-[#2563eb]/70",
@@ -2826,8 +3315,8 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2826
3315
  `${distance.id}-connector-${index}`
2827
3316
  )
2828
3317
  ),
2829
- distance.horizontal && distance.horizontal.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2830
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3318
+ distance.horizontal && distance.horizontal.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3319
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2831
3320
  "div",
2832
3321
  {
2833
3322
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -2838,7 +3327,7 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2838
3327
  }
2839
3328
  }
2840
3329
  ),
2841
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3330
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2842
3331
  MeasureTag,
2843
3332
  {
2844
3333
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -2850,8 +3339,8 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2850
3339
  }
2851
3340
  )
2852
3341
  ] }) : null,
2853
- distance.vertical && distance.vertical.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2854
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3342
+ distance.vertical && distance.vertical.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3343
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2855
3344
  "div",
2856
3345
  {
2857
3346
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -2862,7 +3351,7 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2862
3351
  }
2863
3352
  }
2864
3353
  ),
2865
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3354
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2866
3355
  MeasureTag,
2867
3356
  {
2868
3357
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -2880,10 +3369,11 @@ var DistanceOverlayItem = (0, import_react17.memo)(function DistanceOverlayItem2
2880
3369
  });
2881
3370
 
2882
3371
  // render/measurer-overlay.tsx
2883
- var import_jsx_runtime7 = require("react/jsx-runtime");
2884
- var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3372
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3373
+ var MeasurerOverlay = (0, import_react19.memo)(function MeasurerOverlay2({
2885
3374
  enabled,
2886
3375
  toolMode,
3376
+ guidePointerEvents,
2887
3377
  guidesEnabled,
2888
3378
  altPressed,
2889
3379
  isDragging,
@@ -2920,16 +3410,20 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
2920
3410
  onGuidePointerUp,
2921
3411
  onGuidePointerCancel
2922
3412
  }) {
2923
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3413
+ const overlayVisible = enabled;
3414
+ const overlayInteractive = overlayVisible && toolMode !== "none" && toolMode !== "text-inspector" && toolMode !== "xray" && toolMode !== "rulers";
3415
+ const selectionVisible = toolMode === "select";
3416
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2924
3417
  "div",
2925
3418
  {
2926
- 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"}`,
3419
+ 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"}`,
3420
+ style: { pointerEvents: overlayInteractive ? "auto" : "none" },
2927
3421
  onPointerDown,
2928
3422
  onPointerMove,
2929
3423
  onPointerUp,
2930
3424
  onPointerLeave,
2931
3425
  children: [
2932
- !guidesEnabled ? displayedMeasurements.map((measurement, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3426
+ selectionVisible ? displayedMeasurements.map((measurement, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2933
3427
  MeasurementBox,
2934
3428
  {
2935
3429
  measurement,
@@ -2939,8 +3433,8 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
2939
3433
  },
2940
3434
  measurement.id
2941
3435
  )) : null,
2942
- !guidesEnabled && activeRect && isDragging ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2943
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3436
+ selectionVisible && activeRect && isDragging ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3437
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2944
3438
  "div",
2945
3439
  {
2946
3440
  className: "msr:pointer-events-none msr:absolute",
@@ -2952,28 +3446,28 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
2952
3446
  backgroundColor: fillColor
2953
3447
  },
2954
3448
  children: [
2955
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3449
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2956
3450
  "div",
2957
3451
  {
2958
3452
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
2959
3453
  style: { backgroundColor: outlineColor }
2960
3454
  }
2961
3455
  ),
2962
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3456
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2963
3457
  "div",
2964
3458
  {
2965
3459
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
2966
3460
  style: { backgroundColor: outlineColor }
2967
3461
  }
2968
3462
  ),
2969
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3463
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2970
3464
  "div",
2971
3465
  {
2972
3466
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
2973
3467
  style: { backgroundColor: outlineColor }
2974
3468
  }
2975
3469
  ),
2976
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3470
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2977
3471
  "div",
2978
3472
  {
2979
3473
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -2983,7 +3477,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
2983
3477
  ]
2984
3478
  }
2985
3479
  ),
2986
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3480
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2987
3481
  MeasureTag,
2988
3482
  {
2989
3483
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -2999,7 +3493,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
2999
3493
  }
3000
3494
  )
3001
3495
  ] }) : null,
3002
- !guidesEnabled && hoverRectToShow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3496
+ selectionVisible && hoverRectToShow ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3003
3497
  "div",
3004
3498
  {
3005
3499
  className: "msr:pointer-events-none msr:absolute",
@@ -3011,28 +3505,28 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3011
3505
  backgroundColor: fillColor
3012
3506
  },
3013
3507
  children: [
3014
- hoverEdgeVisibility?.top ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3508
+ hoverEdgeVisibility?.top ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3015
3509
  "div",
3016
3510
  {
3017
3511
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-px msr:w-full",
3018
3512
  style: { backgroundColor: outlineColor }
3019
3513
  }
3020
3514
  ) : null,
3021
- hoverEdgeVisibility?.right ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3515
+ hoverEdgeVisibility?.right ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3022
3516
  "div",
3023
3517
  {
3024
3518
  className: "msr:absolute msr:right-0 msr:top-0 msr:h-full msr:w-px",
3025
3519
  style: { backgroundColor: outlineColor }
3026
3520
  }
3027
3521
  ) : null,
3028
- hoverEdgeVisibility?.bottom ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3522
+ hoverEdgeVisibility?.bottom ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3029
3523
  "div",
3030
3524
  {
3031
3525
  className: "msr:absolute msr:bottom-0 msr:left-0 msr:h-px msr:w-full",
3032
3526
  style: { backgroundColor: outlineColor }
3033
3527
  }
3034
3528
  ) : null,
3035
- hoverEdgeVisibility?.left ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3529
+ hoverEdgeVisibility?.left ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3036
3530
  "div",
3037
3531
  {
3038
3532
  className: "msr:absolute msr:left-0 msr:top-0 msr:h-full msr:w-px",
@@ -3042,7 +3536,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3042
3536
  ]
3043
3537
  }
3044
3538
  ) : null,
3045
- guidesEnabled && guidePreview ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3539
+ guidesEnabled && guidePreview ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3046
3540
  "div",
3047
3541
  {
3048
3542
  className: "msr:pointer-events-none msr:absolute",
@@ -3057,7 +3551,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3057
3551
  height: GUIDE_HITBOX_SIZE,
3058
3552
  width: "100%"
3059
3553
  },
3060
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3554
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3061
3555
  "div",
3062
3556
  {
3063
3557
  className: "msr:absolute",
@@ -3078,7 +3572,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3078
3572
  )
3079
3573
  }
3080
3574
  ) : null,
3081
- !guidesEnabled ? displayedSelectedMeasurements.map((measurement, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3575
+ selectionVisible ? displayedSelectedMeasurements.map((measurement, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3082
3576
  SelectedMeasurementBox,
3083
3577
  {
3084
3578
  measurement,
@@ -3088,7 +3582,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3088
3582
  },
3089
3583
  measurement.id
3090
3584
  )) : null,
3091
- heldDistances.map((distance) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3585
+ heldDistances.map((distance) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3092
3586
  DistanceOverlayItem,
3093
3587
  {
3094
3588
  distance,
@@ -3097,7 +3591,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3097
3591
  },
3098
3592
  `held-${distance.id}`
3099
3593
  )),
3100
- !guidesEnabled && altPressed && optionPairOverlay ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3594
+ selectionVisible && altPressed && optionPairOverlay ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3101
3595
  DistanceOverlayItem,
3102
3596
  {
3103
3597
  distance: optionPairOverlay,
@@ -3105,7 +3599,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3105
3599
  },
3106
3600
  `option-${optionPairOverlay.id}`
3107
3601
  ) : null,
3108
- guidesEnabled && altPressed && guideDistanceOverlay ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3602
+ guidesEnabled && altPressed && guideDistanceOverlay ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3109
3603
  DistanceOverlayItem,
3110
3604
  {
3111
3605
  distance: guideDistanceOverlay,
@@ -3113,9 +3607,9 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3113
3607
  },
3114
3608
  `guide-preview-${guideDistanceOverlay.id}`
3115
3609
  ) : null,
3116
- !guidesEnabled && altPressed && optionContainerLines ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3117
- optionContainerLines.top.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3118
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3610
+ selectionVisible && altPressed && optionContainerLines ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3611
+ optionContainerLines.top.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3612
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3119
3613
  "div",
3120
3614
  {
3121
3615
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -3126,7 +3620,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3126
3620
  }
3127
3621
  }
3128
3622
  ),
3129
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3623
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3130
3624
  MeasureTag,
3131
3625
  {
3132
3626
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -3138,8 +3632,8 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3138
3632
  }
3139
3633
  )
3140
3634
  ] }) : null,
3141
- optionContainerLines.bottom.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3142
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3635
+ optionContainerLines.bottom.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3636
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3143
3637
  "div",
3144
3638
  {
3145
3639
  className: "msr:absolute msr:w-px msr:bg-[#2563eb]",
@@ -3150,7 +3644,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3150
3644
  }
3151
3645
  }
3152
3646
  ),
3153
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3647
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3154
3648
  MeasureTag,
3155
3649
  {
3156
3650
  className: "msr:-translate-y-1/2 msr:bg-ink-900/90",
@@ -3162,8 +3656,8 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3162
3656
  }
3163
3657
  )
3164
3658
  ] }) : null,
3165
- optionContainerLines.left.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3166
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3659
+ optionContainerLines.left.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3660
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3167
3661
  "div",
3168
3662
  {
3169
3663
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -3174,7 +3668,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3174
3668
  }
3175
3669
  }
3176
3670
  ),
3177
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3671
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3178
3672
  MeasureTag,
3179
3673
  {
3180
3674
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -3186,8 +3680,8 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3186
3680
  }
3187
3681
  )
3188
3682
  ] }) : null,
3189
- optionContainerLines.right.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3190
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3683
+ optionContainerLines.right.value > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3684
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3191
3685
  "div",
3192
3686
  {
3193
3687
  className: "msr:absolute msr:h-px msr:bg-[#2563eb]",
@@ -3198,7 +3692,7 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3198
3692
  }
3199
3693
  }
3200
3694
  ),
3201
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3695
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3202
3696
  MeasureTag,
3203
3697
  {
3204
3698
  className: "msr:-translate-x-1/2 msr:bg-ink-900/90",
@@ -3216,25 +3710,28 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3216
3710
  const isHovered = hoverGuide?.id === guide.id;
3217
3711
  const strokeWidth = isSelected || isHovered ? 2 : 1;
3218
3712
  const strokeColor = isSelected ? guideColorActive : isHovered ? guideColorHover : guideColorDefault;
3219
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3713
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3220
3714
  "div",
3221
3715
  {
3222
3716
  className: "msr:absolute",
3717
+ "data-mesurer-guide": "true",
3223
3718
  style: guide.orientation === "vertical" ? {
3224
3719
  left: guide.position - GUIDE_HITBOX_SIZE / 2,
3225
3720
  top: 0,
3226
3721
  width: GUIDE_HITBOX_SIZE,
3227
- height: "100%"
3722
+ height: "100%",
3723
+ pointerEvents: guidePointerEvents ? "auto" : "none"
3228
3724
  } : {
3229
3725
  top: guide.position - GUIDE_HITBOX_SIZE / 2,
3230
3726
  left: 0,
3231
3727
  height: GUIDE_HITBOX_SIZE,
3232
- width: "100%"
3728
+ width: "100%",
3729
+ pointerEvents: guidePointerEvents ? "auto" : "none"
3233
3730
  },
3234
3731
  onPointerDown: (event) => onGuidePointerDown(guide, event),
3235
3732
  onPointerUp: (event) => onGuidePointerUp(guide, event),
3236
3733
  onPointerCancel: (event) => onGuidePointerCancel(guide, event),
3237
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3734
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3238
3735
  "div",
3239
3736
  {
3240
3737
  className: "msr:absolute",
@@ -3262,46 +3759,991 @@ var MeasurerOverlay = (0, import_react18.memo)(function MeasurerOverlay2({
3262
3759
  );
3263
3760
  });
3264
3761
 
3265
- // measurer.tsx
3266
- var import_jsx_runtime8 = require("react/jsx-runtime");
3267
- var subscribeHydration = () => () => {
3762
+ // runtime/text-inspector-dom.ts
3763
+ var INK_50 = "#f8fafc";
3764
+ var INK_200 = "#e2e8f0";
3765
+ var INK_500 = "#64748b";
3766
+ var INK_900 = "#0f172a";
3767
+ var makeEdge = (document2, side, color) => {
3768
+ const edge = document2.createElement("div");
3769
+ Object.assign(edge.style, {
3770
+ position: "absolute",
3771
+ backgroundColor: color,
3772
+ ...side === "top" || side === "bottom" ? { left: "0", [side]: "0", width: "100%", height: "1px" } : { top: "0", [side]: "0", width: "1px", height: "100%" }
3773
+ });
3774
+ return edge;
3268
3775
  };
3269
- var useHydrated = () => (0, import_react19.useSyncExternalStore)(
3270
- subscribeHydration,
3271
- () => true,
3272
- () => false
3273
- );
3274
- var stripMeasurement = (measurement) => ({
3275
- ...measurement,
3276
- elementRef: void 0
3277
- });
3278
- var stripDistance = (distance) => ({
3279
- ...distance,
3280
- elementRefA: void 0,
3281
- elementRefB: void 0
3282
- });
3283
- function MeasurerClient({
3284
- highlightColor,
3285
- guideColor,
3286
- hoverHighlightEnabled,
3776
+ var makeBox = (document2, fill, outline) => {
3777
+ const box = document2.createElement("div");
3778
+ Object.assign(box.style, {
3779
+ position: "fixed",
3780
+ zIndex: "0",
3781
+ pointerEvents: "none",
3782
+ backgroundColor: fill,
3783
+ boxSizing: "border-box"
3784
+ });
3785
+ box.className = "mesurer-ti-box";
3786
+ box.dataset.state = "hidden";
3787
+ for (const side of ["top", "right", "bottom", "left"]) {
3788
+ box.appendChild(makeEdge(document2, side, outline));
3789
+ }
3790
+ return box;
3791
+ };
3792
+ var positionBox = (box, rect) => {
3793
+ Object.assign(box.style, {
3794
+ left: `${rect.left}px`,
3795
+ top: `${rect.top}px`,
3796
+ width: `${rect.width}px`,
3797
+ height: `${rect.height}px`
3798
+ });
3799
+ };
3800
+ var makeCard = (document2, pinned) => {
3801
+ const card = document2.createElement("div");
3802
+ Object.assign(card.style, {
3803
+ position: "fixed",
3804
+ zIndex: "1",
3805
+ pointerEvents: pinned ? "auto" : "none",
3806
+ background: "#ffffff",
3807
+ color: INK_900,
3808
+ borderRadius: "13px",
3809
+ padding: "10px 12px",
3810
+ fontSize: "11px",
3811
+ lineHeight: "1.5",
3812
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
3813
+ fontVariantNumeric: "tabular-nums",
3814
+ userSelect: "none",
3815
+ whiteSpace: "nowrap",
3816
+ width: "320px",
3817
+ minWidth: "320px",
3818
+ maxWidth: "320px",
3819
+ boxSizing: "border-box",
3820
+ 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)"
3821
+ });
3822
+ card.className = pinned ? "mesurer-ti-card mesurer-ti-card--pinned" : "mesurer-ti-card";
3823
+ card.dataset.state = "hidden";
3824
+ return card;
3825
+ };
3826
+ var populateCard = (document2, card, info, pinned) => {
3827
+ card.replaceChildren();
3828
+ const header = document2.createElement("div");
3829
+ Object.assign(header.style, {
3830
+ display: "flex",
3831
+ alignItems: "center",
3832
+ gap: "8px",
3833
+ marginBottom: "8px",
3834
+ paddingBottom: "6px"
3835
+ });
3836
+ const tag = document2.createElement("span");
3837
+ Object.assign(tag.style, {
3838
+ color: INK_50,
3839
+ background: INK_900,
3840
+ borderRadius: "4px",
3841
+ padding: "1px 5px",
3842
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3843
+ fontSize: "10px",
3844
+ fontWeight: "500"
3845
+ });
3846
+ tag.textContent = info.tagName;
3847
+ header.appendChild(tag);
3848
+ if (info.textSnippet) {
3849
+ const snippet = document2.createElement("span");
3850
+ Object.assign(snippet.style, {
3851
+ color: INK_500,
3852
+ overflow: "hidden",
3853
+ textOverflow: "ellipsis",
3854
+ whiteSpace: "nowrap",
3855
+ flex: "1",
3856
+ minWidth: "0",
3857
+ fontSize: "10px"
3858
+ });
3859
+ snippet.textContent = info.textSnippet;
3860
+ header.appendChild(snippet);
3861
+ }
3862
+ if (pinned) {
3863
+ const close = document2.createElement("button");
3864
+ close.type = "button";
3865
+ close.className = "mesurer-ti-close";
3866
+ close.setAttribute("aria-label", "Close");
3867
+ close.textContent = "\xD7";
3868
+ Object.assign(close.style, {
3869
+ all: "unset",
3870
+ marginLeft: info.textSnippet ? "0" : "auto",
3871
+ flex: "0 0 auto",
3872
+ width: "16px",
3873
+ height: "16px",
3874
+ lineHeight: "14px",
3875
+ textAlign: "center",
3876
+ borderRadius: "4px",
3877
+ color: INK_500,
3878
+ fontSize: "14px"
3879
+ });
3880
+ header.appendChild(close);
3881
+ }
3882
+ card.appendChild(header);
3883
+ const grid = document2.createElement("div");
3884
+ Object.assign(grid.style, {
3885
+ display: "grid",
3886
+ gridTemplateColumns: "auto 1fr",
3887
+ columnGap: "12px",
3888
+ rowGap: "3px",
3889
+ alignItems: "baseline"
3890
+ });
3891
+ for (const row of info.rows) {
3892
+ const label = document2.createElement("span");
3893
+ label.style.color = INK_500;
3894
+ label.style.fontSize = "11px";
3895
+ label.textContent = row.label;
3896
+ const value = document2.createElement("span");
3897
+ Object.assign(value.style, {
3898
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3899
+ fontSize: "11px",
3900
+ color: INK_900,
3901
+ overflow: "hidden",
3902
+ textOverflow: "ellipsis",
3903
+ whiteSpace: "nowrap"
3904
+ });
3905
+ if (!row.varName) {
3906
+ value.textContent = row.value;
3907
+ } else {
3908
+ const variable = document2.createElement("span");
3909
+ variable.textContent = row.value;
3910
+ const separator = document2.createElement("span");
3911
+ separator.style.color = INK_200;
3912
+ separator.style.margin = "0 6px";
3913
+ separator.textContent = "\xB7";
3914
+ const name = document2.createElement("span");
3915
+ name.style.color = "#0369a1";
3916
+ name.textContent = row.varName;
3917
+ value.append(variable, separator, name);
3918
+ }
3919
+ grid.append(label, value);
3920
+ }
3921
+ card.appendChild(grid);
3922
+ };
3923
+ var positionCard = (window2, card, rect, offsetX = 0, offsetY = 0) => {
3924
+ const center = rect.left + rect.width / 2 + offsetX;
3925
+ card.style.left = `${center}px`;
3926
+ const size = card.getBoundingClientRect();
3927
+ let top = rect.bottom + 4 + offsetY;
3928
+ if (top + size.height > window2.innerHeight - 8) {
3929
+ top = rect.top - size.height - 4 + offsetY;
3930
+ }
3931
+ card.style.top = `${Math.max(8, top)}px`;
3932
+ const half = size.width / 2;
3933
+ card.style.left = `${Math.min(
3934
+ Math.max(center, 8 + half),
3935
+ window2.innerWidth - 8 - half
3936
+ )}px`;
3937
+ };
3938
+
3939
+ // runtime/text-inspector-typography.ts
3940
+ var FONT_WEIGHT_KEYWORD = {
3941
+ "100": "thin",
3942
+ "200": "extralight",
3943
+ "300": "light",
3944
+ "400": "normal",
3945
+ "500": "medium",
3946
+ "600": "semibold",
3947
+ "700": "bold",
3948
+ "800": "extrabold",
3949
+ "900": "black"
3950
+ };
3951
+ var TYPO_PROPS = [
3952
+ "font-family",
3953
+ "font-size",
3954
+ "font-weight",
3955
+ "line-height",
3956
+ "letter-spacing"
3957
+ ];
3958
+ var TYPO_LABELS = {
3959
+ Family: "font-family",
3960
+ Size: "font-size",
3961
+ Weight: "font-weight",
3962
+ Line: "line-height",
3963
+ Tracking: "letter-spacing"
3964
+ };
3965
+ var formatPx = (raw) => {
3966
+ if (!raw || raw === "normal") return raw || "normal";
3967
+ const match = /^(-?[\d.]+)px$/.exec(raw);
3968
+ if (!match) return raw;
3969
+ return `${Math.round(Number(match[1]) * 10) / 10}px`;
3970
+ };
3971
+ var firstFontFamily = (families) => (families.split(",")[0] ?? "").trim().replace(/^['"]|['"]$/g, "");
3972
+ var weightWithKeyword = (weight) => {
3973
+ const keyword = FONT_WEIGHT_KEYWORD[weight];
3974
+ return keyword ? `${weight} / ${keyword}` : weight;
3975
+ };
3976
+ var extractVarName = (value) => {
3977
+ if (!value) return null;
3978
+ return /var\(\s*(--[a-zA-Z0-9_-]+)/.exec(value)?.[1] ?? null;
3979
+ };
3980
+ var selectorSpecificity = (selector) => {
3981
+ const ids = (selector.match(/#[\w-]+/g) ?? []).length;
3982
+ const classes = (selector.match(/\.[\w-]+|\[[^\]]+\]|:[\w-]+/g) ?? []).length;
3983
+ const elements = (selector.match(/(?:^|[ >+~])([a-zA-Z][\w-]*)/g) ?? []).length;
3984
+ return ids * 1e4 + classes * 100 + elements;
3985
+ };
3986
+ var wins = (next, previous) => {
3987
+ if (!previous) return true;
3988
+ if (next.important !== previous.important) return next.important;
3989
+ if (next.specificity !== previous.specificity) {
3990
+ return next.specificity > previous.specificity;
3991
+ }
3992
+ return next.order > previous.order;
3993
+ };
3994
+ var TypographyInspector = class {
3995
+ constructor(document2, window2) {
3996
+ this.document = document2;
3997
+ this.window = window2;
3998
+ }
3999
+ collectRules(rules, output, mediaOk, order) {
4000
+ for (let i = 0; i < rules.length; i++) {
4001
+ const rule = rules[i];
4002
+ const currentOrder = order.value++;
4003
+ if (rule.type === 4) {
4004
+ const mediaRule = rule;
4005
+ let matches = mediaOk;
4006
+ try {
4007
+ matches = mediaOk && this.window.matchMedia(mediaRule.media.mediaText).matches;
4008
+ } catch {
4009
+ matches = false;
4010
+ }
4011
+ this.collectRules(mediaRule.cssRules, output, matches, order);
4012
+ } else if (rule.type === 12) {
4013
+ this.collectRules(rule.cssRules, output, mediaOk, order);
4014
+ } else if (rule.type === 1) {
4015
+ output.push({
4016
+ rule,
4017
+ mediaOk,
4018
+ order: currentOrder
4019
+ });
4020
+ }
4021
+ }
4022
+ }
4023
+ getRules() {
4024
+ const rules = [];
4025
+ const order = { value: 0 };
4026
+ for (const sheet of Array.from(this.document.styleSheets)) {
4027
+ try {
4028
+ this.collectRules(sheet.cssRules, rules, true, order);
4029
+ } catch {
4030
+ continue;
4031
+ }
4032
+ }
4033
+ return rules;
4034
+ }
4035
+ findVarReferences(el) {
4036
+ const result = Object.fromEntries(
4037
+ TYPO_PROPS.map((prop) => [prop, null])
4038
+ );
4039
+ const rules = this.getRules();
4040
+ for (const prop of TYPO_PROPS) {
4041
+ let node = el;
4042
+ while (node && result[prop] === null) {
4043
+ let winner;
4044
+ const inlineValue = node.style.getPropertyValue(prop);
4045
+ const inlineName = extractVarName(inlineValue);
4046
+ if (inlineName) {
4047
+ winner = {
4048
+ name: inlineName,
4049
+ specificity: Number.MAX_SAFE_INTEGER,
4050
+ order: Number.MAX_SAFE_INTEGER,
4051
+ important: node.style.getPropertyPriority(prop) === "important"
4052
+ };
4053
+ }
4054
+ for (const { rule, mediaOk, order } of rules) {
4055
+ if (!mediaOk) continue;
4056
+ let matches = false;
4057
+ try {
4058
+ matches = node.matches(rule.selectorText);
4059
+ } catch {
4060
+ continue;
4061
+ }
4062
+ if (!matches) continue;
4063
+ const name = extractVarName(rule.style.getPropertyValue(prop));
4064
+ if (!name) continue;
4065
+ const candidate = {
4066
+ name,
4067
+ specificity: selectorSpecificity(rule.selectorText),
4068
+ order,
4069
+ important: rule.style.getPropertyPriority(prop) === "important"
4070
+ };
4071
+ if (wins(candidate, winner)) winner = candidate;
4072
+ }
4073
+ if (winner) result[prop] = winner.name;
4074
+ node = node.parentElement;
4075
+ }
4076
+ }
4077
+ return result;
4078
+ }
4079
+ getFast(el) {
4080
+ const styles = this.window.getComputedStyle(el);
4081
+ const rows = [
4082
+ { label: "Family", value: firstFontFamily(styles.fontFamily), varName: null },
4083
+ { label: "Size", value: formatPx(styles.fontSize), varName: null },
4084
+ {
4085
+ label: "Weight",
4086
+ value: weightWithKeyword(styles.fontWeight),
4087
+ varName: null
4088
+ },
4089
+ {
4090
+ label: "Line",
4091
+ value: styles.lineHeight === "normal" ? "normal" : formatPx(styles.lineHeight),
4092
+ varName: null
4093
+ },
4094
+ {
4095
+ label: "Tracking",
4096
+ value: styles.letterSpacing === "normal" ? "normal" : formatPx(styles.letterSpacing),
4097
+ varName: null
4098
+ }
4099
+ ];
4100
+ const directText = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.nodeValue?.trim() ?? "").filter(Boolean).join(" ").replace(/\s+/g, " ");
4101
+ return {
4102
+ rows,
4103
+ tagName: el.tagName.toLowerCase(),
4104
+ textSnippet: directText.length > 40 ? `${directText.slice(0, 40)}\u2026` : directText
4105
+ };
4106
+ }
4107
+ getFull(el, base = this.getFast(el)) {
4108
+ const vars = this.findVarReferences(el);
4109
+ return {
4110
+ ...base,
4111
+ rows: base.rows.map((row) => ({
4112
+ ...row,
4113
+ varName: vars[TYPO_LABELS[row.label]]
4114
+ }))
4115
+ };
4116
+ }
4117
+ };
4118
+
4119
+ // runtime/text-inspector.ts
4120
+ var DEFAULT_EXTENSION_HOST_ID = "mesurer-extension-host";
4121
+ var DEFAULT_OVERLAY_ID = "mesurer-text-inspector-overlay";
4122
+ var DEFAULT_STYLE_ID = "mesurer-text-inspector-styles";
4123
+ var DEFAULT_BODY_MODE_CLASS = "mesurer-text-inspect-mode";
4124
+ var inspectorInstanceCount = 0;
4125
+ var FILL_HOVER = "color-mix(in oklch, oklch(0.62 0.18 255) 8%, transparent)";
4126
+ var OUTLINE_HOVER = "color-mix(in oklch, oklch(0.62 0.18 255) 80%, transparent)";
4127
+ var FILL_PINNED = "color-mix(in oklch, oklch(0.62 0.18 255) 4%, transparent)";
4128
+ var OUTLINE_PINNED = "color-mix(in oklch, oklch(0.62 0.18 255) 35%, transparent)";
4129
+ var DEFAULT_SKIP_TAGS = [
4130
+ "HTML",
4131
+ "BODY",
4132
+ "SCRIPT",
4133
+ "STYLE",
4134
+ "META",
4135
+ "LINK",
4136
+ "NOSCRIPT",
4137
+ "IMG",
4138
+ "VIDEO",
4139
+ "AUDIO",
4140
+ "IFRAME"
4141
+ ];
4142
+ var createInspectorStyles = (extensionHostId, overlayId, bodyModeClass) => `
4143
+ .${bodyModeClass},
4144
+ .${bodyModeClass} * {
4145
+ cursor: help !important;
4146
+ }
4147
+ .${bodyModeClass} #${extensionHostId},
4148
+ .${bodyModeClass} #${extensionHostId} * {
4149
+ cursor: auto !important;
4150
+ }
4151
+ #${overlayId} .mesurer-ti-card--pinned {
4152
+ cursor: grab;
4153
+ }
4154
+ #${overlayId} .mesurer-ti-card--pinned:active {
4155
+ cursor: grabbing;
4156
+ }
4157
+ #${overlayId} .mesurer-ti-close {
4158
+ cursor: pointer;
4159
+ }
4160
+ #${overlayId} .mesurer-ti-close:hover {
4161
+ background: rgba(15, 23, 42, 0.06);
4162
+ color: #0f172a;
4163
+ }
4164
+ #${overlayId} .mesurer-ti-box,
4165
+ #${overlayId} .mesurer-ti-card {
4166
+ opacity: 1;
4167
+ }
4168
+ #${overlayId} .mesurer-ti-box[data-state="hidden"],
4169
+ #${overlayId} .mesurer-ti-card[data-state="hidden"] {
4170
+ opacity: 0;
4171
+ }
4172
+ #${overlayId} .mesurer-ti-card {
4173
+ transform: translateX(-50%);
4174
+ }
4175
+ `;
4176
+ var createIdleScheduler = (window2) => {
4177
+ const browser = window2;
4178
+ if (browser.requestIdleCallback) {
4179
+ return {
4180
+ request: (callback) => browser.requestIdleCallback(
4181
+ () => callback(),
4182
+ { timeout: 250 }
4183
+ ),
4184
+ cancel: (id) => browser.cancelIdleCallback?.(id)
4185
+ };
4186
+ }
4187
+ return {
4188
+ request: (callback) => window2.setTimeout(callback, 32),
4189
+ cancel: (id) => window2.clearTimeout(id)
4190
+ };
4191
+ };
4192
+ var createTextInspector = (options = {}, legacy = false) => {
4193
+ const portalDocument = options.portalTarget?.ownerDocument;
4194
+ const availableDocument = portalDocument ?? (typeof globalThis.document === "undefined" ? null : globalThis.document);
4195
+ const availableWindow = availableDocument?.defaultView ?? (typeof globalThis.window === "undefined" ? null : globalThis.window);
4196
+ const document2 = availableDocument;
4197
+ const window2 = availableWindow;
4198
+ const realm = availableWindow;
4199
+ const HTMLElementConstructor = realm?.HTMLElement ?? globalThis.HTMLElement;
4200
+ const SVGElementConstructor = realm?.SVGElement ?? globalThis.SVGElement;
4201
+ const extensionHostId = DEFAULT_EXTENSION_HOST_ID;
4202
+ const instanceId = (options.id ?? (legacy ? DEFAULT_OVERLAY_ID : `mesurer-text-inspector-${++inspectorInstanceCount}`)).replace(
4203
+ /[^a-zA-Z0-9_-]/g,
4204
+ "-"
4205
+ );
4206
+ const overlayId = legacy ? DEFAULT_OVERLAY_ID : `${instanceId}-overlay`;
4207
+ const styleId = legacy ? DEFAULT_STYLE_ID : `${instanceId}-styles`;
4208
+ const bodyModeClass = legacy ? DEFAULT_BODY_MODE_CLASS : `${instanceId}-mode`;
4209
+ const skipTags = new Set(
4210
+ (options.ignoredTags ?? DEFAULT_SKIP_TAGS).map((tag) => tag.toUpperCase())
4211
+ );
4212
+ const maxPinned = Number.isFinite(options.maxPinned) ? Math.max(1, Math.floor(options.maxPinned)) : Infinity;
4213
+ const inspectorStyles = createInspectorStyles(
4214
+ extensionHostId,
4215
+ overlayId,
4216
+ bodyModeClass
4217
+ );
4218
+ const EXTENSION_HOST_ID = extensionHostId;
4219
+ const OVERLAY_ID = overlayId;
4220
+ const STYLE_ID = styleId;
4221
+ const BODY_MODE_CLASS = bodyModeClass;
4222
+ const SKIP_TAGS = skipTags;
4223
+ const INSPECTOR_STYLES = inspectorStyles;
4224
+ const portalIsShadowRoot = options.portalTarget?.nodeType === 11;
4225
+ const typography = new TypographyInspector(document2, window2);
4226
+ const pinned = [];
4227
+ const history = [];
4228
+ const future = [];
4229
+ let enabled = false;
4230
+ let overlay = null;
4231
+ let hoverBox = null;
4232
+ let hoverCard = null;
4233
+ let hoveredEl = null;
4234
+ let pointerX = 0;
4235
+ let pointerY = 0;
4236
+ let frameScheduled = false;
4237
+ let pendingElement = null;
4238
+ let pendingIdleId = -1;
4239
+ let idleScheduler = null;
4240
+ const getIdleScheduler = () => {
4241
+ idleScheduler ?? (idleScheduler = createIdleScheduler(window2));
4242
+ return idleScheduler;
4243
+ };
4244
+ const hasDirectText = (el) => Array.from(el.childNodes).some(
4245
+ (node) => node.nodeType === Node.TEXT_NODE && !!node.nodeValue?.trim()
4246
+ );
4247
+ const isInspectable = (el) => !!el && el instanceof HTMLElementConstructor && !SKIP_TAGS.has(el.tagName) && !(el instanceof SVGElementConstructor) && hasDirectText(el);
4248
+ const isOverlayNode = (node) => {
4249
+ for (let current = node; current; current = current.parentNode) {
4250
+ if (current instanceof HTMLElementConstructor) {
4251
+ const element = current;
4252
+ if (element.id === OVERLAY_ID || element.id === EXTENSION_HOST_ID) {
4253
+ return true;
4254
+ }
4255
+ }
4256
+ }
4257
+ return false;
4258
+ };
4259
+ const pickElementAt = (x, y) => {
4260
+ return document2.elementsFromPoint(x, y).find(
4261
+ (el) => !isOverlayNode(el) && isInspectable(el)
4262
+ ) ?? null;
4263
+ };
4264
+ const pointerOverToolbar = (x, y) => {
4265
+ const shadow = document2.getElementById(EXTENSION_HOST_ID)?.shadowRoot;
4266
+ const element = shadow?.elementFromPoint(x, y);
4267
+ if (!element) return false;
4268
+ for (let node = element; node; node = node.parentElement) {
4269
+ if (node.classList.contains("mesurer-toolbar-surface")) return true;
4270
+ }
4271
+ return false;
4272
+ };
4273
+ const ensureStyles = () => {
4274
+ const append = (root) => {
4275
+ if (root.querySelector(`#${STYLE_ID}`)) return;
4276
+ const style = document2.createElement("style");
4277
+ style.id = STYLE_ID;
4278
+ style.textContent = INSPECTOR_STYLES;
4279
+ root.nodeType === 9 ? root.head.appendChild(style) : root.appendChild(style);
4280
+ };
4281
+ append(document2);
4282
+ if (portalIsShadowRoot) append(options.portalTarget);
4283
+ };
4284
+ const ensureOverlay = () => {
4285
+ if (overlay?.isConnected) return overlay;
4286
+ overlay = document2.createElement("div");
4287
+ overlay.id = OVERLAY_ID;
4288
+ Object.assign(overlay.style, {
4289
+ position: "fixed",
4290
+ inset: "0",
4291
+ pointerEvents: "none",
4292
+ zIndex: "2147483646"
4293
+ });
4294
+ (options.portalTarget ?? document2.body).appendChild(overlay);
4295
+ return overlay;
4296
+ };
4297
+ const setState = (element, visible) => {
4298
+ element.dataset.state = visible ? "visible" : "hidden";
4299
+ };
4300
+ const ensureHover = () => {
4301
+ const root = ensureOverlay();
4302
+ hoverBox ?? (hoverBox = makeBox(document2, FILL_HOVER, OUTLINE_HOVER));
4303
+ hoverCard ?? (hoverCard = makeCard(document2, false));
4304
+ if (!hoverBox.parentNode) root.appendChild(hoverBox);
4305
+ if (!hoverCard.parentNode) root.appendChild(hoverCard);
4306
+ };
4307
+ const scheduleEnrichment = (element, fast) => {
4308
+ if (pendingElement === element) return;
4309
+ if (pendingIdleId !== -1) getIdleScheduler().cancel(pendingIdleId);
4310
+ pendingElement = element;
4311
+ pendingIdleId = getIdleScheduler().request(() => {
4312
+ pendingIdleId = -1;
4313
+ pendingElement = null;
4314
+ if (!enabled) return;
4315
+ const full = typography.getFull(element, fast);
4316
+ if (hoveredEl !== element || !hoverCard) return;
4317
+ populateCard(document2, hoverCard, full, false);
4318
+ positionCard(window2, hoverCard, element.getBoundingClientRect());
4319
+ options.onInspect?.(element, full);
4320
+ });
4321
+ };
4322
+ const inspect = (element) => {
4323
+ if (!enabled || !isInspectable(element)) return false;
4324
+ ensureHover();
4325
+ const rect = element.getBoundingClientRect();
4326
+ hoveredEl = element;
4327
+ const fast = typography.getFast(element);
4328
+ populateCard(document2, hoverCard, fast, false);
4329
+ scheduleEnrichment(element, fast);
4330
+ positionBox(hoverBox, rect);
4331
+ positionCard(window2, hoverCard, rect);
4332
+ setState(hoverBox, true);
4333
+ setState(hoverCard, true);
4334
+ return true;
4335
+ };
4336
+ const updateHover = () => {
4337
+ if (!enabled || pointerOverToolbar(pointerX, pointerY)) {
4338
+ hideHover();
4339
+ return;
4340
+ }
4341
+ const element = pickElementAt(pointerX, pointerY);
4342
+ if (!element) {
4343
+ hideHover();
4344
+ return;
4345
+ }
4346
+ if (element !== hoveredEl) inspect(element);
4347
+ else {
4348
+ positionBox(hoverBox, element.getBoundingClientRect());
4349
+ positionCard(window2, hoverCard, element.getBoundingClientRect());
4350
+ }
4351
+ };
4352
+ const hideHover = () => {
4353
+ hoveredEl = null;
4354
+ if (hoverBox) setState(hoverBox, false);
4355
+ if (hoverCard) setState(hoverCard, false);
4356
+ };
4357
+ const snapshotPins = () => pinned.map((pin) => {
4358
+ const rect = pin.card.getBoundingClientRect();
4359
+ return {
4360
+ sourceEl: pin.sourceEl,
4361
+ left: pin.userPlaced ? rect.left + rect.width / 2 : 0,
4362
+ top: pin.userPlaced ? rect.top : 0,
4363
+ userPlaced: pin.userPlaced
4364
+ };
4365
+ });
4366
+ const recordPinState = () => {
4367
+ history.push(snapshotPins());
4368
+ future.length = 0;
4369
+ };
4370
+ const removePin = (pin, record = true, notify = record) => {
4371
+ const index = pinned.indexOf(pin);
4372
+ if (index === -1) return;
4373
+ if (record) recordPinState();
4374
+ pinned.splice(index, 1);
4375
+ pin.detach();
4376
+ pin.box.remove();
4377
+ pin.card.remove();
4378
+ if (notify) options.onUnpin?.(pin.sourceEl);
4379
+ };
4380
+ const clearPins = (notify = false) => {
4381
+ while (pinned.length) {
4382
+ removePin(pinned[pinned.length - 1], false, notify);
4383
+ }
4384
+ };
4385
+ const attachDrag = (pin) => {
4386
+ let pointerId = -1;
4387
+ let startX = 0;
4388
+ let startY = 0;
4389
+ let originLeft = 0;
4390
+ let originTop = 0;
4391
+ let active = false;
4392
+ let dragged = false;
4393
+ let historyRecorded = false;
4394
+ const slop = 6;
4395
+ const onMove = (event) => {
4396
+ if (event.pointerId !== pointerId) return;
4397
+ const dx = event.clientX - startX;
4398
+ const dy = event.clientY - startY;
4399
+ if (!active) active = Math.abs(dx) > slop || Math.abs(dy) > slop;
4400
+ if (!active) return;
4401
+ if (!historyRecorded) {
4402
+ recordPinState();
4403
+ historyRecorded = true;
4404
+ }
4405
+ dragged = true;
4406
+ pin.card.style.left = `${Math.min(
4407
+ window2.innerWidth - 8,
4408
+ Math.max(8, originLeft + dx)
4409
+ )}px`;
4410
+ pin.card.style.top = `${Math.min(
4411
+ window2.innerHeight - 8,
4412
+ Math.max(8, originTop + dy)
4413
+ )}px`;
4414
+ pin.userPlaced = true;
4415
+ };
4416
+ const onEnd = (event) => {
4417
+ if (event.pointerId !== pointerId && pointerId !== -1) return;
4418
+ window2.removeEventListener("pointermove", onMove);
4419
+ window2.removeEventListener("pointerup", onEnd);
4420
+ window2.removeEventListener("pointercancel", onEnd);
4421
+ pointerId = -1;
4422
+ active = false;
4423
+ historyRecorded = false;
4424
+ if (dragged) {
4425
+ const swallow = (click) => {
4426
+ click.preventDefault();
4427
+ click.stopPropagation();
4428
+ window2.removeEventListener("click", swallow, true);
4429
+ };
4430
+ window2.addEventListener("click", swallow, true);
4431
+ }
4432
+ dragged = false;
4433
+ };
4434
+ const onDown = (event) => {
4435
+ if (event.button !== 0) return;
4436
+ if (event.target?.classList.contains("mesurer-ti-close")) {
4437
+ return;
4438
+ }
4439
+ const rect = pin.card.getBoundingClientRect();
4440
+ originLeft = rect.left + rect.width / 2;
4441
+ originTop = rect.top;
4442
+ startX = event.clientX;
4443
+ startY = event.clientY;
4444
+ pointerId = event.pointerId;
4445
+ active = false;
4446
+ dragged = false;
4447
+ window2.addEventListener("pointermove", onMove);
4448
+ window2.addEventListener("pointerup", onEnd);
4449
+ window2.addEventListener("pointercancel", onEnd);
4450
+ };
4451
+ pin.card.addEventListener("pointerdown", onDown);
4452
+ return () => {
4453
+ pin.card.removeEventListener("pointerdown", onDown);
4454
+ window2.removeEventListener("pointermove", onMove);
4455
+ window2.removeEventListener("pointerup", onEnd);
4456
+ window2.removeEventListener("pointercancel", onEnd);
4457
+ };
4458
+ };
4459
+ const createPin = (sourceEl, state, record = true, notify = record) => {
4460
+ const existing = pinned.find((pin2) => pin2.sourceEl === sourceEl);
4461
+ if (existing) {
4462
+ const root2 = ensureOverlay();
4463
+ root2.append(existing.box, existing.card);
4464
+ return;
4465
+ }
4466
+ if (record) {
4467
+ recordPinState();
4468
+ if (pinned.length >= maxPinned) removePin(pinned[0], false, true);
4469
+ }
4470
+ const root = ensureOverlay();
4471
+ const box = makeBox(document2, FILL_PINNED, OUTLINE_PINNED);
4472
+ const card = makeCard(document2, true);
4473
+ const info = typography.getFull(sourceEl);
4474
+ populateCard(document2, card, info, true);
4475
+ root.append(box, card);
4476
+ const pin = {
4477
+ sourceEl,
4478
+ box,
4479
+ card,
4480
+ userPlaced: state?.userPlaced ?? false,
4481
+ detach: () => {
4482
+ }
4483
+ };
4484
+ const rect = sourceEl.getBoundingClientRect();
4485
+ positionBox(box, rect);
4486
+ positionCard(window2, card, rect);
4487
+ if (state?.userPlaced) {
4488
+ card.style.left = `${state.left}px`;
4489
+ card.style.top = `${state.top}px`;
4490
+ }
4491
+ setState(box, true);
4492
+ setState(card, true);
4493
+ pinned.push(pin);
4494
+ card.querySelector(".mesurer-ti-close")?.addEventListener(
4495
+ "click",
4496
+ (event) => {
4497
+ event.preventDefault();
4498
+ event.stopPropagation();
4499
+ removePin(pin);
4500
+ }
4501
+ );
4502
+ pin.detach = attachDrag(pin);
4503
+ if (notify) options.onPin?.(sourceEl, info);
4504
+ };
4505
+ const restorePins = (states) => {
4506
+ clearPins(false);
4507
+ for (const state of states) {
4508
+ if (state.sourceEl.isConnected) createPin(state.sourceEl, state, false, false);
4509
+ }
4510
+ };
4511
+ const undo = () => {
4512
+ const previous = history.pop();
4513
+ if (!previous) return false;
4514
+ future.push(snapshotPins());
4515
+ restorePins(previous);
4516
+ return true;
4517
+ };
4518
+ const redo = () => {
4519
+ const next = future.pop();
4520
+ if (!next) return false;
4521
+ history.push(snapshotPins());
4522
+ restorePins(next);
4523
+ return true;
4524
+ };
4525
+ const clear = () => {
4526
+ if (!pinned.length) return;
4527
+ recordPinState();
4528
+ clearPins(true);
4529
+ };
4530
+ const syncPins = () => {
4531
+ for (const pin of [...pinned]) {
4532
+ if (!pin.sourceEl.isConnected) {
4533
+ removePin(pin, false);
4534
+ continue;
4535
+ }
4536
+ const rect = pin.sourceEl.getBoundingClientRect();
4537
+ positionBox(pin.box, rect);
4538
+ if (!pin.userPlaced) positionCard(window2, pin.card, rect);
4539
+ const visible = rect.bottom >= 0 && rect.right >= 0 && rect.left <= window2.innerWidth && rect.top <= window2.innerHeight;
4540
+ setState(pin.box, visible);
4541
+ setState(pin.card, visible);
4542
+ pin.card.style.pointerEvents = visible ? "auto" : "none";
4543
+ }
4544
+ };
4545
+ const scheduleFrame = () => {
4546
+ if (frameScheduled) return;
4547
+ frameScheduled = true;
4548
+ requestAnimationFrame(() => {
4549
+ frameScheduled = false;
4550
+ if (!enabled) return;
4551
+ updateHover();
4552
+ syncPins();
4553
+ });
4554
+ };
4555
+ const onMouseMove = (event) => {
4556
+ pointerX = event.clientX;
4557
+ pointerY = event.clientY;
4558
+ scheduleFrame();
4559
+ };
4560
+ const onMouseOut = (event) => {
4561
+ if (!event.relatedTarget) hideHover();
4562
+ };
4563
+ const onScrollOrResize = () => scheduleFrame();
4564
+ const isInspectorUIEvent = (event) => event.composedPath().some(
4565
+ (node) => node instanceof HTMLElementConstructor && (() => {
4566
+ const element = node;
4567
+ 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");
4568
+ })()
4569
+ );
4570
+ const onClick = (event) => {
4571
+ if (isInspectorUIEvent(event) || pointerOverToolbar(event.clientX, event.clientY)) {
4572
+ return;
4573
+ }
4574
+ event.preventDefault();
4575
+ event.stopImmediatePropagation();
4576
+ if (event.button === 0) {
4577
+ const source = pickElementAt(event.clientX, event.clientY);
4578
+ if (source) createPin(source);
4579
+ }
4580
+ };
4581
+ const onAuxClick = (event) => {
4582
+ if (isInspectorUIEvent(event) || pointerOverToolbar(event.clientX, event.clientY)) {
4583
+ return;
4584
+ }
4585
+ event.preventDefault();
4586
+ event.stopImmediatePropagation();
4587
+ };
4588
+ const enable = () => {
4589
+ if (enabled || !availableDocument || !availableWindow) return;
4590
+ enabled = true;
4591
+ ensureStyles();
4592
+ ensureOverlay();
4593
+ document2.body.classList.add(BODY_MODE_CLASS);
4594
+ window2.addEventListener("mousemove", onMouseMove, true);
4595
+ window2.addEventListener("mouseout", onMouseOut, true);
4596
+ window2.addEventListener("click", onClick, true);
4597
+ window2.addEventListener("auxclick", onAuxClick, true);
4598
+ window2.addEventListener("scroll", onScrollOrResize, true);
4599
+ window2.addEventListener("resize", onScrollOrResize, true);
4600
+ };
4601
+ const disable = () => {
4602
+ if (!enabled) return;
4603
+ enabled = false;
4604
+ if (pendingIdleId !== -1) getIdleScheduler().cancel(pendingIdleId);
4605
+ pendingIdleId = -1;
4606
+ pendingElement = null;
4607
+ history.length = 0;
4608
+ future.length = 0;
4609
+ window2.removeEventListener("mousemove", onMouseMove, true);
4610
+ window2.removeEventListener("mouseout", onMouseOut, true);
4611
+ window2.removeEventListener("click", onClick, true);
4612
+ window2.removeEventListener("auxclick", onAuxClick, true);
4613
+ window2.removeEventListener("scroll", onScrollOrResize, true);
4614
+ window2.removeEventListener("resize", onScrollOrResize, true);
4615
+ hideHover();
4616
+ hoverBox?.remove();
4617
+ hoverCard?.remove();
4618
+ hoverBox = null;
4619
+ hoverCard = null;
4620
+ clearPins();
4621
+ document2.body.classList.remove(BODY_MODE_CLASS);
4622
+ };
4623
+ const cleanup = () => {
4624
+ disable();
4625
+ overlay?.remove();
4626
+ overlay = null;
4627
+ if (typeof document2 !== "undefined") {
4628
+ document2.getElementById(STYLE_ID)?.remove();
4629
+ if (portalIsShadowRoot) {
4630
+ options.portalTarget.querySelector(`#${STYLE_ID}`)?.remove();
4631
+ }
4632
+ }
4633
+ };
4634
+ return {
4635
+ enable,
4636
+ disable,
4637
+ undo,
4638
+ redo,
4639
+ canUndo: () => history.length > 0,
4640
+ canRedo: () => future.length > 0,
4641
+ clear,
4642
+ inspect,
4643
+ isEnabled: () => enabled,
4644
+ cleanup,
4645
+ destroy: cleanup
4646
+ };
4647
+ };
4648
+ var TextInspector = createTextInspector({}, true);
4649
+
4650
+ // measurer.tsx
4651
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4652
+ var measurerInstanceCount = 0;
4653
+ var XRAY_STYLE_ID = "mesurer-xray-styles";
4654
+ var XRAY_STYLES = `
4655
+ .xray-mode * {
4656
+ outline: solid 1px blue !important;
4657
+ }
4658
+ .xray-mode #mesurer-extension-host,
4659
+ .xray-mode #mesurer-extension-host *,
4660
+ .xray-mode .mesurer-root,
4661
+ .xray-mode .mesurer-root *,
4662
+ .xray-mode .mesurer-toolbar-surface,
4663
+ .xray-mode .mesurer-toolbar-surface * {
4664
+ outline: none !important;
4665
+ }
4666
+ `;
4667
+ var subscribeHydration = () => () => {
4668
+ };
4669
+ var useHydrated = () => (0, import_react20.useSyncExternalStore)(
4670
+ subscribeHydration,
4671
+ () => true,
4672
+ () => false
4673
+ );
4674
+ var stripMeasurement = (measurement) => ({
4675
+ ...measurement,
4676
+ elementRef: void 0
4677
+ });
4678
+ var stripDistance = (distance) => ({
4679
+ ...distance,
4680
+ elementRefA: void 0,
4681
+ elementRefB: void 0
4682
+ });
4683
+ function MeasurerClient({
4684
+ highlightColor,
4685
+ guideColor,
4686
+ hoverHighlightEnabled,
3287
4687
  persistOnReload,
3288
- portalTarget
4688
+ portalTarget,
4689
+ persistKey
3289
4690
  }) {
3290
- const selectionRectRef = (0, import_react19.useRef)(null);
3291
- const toolbarRef = (0, import_react19.useRef)(null);
3292
- const selectionAnimationCleanupTimeoutRef = (0, import_react19.useRef)(null);
3293
- const persistedState = (0, import_react19.useMemo)(() => {
4691
+ const instanceIdRef = (0, import_react20.useRef)(null);
4692
+ if (instanceIdRef.current === null) {
4693
+ instanceIdRef.current = ++measurerInstanceCount;
4694
+ }
4695
+ const storageKey = persistKey ?? (instanceIdRef.current === 1 ? "mesurer-state" : `mesurer-state-${instanceIdRef.current}`);
4696
+ const ownerDocument = portalTarget.ownerDocument ?? document;
4697
+ const ownerWindow = ownerDocument.defaultView ?? window;
4698
+ const guideScrollRef = (0, import_react20.useRef)({
4699
+ x: ownerWindow.scrollX,
4700
+ y: ownerWindow.scrollY
4701
+ });
4702
+ const textInspectorRef = (0, import_react20.useRef)(null);
4703
+ if (!textInspectorRef.current) {
4704
+ textInspectorRef.current = createTextInspector({ portalTarget });
4705
+ }
4706
+ const textInspector = textInspectorRef.current;
4707
+ const selectionRectRef = (0, import_react20.useRef)(null);
4708
+ const toolbarRef = (0, import_react20.useRef)(null);
4709
+ const selectionAnimationCleanupTimeoutRef = (0, import_react20.useRef)(null);
4710
+ const guideDragRef = (0, import_react20.useRef)(null);
4711
+ const guideUserSelectRef = (0, import_react20.useRef)(null);
4712
+ const persistedState = (0, import_react20.useMemo)(() => {
3294
4713
  if (!persistOnReload) return null;
3295
- const stored = window.localStorage.getItem("mesurer-state");
3296
- if (!stored) return null;
3297
4714
  try {
4715
+ const stored = window.localStorage.getItem(storageKey);
4716
+ if (!stored) return null;
3298
4717
  const parsed = JSON.parse(stored);
3299
4718
  if (!parsed || parsed.version !== 1) return null;
3300
4719
  return parsed;
3301
4720
  } catch {
3302
4721
  return null;
3303
4722
  }
3304
- }, [persistOnReload]);
4723
+ }, [persistOnReload, storageKey]);
4724
+ const enabledRef = (0, import_react20.useRef)(false);
4725
+ const toolModeRef = (0, import_react20.useRef)(
4726
+ persistedState?.toolMode === "rulers" ? "none" : persistedState?.toolMode ?? "none"
4727
+ );
4728
+ const rulersVisibleRef = (0, import_react20.useRef)(
4729
+ persistedState?.rulersVisible ?? persistedState?.toolMode === "rulers"
4730
+ );
4731
+ const guideOrientationRef = (0, import_react20.useRef)(
4732
+ persistedState?.guideOrientation ?? "vertical"
4733
+ );
4734
+ const measurementsRef = (0, import_react20.useRef)(
4735
+ persistedState?.measurements ?? []
4736
+ );
4737
+ const activeMeasurementRef = (0, import_react20.useRef)(
4738
+ persistedState?.activeMeasurement ?? null
4739
+ );
4740
+ const heldDistancesRef = (0, import_react20.useRef)(
4741
+ persistedState?.heldDistances ?? []
4742
+ );
4743
+ const guidesRef = (0, import_react20.useRef)(persistedState?.guides ?? []);
4744
+ const selectedGuideIdsRef = (0, import_react20.useRef)(
4745
+ persistedState?.selectedGuideIds ?? []
4746
+ );
3305
4747
  const { overlayRef, selectedElementRef, hoverElementRef } = useOverlayRefs();
3306
4748
  const {
3307
4749
  selectionOriginRect,
@@ -3327,12 +4769,15 @@ function MeasurerClient({
3327
4769
  setAltPressed,
3328
4770
  toolMode,
3329
4771
  setToolMode,
4772
+ rulersVisible,
4773
+ setRulersVisible,
3330
4774
  guidesEnabled,
3331
4775
  multiMeasureEnabled,
3332
4776
  snapGuidesEnabled
3333
4777
  } = useMeasureToggles({
3334
4778
  initialEnabled: persistedState?.enabled,
3335
- initialToolMode: persistedState?.toolMode
4779
+ initialToolMode: persistedState?.toolMode === "rulers" ? "none" : persistedState?.toolMode,
4780
+ initialRulersVisible: persistedState?.rulersVisible ?? persistedState?.toolMode === "rulers"
3336
4781
  });
3337
4782
  const { start, setStart, end, setEnd, isDragging, setIsDragging } = useDragState();
3338
4783
  const {
@@ -3364,85 +4809,170 @@ function MeasurerClient({
3364
4809
  initialGuides: persistedState?.guides ?? [],
3365
4810
  initialSelectedGuideIds: persistedState?.selectedGuideIds ?? []
3366
4811
  });
3367
- const [toolbarActive, setToolbarActive] = (0, import_react19.useState)(true);
3368
- const { clearGuideDragHold, scheduleGuideDragHold } = useGuideDragHold();
3369
- const [guidePreview, setGuidePreview] = (0, import_react19.useState)(null);
3370
- const [guideOrientation, setGuideOrientation] = (0, import_react19.useState)(persistedState?.guideOrientation ?? "vertical");
3371
- const persistPayload = (0, import_react19.useMemo)(() => {
3372
- if (!persistOnReload) return null;
3373
- return JSON.stringify({
3374
- version: 1,
3375
- enabled,
3376
- toolMode,
3377
- guideOrientation,
3378
- guides,
3379
- selectedGuideIds,
3380
- measurements: measurements.map(stripMeasurement),
3381
- activeMeasurement: activeMeasurement ? stripMeasurement(activeMeasurement) : null,
3382
- heldDistances: heldDistances.map(stripDistance)
3383
- });
3384
- }, [
3385
- activeMeasurement,
3386
- enabled,
3387
- guideOrientation,
3388
- guides,
3389
- heldDistances,
3390
- measurements,
3391
- persistOnReload,
3392
- selectedGuideIds,
3393
- toolMode
3394
- ]);
3395
- (0, import_react19.useEffect)(() => {
4812
+ const [toolbarActive, setToolbarActive] = (0, import_react20.useState)(true);
4813
+ const { clearGuideDragHold, scheduleGuideDragHold } = useGuideDragHold(ownerWindow);
4814
+ const [guidePreview, setGuidePreview] = (0, import_react20.useState)(null);
4815
+ const [guideOrientation, setGuideOrientation] = (0, import_react20.useState)(persistedState?.guideOrientation ?? "vertical");
4816
+ enabledRef.current = enabled;
4817
+ toolModeRef.current = toolMode;
4818
+ rulersVisibleRef.current = rulersVisible;
4819
+ guideOrientationRef.current = guideOrientation;
4820
+ measurementsRef.current = measurements;
4821
+ activeMeasurementRef.current = activeMeasurement;
4822
+ heldDistancesRef.current = heldDistances;
4823
+ guidesRef.current = guides;
4824
+ selectedGuideIdsRef.current = selectedGuideIds;
4825
+ const persistState = (0, import_react20.useCallback)(() => {
3396
4826
  if (!persistOnReload) return;
3397
4827
  if (typeof window === "undefined") return;
3398
- if (!persistPayload) return;
3399
4828
  try {
3400
- window.localStorage.setItem("mesurer-state", persistPayload);
4829
+ window.localStorage.setItem(
4830
+ storageKey,
4831
+ JSON.stringify({
4832
+ version: 1,
4833
+ enabled: enabledRef.current,
4834
+ toolMode: toolModeRef.current,
4835
+ rulersVisible: rulersVisibleRef.current,
4836
+ guideOrientation: guideOrientationRef.current,
4837
+ guides: guidesRef.current,
4838
+ selectedGuideIds: selectedGuideIdsRef.current,
4839
+ measurements: measurementsRef.current.map(stripMeasurement),
4840
+ activeMeasurement: activeMeasurementRef.current ? stripMeasurement(activeMeasurementRef.current) : null,
4841
+ heldDistances: heldDistancesRef.current.map(stripDistance)
4842
+ })
4843
+ );
3401
4844
  } catch {
3402
4845
  }
3403
- const handlePageHide = () => {
3404
- try {
3405
- window.localStorage.setItem("mesurer-state", persistPayload);
3406
- } catch {
4846
+ }, [persistOnReload, storageKey]);
4847
+ const setEnabledPersisted = (0, import_react20.useCallback)(
4848
+ (value) => {
4849
+ const next = typeof value === "function" ? value(enabledRef.current) : value;
4850
+ if (Object.is(next, enabledRef.current)) return;
4851
+ enabledRef.current = next;
4852
+ setEnabled(next);
4853
+ persistState();
4854
+ },
4855
+ [persistState, setEnabled]
4856
+ );
4857
+ const setRulersVisiblePersisted = (0, import_react20.useCallback)(
4858
+ (value) => {
4859
+ const next = typeof value === "function" ? value(rulersVisibleRef.current) : value;
4860
+ if (Object.is(next, rulersVisibleRef.current)) return;
4861
+ rulersVisibleRef.current = next;
4862
+ setRulersVisible(next);
4863
+ persistState();
4864
+ },
4865
+ [persistState, setRulersVisible]
4866
+ );
4867
+ const setToolModePersisted = (0, import_react20.useCallback)(
4868
+ (value) => {
4869
+ const next = typeof value === "function" ? value(toolModeRef.current) : value;
4870
+ if (Object.is(next, toolModeRef.current)) return;
4871
+ toolModeRef.current = next;
4872
+ setToolMode(next);
4873
+ if (next === "text-inspector") {
4874
+ textInspector.enable();
4875
+ } else {
4876
+ textInspector.disable();
3407
4877
  }
3408
- };
3409
- window.addEventListener("pagehide", handlePageHide);
3410
- return () => {
3411
- window.removeEventListener("pagehide", handlePageHide);
3412
- };
3413
- }, [persistOnReload, persistPayload]);
4878
+ persistState();
4879
+ },
4880
+ [persistState, setToolMode, textInspector]
4881
+ );
4882
+ const setGuideOrientationPersisted = (0, import_react20.useCallback)(
4883
+ (value) => {
4884
+ const next = typeof value === "function" ? value(guideOrientationRef.current) : value;
4885
+ if (Object.is(next, guideOrientationRef.current)) return;
4886
+ guideOrientationRef.current = next;
4887
+ setGuideOrientation(next);
4888
+ persistState();
4889
+ },
4890
+ [persistState, setGuideOrientation]
4891
+ );
4892
+ const setMeasurementsPersisted = (0, import_react20.useCallback)(
4893
+ (value) => {
4894
+ const next = typeof value === "function" ? value(measurementsRef.current) : value;
4895
+ if (Object.is(next, measurementsRef.current)) return;
4896
+ measurementsRef.current = next;
4897
+ setMeasurements(next);
4898
+ persistState();
4899
+ },
4900
+ [persistState, setMeasurements]
4901
+ );
4902
+ const setActiveMeasurementPersisted = (0, import_react20.useCallback)(
4903
+ (value) => {
4904
+ const next = typeof value === "function" ? value(activeMeasurementRef.current) : value;
4905
+ if (Object.is(next, activeMeasurementRef.current)) return;
4906
+ activeMeasurementRef.current = next;
4907
+ setActiveMeasurement(next);
4908
+ persistState();
4909
+ },
4910
+ [persistState, setActiveMeasurement]
4911
+ );
4912
+ const setHeldDistancesPersisted = (0, import_react20.useCallback)(
4913
+ (value) => {
4914
+ const next = typeof value === "function" ? value(heldDistancesRef.current) : value;
4915
+ if (Object.is(next, heldDistancesRef.current)) return;
4916
+ heldDistancesRef.current = next;
4917
+ setHeldDistances(next);
4918
+ persistState();
4919
+ },
4920
+ [persistState, setHeldDistances]
4921
+ );
4922
+ const setGuidesPersisted = (0, import_react20.useCallback)(
4923
+ (value) => {
4924
+ const next = typeof value === "function" ? value(guidesRef.current) : value;
4925
+ if (Object.is(next, guidesRef.current)) return;
4926
+ guidesRef.current = next;
4927
+ setGuides(next);
4928
+ persistState();
4929
+ },
4930
+ [persistState, setGuides]
4931
+ );
4932
+ const setSelectedGuideIdsPersisted = (0, import_react20.useCallback)(
4933
+ (value) => {
4934
+ const next = typeof value === "function" ? value(selectedGuideIdsRef.current) : value;
4935
+ if (Object.is(next, selectedGuideIdsRef.current)) return;
4936
+ selectedGuideIdsRef.current = next;
4937
+ setSelectedGuideIds(next);
4938
+ persistState();
4939
+ },
4940
+ [persistState, setSelectedGuideIds]
4941
+ );
3414
4942
  const {
3415
4943
  recordSnapshot,
3416
4944
  createActionCommit,
3417
4945
  setToolModeWithHistory,
3418
4946
  setGuideOrientationWithHistory,
3419
- setEnabledWithHistory
4947
+ setEnabledWithHistory,
4948
+ undo: undoHistory,
4949
+ redo: redoHistory
3420
4950
  } = useMeasurerHistory({
3421
4951
  toggles: {
3422
4952
  enabled,
3423
- setEnabled,
4953
+ setEnabled: setEnabledPersisted,
3424
4954
  toolMode,
3425
- setToolMode,
4955
+ setToolMode: setToolModePersisted,
3426
4956
  guideOrientation,
3427
- setGuideOrientation
4957
+ setGuideOrientation: setGuideOrientationPersisted
3428
4958
  },
3429
4959
  measurements: {
3430
4960
  measurements,
3431
- setMeasurements,
4961
+ setMeasurements: setMeasurementsPersisted,
3432
4962
  activeMeasurement,
3433
- setActiveMeasurement,
4963
+ setActiveMeasurement: setActiveMeasurementPersisted,
3434
4964
  selectedMeasurements,
3435
4965
  setSelectedMeasurements,
3436
4966
  selectedMeasurement,
3437
4967
  setSelectedMeasurement,
3438
4968
  heldDistances,
3439
- setHeldDistances
4969
+ setHeldDistances: setHeldDistancesPersisted
3440
4970
  },
3441
4971
  guides: {
3442
4972
  guides,
3443
- setGuides,
4973
+ setGuides: setGuidesPersisted,
3444
4974
  selectedGuideIds,
3445
- setSelectedGuideIds,
4975
+ setSelectedGuideIds: setSelectedGuideIdsPersisted,
3446
4976
  draggingGuideId,
3447
4977
  setDraggingGuideId
3448
4978
  },
@@ -3457,96 +4987,254 @@ function MeasurerClient({
3457
4987
  clearSelectionRect
3458
4988
  }
3459
4989
  });
3460
- const clearAll = (0, import_react19.useCallback)(() => {
4990
+ const undo = (0, import_react20.useCallback)(() => {
4991
+ if (toolMode === "text-inspector" && textInspector.undo()) return;
4992
+ undoHistory();
4993
+ }, [textInspector, toolMode, undoHistory]);
4994
+ const redo = (0, import_react20.useCallback)(() => {
4995
+ if (toolMode === "text-inspector" && textInspector.redo()) return;
4996
+ redoHistory();
4997
+ }, [redoHistory, textInspector, toolMode]);
4998
+ const clearAll = (0, import_react20.useCallback)(() => {
4999
+ if (toolMode === "text-inspector") {
5000
+ textInspector.clear();
5001
+ }
3461
5002
  recordSnapshot();
3462
5003
  clearGuideDragHold();
3463
5004
  setStart(null);
3464
5005
  setEnd(null);
3465
5006
  setIsDragging(false);
3466
- setActiveMeasurement(null);
3467
- setMeasurements([]);
5007
+ setActiveMeasurementPersisted(null);
5008
+ setMeasurementsPersisted([]);
3468
5009
  setSelectedMeasurement(null);
3469
5010
  setSelectedMeasurements([]);
3470
5011
  clearSelectionRect();
3471
5012
  setSelectedElement(null);
3472
5013
  setHoverRect(null);
3473
5014
  setHoverElement(null);
3474
- setGuides([]);
3475
- setSelectedGuideIds([]);
3476
- setHeldDistances([]);
5015
+ setGuidesPersisted([]);
5016
+ setSelectedGuideIdsPersisted([]);
5017
+ setHeldDistancesPersisted([]);
3477
5018
  }, [
3478
5019
  clearGuideDragHold,
3479
5020
  clearSelectionRect,
3480
5021
  recordSnapshot,
3481
- setActiveMeasurement,
5022
+ setActiveMeasurementPersisted,
3482
5023
  setEnd,
3483
- setGuides,
3484
- setHeldDistances,
5024
+ setGuidesPersisted,
5025
+ setHeldDistancesPersisted,
3485
5026
  setHoverElement,
3486
5027
  setHoverRect,
3487
5028
  setIsDragging,
3488
- setMeasurements,
5029
+ setMeasurementsPersisted,
3489
5030
  setSelectedElement,
3490
- setSelectedGuideIds,
5031
+ setSelectedGuideIdsPersisted,
3491
5032
  setSelectedMeasurement,
3492
5033
  setSelectedMeasurements,
3493
- setStart
5034
+ setStart,
5035
+ textInspector,
5036
+ toolMode
3494
5037
  ]);
3495
- const removeSelectedGuides = (0, import_react19.useCallback)(() => {
5038
+ const removeSelectedGuides = (0, import_react20.useCallback)(() => {
3496
5039
  if (selectedGuideIds.length === 0) return false;
3497
5040
  recordSnapshot();
3498
- setGuides(
5041
+ setGuidesPersisted(
3499
5042
  (prev) => prev.filter((guide) => !selectedGuideIds.includes(guide.id))
3500
5043
  );
3501
- setSelectedGuideIds([]);
5044
+ setSelectedGuideIdsPersisted([]);
3502
5045
  return true;
3503
- }, [recordSnapshot, selectedGuideIds, setGuides, setSelectedGuideIds]);
5046
+ }, [
5047
+ recordSnapshot,
5048
+ selectedGuideIds,
5049
+ setGuidesPersisted,
5050
+ setSelectedGuideIdsPersisted
5051
+ ]);
3504
5052
  useHotkeys({
5053
+ eventTarget: ownerWindow,
3505
5054
  clearAll,
5055
+ undo,
5056
+ redo,
3506
5057
  removeSelectedGuides,
3507
5058
  setEnabled: setEnabledWithHistory,
3508
5059
  setToolMode: setToolModeWithHistory,
5060
+ setRulersVisible: setRulersVisiblePersisted,
3509
5061
  setAltPressed,
3510
5062
  isOverlayActive: () => enabled && (toolMode !== "none" || toolbarActive),
3511
5063
  setGuideOrientation: setGuideOrientationWithHistory,
3512
5064
  onInteract: () => setToolbarActive(true)
3513
5065
  });
3514
5066
  useResizeSync({
3515
- setMeasurements,
3516
- setActiveMeasurement,
3517
- setHeldDistances,
5067
+ document: ownerDocument,
5068
+ window: ownerWindow,
5069
+ setMeasurements: setMeasurementsPersisted,
5070
+ setActiveMeasurement: setActiveMeasurementPersisted,
5071
+ setHeldDistances: setHeldDistancesPersisted,
3518
5072
  setSelectedMeasurement,
3519
- setGuides,
5073
+ setGuides: setGuidesPersisted,
3520
5074
  selectedElementRef
3521
5075
  });
5076
+ (0, import_react20.useEffect)(() => {
5077
+ const handleScroll = () => {
5078
+ const next = {
5079
+ x: ownerWindow.scrollX,
5080
+ y: ownerWindow.scrollY
5081
+ };
5082
+ const deltaX = next.x - guideScrollRef.current.x;
5083
+ const deltaY = next.y - guideScrollRef.current.y;
5084
+ guideScrollRef.current = next;
5085
+ if (deltaX === 0 && deltaY === 0) return;
5086
+ setGuidesPersisted(
5087
+ (prev) => prev.map((guide) => ({
5088
+ ...guide,
5089
+ position: guide.position - (guide.orientation === "vertical" ? deltaX : deltaY)
5090
+ }))
5091
+ );
5092
+ };
5093
+ ownerWindow.addEventListener("scroll", handleScroll, true);
5094
+ return () => ownerWindow.removeEventListener("scroll", handleScroll, true);
5095
+ }, [ownerWindow, setGuidesPersisted]);
3522
5096
  useLiveElementTracking({
5097
+ document: ownerDocument,
5098
+ window: ownerWindow,
3523
5099
  enabled,
3524
5100
  selectedElementRef,
3525
5101
  hoverElementRef,
3526
5102
  setSelectedMeasurement,
3527
5103
  setSelectedMeasurements,
3528
5104
  setHoverRect,
3529
- setMeasurements,
3530
- setActiveMeasurement,
3531
- setHeldDistances
5105
+ setMeasurements: setMeasurementsPersisted,
5106
+ setActiveMeasurement: setActiveMeasurementPersisted,
5107
+ setHeldDistances: setHeldDistancesPersisted
3532
5108
  });
3533
- (0, import_react19.useEffect)(() => {
5109
+ (0, import_react20.useEffect)(() => {
3534
5110
  if (!toolbarActive || toolMode !== "none") return;
3535
5111
  const handlePointerDown2 = (event) => {
3536
5112
  const toolbarNode = toolbarRef.current;
3537
5113
  if (toolbarNode && toolbarNode.contains(event.target)) return;
3538
5114
  setToolbarActive(false);
3539
5115
  };
3540
- window.addEventListener("pointerdown", handlePointerDown2);
5116
+ ownerWindow.addEventListener("pointerdown", handlePointerDown2);
3541
5117
  return () => {
3542
- window.removeEventListener("pointerdown", handlePointerDown2);
5118
+ ownerWindow.removeEventListener("pointerdown", handlePointerDown2);
3543
5119
  };
3544
- }, [toolbarActive, toolMode]);
3545
- (0, import_react19.useEffect)(() => {
3546
- if (hoverHighlightEnabled) return;
3547
- setHoverRect(null);
3548
- }, [hoverHighlightEnabled, setHoverRect]);
3549
- (0, import_react19.useEffect)(() => {
5120
+ }, [ownerWindow, toolbarActive, toolMode]);
5121
+ (0, import_react20.useEffect)(() => {
5122
+ if (!enabled) return;
5123
+ const handleGuidePointerDown2 = (event) => {
5124
+ if (toolbarRef.current?.contains(event.target)) return;
5125
+ const guideTarget = event.composedPath().some(
5126
+ (target) => target instanceof ownerWindow.Element && target.hasAttribute("data-mesurer-guide")
5127
+ );
5128
+ if (guideTarget && toolMode !== "none") return;
5129
+ const point = { x: event.clientX, y: event.clientY };
5130
+ const guide = guides.find((candidate) => {
5131
+ const distance = candidate.orientation === "vertical" ? Math.abs(candidate.position - point.x) : Math.abs(candidate.position - point.y);
5132
+ return distance <= GUIDE_HITBOX_SIZE / 2;
5133
+ });
5134
+ if (!guide) return;
5135
+ if (event.button === 0 && !event.shiftKey && toolMode === "none") {
5136
+ guideDragRef.current = {
5137
+ id: guide.id,
5138
+ orientation: guide.orientation,
5139
+ pointerId: event.pointerId,
5140
+ commit: createActionCommit(),
5141
+ committed: false
5142
+ };
5143
+ }
5144
+ setSelectedGuideIdsPersisted(
5145
+ (prev) => event.shiftKey ? prev.includes(guide.id) ? prev.filter((id) => id !== guide.id) : [...prev, guide.id] : [guide.id]
5146
+ );
5147
+ };
5148
+ const handleGuidePointerMove = (event) => {
5149
+ const drag = guideDragRef.current;
5150
+ if (!drag || drag.pointerId !== event.pointerId) return;
5151
+ const position = drag.orientation === "vertical" ? event.clientX : event.clientY;
5152
+ if (!drag.committed) {
5153
+ event.preventDefault();
5154
+ if (guideUserSelectRef.current === null) {
5155
+ guideUserSelectRef.current = ownerDocument.documentElement.style.userSelect;
5156
+ ownerDocument.documentElement.style.userSelect = "none";
5157
+ }
5158
+ ownerWindow.getSelection()?.removeAllRanges();
5159
+ drag.commit();
5160
+ drag.committed = true;
5161
+ }
5162
+ setGuidesPersisted(
5163
+ (prev) => prev.map(
5164
+ (guide) => guide.id === drag.id ? { ...guide, position } : guide
5165
+ )
5166
+ );
5167
+ };
5168
+ const handleGuidePointerEnd = (event) => {
5169
+ if (guideDragRef.current?.pointerId === event.pointerId) {
5170
+ guideDragRef.current = null;
5171
+ if (guideUserSelectRef.current !== null) {
5172
+ ownerDocument.documentElement.style.userSelect = guideUserSelectRef.current;
5173
+ guideUserSelectRef.current = null;
5174
+ }
5175
+ }
5176
+ };
5177
+ ownerWindow.addEventListener("pointerdown", handleGuidePointerDown2, true);
5178
+ ownerWindow.addEventListener("pointermove", handleGuidePointerMove, true);
5179
+ ownerWindow.addEventListener("pointerup", handleGuidePointerEnd, true);
5180
+ ownerWindow.addEventListener("pointercancel", handleGuidePointerEnd, true);
5181
+ return () => {
5182
+ ownerWindow.removeEventListener(
5183
+ "pointerdown",
5184
+ handleGuidePointerDown2,
5185
+ true
5186
+ );
5187
+ ownerWindow.removeEventListener("pointermove", handleGuidePointerMove, true);
5188
+ ownerWindow.removeEventListener("pointerup", handleGuidePointerEnd, true);
5189
+ ownerWindow.removeEventListener(
5190
+ "pointercancel",
5191
+ handleGuidePointerEnd,
5192
+ true
5193
+ );
5194
+ if (guideUserSelectRef.current !== null) {
5195
+ ownerDocument.documentElement.style.userSelect = guideUserSelectRef.current;
5196
+ guideUserSelectRef.current = null;
5197
+ }
5198
+ };
5199
+ }, [
5200
+ createActionCommit,
5201
+ enabled,
5202
+ guides,
5203
+ ownerWindow,
5204
+ setGuidesPersisted,
5205
+ setSelectedGuideIdsPersisted,
5206
+ toolMode
5207
+ ]);
5208
+ (0, import_react20.useEffect)(() => {
5209
+ if (toolMode === "text-inspector") {
5210
+ textInspector.enable();
5211
+ } else {
5212
+ textInspector.disable();
5213
+ }
5214
+ }, [textInspector, toolMode]);
5215
+ (0, import_react20.useEffect)(() => {
5216
+ let style = ownerDocument.getElementById(XRAY_STYLE_ID);
5217
+ if (!style) {
5218
+ style = ownerDocument.createElement("style");
5219
+ style.id = XRAY_STYLE_ID;
5220
+ style.textContent = XRAY_STYLES;
5221
+ ownerDocument.head.appendChild(style);
5222
+ }
5223
+ if (toolMode === "xray") {
5224
+ ownerDocument.body.classList.add("xray-mode");
5225
+ } else {
5226
+ ownerDocument.body.classList.remove("xray-mode");
5227
+ }
5228
+ return () => {
5229
+ ownerDocument.body.classList.remove("xray-mode");
5230
+ };
5231
+ }, [ownerDocument, toolMode]);
5232
+ (0, import_react20.useEffect)(() => {
5233
+ return () => {
5234
+ textInspector.destroy();
5235
+ };
5236
+ }, [textInspector]);
5237
+ (0, import_react20.useEffect)(() => {
3550
5238
  const hasSelectionAnimationState = !!selectionOriginRect || !!selectedMeasurement?.originRect || selectedMeasurements.some((measurement) => !!measurement.originRect);
3551
5239
  if (!hasSelectionAnimationState) {
3552
5240
  if (selectionAnimationCleanupTimeoutRef.current !== null) {
@@ -3575,6 +5263,12 @@ function MeasurerClient({
3575
5263
  return changed ? next : prev;
3576
5264
  });
3577
5265
  }, MEASURE_TRANSITION_MS);
5266
+ return () => {
5267
+ if (selectionAnimationCleanupTimeoutRef.current !== null) {
5268
+ window.clearTimeout(selectionAnimationCleanupTimeoutRef.current);
5269
+ selectionAnimationCleanupTimeoutRef.current = null;
5270
+ }
5271
+ };
3578
5272
  }, [
3579
5273
  selectionOriginRect,
3580
5274
  selectedMeasurement,
@@ -3583,13 +5277,6 @@ function MeasurerClient({
3583
5277
  setSelectedMeasurements,
3584
5278
  setSelectionOriginRect
3585
5279
  ]);
3586
- (0, import_react19.useEffect)(() => {
3587
- return () => {
3588
- if (selectionAnimationCleanupTimeoutRef.current !== null) {
3589
- window.clearTimeout(selectionAnimationCleanupTimeoutRef.current);
3590
- }
3591
- };
3592
- }, []);
3593
5280
  const displayedMeasurements = holdEnabled ? measurements : multiMeasureEnabled && measurements.length > 0 ? measurements : activeMeasurement ? [activeMeasurement] : [];
3594
5281
  const {
3595
5282
  activeRect,
@@ -3611,6 +5298,8 @@ function MeasurerClient({
3611
5298
  hoverEdgeVisibility,
3612
5299
  measurementEdgeVisibility
3613
5300
  } = useMeasurerDerived({
5301
+ document: ownerDocument,
5302
+ window: ownerWindow,
3614
5303
  start,
3615
5304
  end,
3616
5305
  selectedMeasurements,
@@ -3636,6 +5325,8 @@ function MeasurerClient({
3636
5325
  handlePointerUp,
3637
5326
  handlePointerLeave
3638
5327
  } = useMeasurerPointer({
5328
+ document: ownerDocument,
5329
+ window: ownerWindow,
3639
5330
  toolbarRef,
3640
5331
  overlayRef,
3641
5332
  selectionRectRef,
@@ -3661,15 +5352,15 @@ function MeasurerClient({
3661
5352
  optionPairOverlay,
3662
5353
  setAltPressed,
3663
5354
  setGuidePreview,
3664
- setSelectedGuideIds,
3665
- setGuides,
5355
+ setSelectedGuideIds: setSelectedGuideIdsPersisted,
5356
+ setGuides: setGuidesPersisted,
3666
5357
  setStart,
3667
5358
  setEnd,
3668
5359
  setIsDragging,
3669
- setHeldDistances,
5360
+ setHeldDistances: setHeldDistancesPersisted,
3670
5361
  setDraggingGuideId,
3671
- setActiveMeasurement,
3672
- setMeasurements,
5362
+ setActiveMeasurement: setActiveMeasurementPersisted,
5363
+ setMeasurements: setMeasurementsPersisted,
3673
5364
  setSelectedMeasurements,
3674
5365
  setSelectedMeasurement,
3675
5366
  setSelectionOriginRect,
@@ -3679,27 +5370,65 @@ function MeasurerClient({
3679
5370
  setHoverPointer,
3680
5371
  clearSelectionRect
3681
5372
  });
3682
- const removeHeldDistance = (0, import_react19.useCallback)(
5373
+ const removeHeldDistance = (0, import_react20.useCallback)(
3683
5374
  (id) => {
3684
5375
  recordSnapshot();
3685
- setHeldDistances((prev) => prev.filter((distance) => distance.id !== id));
5376
+ setHeldDistancesPersisted(
5377
+ (prev) => prev.filter((distance) => distance.id !== id)
5378
+ );
5379
+ },
5380
+ [recordSnapshot, setHeldDistancesPersisted]
5381
+ );
5382
+ const startGuideFromRuler = (0, import_react20.useCallback)(
5383
+ (orientation, position) => {
5384
+ const id = createId();
5385
+ const commit = createActionCommit();
5386
+ commit();
5387
+ setSelectedGuideIdsPersisted([]);
5388
+ setGuidesPersisted((prev) => [...prev, { id, orientation, position }]);
5389
+ return id;
5390
+ },
5391
+ [
5392
+ createActionCommit,
5393
+ setGuidesPersisted,
5394
+ setSelectedGuideIdsPersisted
5395
+ ]
5396
+ );
5397
+ const moveGuideFromRuler = (0, import_react20.useCallback)(
5398
+ (id, position) => {
5399
+ setGuidesPersisted(
5400
+ (prev) => prev.map((guide) => guide.id === id ? { ...guide, position } : guide)
5401
+ );
3686
5402
  },
3687
- [recordSnapshot, setHeldDistances]
5403
+ [setGuidesPersisted]
3688
5404
  );
3689
- const handleGuidePointerDown = (0, import_react19.useCallback)(
5405
+ const finishGuideFromRuler = (0, import_react20.useCallback)(
5406
+ (id) => {
5407
+ setSelectedGuideIdsPersisted([id]);
5408
+ },
5409
+ [setSelectedGuideIdsPersisted]
5410
+ );
5411
+ const cancelGuideFromRuler = (0, import_react20.useCallback)(
5412
+ (id) => {
5413
+ setGuidesPersisted((prev) => prev.filter((guide) => guide.id !== id));
5414
+ },
5415
+ [setGuidesPersisted]
5416
+ );
5417
+ const handleGuidePointerDown = (0, import_react20.useCallback)(
3690
5418
  (guide, event) => {
3691
5419
  const commit = createActionCommit();
3692
5420
  if (!enabled) return;
3693
5421
  event.stopPropagation();
5422
+ event.preventDefault();
3694
5423
  if (event.shiftKey) {
3695
5424
  commit();
3696
- setSelectedGuideIds(
5425
+ setSelectedGuideIdsPersisted(
3697
5426
  (prev) => prev.includes(guide.id) ? prev.filter((id) => id !== guide.id) : [...prev, guide.id]
3698
5427
  );
3699
5428
  return;
3700
5429
  }
3701
5430
  commit();
3702
- setSelectedGuideIds([guide.id]);
5431
+ setSelectedGuideIdsPersisted([guide.id]);
3703
5432
  scheduleGuideDragHold(guide.id, setDraggingGuideId);
3704
5433
  event.currentTarget.setPointerCapture(event.pointerId);
3705
5434
  },
@@ -3708,10 +5437,10 @@ function MeasurerClient({
3708
5437
  enabled,
3709
5438
  scheduleGuideDragHold,
3710
5439
  setDraggingGuideId,
3711
- setSelectedGuideIds
5440
+ setSelectedGuideIdsPersisted
3712
5441
  ]
3713
5442
  );
3714
- const handleGuidePointerUp = (0, import_react19.useCallback)(
5443
+ const handleGuidePointerUp = (0, import_react20.useCallback)(
3715
5444
  (guide, event) => {
3716
5445
  event.stopPropagation();
3717
5446
  clearGuideDragHold();
@@ -3723,17 +5452,29 @@ function MeasurerClient({
3723
5452
  [clearGuideDragHold, setDraggingGuideId]
3724
5453
  );
3725
5454
  return (0, import_react_dom.createPortal)(
3726
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
5455
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3727
5456
  "div",
3728
5457
  {
3729
5458
  ref: overlayRef,
3730
5459
  className: "mesurer-root msr:pointer-events-none msr:fixed msr:inset-0 msr:z-50",
3731
5460
  children: [
3732
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5461
+ enabled && rulersVisible ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5462
+ RulersOverlay,
5463
+ {
5464
+ onStartGuide: startGuideFromRuler,
5465
+ onMoveGuide: moveGuideFromRuler,
5466
+ onFinishGuide: finishGuideFromRuler,
5467
+ onCancelGuide: cancelGuideFromRuler,
5468
+ guides,
5469
+ selectedGuideIds
5470
+ }
5471
+ ) : null,
5472
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3733
5473
  MeasurerOverlay,
3734
5474
  {
3735
5475
  enabled,
3736
5476
  toolMode,
5477
+ guidePointerEvents: enabled && (toolMode !== "none" || rulersVisible),
3737
5478
  guidesEnabled,
3738
5479
  altPressed,
3739
5480
  isDragging,
@@ -3771,13 +5512,16 @@ function MeasurerClient({
3771
5512
  onGuidePointerCancel: handleGuidePointerUp
3772
5513
  }
3773
5514
  ),
3774
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5515
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3775
5516
  Toolbar,
3776
5517
  {
3777
5518
  ref: toolbarRef,
5519
+ eventTarget: ownerWindow,
3778
5520
  toolMode,
3779
5521
  setEnabled: setEnabledWithHistory,
3780
5522
  setToolMode: setToolModeWithHistory,
5523
+ rulersVisible,
5524
+ setRulersVisible: setRulersVisiblePersisted,
3781
5525
  guideOrientation,
3782
5526
  setGuideOrientation: setGuideOrientationWithHistory,
3783
5527
  onInteract: () => setToolbarActive(true)
@@ -3794,25 +5538,29 @@ function Measurer({
3794
5538
  guideColor = "oklch(0.63 0.26 29.23)",
3795
5539
  hoverHighlightEnabled = true,
3796
5540
  persistOnReload = false,
3797
- portalTarget
5541
+ portalTarget,
5542
+ persistKey
3798
5543
  }) {
3799
5544
  if (typeof document !== "undefined") {
3800
5545
  ensureMeasurerStyles(MESURER_STYLES, portalTarget);
3801
5546
  }
3802
5547
  const hydrated = useHydrated();
3803
5548
  if (!hydrated) return null;
3804
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5549
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3805
5550
  MeasurerClient,
3806
5551
  {
3807
5552
  highlightColor,
3808
5553
  guideColor,
3809
5554
  hoverHighlightEnabled,
3810
5555
  persistOnReload,
5556
+ persistKey,
3811
5557
  portalTarget: portalTarget ?? document.body
3812
5558
  }
3813
5559
  );
3814
5560
  }
3815
5561
  // Annotate the CommonJS export names for ESM import in node:
3816
5562
  0 && (module.exports = {
3817
- Measurer
5563
+ Measurer,
5564
+ TextInspector,
5565
+ createTextInspector
3818
5566
  });