cursedbelt 5.45.0 → 5.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/react/canvas-hud/CanvasMinimap.d.ts +32 -0
  2. package/dist/react/canvas-hud/CanvasMinimap.d.ts.map +1 -0
  3. package/dist/react/canvas-hud/CanvasMinimap.js +32 -0
  4. package/dist/react/canvas-hud/CanvasMinimap.js.map +1 -0
  5. package/dist/react/canvas-hud/CanvasNodeMenu.d.ts +34 -0
  6. package/dist/react/canvas-hud/CanvasNodeMenu.d.ts.map +1 -0
  7. package/dist/react/canvas-hud/CanvasNodeMenu.js +21 -0
  8. package/dist/react/canvas-hud/CanvasNodeMenu.js.map +1 -0
  9. package/dist/react/canvas-hud/CanvasSearchField.d.ts +27 -0
  10. package/dist/react/canvas-hud/CanvasSearchField.d.ts.map +1 -0
  11. package/dist/react/canvas-hud/CanvasSearchField.js +35 -0
  12. package/dist/react/canvas-hud/CanvasSearchField.js.map +1 -0
  13. package/dist/react/canvas-hud/HudControls.d.ts +43 -0
  14. package/dist/react/canvas-hud/HudControls.d.ts.map +1 -0
  15. package/dist/react/canvas-hud/HudControls.js +22 -0
  16. package/dist/react/canvas-hud/HudControls.js.map +1 -0
  17. package/dist/react/canvas-hud/index.d.ts +13 -0
  18. package/dist/react/canvas-hud/index.d.ts.map +1 -0
  19. package/dist/react/canvas-hud/index.js +7 -0
  20. package/dist/react/canvas-hud/index.js.map +1 -0
  21. package/dist/react/canvas-hud/useCanvasWheel.d.ts +41 -0
  22. package/dist/react/canvas-hud/useCanvasWheel.d.ts.map +1 -0
  23. package/dist/react/canvas-hud/useCanvasWheel.js +65 -0
  24. package/dist/react/canvas-hud/useCanvasWheel.js.map +1 -0
  25. package/dist/react/canvas-hud/viewport.d.ts +68 -0
  26. package/dist/react/canvas-hud/viewport.d.ts.map +1 -0
  27. package/dist/react/canvas-hud/viewport.js +77 -0
  28. package/dist/react/canvas-hud/viewport.js.map +1 -0
  29. package/dist/react/components/AppShell.d.ts.map +1 -1
  30. package/dist/react/components/AppShell.js +23 -3
  31. package/dist/react/components/AppShell.js.map +1 -1
  32. package/dist/react/components/AsyncPanel.d.ts.map +1 -1
  33. package/dist/react/components/AsyncPanel.js +1 -1
  34. package/dist/react/components/AsyncPanel.js.map +1 -1
  35. package/dist/react/components/Checkbox.d.ts.map +1 -1
  36. package/dist/react/components/Checkbox.js +7 -2
  37. package/dist/react/components/Checkbox.js.map +1 -1
  38. package/dist/react/components/Field.d.ts.map +1 -1
  39. package/dist/react/components/Field.js +4 -1
  40. package/dist/react/components/Field.js.map +1 -1
  41. package/dist/react/components/PageGuideButton.d.ts +1 -1
  42. package/dist/react/components/PageGuideButton.d.ts.map +1 -1
  43. package/dist/react/components/PageGuideButton.js +45 -6
  44. package/dist/react/components/PageGuideButton.js.map +1 -1
  45. package/dist/react/components/PageGuidePopover.d.ts +14 -0
  46. package/dist/react/components/PageGuidePopover.d.ts.map +1 -0
  47. package/dist/react/components/PageGuidePopover.js +25 -0
  48. package/dist/react/components/PageGuidePopover.js.map +1 -0
  49. package/dist/react/components/TagsInput.d.ts.map +1 -1
  50. package/dist/react/components/TagsInput.js +3 -1
  51. package/dist/react/components/TagsInput.js.map +1 -1
  52. package/dist/react/folder-tree/FolderPicker.d.ts.map +1 -1
  53. package/dist/react/folder-tree/FolderPicker.js +5 -1
  54. package/dist/react/folder-tree/FolderPicker.js.map +1 -1
  55. package/dist/react/icons.d.ts +28 -0
  56. package/dist/react/icons.d.ts.map +1 -1
  57. package/dist/react/icons.js +28 -0
  58. package/dist/react/icons.js.map +1 -1
  59. package/dist/react/index.d.ts +1 -1
  60. package/dist/react/index.d.ts.map +1 -1
  61. package/dist/react/index.js +1 -1
  62. package/dist/react/index.js.map +1 -1
  63. package/dist/react/lib/whenIdle.d.ts +7 -0
  64. package/dist/react/lib/whenIdle.d.ts.map +1 -0
  65. package/dist/react/lib/whenIdle.js +14 -0
  66. package/dist/react/lib/whenIdle.js.map +1 -0
  67. package/dist/react/media/DenseGalleryShell.d.ts +7 -1
  68. package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
  69. package/dist/react/media/DenseGalleryShell.js +3 -3
  70. package/dist/react/media/DenseGalleryShell.js.map +1 -1
  71. package/dist/react/media/denseGallery.css +91 -0
  72. package/dist/react/media-gallery/MediaGallery.d.ts +5 -2
  73. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  74. package/dist/react/media-gallery/MediaGallery.js +1 -1
  75. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  76. package/dist/styles-areas/auth.css +2 -2
  77. package/dist/styles-areas/calendar.css +5 -6
  78. package/dist/styles-areas/canvas-hud.css +30 -0
  79. package/dist/styles-areas/core.css +5 -5
  80. package/dist/styles-areas/folder-tree.css +6 -6
  81. package/dist/styles-areas/layout-engine.css +4 -4
  82. package/dist/styles-areas/media-gallery.css +7 -6
  83. package/dist/styles-areas/nav.css +3 -3
  84. package/dist/styles-areas/palette.css +1 -1
  85. package/dist/styles-areas/rich-text.css +2 -2
  86. package/dist/styles-areas/sharing.css +4 -4
  87. package/dist/styles-areas/test-report.css +2 -2
  88. package/dist/styles-utilities.css +24 -3
  89. package/package.json +8 -1
  90. package/scripts/generateAreaStyles.ts +3 -1
  91. package/scripts/styleAreas.ts +88 -6
  92. package/src/bundlerOnlyEntries.spec.ts +25 -8
  93. package/src/react/canvas-hud/CanvasMinimap.tsx +88 -0
  94. package/src/react/canvas-hud/CanvasNodeMenu.tsx +91 -0
  95. package/src/react/canvas-hud/CanvasSearchField.tsx +91 -0
  96. package/src/react/canvas-hud/HudControls.spec.tsx +160 -0
  97. package/src/react/canvas-hud/HudControls.tsx +128 -0
  98. package/src/react/canvas-hud/index.ts +33 -0
  99. package/src/react/canvas-hud/useCanvasWheel.ts +99 -0
  100. package/src/react/canvas-hud/viewport.spec.ts +105 -0
  101. package/src/react/canvas-hud/viewport.ts +124 -0
  102. package/src/react/components/AppShell.tsx +23 -4
  103. package/src/react/components/AsyncPanel.spec.tsx +10 -0
  104. package/src/react/components/AsyncPanel.tsx +3 -1
  105. package/src/react/components/Checkbox.spec.tsx +18 -0
  106. package/src/react/components/Checkbox.tsx +8 -5
  107. package/src/react/components/Field.spec.tsx +3 -2
  108. package/src/react/components/Field.tsx +7 -4
  109. package/src/react/components/Field.width.spec.tsx +3 -3
  110. package/src/react/components/PageGuideButton.spec.tsx +39 -0
  111. package/src/react/components/PageGuideButton.tsx +91 -23
  112. package/src/react/components/PageGuidePopover.tsx +52 -0
  113. package/src/react/components/TagsInput.spec.tsx +8 -0
  114. package/src/react/components/TagsInput.tsx +3 -1
  115. package/src/react/components/entryChunks.graph.spec.ts +89 -0
  116. package/src/react/folder-tree/FolderPicker.spec.tsx +8 -0
  117. package/src/react/folder-tree/FolderPicker.tsx +4 -1
  118. package/src/react/icons.spec.ts +21 -1
  119. package/src/react/icons.tsx +135 -1
  120. package/src/react/index.ts +10 -0
  121. package/src/react/lib/whenIdle.ts +13 -0
  122. package/src/react/media/DenseGalleryShell.tsx +20 -4
  123. package/src/react/media/denseGallery.css +91 -0
  124. package/src/react/media-gallery/MediaGallery.spec.tsx +26 -0
  125. package/src/react/media-gallery/MediaGallery.tsx +10 -3
  126. package/src/styles-areas/auth.css +2 -2
  127. package/src/styles-areas/calendar.css +5 -6
  128. package/src/styles-areas/canvas-hud.css +30 -0
  129. package/src/styles-areas/core.css +5 -5
  130. package/src/styles-areas/folder-tree.css +6 -6
  131. package/src/styles-areas/layout-engine.css +4 -4
  132. package/src/styles-areas/media-gallery.css +7 -6
  133. package/src/styles-areas/nav.css +3 -3
  134. package/src/styles-areas/palette.css +1 -1
  135. package/src/styles-areas/rich-text.css +2 -2
  136. package/src/styles-areas/sharing.css +4 -4
  137. package/src/styles-areas/test-report.css +2 -2
  138. package/src/styles-utilities.css +24 -3
  139. package/src/stylesAreas.spec.ts +17 -0
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type CanvasBounds, type CanvasSize, type CanvasView } from './viewport.js';
3
+ export interface CanvasMinimapProps {
4
+ /** The content's extent in WORLD units — what the thumbnail shows. */
5
+ bounds: CanvasBounds;
6
+ view: CanvasView;
7
+ /** The canvas viewport in screen pixels, for the visible window. */
8
+ viewport: CanvasSize;
9
+ /** A click on the thumbnail, in WORLD units — the canvas centres there. */
10
+ onJump?: (point: {
11
+ x: number;
12
+ y: number;
13
+ }) => void;
14
+ /** The thumbnail's longest side in pixels. Default 168. */
15
+ size?: number;
16
+ /** Drawn inside the SVG in WORLD units, under the window: dots, zones, edges. */
17
+ children?: ReactNode;
18
+ /** A one-line caption under the map. Default "Tap to jump" when `onJump` is set. */
19
+ hint?: ReactNode;
20
+ className?: string;
21
+ }
22
+ /**
23
+ * CNV-3's minimap — the whole content as a low-opacity thumbnail with the visible window drawn
24
+ * on it. What a canvas draws on it (a dot per node, a heat zone per cluster) is `children` in its
25
+ * own world coordinates; the frame, the scaling and the window are this component's.
26
+ *
27
+ * The SVG is `aria-hidden`: the jump is a pointer shortcut over content that is already
28
+ * reachable by keyboard on the canvas itself, and a parallel interface over unlabeled dots would
29
+ * be a second, worse way to do what Tab already does (family's reasoning, kept).
30
+ */
31
+ export declare function CanvasMinimap({ bounds, view, viewport, onJump, size, children, hint, className, }: CanvasMinimapProps): import("react").JSX.Element | null;
32
+ //# sourceMappingURL=CanvasMinimap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasMinimap.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/CanvasMinimap.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAiB,MAAM,eAAe,CAAC;AAEnG,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,oEAAoE;IACpE,QAAQ,EAAE,UAAU,CAAC;IACrB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACnD,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,oFAAoF;IACpF,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,IAAU,EACV,QAAQ,EACR,IAAI,EACJ,SAAS,GACV,EAAE,kBAAkB,sCAgDpB"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '../lib/cn.js';
3
+ import { visibleBounds } from './viewport.js';
4
+ /**
5
+ * CNV-3's minimap — the whole content as a low-opacity thumbnail with the visible window drawn
6
+ * on it. What a canvas draws on it (a dot per node, a heat zone per cluster) is `children` in its
7
+ * own world coordinates; the frame, the scaling and the window are this component's.
8
+ *
9
+ * The SVG is `aria-hidden`: the jump is a pointer shortcut over content that is already
10
+ * reachable by keyboard on the canvas itself, and a parallel interface over unlabeled dots would
11
+ * be a second, worse way to do what Tab already does (family's reasoning, kept).
12
+ */
13
+ export function CanvasMinimap({ bounds, view, viewport, onJump, size = 168, children, hint, className, }) {
14
+ if (bounds.width <= 0 || bounds.height <= 0 || viewport.width <= 0 || view.scale <= 0)
15
+ return null;
16
+ const ratio = Math.min(size / bounds.width, (size * 0.62) / bounds.height);
17
+ const width = Math.max(1, bounds.width * ratio);
18
+ const height = Math.max(1, bounds.height * ratio);
19
+ const seen = visibleBounds(view, viewport);
20
+ const jump = (event) => {
21
+ if (!onJump)
22
+ return;
23
+ const rect = event.currentTarget.getBoundingClientRect();
24
+ onJump({
25
+ x: bounds.x + (event.clientX - rect.left) / ratio,
26
+ y: bounds.y + (event.clientY - rect.top) / ratio,
27
+ });
28
+ };
29
+ const caption = hint === undefined ? (onJump ? 'Tap to jump' : null) : hint;
30
+ return (_jsxs("div", { "data-testid": 'canvas-minimap', className: cn('pointer-events-auto flex flex-col items-center gap-1 rounded-lg border border-border/60 bg-background/70 p-2 shadow-raised backdrop-blur-md', className), children: [_jsxs("svg", { width: width, height: height, viewBox: `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`, onClick: jump, "aria-hidden": 'true', className: cn('overflow-hidden', onJump && 'cursor-pointer'), children: [children, _jsx("rect", { "data-testid": 'canvas-minimap-window', x: seen.x, y: seen.y, width: seen.width, height: seen.height, fill: 'var(--accent)', fillOpacity: 0.12, stroke: 'var(--accent)', strokeWidth: 1.5 / ratio, rx: 3 / ratio })] }), caption ? _jsx("span", { className: 'text-caption text-muted-foreground', children: caption }) : null] }));
31
+ }
32
+ //# sourceMappingURL=CanvasMinimap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasMinimap.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/CanvasMinimap.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAuD,aAAa,EAAE,MAAM,eAAe,CAAC;AAmBnG;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,IAAI,GAAG,GAAG,EACV,QAAQ,EACR,IAAI,EACJ,SAAS,GACU;IACnB,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnG,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,CAAC,KAAgC,EAAE,EAAE;QAChD,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC;QACzD,MAAM,CAAC;YACL,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK;YACjD,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK;SACjD,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,OAAO,CACL,8BACc,gBAAgB,EAC5B,SAAS,EAAE,EAAE,CACX,6IAA6I,EAC7I,SAAS,CACV,aAED,eACE,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EACnE,OAAO,EAAE,IAAI,iBACD,MAAM,EAClB,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,MAAM,IAAI,gBAAgB,CAAC,aAE3D,QAAQ,EACT,8BACc,uBAAuB,EACnC,CAAC,EAAE,IAAI,CAAC,CAAC,EACT,CAAC,EAAE,IAAI,CAAC,CAAC,EACT,KAAK,EAAE,IAAI,CAAC,KAAK,EACjB,MAAM,EAAE,IAAI,CAAC,MAAM,EACnB,IAAI,EAAC,eAAe,EACpB,WAAW,EAAE,IAAI,EACjB,MAAM,EAAC,eAAe,EACtB,WAAW,EAAE,GAAG,GAAG,KAAK,EACxB,EAAE,EAAE,CAAC,GAAG,KAAK,GACb,IACE,EACL,OAAO,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,oCAAoC,YAAE,OAAO,GAAQ,CAAC,CAAC,CAAC,IAAI,IACnF,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface CanvasNodeMenuItem {
3
+ label: string;
4
+ onSelect: () => void;
5
+ /** Drawn disabled. The section's `note` is where the reason goes. */
6
+ disabled?: boolean;
7
+ destructive?: boolean;
8
+ }
9
+ export interface CanvasNodeMenuSection {
10
+ heading?: string;
11
+ items: CanvasNodeMenuItem[];
12
+ /** One quiet line under the rows: what they change, or why they cannot. */
13
+ note?: ReactNode;
14
+ }
15
+ export interface CanvasNodeMenuProps {
16
+ trigger: ReactNode;
17
+ open: boolean;
18
+ onOpenChange: (open: boolean) => void;
19
+ sections: CanvasNodeMenuSection[];
20
+ align?: 'start' | 'center' | 'end';
21
+ side?: 'top' | 'right' | 'bottom' | 'left';
22
+ className?: string;
23
+ }
24
+ /**
25
+ * A node's menu on a canvas — a `Popover`, deliberately not a `DropdownMenu`.
26
+ *
27
+ * A menu takes the arrow keys for its own rows; on a canvas they belong to the CANVAS, which
28
+ * moves its cursor to the next node and closes this (family's branch menu, the owner's ask of
29
+ * 2026-09-24: *"I want it to disappear if I click off of it or use arrows to move to another
30
+ * person"*). So the rows are Tab-reachable buttons, the keys bubble, and an outside press or
31
+ * Escape closes it. Pointer-downs stop here so pressing a row never starts a pan.
32
+ */
33
+ export declare function CanvasNodeMenu({ trigger, open, onOpenChange, sections, align, side, className, }: CanvasNodeMenuProps): import("react").JSX.Element;
34
+ //# sourceMappingURL=CanvasNodeMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasNodeMenu.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/CanvasNodeMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,KAAe,EACf,IAAe,EACf,SAAS,GACV,EAAE,mBAAmB,+BA2CrB"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from '../components/Button.js';
3
+ import { Popover } from '../components/Popover.js';
4
+ import { cn } from '../lib/cn.js';
5
+ /**
6
+ * A node's menu on a canvas — a `Popover`, deliberately not a `DropdownMenu`.
7
+ *
8
+ * A menu takes the arrow keys for its own rows; on a canvas they belong to the CANVAS, which
9
+ * moves its cursor to the next node and closes this (family's branch menu, the owner's ask of
10
+ * 2026-09-24: *"I want it to disappear if I click off of it or use arrows to move to another
11
+ * person"*). So the rows are Tab-reachable buttons, the keys bubble, and an outside press or
12
+ * Escape closes it. Pointer-downs stop here so pressing a row never starts a pan.
13
+ */
14
+ export function CanvasNodeMenu({ trigger, open, onOpenChange, sections, align = 'start', side = 'bottom', className, }) {
15
+ const stop = (event) => event.stopPropagation();
16
+ return (_jsx(Popover, { open: open, onOpenChange: onOpenChange, align: align, side: side, className: cn('w-64 p-1', className), trigger: trigger, children: _jsx("div", { role: 'menu', onPointerDown: stop, className: 'flex flex-col', children: sections.map((section, sectionIndex) => (_jsxs("div", { className: cn('flex flex-col py-1', sectionIndex > 0 && 'border-t border-border'), children: [section.heading ? _jsx("p", { className: 'cb-meta-label px-2 pt-1 pb-1.5', children: section.heading }) : null, section.items.map((item) => (_jsx(Button, { role: 'menuitem', variant: item.destructive ? 'destructive' : 'ghost', size: 'sm', tooltip: item.label, tooltipMobile: 'off', disabled: item.disabled, className: 'w-full justify-start', onClick: (event) => {
17
+ event.stopPropagation();
18
+ item.onSelect();
19
+ }, children: item.label }, item.label))), section.note ? _jsx("p", { className: 'px-2 pt-1 text-caption text-muted-foreground', children: section.note }) : null] }, sectionIndex))) }) }));
20
+ }
21
+ //# sourceMappingURL=CanvasNodeMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasNodeMenu.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/CanvasNodeMenu.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AA2BlC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,KAAK,GAAG,OAAO,EACf,IAAI,GAAG,QAAQ,EACf,SAAS,GACW;IACpB,MAAM,IAAI,GAAG,CAAC,KAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;IAC9D,OAAO,CACL,KAAC,OAAO,IACN,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,EACpC,OAAO,EAAE,OAAO,YAEhB,cAAK,IAAI,EAAC,MAAM,EAAC,aAAa,EAAE,IAAI,EAAE,SAAS,EAAC,eAAe,YAC5D,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,CACvC,eAGE,SAAS,EAAE,EAAE,CAAC,oBAAoB,EAAE,YAAY,GAAG,CAAC,IAAI,wBAAwB,CAAC,aAEhF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,gCAAgC,YAAE,OAAO,CAAC,OAAO,GAAK,CAAC,CAAC,CAAC,IAAI,EAC5F,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAC3B,KAAC,MAAM,IAEL,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,EACnD,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,IAAI,CAAC,KAAK,EACnB,aAAa,EAAC,KAAK,EACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,SAAS,EAAC,sBAAsB,EAChC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;4BACjB,KAAK,CAAC,eAAe,EAAE,CAAC;4BACxB,IAAI,CAAC,QAAQ,EAAE,CAAC;wBAClB,CAAC,YAEA,IAAI,CAAC,KAAK,IAbN,IAAI,CAAC,KAAK,CAcR,CACV,CAAC,EACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,8CAA8C,YAAE,OAAO,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,KAtBhG,YAAY,CAuBb,CACP,CAAC,GACE,GACE,CACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ export interface CanvasSearchFieldProps {
2
+ value: string;
3
+ onChange: (value: string) => void;
4
+ /** Step to the next (1) or previous (-1) match — Enter and Shift+Enter, and the ‹ › keys. */
5
+ onStep?: (delta: 1 | -1) => void;
6
+ /** How many matches the query has, and which one is current (0-based). */
7
+ matches?: number;
8
+ index?: number;
9
+ placeholder?: string;
10
+ 'aria-label'?: string;
11
+ className?: string;
12
+ }
13
+ /**
14
+ * The find box that floats over a canvas, and the one that does not steal the canvas's keys.
15
+ *
16
+ * 🔴 A canvas binds the arrow keys on an ancestor of this field. Without stopping them here,
17
+ * typing a name and pressing ← to fix a typo walked the reader to a cousin instead of moving
18
+ * the caret (family, 2026-09). Every key stops at the field; Enter and Shift+Enter step through
19
+ * the matches, the convention of every find-in-page; Escape empties the box rather than blurring.
20
+ *
21
+ * It is the belt's `Input` drawn borderless, so the pill around it is the field — and NOT
22
+ * `SearchInput`: a find box over a canvas matches names, never wildcards or regexes, and the
23
+ * syntax parser and its badge were 5.6 kB gzip on family's first paint for nothing (task 2401).
24
+ * A ✕ empties it too, because a phone has no Escape key.
25
+ */
26
+ export declare function CanvasSearchField({ value, onChange, onStep, matches, index, placeholder, 'aria-label': ariaLabel, className, }: CanvasSearchFieldProps): import("react").JSX.Element;
27
+ //# sourceMappingURL=CanvasSearchField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasSearchField.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/CanvasSearchField.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,6FAA6F;IAC7F,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;IACjC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAW,EACX,KAAS,EACT,WAAqB,EACrB,YAAY,EAAE,SAAS,EACvB,SAAS,GACV,EAAE,sBAAsB,+BAgDxB"}
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HudPill } from '../components/HudPill.js';
3
+ import { Input } from '../components/Input.js';
4
+ import { IconSearch } from '../icons.js';
5
+ import { cn } from '../lib/cn.js';
6
+ import { HudButton } from './HudControls.js';
7
+ /**
8
+ * The find box that floats over a canvas, and the one that does not steal the canvas's keys.
9
+ *
10
+ * 🔴 A canvas binds the arrow keys on an ancestor of this field. Without stopping them here,
11
+ * typing a name and pressing ← to fix a typo walked the reader to a cousin instead of moving
12
+ * the caret (family, 2026-09). Every key stops at the field; Enter and Shift+Enter step through
13
+ * the matches, the convention of every find-in-page; Escape empties the box rather than blurring.
14
+ *
15
+ * It is the belt's `Input` drawn borderless, so the pill around it is the field — and NOT
16
+ * `SearchInput`: a find box over a canvas matches names, never wildcards or regexes, and the
17
+ * syntax parser and its badge were 5.6 kB gzip on family's first paint for nothing (task 2401).
18
+ * A ✕ empties it too, because a phone has no Escape key.
19
+ */
20
+ export function CanvasSearchField({ value, onChange, onStep, matches = 0, index = 0, placeholder = 'Find…', 'aria-label': ariaLabel, className, }) {
21
+ const searching = value.trim().length > 0;
22
+ const onKeyDown = (event) => {
23
+ if (event.key === 'Enter' && onStep) {
24
+ event.preventDefault();
25
+ onStep(event.shiftKey ? -1 : 1);
26
+ }
27
+ if (event.key === 'Escape' && value !== '') {
28
+ event.preventDefault();
29
+ onChange('');
30
+ }
31
+ event.stopPropagation();
32
+ };
33
+ return (_jsxs(HudPill, { className: cn('gap-0.5 rounded-lg py-0.5 pr-1 pl-0', className), children: [_jsx(IconSearch, { size: 16, className: 'ml-2 shrink-0 text-muted-foreground' }), _jsx(Input, { type: 'search', size: 'sm', value: value, onChange: (event) => onChange(event.target.value), placeholder: placeholder, "aria-label": ariaLabel ?? placeholder, onKeyDown: onKeyDown, className: 'w-40 border-transparent bg-transparent pl-1 shadow-none focus-visible:border-transparent focus-visible:ring-0 [&::-webkit-search-cancel-button]:hidden' }), searching ? (_jsx("span", { className: 'min-w-10 text-center text-xs text-muted-foreground tabular-nums', "aria-live": 'polite', children: matches === 0 ? 'none' : `${index + 1}/${matches}` })) : null, onStep && matches > 1 ? (_jsxs(_Fragment, { children: [_jsx(HudButton, { label: 'Previous match \u2014 Shift+Enter', onClick: () => onStep(-1), children: _jsx("span", { "aria-hidden": true, children: "\u2039" }) }), _jsx(HudButton, { label: 'Next match \u2014 Enter', onClick: () => onStep(1), children: _jsx("span", { "aria-hidden": true, children: "\u203A" }) })] })) : null, searching ? (_jsx(HudButton, { label: 'Clear the search \u2014 Escape', onClick: () => onChange(''), children: _jsx("span", { "aria-hidden": true, children: "\u00D7" }) })) : null] }));
34
+ }
35
+ //# sourceMappingURL=CanvasSearchField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasSearchField.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/CanvasSearchField.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAe7C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAO,GAAG,CAAC,EACX,KAAK,GAAG,CAAC,EACT,WAAW,GAAG,OAAO,EACrB,YAAY,EAAE,SAAS,EACvB,SAAS,GACc;IACvB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,CAAC,KAAsC,EAAE,EAAE;QAC3D,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,MAAM,EAAE,CAAC;YACpC,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC3C,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,QAAQ,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;QACD,KAAK,CAAC,eAAe,EAAE,CAAC;IAC1B,CAAC,CAAC;IACF,OAAO,CACL,MAAC,OAAO,IAAC,SAAS,EAAE,EAAE,CAAC,qCAAqC,EAAE,SAAS,CAAC,aACtE,KAAC,UAAU,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,qCAAqC,GAAG,EACxE,KAAC,KAAK,IACJ,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,IAAI,EACT,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EACjD,WAAW,EAAE,WAAW,gBACZ,SAAS,IAAI,WAAW,EACpC,SAAS,EAAE,SAAS,EACpB,SAAS,EAAC,wJAAwJ,GAClK,EACD,SAAS,CAAC,CAAC,CAAC,CACX,eAAM,SAAS,EAAC,iEAAiE,eAAW,QAAQ,YACjG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,OAAO,EAAE,GAC9C,CACR,CAAC,CAAC,CAAC,IAAI,EACP,MAAM,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CACvB,8BACE,KAAC,SAAS,IAAC,KAAK,EAAC,mCAA8B,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YACvE,yDAA0B,GAChB,EACZ,KAAC,SAAS,IAAC,KAAK,EAAC,yBAAoB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,YAC5D,yDAA0B,GAChB,IACX,CACJ,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,SAAS,IAAC,KAAK,EAAC,gCAA2B,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,YACtE,yDAA0B,GAChB,CACb,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface HudButtonProps {
3
+ /** What it does — the accessible name and the hover hint. */
4
+ label: string;
5
+ onClick: () => void;
6
+ children: ReactNode;
7
+ /** Present only on a control that TOGGLES, so the others announce no pressed state. */
8
+ pressed?: boolean;
9
+ /** Why it cannot act right now: drawn disabled, still focusable, the reason on hover. */
10
+ unavailableReason?: string | null;
11
+ className?: string;
12
+ }
13
+ /** One HUD control — an icon-sized belt `IconButton`, with a pressed state for a toggle. */
14
+ export declare function HudButton({ label, onClick, children, pressed, unavailableReason, className }: HudButtonProps): import("react").JSX.Element;
15
+ export interface HudZoomControlsProps {
16
+ /** The live scale; drawn as a percentage. */
17
+ scale: number;
18
+ onZoomIn: () => void;
19
+ onZoomOut: () => void;
20
+ /** Fit the content to the screen. */
21
+ onFit: () => void;
22
+ /** Center on the focused node. Omit it when the canvas has no focus to centre on. */
23
+ onCenter?: () => void;
24
+ fitLabel?: string;
25
+ centerLabel?: string;
26
+ /** The canvas's own utilities, drawn before the zoom keys in the same pill (CNV-4 — utilities
27
+ * only; preferences and primary actions belong in the page's panel). */
28
+ children?: ReactNode;
29
+ className?: string;
30
+ }
31
+ /** CNV-3's essentials as one pill: −, the zoom %, +, Fit and Center. */
32
+ export declare function HudZoomControls({ scale, onZoomIn, onZoomOut, onFit, onCenter, fitLabel, centerLabel, children, className, }: HudZoomControlsProps): import("react").JSX.Element;
33
+ export interface CanvasZoomHintProps {
34
+ /** `useCanvasWheel(…).hint` — up while the reader wheels over an embedded canvas. */
35
+ show: boolean;
36
+ className?: string;
37
+ }
38
+ /**
39
+ * CNV-1's "Hold ⌘ to zoom" — what an embedded canvas says when a plain wheel scrolled the page
40
+ * instead of zooming it, the way an embedded Google Map does. Says Ctrl off a Mac.
41
+ */
42
+ export declare function CanvasZoomHint({ show, className }: CanvasZoomHintProps): import("react").JSX.Element;
43
+ //# sourceMappingURL=HudControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HudControls.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/HudControls.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAoBvC,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,uFAAuF;IACvF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAAE,cAAc,+BAc5G;AAED,MAAM,WAAW,oBAAoB;IACnC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,qCAAqC;IACrC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;6EACyE;IACzE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,EACR,QAA0B,EAC1B,WAAuC,EACvC,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,+BAuBtB;AAED,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,mBAAmB,+BAetE"}
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HudPill } from '../components/HudPill.js';
3
+ import { IconButton } from '../components/IconButton.js';
4
+ import { IconCenter, IconFit, IconZoomIn, IconZoomOut } from '../icons.js';
5
+ import { cn } from '../lib/cn.js';
6
+ /** One HUD control — an icon-sized belt `IconButton`, with a pressed state for a toggle. */
7
+ export function HudButton({ label, onClick, children, pressed, unavailableReason, className }) {
8
+ return (_jsx(IconButton, { label: label, size: 'sm', "aria-pressed": pressed, unavailableReason: unavailableReason ?? undefined, tooltipMobile: 'off', onClick: onClick, className: cn('text-muted-foreground aria-pressed:bg-muted aria-pressed:text-foreground', className), children: children }));
9
+ }
10
+ /** CNV-3's essentials as one pill: −, the zoom %, +, Fit and Center. */
11
+ export function HudZoomControls({ scale, onZoomIn, onZoomOut, onFit, onCenter, fitLabel = 'Fit to screen', centerLabel = 'Center on the selection', children, className, }) {
12
+ return (_jsxs(HudPill, { role: 'group', "aria-label": 'View', className: cn('gap-0.5 rounded-lg px-1 py-1', className), children: [children, _jsx(HudButton, { label: 'Zoom out', onClick: onZoomOut, children: _jsx(IconZoomOut, {}) }), _jsxs("span", { className: 'min-w-11 text-center text-xs text-muted-foreground tabular-nums', "data-testid": 'hud-zoom', children: [Math.round(scale * 100), "%"] }), _jsx(HudButton, { label: 'Zoom in', onClick: onZoomIn, children: _jsx(IconZoomIn, {}) }), _jsx(HudButton, { label: fitLabel, onClick: onFit, children: _jsx(IconFit, {}) }), onCenter ? (_jsx(HudButton, { label: centerLabel, onClick: onCenter, children: _jsx(IconCenter, {}) })) : null] }));
13
+ }
14
+ /**
15
+ * CNV-1's "Hold ⌘ to zoom" — what an embedded canvas says when a plain wheel scrolled the page
16
+ * instead of zooming it, the way an embedded Google Map does. Says Ctrl off a Mac.
17
+ */
18
+ export function CanvasZoomHint({ show, className }) {
19
+ const mac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
20
+ return (_jsx("div", { "aria-hidden": !show, "data-testid": 'canvas-zoom-hint', className: cn('pointer-events-none absolute inset-0 grid place-items-center bg-background/40 transition-opacity duration-200', show ? 'opacity-100' : 'opacity-0', className), children: _jsxs(HudPill, { className: 'px-3 py-1.5 text-sm', children: ["Hold ", mac ? '⌘' : 'Ctrl', " to zoom"] }) }));
21
+ }
22
+ //# sourceMappingURL=HudControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HudControls.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/HudControls.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AA4BlC,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAkB;IAC3G,OAAO,CACL,KAAC,UAAU,IACT,KAAK,EAAE,KAAK,EACZ,IAAI,EAAC,IAAI,kBACK,OAAO,EACrB,iBAAiB,EAAE,iBAAiB,IAAI,SAAS,EACjD,aAAa,EAAC,KAAK,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,EAAE,CAAC,0EAA0E,EAAE,SAAS,CAAC,YAEnG,QAAQ,GACE,CACd,CAAC;AACJ,CAAC;AAmBD,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,EACR,QAAQ,GAAG,eAAe,EAC1B,WAAW,GAAG,yBAAyB,EACvC,QAAQ,EACR,SAAS,GACY;IACrB,OAAO,CACL,MAAC,OAAO,IAAC,IAAI,EAAC,OAAO,gBAAY,MAAM,EAAC,SAAS,EAAE,EAAE,CAAC,8BAA8B,EAAE,SAAS,CAAC,aAC7F,QAAQ,EACT,KAAC,SAAS,IAAC,KAAK,EAAC,UAAU,EAAC,OAAO,EAAE,SAAS,YAC5C,KAAC,WAAW,KAAG,GACL,EACZ,gBAAM,SAAS,EAAC,iEAAiE,iBAAa,UAAU,aACrG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,SACnB,EACP,KAAC,SAAS,IAAC,KAAK,EAAC,SAAS,EAAC,OAAO,EAAE,QAAQ,YAC1C,KAAC,UAAU,KAAG,GACJ,EACZ,KAAC,SAAS,IAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,YACxC,KAAC,OAAO,KAAG,GACD,EACX,QAAQ,CAAC,CAAC,CAAC,CACV,KAAC,SAAS,IAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,YAC9C,KAAC,UAAU,KAAG,GACJ,CACb,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAC;AACJ,CAAC;AAQD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAuB;IACrE,MAAM,GAAG,GAAG,OAAO,SAAS,KAAK,WAAW,IAAI,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,SAAS,CAAC,CAAC;IAClH,OAAO,CACL,6BACe,CAAC,IAAI,iBACN,kBAAkB,EAC9B,SAAS,EAAE,EAAE,CACX,+GAA+G,EAC/G,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,EAClC,SAAS,CACV,YAED,MAAC,OAAO,IAAC,SAAS,EAAC,qBAAqB,sBAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,gBAAmB,GAChF,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ export type { CanvasMinimapProps } from './CanvasMinimap.js';
2
+ export { CanvasMinimap } from './CanvasMinimap.js';
3
+ export type { CanvasNodeMenuItem, CanvasNodeMenuProps, CanvasNodeMenuSection } from './CanvasNodeMenu.js';
4
+ export { CanvasNodeMenu } from './CanvasNodeMenu.js';
5
+ export type { CanvasSearchFieldProps } from './CanvasSearchField.js';
6
+ export { CanvasSearchField } from './CanvasSearchField.js';
7
+ export type { CanvasZoomHintProps, HudButtonProps, HudZoomControlsProps } from './HudControls.js';
8
+ export { CanvasZoomHint, HudButton, HudZoomControls } from './HudControls.js';
9
+ export type { CanvasWheelMode, CanvasWheelOptions } from './useCanvasWheel.js';
10
+ export { readWheel, useCanvasWheel, useLevelOfDetail } from './useCanvasWheel.js';
11
+ export type { CanvasBounds, CanvasPoint, CanvasSize, CanvasView, FitOptions, LevelOfDetail, LevelOfDetailThresholds, } from './viewport.js';
12
+ export { centerView, clampScale, fitScale, fitView, LEVEL_OF_DETAIL, levelOfDetail, visibleBounds, wheelZoomFactor, zoomAt, } from './viewport.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC1G,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC9E,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAClF,YAAY,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,uBAAuB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,UAAU,EACV,UAAU,EACV,QAAQ,EACR,OAAO,EACP,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,MAAM,GACP,MAAM,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { CanvasMinimap } from './CanvasMinimap.js';
2
+ export { CanvasNodeMenu } from './CanvasNodeMenu.js';
3
+ export { CanvasSearchField } from './CanvasSearchField.js';
4
+ export { CanvasZoomHint, HudButton, HudZoomControls } from './HudControls.js';
5
+ export { readWheel, useCanvasWheel, useLevelOfDetail } from './useCanvasWheel.js';
6
+ export { centerView, clampScale, fitScale, fitView, LEVEL_OF_DETAIL, levelOfDetail, visibleBounds, wheelZoomFactor, zoomAt, } from './viewport.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE9E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAUlF,OAAO,EACL,UAAU,EACV,UAAU,EACV,QAAQ,EACR,OAAO,EACP,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,MAAM,GACP,MAAM,eAAe,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { type RefObject } from 'react';
2
+ import { type CanvasPoint, type LevelOfDetail, type LevelOfDetailThresholds } from './viewport.js';
3
+ /**
4
+ * How a canvas reads the wheel (CNV-1 — one gesture, one meaning, on every surface):
5
+ *
6
+ * - `'full'` — the canvas owns the viewport and the page does not scroll. Pinch and ⌘/Ctrl+wheel
7
+ * zoom; a plain wheel or a two-finger swipe PANS (Figma, Miro).
8
+ * - `'full-zoom'` — the same, except a plain wheel ZOOMS, the way a full-page map does. For a
9
+ * canvas whose readers already zoom with a mouse wheel (family's tree): a shift+wheel pans.
10
+ * - `'embedded'` — the page scrolls past the canvas. A plain wheel is the PAGE's and is left
11
+ * alone; pinch and ⌘/Ctrl+wheel zoom; the hook raises `hint` so the canvas can say
12
+ * "Hold ⌘ to zoom", the way an embedded Google Map does.
13
+ */
14
+ export type CanvasWheelMode = 'full' | 'full-zoom' | 'embedded';
15
+ export interface CanvasWheelOptions {
16
+ mode: CanvasWheelMode;
17
+ /** Zoom by `factor` about `anchor` (viewport pixels). The canvas clamps. */
18
+ onZoom: (factor: number, anchor: CanvasPoint) => void;
19
+ /** Pan by screen pixels. Unused in `'embedded'` mode. */
20
+ onPan?: (dx: number, dy: number) => void;
21
+ /** How long the embedded hint stays up after the last plain wheel. Default 1200 ms. */
22
+ hintMs?: number;
23
+ }
24
+ /** The decision, separated from the DOM so it is testable: what one wheel event means. */
25
+ export declare function readWheel(mode: CanvasWheelMode, event: Pick<WheelEvent, 'ctrlKey' | 'metaKey' | 'shiftKey' | 'deltaX' | 'deltaY'>): 'zoom' | 'pan' | 'page';
26
+ /**
27
+ * Binds the wheel on `ref` per {@link CanvasWheelMode}.
28
+ *
29
+ * 🔴 A NON-passive native listener, never a JSX `onWheel`: React registers `wheel` at the root as
30
+ * passive, so `preventDefault()` there is ignored and the page scrolls (or zooms the whole
31
+ * browser on a pinch) under the canvas.
32
+ */
33
+ export declare function useCanvasWheel(ref: RefObject<HTMLElement | null>, options: CanvasWheelOptions): {
34
+ hint: boolean;
35
+ };
36
+ /**
37
+ * CNV-5 — the density to draw at this scale. A string that changes only at a threshold, so an
38
+ * effect or memo keyed on it re-runs three times across the whole zoom range, not every frame.
39
+ */
40
+ export declare function useLevelOfDetail(scale: number, thresholds?: LevelOfDetailThresholds): LevelOfDetail;
41
+ //# sourceMappingURL=useCanvasWheel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasWheel.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/useCanvasWheel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA+B,MAAM,OAAO,CAAC;AACpE,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAG7B,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,4EAA4E;IAC5E,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACtD,yDAAyD;IACzD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0FAA0F;AAC1F,wBAAgB,SAAS,CACvB,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAChF,MAAM,GAAG,KAAK,GAAG,MAAM,CAMzB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,OAAO,EAAE,kBAAkB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAmCjH;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,uBAAyC,GACpD,aAAa,CAEf"}
@@ -0,0 +1,65 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { LEVEL_OF_DETAIL, levelOfDetail, wheelZoomFactor, } from './viewport.js';
3
+ /** The decision, separated from the DOM so it is testable: what one wheel event means. */
4
+ export function readWheel(mode, event) {
5
+ // A trackpad pinch arrives as a wheel event with ctrlKey set, on every browser.
6
+ if (event.ctrlKey || event.metaKey)
7
+ return 'zoom';
8
+ if (mode === 'embedded')
9
+ return 'page';
10
+ if (mode === 'full-zoom')
11
+ return event.shiftKey ? 'pan' : 'zoom';
12
+ return 'pan';
13
+ }
14
+ /**
15
+ * Binds the wheel on `ref` per {@link CanvasWheelMode}.
16
+ *
17
+ * 🔴 A NON-passive native listener, never a JSX `onWheel`: React registers `wheel` at the root as
18
+ * passive, so `preventDefault()` there is ignored and the page scrolls (or zooms the whole
19
+ * browser on a pinch) under the canvas.
20
+ */
21
+ export function useCanvasWheel(ref, options) {
22
+ const latest = useRef(options);
23
+ latest.current = options;
24
+ const [hint, setHint] = useState(false);
25
+ useEffect(() => {
26
+ const element = ref.current;
27
+ if (!element)
28
+ return;
29
+ let timer;
30
+ const onWheel = (event) => {
31
+ const { mode, onZoom, onPan, hintMs = 1200 } = latest.current;
32
+ const meaning = readWheel(mode, event);
33
+ if (meaning === 'page') {
34
+ setHint(true);
35
+ clearTimeout(timer);
36
+ timer = setTimeout(() => setHint(false), hintMs);
37
+ return;
38
+ }
39
+ event.preventDefault();
40
+ if (meaning === 'zoom') {
41
+ const rect = element.getBoundingClientRect();
42
+ onZoom(wheelZoomFactor(event.deltaY), { x: event.clientX - rect.left, y: event.clientY - rect.top });
43
+ return;
44
+ }
45
+ // A shift+wheel from a mouse reports its motion on Y; a sideways trackpad swipe on X.
46
+ const dx = event.shiftKey && event.deltaX === 0 ? event.deltaY : event.deltaX;
47
+ const dy = event.shiftKey && event.deltaX === 0 ? 0 : event.deltaY;
48
+ onPan?.(-dx, -dy);
49
+ };
50
+ element.addEventListener('wheel', onWheel, { passive: false });
51
+ return () => {
52
+ clearTimeout(timer);
53
+ element.removeEventListener('wheel', onWheel);
54
+ };
55
+ }, [ref]);
56
+ return { hint };
57
+ }
58
+ /**
59
+ * CNV-5 — the density to draw at this scale. A string that changes only at a threshold, so an
60
+ * effect or memo keyed on it re-runs three times across the whole zoom range, not every frame.
61
+ */
62
+ export function useLevelOfDetail(scale, thresholds = LEVEL_OF_DETAIL) {
63
+ return levelOfDetail(scale, thresholds);
64
+ }
65
+ //# sourceMappingURL=useCanvasWheel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasWheel.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/useCanvasWheel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The camera arithmetic every pan-and-zoom canvas needs (CNV-2, CNV-3, CNV-5).
3
+ *
4
+ * Pure — no React, no DOM — so each rule is pinned by a spec rather than by opening an app.
5
+ * A view maps a WORLD point to the screen as `screen = world × scale + (x, y)`: `x`/`y` are the
6
+ * screen position of the world origin, which is what a CSS `translate(x, y) scale(scale)` on the
7
+ * stage means. Every canvas in the fleet already used that convention (family's `Camera`,
8
+ * patterns' `viewportGestures.ts`, desk's xyflow viewport), which is why it is the one here.
9
+ */
10
+ export interface CanvasView {
11
+ x: number;
12
+ y: number;
13
+ scale: number;
14
+ }
15
+ export interface CanvasPoint {
16
+ x: number;
17
+ y: number;
18
+ }
19
+ export interface CanvasSize {
20
+ width: number;
21
+ height: number;
22
+ }
23
+ /** A rectangle in WORLD units — the content's extent, or the visible window. */
24
+ export interface CanvasBounds extends CanvasPoint, CanvasSize {
25
+ }
26
+ export declare function clampScale(scale: number, min: number, max: number): number;
27
+ /**
28
+ * CNV-2 — zoom to `scale` keeping the screen point `anchor` still: the pointer for a wheel or a
29
+ * pinch, the middle of the viewport for the ± buttons. The caller clamps `scale` first, because
30
+ * the floor is the canvas's own business (family floors it at the whole tree).
31
+ */
32
+ export declare function zoomAt(view: CanvasView, scale: number, anchor: CanvasPoint): CanvasView;
33
+ /** The zoom factor for one wheel event — exponential, so a trackpad pinch (which arrives as a
34
+ * ctrl+wheel stream of small deltas) and a mouse notch feel equally linear. */
35
+ export declare function wheelZoomFactor(deltaY: number): number;
36
+ export interface FitOptions {
37
+ /** Screen pixels of breathing room on every side. Default 48. */
38
+ padding?: number;
39
+ /**
40
+ * The largest scale a fit may land on. Default 1: fit SHRINKS to fit and never magnifies a
41
+ * small graph past its natural size — a two-node graph "fitted" to 400% reads as a zoom
42
+ * nobody asked for (Figma's and Miro's zoom-to-fit behave the same way).
43
+ */
44
+ maxScale?: number;
45
+ /** The smallest. Default 0.02 — a degenerate extent must still be a drawable number. */
46
+ minScale?: number;
47
+ }
48
+ /** The scale at which `bounds` fits the viewport, or `null` while there is nothing to fit
49
+ * (an empty extent, or a viewport not measured yet). */
50
+ export declare function fitScale(bounds: CanvasSize, viewport: CanvasSize, options?: FitOptions): number | null;
51
+ /** CNV-3 "Fit" — the view that centres `bounds` in the viewport at {@link fitScale}. */
52
+ export declare function fitView(bounds: CanvasBounds, viewport: CanvasSize, options?: FitOptions): CanvasView | null;
53
+ /** CNV-3 "Center" — the view that puts the world point `point` in the middle of the viewport. */
54
+ export declare function centerView(point: CanvasPoint, viewport: CanvasSize, scale: number): CanvasView;
55
+ /** The part of the world the viewport shows — what a minimap draws as its window. */
56
+ export declare function visibleBounds(view: CanvasView, viewport: CanvasSize): CanvasBounds;
57
+ /** CNV-5's three densities. */
58
+ export type LevelOfDetail = 'far' | 'mid' | 'close';
59
+ export interface LevelOfDetailThresholds {
60
+ /** At and above this scale, primary labels may appear. Default 0.3. */
61
+ mid: number;
62
+ /** At and above this scale, full labels and metadata. Default 0.5. */
63
+ close: number;
64
+ }
65
+ export declare const LEVEL_OF_DETAIL: LevelOfDetailThresholds;
66
+ /** CNV-5 — which density a canvas draws at this scale. */
67
+ export declare function levelOfDetail(scale: number, thresholds?: LevelOfDetailThresholds): LevelOfDetail;
68
+ //# sourceMappingURL=viewport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewport.d.ts","sourceRoot":"","sources":["../../../src/react/canvas-hud/viewport.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAa,SAAQ,WAAW,EAAE,UAAU;CAAG;AAEhE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,UAAU,CAQvF;AAED;gFACgF;AAChF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;yDACyD;AACzD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM,GAAG,IAAI,CAS1G;AAED,wFAAwF;AACxF,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAE,UAAe,GAAG,UAAU,GAAG,IAAI,CAI/G;AAED,iGAAiG;AACjG,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAM9F;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,GAAG,YAAY,CAOlF;AAED,+BAA+B;AAC/B,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,eAAe,EAAE,uBAAkD,CAAC;AAEjF,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,uBAAyC,GAAG,aAAa,CAIjH"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The camera arithmetic every pan-and-zoom canvas needs (CNV-2, CNV-3, CNV-5).
3
+ *
4
+ * Pure — no React, no DOM — so each rule is pinned by a spec rather than by opening an app.
5
+ * A view maps a WORLD point to the screen as `screen = world × scale + (x, y)`: `x`/`y` are the
6
+ * screen position of the world origin, which is what a CSS `translate(x, y) scale(scale)` on the
7
+ * stage means. Every canvas in the fleet already used that convention (family's `Camera`,
8
+ * patterns' `viewportGestures.ts`, desk's xyflow viewport), which is why it is the one here.
9
+ */
10
+ export function clampScale(scale, min, max) {
11
+ return Math.min(max, Math.max(min, scale));
12
+ }
13
+ /**
14
+ * CNV-2 — zoom to `scale` keeping the screen point `anchor` still: the pointer for a wheel or a
15
+ * pinch, the middle of the viewport for the ± buttons. The caller clamps `scale` first, because
16
+ * the floor is the canvas's own business (family floors it at the whole tree).
17
+ */
18
+ export function zoomAt(view, scale, anchor) {
19
+ if (!Number.isFinite(scale) || scale <= 0 || view.scale <= 0)
20
+ return view;
21
+ const applied = scale / view.scale;
22
+ return {
23
+ scale,
24
+ x: anchor.x - (anchor.x - view.x) * applied,
25
+ y: anchor.y - (anchor.y - view.y) * applied,
26
+ };
27
+ }
28
+ /** The zoom factor for one wheel event — exponential, so a trackpad pinch (which arrives as a
29
+ * ctrl+wheel stream of small deltas) and a mouse notch feel equally linear. */
30
+ export function wheelZoomFactor(deltaY) {
31
+ return Math.exp(-deltaY * 0.0015);
32
+ }
33
+ /** The scale at which `bounds` fits the viewport, or `null` while there is nothing to fit
34
+ * (an empty extent, or a viewport not measured yet). */
35
+ export function fitScale(bounds, viewport, options = {}) {
36
+ const { padding = 48, maxScale = 1, minScale = 0.02 } = options;
37
+ if (bounds.width <= 0 || bounds.height <= 0 || viewport.width <= 0 || viewport.height <= 0)
38
+ return null;
39
+ const scale = Math.min((viewport.width - padding * 2) / bounds.width, (viewport.height - padding * 2) / bounds.height);
40
+ if (!Number.isFinite(scale))
41
+ return null;
42
+ return clampScale(scale, minScale, maxScale);
43
+ }
44
+ /** CNV-3 "Fit" — the view that centres `bounds` in the viewport at {@link fitScale}. */
45
+ export function fitView(bounds, viewport, options = {}) {
46
+ const scale = fitScale(bounds, viewport, options);
47
+ if (scale === null)
48
+ return null;
49
+ return centerView({ x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 }, viewport, scale);
50
+ }
51
+ /** CNV-3 "Center" — the view that puts the world point `point` in the middle of the viewport. */
52
+ export function centerView(point, viewport, scale) {
53
+ return {
54
+ scale,
55
+ x: viewport.width / 2 - point.x * scale,
56
+ y: viewport.height / 2 - point.y * scale,
57
+ };
58
+ }
59
+ /** The part of the world the viewport shows — what a minimap draws as its window. */
60
+ export function visibleBounds(view, viewport) {
61
+ return {
62
+ x: -view.x / view.scale,
63
+ y: -view.y / view.scale,
64
+ width: viewport.width / view.scale,
65
+ height: viewport.height / view.scale,
66
+ };
67
+ }
68
+ export const LEVEL_OF_DETAIL = { mid: 0.3, close: 0.5 };
69
+ /** CNV-5 — which density a canvas draws at this scale. */
70
+ export function levelOfDetail(scale, thresholds = LEVEL_OF_DETAIL) {
71
+ if (scale >= thresholds.close)
72
+ return 'close';
73
+ if (scale >= thresholds.mid)
74
+ return 'mid';
75
+ return 'far';
76
+ }
77
+ //# sourceMappingURL=viewport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewport.js","sourceRoot":"","sources":["../../../src/react/canvas-hud/viewport.ts"],"names":[],"mappings":"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"}