@weasel-js/core 1.8.1 → 1.9.0
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/CHANGELOG.md +49 -0
- package/README.md +9 -5
- package/dist/{builtins-CMEh6-Oc.d.ts → builtins-CQcvcLXh.d.ts} +98 -31
- package/dist/canvas/DrawCanvas.js +33 -33
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/MinimapCanvas.js +29 -29
- package/dist/canvas/MinimapCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +63 -54
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +26 -5
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +3 -2
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +526 -529
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +35 -35
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/defaultDrawOne.js +6 -1
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/useHitObserver.js +23 -0
- package/dist/canvas/useHitObserver.js.map +1 -0
- package/dist/canvas/useHostAnchor.js.map +1 -1
- package/dist/core/device/useDeviceProfile.js.map +1 -1
- package/dist/core/scene/derivedPath.js.map +1 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/core/viewport/fitViewToBounds.js +3 -3
- package/dist/core/viewport/fitViewToBounds.js.map +1 -1
- package/dist/core/viewport/screenSlop.js +8 -2
- package/dist/core/viewport/screenSlop.js.map +1 -1
- package/dist/features/minimap/actions.js.map +1 -1
- package/dist/features/paths/markerCommands.js +95 -63
- package/dist/features/paths/markerCommands.js.map +1 -1
- package/dist/features/paths/tessellate/dash.js +52 -0
- package/dist/features/paths/tessellate/dash.js.map +1 -0
- package/dist/features/paths/tessellate/insets.js +32 -0
- package/dist/features/paths/tessellate/insets.js.map +1 -0
- package/dist/features/paths/tessellate/metric.js +72 -0
- package/dist/features/paths/tessellate/metric.js.map +1 -0
- package/dist/features/paths/tessellate/stroke.js +74 -134
- package/dist/features/paths/tessellate/stroke.js.map +1 -1
- package/dist/features/paths/tessellate/vertexWidths.js +32 -0
- package/dist/features/paths/tessellate/vertexWidths.js.map +1 -0
- package/dist/features/pointer/PointerContext.js +2 -2
- package/dist/features/pointer/PointerContext.js.map +1 -1
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/text/useTextEdit.js +86 -84
- package/dist/features/text/useTextEdit.js.map +1 -1
- package/dist/index.d.ts +78 -39
- package/dist/index.js +148 -148
- package/dist/interactions/actions/defaults/align.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/booleans.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/clearSelection.js.map +1 -1
- package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
- package/dist/interactions/actions/defaults/clone.js.map +1 -1
- package/dist/interactions/actions/defaults/createOutlines.js +1 -1
- package/dist/interactions/actions/defaults/createOutlines.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/distribute.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/editAnchors.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/escape.js +1 -1
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/flip.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insert.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/move.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/requiresSelection.js.map +1 -1
- package/dist/interactions/actions/defaults/resize.js.map +1 -1
- package/dist/interactions/actions/defaults/rotate.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/slice.js.map +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/gestures/shared/screenTolerance.js.map +1 -1
- package/dist/{registerProgram-ateHDGjw.d.ts → registerProgram-BMZRDgrh.d.ts} +4 -0
- package/dist/renderer/cache/markerCommandCache.js +46 -42
- package/dist/renderer/cache/markerCommandCache.js.map +1 -1
- package/dist/renderer/cache/outlineStrokeMeshCache.js +30 -25
- package/dist/renderer/cache/outlineStrokeMeshCache.js.map +1 -1
- package/dist/renderer/cache/strokeMeshCache.js +42 -31
- package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
- package/dist/renderer/cullDrawCommands.js +32 -31
- package/dist/renderer/cullDrawCommands.js.map +1 -1
- package/dist/renderer/draw.js +315 -362
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/glyphOutlineMesh.js +67 -0
- package/dist/renderer/glyphOutlineMesh.js.map +1 -0
- package/dist/renderer/math/mat3.js +8 -3
- package/dist/renderer/math/mat3.js.map +1 -1
- package/dist/renderer/strokeLengths.js +50 -0
- package/dist/renderer/strokeLengths.js.map +1 -0
- package/dist/renderer.d.ts +2 -2
- package/dist/{test-seams-fYf6m5pu.d.ts → test-seams-CuIxe0xY.d.ts} +1 -1
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +1 -1
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +43 -23
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDeviceProfile.js","names":["COARSE_QUERY","HOVER_QUERY","hasMatchMedia","detectOnce","dpr","DEFAULT_DEVICE_PROFILE","useDetectedFacts","facts","setFacts","useState","useEffect","coarseMq","hoverMq","onCoarse","f","onHover","disposed","densityMq","onDensity","next","arm","DeviceProfileContext","createContext","useDeviceProfile","provided","useContext","detected","base","overrides","useMemo","resolveDeviceProfile","coarsePointer","canHover","targetScale","DeviceProfileProvider","profile","value","createElement","children"],"sources":["../../../src/core/device/useDeviceProfile.ts"],"sourcesContent":["import {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\nimport type { DetectedDeviceFacts, DeviceProfile } from './types';\nimport { DEFAULT_DEVICE_PROFILE, resolveDeviceProfile } from '@weasel-js/routing';\n\nconst COARSE_QUERY = '(pointer: coarse)';\nconst HOVER_QUERY = '(hover: hover)';\n\nfunction hasMatchMedia(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function';\n}\n\nfunction detectOnce(): DetectedDeviceFacts {\n // Density is read unconditionally: `devicePixelRatio` does not depend on\n // `matchMedia` existing. Only *watching* density needs a media query.\n // Gating this read behind `hasMatchMedia()` would report density 1 under\n // any environment without matchMedia — including jsdom, which would\n // silently break `useCanvasSize`'s existing DPR assertions.\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n if (!hasMatchMedia()) {\n return {\n coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,\n canHover: DEFAULT_DEVICE_PROFILE.canHover,\n dpr,\n };\n }\n return {\n coarsePointer: window.matchMedia(COARSE_QUERY).matches,\n canHover: window.matchMedia(HOVER_QUERY).matches,\n dpr,\n };\n}\n\n/**\n * Detect device facts and keep them live.\n *\n * Three subscriptions: pointer coarseness, hover capability, and pixel\n * density. The density one is special — `(resolution: Ndppx)` stops matching\n * as soon as density changes, so a single listener fires once and goes dead.\n * We re-arm a fresh query at the new density on every change.\n */\nfunction useDetectedFacts(): DetectedDeviceFacts {\n const [facts, setFacts] = useState<DetectedDeviceFacts>(detectOnce);\n\n useEffect(() => {\n if (!hasMatchMedia()) return;\n\n const coarseMq = window.matchMedia(COARSE_QUERY);\n const hoverMq = window.matchMedia(HOVER_QUERY);\n\n const onCoarse = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, coarsePointer: e.matches }));\n const onHover = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, canHover: e.matches }));\n\n coarseMq.addEventListener('change', onCoarse as EventListener);\n hoverMq.addEventListener('change', onHover as EventListener);\n\n // Re-arming density watcher.\n let disposed = false;\n let densityMq: MediaQueryList | null = null;\n const onDensity = () => {\n if (disposed) return;\n const next = window.devicePixelRatio || 1;\n setFacts((f) => (f.dpr === next ? f : { ...f, dpr: next }));\n arm();\n };\n function arm(): void {\n if (disposed) return;\n densityMq?.removeEventListener('change', onDensity as EventListener);\n densityMq = window.matchMedia(\n `(resolution: ${window.devicePixelRatio || 1}dppx)`,\n );\n densityMq.addEventListener('change', onDensity as EventListener);\n }\n arm();\n\n // Re-sync once on mount: a query could have changed between the\n // useState initializer and the effect running.\n setFacts({\n coarsePointer: coarseMq.matches,\n canHover: hoverMq.matches,\n dpr: window.devicePixelRatio || 1,\n });\n\n return () => {\n disposed = true;\n coarseMq.removeEventListener('change', onCoarse as EventListener);\n hoverMq.removeEventListener('change', onHover as EventListener);\n densityMq?.removeEventListener('change', onDensity as EventListener);\n };\n }, []);\n\n return facts;\n}\n\nconst DeviceProfileContext = createContext<DeviceProfile | null>(null);\n\n/**\n * Read the ambient device profile.\n *\n * Uses the nearest {@link DeviceProfileProvider} when one exists, and\n * otherwise detects for itself — so a standalone overlay or a labkit panel\n * rendered outside a `<SceneCanvas>` still gets correct facts.\n *\n * `overrides` always wins over both, which is what makes tests and\n * force-touch-chrome demos possible without stubbing `matchMedia`.\n */\nexport function useDeviceProfile(overrides?: Partial<DeviceProfile>): DeviceProfile {\n const provided = useContext(DeviceProfileContext);\n const detected = useDetectedFacts();\n const base: DetectedDeviceFacts = provided ?? detected;\n const { coarsePointer, canHover, dpr, targetScale } = overrides ?? {};\n return useMemo(\n () =>\n resolveDeviceProfile(base, {\n ...(coarsePointer !== undefined ? { coarsePointer } : {}),\n ...(canHover !== undefined ? { canHover } : {}),\n ...(dpr !== undefined ? { dpr } : {}),\n ...(targetScale !== undefined ? { targetScale } : {}),\n }),\n [base, coarsePointer, canHover, dpr, targetScale],\n );\n}\n\n/** Props for `<DeviceProfileProvider>`. */\nexport interface DeviceProfileProviderProps {\n /** Partial override folded over detected facts. */\n value?: Partial<DeviceProfile>;\n children: ReactNode;\n}\n\n/**\n * Publish one resolved profile to a subtree. `<SceneCanvas>` renders this so\n * overlays, affordances, and consumer chrome all read the same object.\n */\nexport function DeviceProfileProvider({ value, children }: DeviceProfileProviderProps) {\n const profile = useDeviceProfile(value);\n return createElement(DeviceProfileContext.Provider, { value: profile }, children);\n}\n"],"mappings":";;;AAYA,IAAMA,IAAe,qBACfC,IAAc;AAEpB,SAASC,IAAyB;CAChC,OAAO,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe;AACvE;AAEA,SAASC,IAAkC;CAMzC,IAAMC,IAAO,OAAO,SAAW,OAAe,OAAO,oBAAqB;CAQ1E,OAPKF,EAAc,IAOZ;EACL,eAAe,OAAO,WAAWF,CAAY,CAAC,CAAC;EAC/C,UAAU,OAAO,WAAWC,CAAW,CAAC,CAAC;EACzC;CACF,IAVS;EACL,eAAeI,EAAuB;EACtC,UAAUA,EAAuB;EACjC;CACF;AAOJ;AAUA,SAASC,IAAwC;CAC/C,IAAM,CAACC,GAAOC,KAAYC,EAA8BN,CAAU;
|
|
1
|
+
{"version":3,"file":"useDeviceProfile.js","names":["COARSE_QUERY","HOVER_QUERY","hasMatchMedia","detectOnce","dpr","DEFAULT_DEVICE_PROFILE","useDetectedFacts","facts","setFacts","useState","useEffect","coarseMq","hoverMq","onCoarse","f","onHover","disposed","densityMq","onDensity","next","arm","DeviceProfileContext","createContext","useDeviceProfile","provided","useContext","detected","base","overrides","useMemo","resolveDeviceProfile","coarsePointer","canHover","targetScale","DeviceProfileProvider","profile","value","createElement","children"],"sources":["../../../src/core/device/useDeviceProfile.ts"],"sourcesContent":["import {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\nimport type { DetectedDeviceFacts, DeviceProfile } from './types';\nimport { DEFAULT_DEVICE_PROFILE, resolveDeviceProfile } from '@weasel-js/routing';\n\nconst COARSE_QUERY = '(pointer: coarse)';\nconst HOVER_QUERY = '(hover: hover)';\n\nfunction hasMatchMedia(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function';\n}\n\nfunction detectOnce(): DetectedDeviceFacts {\n // Density is read unconditionally: `devicePixelRatio` does not depend on\n // `matchMedia` existing. Only *watching* density needs a media query.\n // Gating this read behind `hasMatchMedia()` would report density 1 under\n // any environment without matchMedia — including jsdom, which would\n // silently break `useCanvasSize`'s existing DPR assertions.\n const dpr = (typeof window !== 'undefined' && window.devicePixelRatio) || 1;\n if (!hasMatchMedia()) {\n return {\n coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,\n canHover: DEFAULT_DEVICE_PROFILE.canHover,\n dpr,\n };\n }\n return {\n coarsePointer: window.matchMedia(COARSE_QUERY).matches,\n canHover: window.matchMedia(HOVER_QUERY).matches,\n dpr,\n };\n}\n\n/**\n * Detect device facts and keep them live.\n *\n * Three subscriptions: pointer coarseness, hover capability, and pixel\n * density. The density one is special — `(resolution: Ndppx)` stops matching\n * as soon as density changes, so a single listener fires once and goes dead.\n * We re-arm a fresh query at the new density on every change.\n */\nfunction useDetectedFacts(): DetectedDeviceFacts {\n const [facts, setFacts] = useState<DetectedDeviceFacts>(detectOnce);\n\n useEffect(() => {\n if (!hasMatchMedia()) return;\n\n const coarseMq = window.matchMedia(COARSE_QUERY);\n const hoverMq = window.matchMedia(HOVER_QUERY);\n\n const onCoarse = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, coarsePointer: e.matches }));\n const onHover = (e: MediaQueryListEvent | { matches: boolean }) =>\n setFacts((f) => ({ ...f, canHover: e.matches }));\n\n coarseMq.addEventListener('change', onCoarse as EventListener);\n hoverMq.addEventListener('change', onHover as EventListener);\n\n // Re-arming density watcher.\n let disposed = false;\n let densityMq: MediaQueryList | null = null;\n const onDensity = () => {\n if (disposed) return;\n const next = window.devicePixelRatio || 1;\n setFacts((f) => (f.dpr === next ? f : { ...f, dpr: next }));\n arm();\n };\n function arm(): void {\n if (disposed) return;\n densityMq?.removeEventListener('change', onDensity as EventListener);\n densityMq = window.matchMedia(\n `(resolution: ${window.devicePixelRatio || 1}dppx)`,\n );\n densityMq.addEventListener('change', onDensity as EventListener);\n }\n arm();\n\n // Re-sync once on mount: a query could have changed between the\n // useState initializer and the effect running.\n // eslint-disable-next-line react-hooks/set-state-in-effect -- re-syncs media queries that may have changed before subscribing\n setFacts({\n coarsePointer: coarseMq.matches,\n canHover: hoverMq.matches,\n dpr: window.devicePixelRatio || 1,\n });\n\n return () => {\n disposed = true;\n coarseMq.removeEventListener('change', onCoarse as EventListener);\n hoverMq.removeEventListener('change', onHover as EventListener);\n densityMq?.removeEventListener('change', onDensity as EventListener);\n };\n }, []);\n\n return facts;\n}\n\nconst DeviceProfileContext = createContext<DeviceProfile | null>(null);\n\n/**\n * Read the ambient device profile.\n *\n * Uses the nearest {@link DeviceProfileProvider} when one exists, and\n * otherwise detects for itself — so a standalone overlay or a labkit panel\n * rendered outside a `<SceneCanvas>` still gets correct facts.\n *\n * `overrides` always wins over both, which is what makes tests and\n * force-touch-chrome demos possible without stubbing `matchMedia`.\n */\nexport function useDeviceProfile(overrides?: Partial<DeviceProfile>): DeviceProfile {\n const provided = useContext(DeviceProfileContext);\n const detected = useDetectedFacts();\n const base: DetectedDeviceFacts = provided ?? detected;\n const { coarsePointer, canHover, dpr, targetScale } = overrides ?? {};\n return useMemo(\n () =>\n resolveDeviceProfile(base, {\n ...(coarsePointer !== undefined ? { coarsePointer } : {}),\n ...(canHover !== undefined ? { canHover } : {}),\n ...(dpr !== undefined ? { dpr } : {}),\n ...(targetScale !== undefined ? { targetScale } : {}),\n }),\n [base, coarsePointer, canHover, dpr, targetScale],\n );\n}\n\n/** Props for `<DeviceProfileProvider>`. */\nexport interface DeviceProfileProviderProps {\n /** Partial override folded over detected facts. */\n value?: Partial<DeviceProfile>;\n children: ReactNode;\n}\n\n/**\n * Publish one resolved profile to a subtree. `<SceneCanvas>` renders this so\n * overlays, affordances, and consumer chrome all read the same object.\n */\nexport function DeviceProfileProvider({ value, children }: DeviceProfileProviderProps) {\n const profile = useDeviceProfile(value);\n return createElement(DeviceProfileContext.Provider, { value: profile }, children);\n}\n"],"mappings":";;;AAYA,IAAMA,IAAe,qBACfC,IAAc;AAEpB,SAASC,IAAyB;CAChC,OAAO,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe;AACvE;AAEA,SAASC,IAAkC;CAMzC,IAAMC,IAAO,OAAO,SAAW,OAAe,OAAO,oBAAqB;CAQ1E,OAPKF,EAAc,IAOZ;EACL,eAAe,OAAO,WAAWF,CAAY,CAAC,CAAC;EAC/C,UAAU,OAAO,WAAWC,CAAW,CAAC,CAAC;EACzC;CACF,IAVS;EACL,eAAeI,EAAuB;EACtC,UAAUA,EAAuB;EACjC;CACF;AAOJ;AAUA,SAASC,IAAwC;CAC/C,IAAM,CAACC,GAAOC,KAAYC,EAA8BN,CAAU;CAoDlE,OAlDAO,QAAgB;EACd,IAAI,CAACR,EAAc,GAAG;EAEtB,IAAMS,IAAW,OAAO,WAAWX,CAAY,GACzCY,IAAU,OAAO,WAAWX,CAAW,GAEvCY,KAAY,MAChBL,GAAU,OAAO;GAAE,GAAGM;GAAG,eAAe,EAAE;EAAQ,EAAE,GAChDC,KAAW,MACfP,GAAU,OAAO;GAAE,GAAGM;GAAG,UAAU,EAAE;EAAQ,EAAE;EAGjD,AADAH,EAAS,iBAAiB,UAAUE,CAAyB,GAC7DD,EAAQ,iBAAiB,UAAUG,CAAwB;EAG3D,IAAIC,IAAW,IACXC,IAAmC,MACjCC,UAAkB;GACtB,IAAIF,GAAU;GACd,IAAMG,IAAO,OAAO,oBAAoB;GAExC,AADAX,GAAU,MAAOM,EAAE,QAAQK,IAAOL,IAAI;IAAE,GAAGA;IAAG,KAAKK;GAAK,CAAE,GAC1DC,EAAI;EACN;EACA,SAASA,IAAY;GACnB,AAAIJ,MACJC,GAAW,oBAAoB,UAAUC,CAA0B,GACnE,IAAY,OAAO,WACjB,gBAAgB,OAAO,oBAAoB,EAAE,MAC/C,GACAD,EAAU,iBAAiB,UAAUC,CAA0B;EACjE;EAYA,OAXAE,EAAI,GAKJZ,EAAS;GACP,eAAeG,EAAS;GACxB,UAAUC,EAAQ;GAClB,KAAK,OAAO,oBAAoB;EAClC,CAAC,SAEY;GAIX,AAHA,IAAW,IACXD,EAAS,oBAAoB,UAAUE,CAAyB,GAChED,EAAQ,oBAAoB,UAAUG,CAAwB,GAC9DE,GAAW,oBAAoB,UAAUC,CAA0B;EACrE;CACF,GAAG,CAAC,CAAC,GAEEX;AACT;AAEA,IAAMc,IAAuBC,EAAoC,IAAI;AAYrE,SAAgBC,EAAiB,GAAmD;CAClF,IAAMC,IAAWC,EAAWJ,CAAoB,GAC1CK,IAAWpB,EAAiB,GAC5BqB,IAA4BH,KAAYE,GACxC,EAAE,kBAAe,aAAU,QAAK,mBAAgBE,KAAa,CAAC;CACpE,OAAOC,QAEHC,EAAqBH,GAAM;EACzB,GAAII,MAAkB,KAAA,IAAgC,CAAC,IAArB,EAAE,iBAAc;EAClD,GAAIC,MAAa,KAAA,IAA2B,CAAC,IAAhB,EAAE,YAAS;EACxC,GAAI5B,MAAQ,KAAA,IAAsB,CAAC,IAAX,EAAE,OAAI;EAC9B,GAAI6B,MAAgB,KAAA,IAA8B,CAAC,IAAnB,EAAE,eAAY;CAChD,CAAC,GACH;EAACN;EAAMI;EAAeC;EAAU5B;EAAK6B;CAAW,CAClD;AACF;AAaA,SAAgBC,EAAsB,EAAE,UAAO,eAAwC;CACrF,IAAMC,IAAUZ,EAAiBa,CAAK;CACtC,OAAOC,EAAchB,EAAqB,UAAU,EAAE,OAAOc,EAAQ,GAAGG,CAAQ;AAClF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derivedPath.js","names":["SLOT","resolving","cycleHits","resolveDerivedPath","derivePath","node","ids","dependencyIdsOf","childrenOf","hitsBefore","computed","record","nodeMemo","memoSlot","draw","depOf","sameDeps","dropPoseKeyedMemoSlots","deps","recordDeps","into"],"sources":["../../../src/core/scene/derivedPath.ts"],"sourcesContent":["/**\n * `resolveDerivedPath` — the path a node computes from its dependencies.\n *\n * It sits beside `derivedPose` rather than in `canvas/` because a *pose* can\n * now derive from a dependency's path: an edge label is positioned along the\n * route it labels, and `effectivePose` is what resolves it. The render walk's\n * wrapper stays in `canvas/derivedPath.ts`.\n *\n * Invalidation is both pushed and pulled. The scene pushes — dropping the\n * dependent's memo slot on the edits it performs — and on a memo hit this\n * module re-resolves the dependencies and compares their poses *by value*\n * against the ones the cached path was drawn from. The pull is what covers a\n * dependency that moved with no scene edit behind it: a lookup answering poses\n * of its own (`sceneDepLookup(scene, toPose)`), an ancestor's frame moving, a\n * dependency appearing or going away. Value, not reference: a pose override\n * mutates its buffer in place, so the reference never moves.\n *\n * The pull covers poses only. A derivation is handed each dependency's whole\n * node, so one reading `data` or `layer` still rides on the scene's push.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { dependencyIdsOf } from './dependents';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport { recordDeps, sameDeps, type DepRecord } from './depMemo';\nimport type { DerivedDep, NodeId } from './types';\n\nconst SLOT = 'kit:derivedPath';\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies it\n * without importing the full generic shape. */\nexport interface PathDerivingNode<TPose> {\n id: NodeId;\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n derivePath?:
|
|
1
|
+
{"version":3,"file":"derivedPath.js","names":["SLOT","resolving","cycleHits","resolveDerivedPath","derivePath","node","ids","dependencyIdsOf","childrenOf","hitsBefore","computed","record","nodeMemo","memoSlot","draw","depOf","sameDeps","dropPoseKeyedMemoSlots","deps","recordDeps","into"],"sources":["../../../src/core/scene/derivedPath.ts"],"sourcesContent":["/**\n * `resolveDerivedPath` — the path a node computes from its dependencies.\n *\n * It sits beside `derivedPose` rather than in `canvas/` because a *pose* can\n * now derive from a dependency's path: an edge label is positioned along the\n * route it labels, and `effectivePose` is what resolves it. The render walk's\n * wrapper stays in `canvas/derivedPath.ts`.\n *\n * Invalidation is both pushed and pulled. The scene pushes — dropping the\n * dependent's memo slot on the edits it performs — and on a memo hit this\n * module re-resolves the dependencies and compares their poses *by value*\n * against the ones the cached path was drawn from. The pull is what covers a\n * dependency that moved with no scene edit behind it: a lookup answering poses\n * of its own (`sceneDepLookup(scene, toPose)`), an ancestor's frame moving, a\n * dependency appearing or going away. Value, not reference: a pose override\n * mutates its buffer in place, so the reference never moves.\n *\n * The pull covers poses only. A derivation is handed each dependency's whole\n * node, so one reading `data` or `layer` still rides on the scene's push.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { dependencyIdsOf } from './dependents';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport { recordDeps, sameDeps, type DepRecord } from './depMemo';\nimport type { DerivedDep, DerivePathFn, NodeId } from './types';\n\nconst SLOT = 'kit:derivedPath';\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies it\n * without importing the full generic shape. */\nexport interface PathDerivingNode<TPose> {\n id: NodeId;\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** A cached path, with what it was drawn from. */\ninterface PathMemo<TPose> extends DepRecord<TPose> {\n path: Path | null;\n}\n\n/** Ids whose path is on the stack. A dependency graph is meant to be acyclic\n * and `dependsOn` is unvalidated; now that a path can read another path, a\n * cycle has to resolve to `null` at whichever node closes it rather than\n * overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires, so a value computed from a\n * fallback is not cached — it is an artifact of which node was asked first. */\nlet cycleHits = 0;\n\n/**\n * The path `node` computes from its dependencies, or `null` when it derives\n * from nothing (the normal case) or its `derivePath` has nothing to draw.\n *\n * `depOf` supplies each dependency — its node, the pose it is painted at, and\n * its own path; one it cannot resolve reaches `derivePath` as `undefined`.\n * `childrenOf` answers a node whose `dependsOn` is `'children'`.\n *\n * `memoSlot` separates a `depOf` that answers different poses than the scene's\n * own — the memo key cannot see which lookup filled it.\n */\nexport function resolveDerivedPath<TPose>(\n node: PathDerivingNode<TPose>,\n depOf: (id: NodeId) => DerivedDep<TPose> | undefined,\n childrenOf: (id: NodeId) => readonly NodeId[],\n memoSlot: string = SLOT,\n): Path | null {\n const derivePath = node.derivePath;\n if (derivePath === undefined) return null;\n const ids = dependencyIdsOf(node, childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n let computed = false;\n const record = nodeMemo<PathMemo<TPose>>(node, memoSlot, node.pose, () => {\n computed = true;\n return draw(node, derivePath, ids.map(depOf), { nodes: [], poses: [], path: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n draw(node, derivePath, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.path;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePath` against `deps` and record what it was run against. */\nfunction draw<TPose>(\n node: PathDerivingNode<TPose>,\n derivePath: NonNullable<PathDerivingNode<TPose>['derivePath']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PathMemo<TPose>,\n): PathMemo<TPose> {\n into.path = derivePath(node as never, deps);\n recordDeps(into, deps);\n return into;\n}\n\n"],"mappings":";;;;AA0BA,IAAMA,IAAO,mBAsBPC,oBAAY,IAAI,IAAY,GAI9BC,IAAY;AAahB,SAAgBC,EACd,GACA,GACA,GACA,IAAmBH,GACN;CACb,IAAMI,IAAaC,EAAK;CACxB,IAAID,MAAe,KAAA,GAAW,OAAO;CACrC,IAAME,IAAMC,EAAgBF,GAAMG,CAAU;CAC5C,IAAIF,EAAI,WAAW,GAAG,OAAO;CAC7B,IAAIL,EAAU,IAAII,EAAK,EAAE,GAEvB,OADA,KACO;CAET,EAAU,IAAIA,EAAK,EAAE;CACrB,IAAMI,IAAaP;CACnB,IAAI;EACF,IAAIQ,IAAW,IACTC,IAASC,EAA0BP,GAAMQ,GAAUR,EAAK,aAC5D,IAAW,IACJS,EAAKT,GAAMD,GAAYE,EAAI,IAAIS,CAAK,GAAG;GAAE,OAAO,CAAC;GAAG,OAAO,CAAC;GAAG,MAAM;EAAK,CAAC,EACnF;EAOD,OAJI,CAACL,KAAY,CAACM,EAASL,GAAQL,GAAKS,CAAK,KAC3CD,EAAKT,GAAMD,GAAYE,EAAI,IAAIS,CAAK,GAAGJ,CAAM,GAE3CT,MAAcO,KAAYQ,EAAuBZ,CAAI,GAClDM,EAAO;CAChB,UAAU;EACR,EAAU,OAAON,EAAK,EAAE;CAC1B;AACF;AAGA,SAASS,EACP,GACA,GACA,GACA,GACiB;CAGjB,OAFA,EAAK,OAAOV,EAAWC,GAAea,CAAI,GAC1CC,EAAWC,GAAMF,CAAI,GACdE;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effectivePose.js","names":["POSE_SLOT","PATH_SLOT","resolving","cycleHits","derivedPose","derivedPoseIn","source","node","derive","ids","dependencyIdsOf","hitsBefore","depOf","depIn","id","reading","computed","record","nodeMemo","run","sameDeps","dropPoseKeyedMemoSlots","deps","recordDeps","into","derivedDepOf","SceneDep","posed","poseIn","resolveDerivedPath","depId","override","documentPose","effectivePose","definesFrame"],"sources":["../../../src/core/scene/effectivePose.ts"],"sourcesContent":["/**\n * `effectivePose` — the pose to draw, pick and measure a node at.\n *\n * Three answers, in order: the ephemeral override a gesture published, the\n * pose the node derives from its dependencies, or the pose the document\n * stores. Reading `node.pose` directly is how the render and hit-test paths\n * came to disagree about where a node is, and a derived pose widens that gap\n * from \"mid-drag\" to \"always\".\n */\nimport { recordDeps, sameDeps, type DepRecord } from './depMemo';\nimport { dependencyIdsOf } from './dependents';\nimport { resolveDerivedPath } from './derivedPath';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, Node, NodeId, PoseOverrides } from './types';\n\n/**\n * Which pose a resolution answers: `effective` honors overrides all the way\n * down the dependency chain, `document` ignores them all the way down. Each\n * reading memoizes in its own slots — the memo key is the node and its\n * authored pose, which is the same for both.\n */\ntype Reading = 'effective' | 'document';\n\nconst POSE_SLOT: Record<Reading, string> = {\n effective: 'kit:derivedPose',\n document: 'kit:derivedPose:document',\n};\nconst PATH_SLOT: Record<Reading, string | undefined> = {\n effective: undefined,\n document: 'kit:derivedPath:document',\n};\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies\n * it without importing the full generic shape. */\nexport interface PosedNode<TPose> {\n id: NodeId;\n kind?: 'leaf' | 'container';\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n derivePose?: (\n node: never,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n ) => TPose | null;\n /** Read only when something asks a dependency for its `path`. */\n derivePath?: (\n node: never,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n ) => Path | null;\n}\n\n/** What resolving a pose needs: the overrides, and enough of the scene to\n * reach a node's dependencies. A `Scene` satisfies it. */\nexport interface PoseSource<TPose> {\n readonly overrides: Pick<PoseOverrides<TPose>, 'read'>;\n get(id: NodeId): PosedNode<TPose> | undefined;\n childrenOf(id: NodeId): readonly NodeId[];\n}\n\n/** A cached derived pose, with what it was derived from. */\ninterface PoseMemo<TPose> extends DepRecord<TPose> {\n value: TPose | null;\n}\n\n/** Ids whose derivation is on the stack. A dependency graph is meant to be\n * acyclic and `dependsOn` is unvalidated, so a cycle resolves to the authored\n * pose at whichever node closes it rather than overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires. A derivation that closed a cycle\n * answered from a fallback rather than from its dependencies, so its value is\n * an artifact of which node was asked first and must not be cached. */\nlet cycleHits = 0;\n\n/**\n * The pose `node` computes from its dependencies, or `null` when it derives\n * nothing — it has no `derivePose`, nothing to derive from, or its\n * `derivePose` returned `null`.\n *\n * A dependency is read through {@link effectivePose}, so a group of groups\n * resolves bottom-up and a dragged dependency's override reaches the\n * derivation the same frame it is published.\n */\nexport function derivedPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose | null {\n return derivedPoseIn(source, node, 'effective');\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const derive = node.derivePose;\n if (derive === undefined) return null;\n const ids = dependencyIdsOf(node, source.childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n const depOf = (id: NodeId) => depIn(source, id, reading);\n // Keyed on the authored pose, which is what `dropPoseKeyedMemoSlots`\n // clears. That key only sees a change someone pushed, so the record also\n // carries what the derivation read and re-checks it by value on a hit —\n // see `depMemo.ts`.\n let computed = false;\n const record = nodeMemo<PoseMemo<TPose>>(node, POSE_SLOT[reading], node.pose, () => {\n computed = true;\n return run(node, derive, ids.map(depOf), { nodes: [], poses: [], value: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n run(node, derive, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.value;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePose` against `deps` and record what it was run against. */\nfunction run<TPose>(\n node: PosedNode<TPose>,\n derive: NonNullable<PosedNode<TPose>['derivePose']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PoseMemo<TPose>,\n): PoseMemo<TPose> {\n into.value = derive(node as never, deps);\n recordDeps(into, deps);\n return into;\n}\n\n/**\n * `id` as a derivation sees it: the node, the pose it is painted at, and the\n * path it derives.\n *\n * The pose is resolved eagerly — every derivation reads it — and the path\n * lazily, because most do not and resolving a route costs a router run. Both\n * resolve *through* this function, so a dependency that is itself derived is\n * resolved before it is read.\n */\nexport function derivedDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n): DerivedDep<TPose> | undefined {\n return depIn(source, id, 'effective');\n}\n\nfunction depIn<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reading: Reading,\n): DerivedDep<TPose> | undefined {\n const node = source.get(id);\n if (node === undefined) return undefined;\n return new SceneDep(source, node, reading);\n}\n\n/**\n * A class rather than an object literal with a `get path()`: one of these is\n * built per dependency per derived node per frame, and an own accessor costs\n * an order of magnitude more to define than a prototype one does to inherit.\n */\nclass SceneDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n private readonly posed: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n this.node = posed as unknown as Node<unknown, string, TPose>;\n this.pose = poseIn(source, posed, reading);\n }\n\n get path(): Path | null {\n const { source, reading } = this;\n return resolveDerivedPath(\n this.posed,\n (depId) => depIn(source, depId, reading),\n (depId) => source.childrenOf(depId),\n PATH_SLOT[reading],\n );\n }\n}\n\nfunction poseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose {\n if (reading === 'effective') {\n const override = source.overrides.read(node.id)?.pose;\n if (override !== undefined) return override;\n }\n return derivedPoseIn(source, node, reading) ?? node.pose;\n}\n\n/**\n * The pose the *document* says `node` is at: derived when it derives, else\n * authored. `effectivePose` minus the override step, at every link of the\n * dependency chain — a derived node's document pose holds still while\n * something it derives from is being dragged.\n *\n * For a reader that must not see an in-flight gesture — an action capturing\n * the `from` of a transform op, a placement computed against a sibling, the\n * minimap's framing. A derived pose belongs here and an override does not:\n * derivation is what the document means, an override is what one gesture is\n * currently showing.\n */\nexport function documentPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'document');\n}\n\n/**\n * The pose to draw, pick and measure `node` at.\n *\n * The one rule. Every pose a reader acts on comes from here: the three render\n * walks, the pick walk, the adapters tools and actions commit through.\n */\nexport function effectivePose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'effective');\n}\n\n/**\n * Whether `node`'s pose is a frame its children are expressed in, under a\n * composing strategy.\n *\n * A container that derives its pose from its children cannot be one: moving\n * the frame to cover the children would move the children, and a `RectPose`\n * frame's origin is its corner, so no envelope leaves them in place. Such a\n * container is an envelope instead — its children are stored in its own\n * frame — and the composition fold passes over it.\n */\nexport function definesFrame(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return !(node.dependsOn === 'children' && node.derivePose !== undefined);\n}\n"],"mappings":";;;;;AAwBA,IAAMA,IAAqC;CACzC,WAAW;CACX,UAAU;AACZ,GACMC,IAAiD;CACrD,WAAW,KAAA;CACX,UAAU;AACZ,GAsCMC,oBAAY,IAAI,IAAY,GAK9BC,IAAY;AAWhB,SAAgBC,EACd,GACA,GACc;CACd,OAAOC,EAAcC,GAAQC,GAAM,WAAW;AAChD;AAEA,SAASF,EACP,GACA,GACA,GACc;CACd,IAAMG,IAASD,EAAK;CACpB,IAAIC,MAAW,KAAA,GAAW,OAAO;CACjC,IAAMC,IAAMC,EAAgBH,GAAMD,EAAO,UAAU;CACnD,IAAIG,EAAI,WAAW,GAAG,OAAO;CAC7B,IAAIP,EAAU,IAAIK,EAAK,EAAE,GAEvB,OADA,KACO;CAET,EAAU,IAAIA,EAAK,EAAE;CACrB,IAAMI,IAAaR;CACnB,IAAI;EACF,IAAMS,KAAS,MAAeC,EAAMP,GAAQQ,GAAIC,CAAO,GAKnDC,IAAW,IACTC,IAASC,EAA0BX,GAAMP,EAAUe,IAAUR,EAAK,aACtE,IAAW,IACJY,EAAIZ,GAAMC,GAAQC,EAAI,IAAIG,CAAK,GAAG;GAAE,OAAO,CAAC;GAAG,OAAO,CAAC;GAAG,OAAO;EAAK,CAAC,EAC/E;EAOD,OAJI,CAACI,KAAY,CAACI,EAASH,GAAQR,GAAKG,CAAK,KAC3CO,EAAIZ,GAAMC,GAAQC,EAAI,IAAIG,CAAK,GAAGK,CAAM,GAEtCd,MAAcQ,KAAYU,EAAuBd,CAAI,GAClDU,EAAO;CAChB,UAAU;EACR,EAAU,OAAOV,EAAK,EAAE;CAC1B;AACF;AAGA,SAASY,EACP,GACA,GACA,GACA,GACiB;CAGjB,OAFA,EAAK,QAAQX,EAAOD,GAAee,CAAI,GACvCC,EAAWC,GAAMF,CAAI,GACdE;AACT;AAWA,SAAgBC,EACd,GACA,GAC+B;CAC/B,OAAOZ,EAAMP,GAAQQ,GAAI,WAAW;AACtC;AAEA,SAASD,EACP,GACA,GACA,GAC+B;CAC/B,IAAMN,IAAOD,EAAO,IAAIQ,CAAE;CAC1B,IAAIP,MAAS,KAAA,GACb,OAAO,IAAImB,EAASpB,GAAQC,GAAMQ,CAAO;AAC3C;AAOA,IAAMW,IAAN,MAAmD;CAK9B;CACA;CACA;CANnB;CACA;CAEA,YACE,GACA,GACA,GACA;EAEA,AALiB,KAAA,SAAApB,GACA,KAAA,QAAAqB,GACA,KAAA,UAAAZ,GAEjB,KAAK,OAAOY,GACZ,KAAK,OAAOC,EAAOtB,GAAQqB,GAAOZ,CAAO;CAC3C;CAEA,IAAI,OAAoB;EACtB,IAAM,EAAE,WAAQ,eAAY;EAC5B,OAAOc,EACL,KAAK,QACJ,MAAUhB,EAAMP,GAAQwB,GAAOf,CAAO,IACtC,MAAUT,EAAO,WAAWwB,CAAK,GAClC7B,EAAUc,EACZ;CACF;AACF;AAEA,SAASa,EACP,GACA,GACA,GACO;CACP,IAAIb,MAAY,aAAa;EAC3B,IAAMgB,IAAWzB,EAAO,UAAU,KAAKC,EAAK,EAAE,CAAC,EAAE;EACjD,IAAIwB,MAAa,KAAA,GAAW,OAAOA;CACrC;CACA,OAAO1B,EAAcC,GAAQC,GAAMQ,CAAO,KAAKR,EAAK;AACtD;AAcA,SAAgByB,EACd,GACA,GACO;CACP,OAAOJ,EAAOtB,GAAQC,GAAM,UAAU;AACxC;AAQA,SAAgB0B,EACd,GACA,GACO;CACP,OAAOL,EAAOtB,GAAQC,GAAM,WAAW;AACzC;AAYA,SAAgB2B,EAAa,GAA8D;CACzF,OAAS3B,EAAK,cAAc,cAAcA,EAAK,eAAe,KAAA;AAChE"}
|
|
1
|
+
{"version":3,"file":"effectivePose.js","names":["POSE_SLOT","PATH_SLOT","resolving","cycleHits","derivedPose","derivedPoseIn","source","node","derive","ids","dependencyIdsOf","hitsBefore","depOf","depIn","id","reading","computed","record","nodeMemo","run","sameDeps","dropPoseKeyedMemoSlots","deps","recordDeps","into","derivedDepOf","SceneDep","posed","poseIn","resolveDerivedPath","depId","override","documentPose","effectivePose","definesFrame"],"sources":["../../../src/core/scene/effectivePose.ts"],"sourcesContent":["/**\n * `effectivePose` — the pose to draw, pick and measure a node at.\n *\n * Three answers, in order: the ephemeral override a gesture published, the\n * pose the node derives from its dependencies, or the pose the document\n * stores. Reading `node.pose` directly is how the render and hit-test paths\n * came to disagree about where a node is, and a derived pose widens that gap\n * from \"mid-drag\" to \"always\".\n */\nimport { recordDeps, sameDeps, type DepRecord } from './depMemo';\nimport { dependencyIdsOf } from './dependents';\nimport { resolveDerivedPath } from './derivedPath';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, DerivePathFn, DerivePoseFn, Node, NodeId, PoseOverrides } from './types';\n\n/**\n * Which pose a resolution answers: `effective` honors overrides all the way\n * down the dependency chain, `document` ignores them all the way down. Each\n * reading memoizes in its own slots — the memo key is the node and its\n * authored pose, which is the same for both.\n */\ntype Reading = 'effective' | 'document';\n\nconst POSE_SLOT: Record<Reading, string> = {\n effective: 'kit:derivedPose',\n document: 'kit:derivedPose:document',\n};\nconst PATH_SLOT: Record<Reading, string | undefined> = {\n effective: undefined,\n document: 'kit:derivedPath:document',\n};\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies\n * it without importing the full generic shape. */\nexport interface PosedNode<TPose> {\n id: NodeId;\n kind?: 'leaf' | 'container';\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n derivePose?: DerivePoseFn<TPose>;\n /** Read only when something asks a dependency for its `path`. */\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** What resolving a pose needs: the overrides, and enough of the scene to\n * reach a node's dependencies. A `Scene` satisfies it. */\nexport interface PoseSource<TPose> {\n readonly overrides: Pick<PoseOverrides<TPose>, 'read'>;\n get(id: NodeId): PosedNode<TPose> | undefined;\n childrenOf(id: NodeId): readonly NodeId[];\n}\n\n/** A cached derived pose, with what it was derived from. */\ninterface PoseMemo<TPose> extends DepRecord<TPose> {\n value: TPose | null;\n}\n\n/** Ids whose derivation is on the stack. A dependency graph is meant to be\n * acyclic and `dependsOn` is unvalidated, so a cycle resolves to the authored\n * pose at whichever node closes it rather than overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires. A derivation that closed a cycle\n * answered from a fallback rather than from its dependencies, so its value is\n * an artifact of which node was asked first and must not be cached. */\nlet cycleHits = 0;\n\n/**\n * The pose `node` computes from its dependencies, or `null` when it derives\n * nothing — it has no `derivePose`, nothing to derive from, or its\n * `derivePose` returned `null`.\n *\n * A dependency is read through {@link effectivePose}, so a group of groups\n * resolves bottom-up and a dragged dependency's override reaches the\n * derivation the same frame it is published.\n */\nexport function derivedPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose | null {\n return derivedPoseIn(source, node, 'effective');\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const derive = node.derivePose;\n if (derive === undefined) return null;\n const ids = dependencyIdsOf(node, source.childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n const depOf = (id: NodeId) => depIn(source, id, reading);\n // Keyed on the authored pose, which is what `dropPoseKeyedMemoSlots`\n // clears. That key only sees a change someone pushed, so the record also\n // carries what the derivation read and re-checks it by value on a hit —\n // see `depMemo.ts`.\n let computed = false;\n const record = nodeMemo<PoseMemo<TPose>>(node, POSE_SLOT[reading], node.pose, () => {\n computed = true;\n return run(node, derive, ids.map(depOf), { nodes: [], poses: [], value: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n run(node, derive, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.value;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePose` against `deps` and record what it was run against. */\nfunction run<TPose>(\n node: PosedNode<TPose>,\n derive: NonNullable<PosedNode<TPose>['derivePose']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PoseMemo<TPose>,\n): PoseMemo<TPose> {\n into.value = derive(node as never, deps);\n recordDeps(into, deps);\n return into;\n}\n\n/**\n * `id` as a derivation sees it: the node, the pose it is painted at, and the\n * path it derives.\n *\n * The pose is resolved eagerly — every derivation reads it — and the path\n * lazily, because most do not and resolving a route costs a router run. Both\n * resolve *through* this function, so a dependency that is itself derived is\n * resolved before it is read.\n */\nexport function derivedDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n): DerivedDep<TPose> | undefined {\n return depIn(source, id, 'effective');\n}\n\nfunction depIn<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reading: Reading,\n): DerivedDep<TPose> | undefined {\n const node = source.get(id);\n if (node === undefined) return undefined;\n return new SceneDep(source, node, reading);\n}\n\n/**\n * A class rather than an object literal with a `get path()`: one of these is\n * built per dependency per derived node per frame, and an own accessor costs\n * an order of magnitude more to define than a prototype one does to inherit.\n */\nclass SceneDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n private readonly posed: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n this.node = posed as unknown as Node<unknown, string, TPose>;\n this.pose = poseIn(source, posed, reading);\n }\n\n get path(): Path | null {\n const { source, reading } = this;\n return resolveDerivedPath(\n this.posed,\n (depId) => depIn(source, depId, reading),\n (depId) => source.childrenOf(depId),\n PATH_SLOT[reading],\n );\n }\n}\n\nfunction poseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose {\n if (reading === 'effective') {\n const override = source.overrides.read(node.id)?.pose;\n if (override !== undefined) return override;\n }\n return derivedPoseIn(source, node, reading) ?? node.pose;\n}\n\n/**\n * The pose the *document* says `node` is at: derived when it derives, else\n * authored. `effectivePose` minus the override step, at every link of the\n * dependency chain — a derived node's document pose holds still while\n * something it derives from is being dragged.\n *\n * For a reader that must not see an in-flight gesture — an action capturing\n * the `from` of a transform op, a placement computed against a sibling, the\n * minimap's framing. A derived pose belongs here and an override does not:\n * derivation is what the document means, an override is what one gesture is\n * currently showing.\n */\nexport function documentPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'document');\n}\n\n/**\n * The pose to draw, pick and measure `node` at.\n *\n * The one rule. Every pose a reader acts on comes from here: the three render\n * walks, the pick walk, the adapters tools and actions commit through.\n */\nexport function effectivePose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'effective');\n}\n\n/**\n * Whether `node`'s pose is a frame its children are expressed in, under a\n * composing strategy.\n *\n * A container that derives its pose from its children cannot be one: moving\n * the frame to cover the children would move the children, and a `RectPose`\n * frame's origin is its corner, so no envelope leaves them in place. Such a\n * container is an envelope instead — its children are stored in its own\n * frame — and the composition fold passes over it.\n */\nexport function definesFrame(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return !(node.dependsOn === 'children' && node.derivePose !== undefined);\n}\n"],"mappings":";;;;;AAwBA,IAAMA,IAAqC;CACzC,WAAW;CACX,UAAU;AACZ,GACMC,IAAiD;CACrD,WAAW,KAAA;CACX,UAAU;AACZ,GAgCMC,oBAAY,IAAI,IAAY,GAK9BC,IAAY;AAWhB,SAAgBC,EACd,GACA,GACc;CACd,OAAOC,EAAcC,GAAQC,GAAM,WAAW;AAChD;AAEA,SAASF,EACP,GACA,GACA,GACc;CACd,IAAMG,IAASD,EAAK;CACpB,IAAIC,MAAW,KAAA,GAAW,OAAO;CACjC,IAAMC,IAAMC,EAAgBH,GAAMD,EAAO,UAAU;CACnD,IAAIG,EAAI,WAAW,GAAG,OAAO;CAC7B,IAAIP,EAAU,IAAIK,EAAK,EAAE,GAEvB,OADA,KACO;CAET,EAAU,IAAIA,EAAK,EAAE;CACrB,IAAMI,IAAaR;CACnB,IAAI;EACF,IAAMS,KAAS,MAAeC,EAAMP,GAAQQ,GAAIC,CAAO,GAKnDC,IAAW,IACTC,IAASC,EAA0BX,GAAMP,EAAUe,IAAUR,EAAK,aACtE,IAAW,IACJY,EAAIZ,GAAMC,GAAQC,EAAI,IAAIG,CAAK,GAAG;GAAE,OAAO,CAAC;GAAG,OAAO,CAAC;GAAG,OAAO;EAAK,CAAC,EAC/E;EAOD,OAJI,CAACI,KAAY,CAACI,EAASH,GAAQR,GAAKG,CAAK,KAC3CO,EAAIZ,GAAMC,GAAQC,EAAI,IAAIG,CAAK,GAAGK,CAAM,GAEtCd,MAAcQ,KAAYU,EAAuBd,CAAI,GAClDU,EAAO;CAChB,UAAU;EACR,EAAU,OAAOV,EAAK,EAAE;CAC1B;AACF;AAGA,SAASY,EACP,GACA,GACA,GACA,GACiB;CAGjB,OAFA,EAAK,QAAQX,EAAOD,GAAee,CAAI,GACvCC,EAAWC,GAAMF,CAAI,GACdE;AACT;AAWA,SAAgBC,EACd,GACA,GAC+B;CAC/B,OAAOZ,EAAMP,GAAQQ,GAAI,WAAW;AACtC;AAEA,SAASD,EACP,GACA,GACA,GAC+B;CAC/B,IAAMN,IAAOD,EAAO,IAAIQ,CAAE;CAC1B,IAAIP,MAAS,KAAA,GACb,OAAO,IAAImB,EAASpB,GAAQC,GAAMQ,CAAO;AAC3C;AAOA,IAAMW,IAAN,MAAmD;CAK9B;CACA;CACA;CANnB;CACA;CAEA,YACE,GACA,GACA,GACA;EAEA,AALiB,KAAA,SAAApB,GACA,KAAA,QAAAqB,GACA,KAAA,UAAAZ,GAEjB,KAAK,OAAOY,GACZ,KAAK,OAAOC,EAAOtB,GAAQqB,GAAOZ,CAAO;CAC3C;CAEA,IAAI,OAAoB;EACtB,IAAM,EAAE,WAAQ,eAAY;EAC5B,OAAOc,EACL,KAAK,QACJ,MAAUhB,EAAMP,GAAQwB,GAAOf,CAAO,IACtC,MAAUT,EAAO,WAAWwB,CAAK,GAClC7B,EAAUc,EACZ;CACF;AACF;AAEA,SAASa,EACP,GACA,GACA,GACO;CACP,IAAIb,MAAY,aAAa;EAC3B,IAAMgB,IAAWzB,EAAO,UAAU,KAAKC,EAAK,EAAE,CAAC,EAAE;EACjD,IAAIwB,MAAa,KAAA,GAAW,OAAOA;CACrC;CACA,OAAO1B,EAAcC,GAAQC,GAAMQ,CAAO,KAAKR,EAAK;AACtD;AAcA,SAAgByB,EACd,GACA,GACO;CACP,OAAOJ,EAAOtB,GAAQC,GAAM,UAAU;AACxC;AAQA,SAAgB0B,EACd,GACA,GACO;CACP,OAAOL,EAAOtB,GAAQC,GAAM,WAAW;AACzC;AAYA,SAAgB2B,EAAa,GAA8D;CACzF,OAAS3B,EAAK,cAAc,cAAcA,EAAK,eAAe,KAAA;AAChE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kitRegistry.js","names":["UNION_OF_CHILDREN","unionOfChildren","poses","d","deps","rect","asRectPose","union","unionAABB","pose","p","withKitRegistry","registry"],"sources":["../../../src/core/scene/kitRegistry.ts"],"sourcesContent":["/**\n * The `SceneRegistry` entries the kit owns.\n *\n * A registry-keyed function has to be resolvable by key in whatever scene\n * loads a snapshot, so a function the *kit* attaches to a node cannot live in\n * the consumer's registry alone — a document saved with a grouped container\n * would come back with a dead key. `createScene` merges these under the\n * consumer's, which stays the winner on a key collision.\n */\nimport { unionAABB } from '../geometry/unionBounds';\nimport type { RectPose, SceneRegistry } from './types';\n\n/** Registry key for {@link unionOfChildren}. */\nexport const UNION_OF_CHILDREN = 'kit:unionOfChildren';\n\n/**\n * A container's pose as the envelope of what it holds — `unionAABB`, so a\n * rotated member contributes the extent of its ink rather than its unrotated\n * box. Paired with `dependsOn: 'children'`, this is what makes a group's\n * bounds track its members instead of freezing at the moment it was made.\n *\n * Its members and its result share one frame: a container deriving its pose\n * from its children defines none of its own (see `definesFrame`), so the same\n * union is right under any composition strategy.\n *\n * Returns `null` for an emptied container, which falls back to its authored\n * pose — the group stays where the last member left it rather than collapsing\n * to a zero box at the origin.\n *\n * Reads members as rect poses, and only as rect poses: a scene whose poses are\n * shaped otherwise gets `null` here and its container keeps its authored pose,\n * because this entry is registered into every scene and the scene layer holds\n * no descriptor to read another shape with. To make such a container track its\n * members, register `unionOfChildrenVia(itsDescriptor)` under\n * {@link UNION_OF_CHILDREN} — the consumer's entry wins the collision.\n */\nexport function unionOfChildren<TPose>(\n _node: unknown,\n deps: readonly ({ pose: TPose } | undefined)[],\n): TPose | null {\n const poses: RectPose[] = [];\n for (const d of deps) {\n if (d === undefined) continue;\n const rect = asRectPose(d.pose);\n if (rect === null) return null;\n poses.push(rect);\n }\n if (poses.length === 0) return null;\n const union = unionAABB(poses);\n // Sound by the check above: every member read as a rect, so the union is one\n // too, and a scene holding rect poses is a scene whose `TPose` is one.\n return union === null ? null : (union as unknown as TPose);\n}\n\n/** `pose` as a rect, or `null` when it is some other shape entirely. */\nexport function asRectPose(pose: unknown): RectPose | null {\n if (pose === null || typeof pose !== 'object') return null;\n const p = pose as Partial<RectPose>;\n const rect = typeof p.x === 'number' && typeof p.y === 'number'\n && typeof p.width === 'number' && typeof p.height === 'number';\n return rect ? (pose as RectPose) : null;\n}\n\n/** Merge the kit's own entries under a consumer registry. */\nexport function withKitRegistry<TPose>(registry: SceneRegistry<TPose
|
|
1
|
+
{"version":3,"file":"kitRegistry.js","names":["UNION_OF_CHILDREN","unionOfChildren","poses","d","deps","rect","asRectPose","union","unionAABB","pose","p","withKitRegistry","registry"],"sources":["../../../src/core/scene/kitRegistry.ts"],"sourcesContent":["/**\n * The `SceneRegistry` entries the kit owns.\n *\n * A registry-keyed function has to be resolvable by key in whatever scene\n * loads a snapshot, so a function the *kit* attaches to a node cannot live in\n * the consumer's registry alone — a document saved with a grouped container\n * would come back with a dead key. `createScene` merges these under the\n * consumer's, which stays the winner on a key collision.\n */\nimport { unionAABB } from '../geometry/unionBounds';\nimport type { RectPose, SceneRegistry } from './types';\n\n/** Registry key for {@link unionOfChildren}. */\nexport const UNION_OF_CHILDREN = 'kit:unionOfChildren';\n\n/**\n * A container's pose as the envelope of what it holds — `unionAABB`, so a\n * rotated member contributes the extent of its ink rather than its unrotated\n * box. Paired with `dependsOn: 'children'`, this is what makes a group's\n * bounds track its members instead of freezing at the moment it was made.\n *\n * Its members and its result share one frame: a container deriving its pose\n * from its children defines none of its own (see `definesFrame`), so the same\n * union is right under any composition strategy.\n *\n * Returns `null` for an emptied container, which falls back to its authored\n * pose — the group stays where the last member left it rather than collapsing\n * to a zero box at the origin.\n *\n * Reads members as rect poses, and only as rect poses: a scene whose poses are\n * shaped otherwise gets `null` here and its container keeps its authored pose,\n * because this entry is registered into every scene and the scene layer holds\n * no descriptor to read another shape with. To make such a container track its\n * members, register `unionOfChildrenVia(itsDescriptor)` under\n * {@link UNION_OF_CHILDREN} — the consumer's entry wins the collision.\n */\nexport function unionOfChildren<TPose>(\n _node: unknown,\n deps: readonly ({ pose: TPose } | undefined)[],\n): TPose | null {\n const poses: RectPose[] = [];\n for (const d of deps) {\n if (d === undefined) continue;\n const rect = asRectPose(d.pose);\n if (rect === null) return null;\n poses.push(rect);\n }\n if (poses.length === 0) return null;\n const union = unionAABB(poses);\n // Sound by the check above: every member read as a rect, so the union is one\n // too, and a scene holding rect poses is a scene whose `TPose` is one.\n return union === null ? null : (union as unknown as TPose);\n}\n\n/** `pose` as a rect, or `null` when it is some other shape entirely. */\nexport function asRectPose(pose: unknown): RectPose | null {\n if (pose === null || typeof pose !== 'object') return null;\n const p = pose as Partial<RectPose>;\n const rect = typeof p.x === 'number' && typeof p.y === 'number'\n && typeof p.width === 'number' && typeof p.height === 'number';\n return rect ? (pose as RectPose) : null;\n}\n\n/** Merge the kit's own entries under a consumer registry. */\nexport function withKitRegistry<TPose, TData = unknown, TLayer extends string = string>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n): SceneRegistry<TPose, TData, TLayer> {\n return {\n ...registry,\n derivePose: {\n [UNION_OF_CHILDREN]: unionOfChildren as NonNullable<\n SceneRegistry<TPose, TData, TLayer>['derivePose']\n >[string],\n ...registry.derivePose,\n },\n };\n}\n"],"mappings":";;AAaA,IAAaA,IAAoB;AAuBjC,SAAgBC,EACd,GACA,GACc;CACd,IAAMC,IAAoB,CAAC;CAC3B,KAAK,IAAMC,KAAKC,GAAM;EACpB,IAAID,MAAM,KAAA,GAAW;EACrB,IAAME,IAAOC,EAAWH,EAAE,IAAI;EAC9B,IAAIE,MAAS,MAAM,OAAO;EAC1B,EAAM,KAAKA,CAAI;CACjB;CACA,IAAIH,EAAM,WAAW,GAAG,OAAO;CAC/B,IAAMK,IAAQC,EAAUN,CAAK;CAG7B,OAAOK,MAAU,OAAO,OAAQA;AAClC;AAGA,SAAgBD,EAAW,GAAgC;CACzD,IAAqB,OAAOG,KAAS,aAAjCA,GAA2C,OAAO;CACtD,IAAMC,IAAID;CAGV,OAFa,OAAOC,EAAE,KAAM,YAAY,OAAOA,EAAE,KAAM,YAClD,OAAOA,EAAE,SAAU,YAAY,OAAOA,EAAE,UAAW,WACzCD,IAAoB;AACrC;AAGA,SAAgBE,EACd,GACqC;CACrC,OAAO;EACL,GAAGC;EACH,YAAY;IACTZ,IAAoBC;GAGrB,GAAGW,EAAS;EACd;CACF;AACF"}
|