@weasel-js/core 1.13.0-rc.0 → 1.14.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 +408 -0
- package/README.md +8 -12
- package/dist/affordances/hitAffordanceRegions.js +1 -1
- package/dist/affordances/hitAffordanceRegions.js.map +1 -1
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/animation/clipTargets.js +1 -1
- package/dist/animation/clipTargets.js.map +1 -1
- package/dist/animation/poseTarget.js +5 -32
- package/dist/animation/poseTarget.js.map +1 -1
- package/dist/{autoPoseDescriptor-CZ1eXbzY.d.ts → autoPoseDescriptor-C25QVb0W.d.ts} +1 -1
- package/dist/barrel/canvas.js +8 -8
- package/dist/barrel/chrome.js +9 -9
- package/dist/barrel/input.js +1 -1
- package/dist/barrel/interactions.js +3 -3
- package/dist/barrel/paint.js +2 -2
- package/dist/barrel/paths.js +19 -17
- package/dist/barrel/renderer.js +11 -7
- package/dist/barrel/routing.js +4 -1
- package/dist/barrel/scene.js +16 -12
- package/dist/barrel/text.js +5 -5
- package/dist/barrel/viewport.js +8 -8
- package/dist/canvas/Canvas.js +2 -2
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +20 -14
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/GestureReason.js +64 -0
- package/dist/canvas/GestureReason.js.map +1 -0
- package/dist/canvas/GestureReason.module.js +7 -0
- package/dist/canvas/GestureReason.module.js.map +1 -0
- package/dist/canvas/LeanSceneCanvas.js +3 -1
- package/dist/canvas/LeanSceneCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +2 -0
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/PickHud.js +7 -3
- package/dist/canvas/PickHud.js.map +1 -1
- package/dist/canvas/SceneCanvas/CanvasKitProviders.js +1 -1
- package/dist/canvas/SceneCanvas/CanvasKitProviders.js.map +1 -1
- package/dist/canvas/SceneCanvas/DevHuds.js +2 -2
- package/dist/canvas/SceneCanvas/DevHuds.js.map +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +4 -2
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +3 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/brushOverlay.js.map +1 -1
- package/dist/canvas/SceneCanvas/builtinToolSets.js +17 -9
- package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +75 -19
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +7 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultTextEditor.js +108 -0
- package/dist/canvas/SceneCanvas/defaultTextEditor.js.map +1 -0
- package/dist/canvas/SceneCanvas/describePresets.js +47 -0
- package/dist/canvas/SceneCanvas/describePresets.js.map +1 -0
- package/dist/canvas/SceneCanvas/features.js +57 -16
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/mergeLayers.js +10 -1
- package/dist/canvas/SceneCanvas/mergeLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/namedPresets.js +52 -24
- package/dist/canvas/SceneCanvas/namedPresets.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/arrange.js +35 -10
- package/dist/canvas/SceneCanvas/presets/arrange.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/baseline.js +9 -0
- package/dist/canvas/SceneCanvas/presets/baseline.js.map +1 -0
- package/dist/canvas/SceneCanvas/presets/chrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/composites.js +43 -0
- package/dist/canvas/SceneCanvas/presets/composites.js.map +1 -0
- package/dist/canvas/SceneCanvas/presets/edit.js +79 -26
- package/dist/canvas/SceneCanvas/presets/edit.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/ingest.js +1 -0
- package/dist/canvas/SceneCanvas/presets/ingest.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/paths.js +58 -16
- package/dist/canvas/SceneCanvas/presets/paths.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/select.js +4 -2
- package/dist/canvas/SceneCanvas/presets/select.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/tools.js +24 -0
- package/dist/canvas/SceneCanvas/presets/tools.js.map +1 -0
- package/dist/canvas/SceneCanvas/presets/transform.js +4 -0
- package/dist/canvas/SceneCanvas/presets/transform.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/view.js +3 -1
- package/dist/canvas/SceneCanvas/presets/view.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +1 -1
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/toolRegistry.js +5 -4
- package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +26 -10
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useContributionRoles.js +17 -2
- package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDepartureGhostLayer.js +5 -4
- package/dist/canvas/SceneCanvas/useDepartureGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js +1 -1
- package/dist/canvas/SceneCanvas/useNodeAtPoint.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +9 -8
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasPathEdit.js +2 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasPathEdit.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +4 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneChrome.js +7 -4
- package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +9 -9
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useScenePicking.js +4 -7
- package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneRedrawSources.js +2 -1
- package/dist/canvas/SceneCanvas/useSceneRedrawSources.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +6 -6
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +2 -2
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSelectionOverlayLayer.js +1 -1
- package/dist/canvas/SceneCanvas/useSelectionOverlayLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSurfaceViewInputs.js.map +1 -1
- package/dist/canvas/SceneCanvas/useToolKeybindings.js +2 -2
- package/dist/canvas/SceneCanvas/useToolKeybindings.js.map +1 -1
- package/dist/canvas/SceneCanvas/useViewportActions.js +5 -2
- package/dist/canvas/SceneCanvas/useViewportActions.js.map +1 -1
- package/dist/canvas/SceneCanvas/worldPointAt.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +22 -11
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +1 -0
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/ViewMarker.js.map +1 -1
- package/dist/canvas/accentColor.js +14 -1
- package/dist/canvas/accentColor.js.map +1 -1
- package/dist/canvas/affordanceAt.js +2 -2
- package/dist/canvas/affordanceAt.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js +1 -1
- package/dist/canvas/buildSceneLayer.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +5 -3
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +6 -2
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/deps/editAnchors.js +4 -4
- package/dist/canvas/deps/editAnchors.js.map +1 -1
- package/dist/canvas/deps/enteredGroup.js +14 -5
- package/dist/canvas/deps/enteredGroup.js.map +1 -1
- package/dist/canvas/deps/ingestion.js +5 -1
- package/dist/canvas/deps/ingestion.js.map +1 -1
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/insertSite.js +5 -3
- package/dist/canvas/deps/insertSite.js.map +1 -1
- package/dist/canvas/deps/nodeAtPoint.js +1 -12
- package/dist/canvas/deps/nodeAtPoint.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +3 -3
- package/dist/canvas/deps/pathEditCommits.js.map +1 -1
- package/dist/canvas/deps/slice.js +16 -2
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/deps/snap.js +5 -5
- package/dist/canvas/deps/snap.js.map +1 -1
- package/dist/canvas/deps/textEdit.js +5 -6
- package/dist/canvas/deps/textEdit.js.map +1 -1
- package/dist/canvas/derivedPath.js +8 -3
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/getNodeAtPoint.js +2 -2
- package/dist/canvas/getNodeAtPoint.js.map +1 -1
- package/dist/canvas/insertPreviewExtent.js.map +1 -1
- package/dist/canvas/layerSlots.js.map +1 -1
- package/dist/canvas/nodeShapes/measurePainter.js +1 -1
- package/dist/canvas/nodeShapes/measurePainter.js.map +1 -1
- package/dist/canvas/nodeShapes/strokedBounds.js +5 -3
- package/dist/canvas/nodeShapes/strokedBounds.js.map +1 -1
- package/dist/canvas/overlayRoleStrokes.js +6 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/pickIndex/scenePickIndex.js +10 -12
- package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +10 -19
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +17 -4
- package/dist/canvas/sceneClipboard.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +7 -5
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/surfaceContribution.js.map +1 -1
- package/dist/canvas/useCanvasPaint.js +12 -4
- package/dist/canvas/useCanvasPaint.js.map +1 -1
- package/dist/canvas/useHitObserver.js +1 -1
- package/dist/canvas/useHitObserver.js.map +1 -1
- package/dist/canvas/useViewAim.js +1 -1
- package/dist/canvas/useViewAim.js.map +1 -1
- package/dist/canvas/useViewHelpers.js +8 -5
- package/dist/canvas/useViewHelpers.js.map +1 -1
- package/dist/canvas/useViewNesting.js +39 -12
- package/dist/canvas/useViewNesting.js.map +1 -1
- package/dist/canvas/viewInputs.js.map +1 -1
- package/dist/canvas/viewMarkers.js +2 -2
- package/dist/canvas/viewMarkers.js.map +1 -1
- package/dist/canvas/viewRegistry.js +2 -2
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/canvas/viewSnap.js +67 -0
- package/dist/canvas/viewSnap.js.map +1 -0
- package/dist/clipboard.d.ts +6 -12
- package/dist/{clone-qb2e1F5p.d.ts → clone-Bz4iqGfE.d.ts} +10 -5
- package/dist/clone.d.ts +5 -5
- package/dist/core/geometry/booleanOfPaths.js +23 -0
- package/dist/core/geometry/booleanOfPaths.js.map +1 -0
- package/dist/core/geometry/pathPoseDescriptor.js +12 -9
- package/dist/core/geometry/pathPoseDescriptor.js.map +1 -1
- package/dist/core/geometry/poseDescriptor.js +12 -3
- package/dist/core/geometry/poseDescriptor.js.map +1 -1
- package/dist/core/geometry/targetFrame.js +2 -13
- package/dist/core/geometry/targetFrame.js.map +1 -1
- package/dist/core/layers/render.js +2 -1
- package/dist/core/layers/render.js.map +1 -1
- package/dist/core/layers/retain.js +5 -3
- package/dist/core/layers/retain.js.map +1 -1
- package/dist/core/ops/setPath.js +3 -0
- package/dist/core/ops/setPath.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/paint/paintKindEntry.js.map +1 -1
- package/dist/core/paint/paintKinds.js +8 -1
- package/dist/core/paint/paintKinds.js.map +1 -1
- package/dist/core/scene/NodeProperties.js.map +1 -1
- package/dist/core/scene/NodeRouting.js.map +1 -1
- package/dist/core/scene/booleanGroup.js +33 -0
- package/dist/core/scene/booleanGroup.js.map +1 -0
- package/dist/core/scene/booleanOfChildren.js +53 -0
- package/dist/core/scene/booleanOfChildren.js.map +1 -0
- package/dist/core/scene/carryDescendants.js +18 -6
- package/dist/core/scene/carryDescendants.js.map +1 -1
- package/dist/core/scene/composeFrame.js +42 -5
- package/dist/core/scene/composeFrame.js.map +1 -1
- package/dist/core/scene/composePose.js +35 -16
- package/dist/core/scene/composePose.js.map +1 -1
- package/dist/core/scene/depMemo.js +36 -18
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/derivedParts.js +2 -23
- package/dist/core/scene/derivedParts.js.map +1 -1
- package/dist/core/scene/effectivePose.js +10 -0
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/historyForm.js +5 -4
- package/dist/core/scene/historyForm.js.map +1 -1
- package/dist/core/scene/kitOps.js +5 -71
- package/dist/core/scene/kitOps.js.map +1 -1
- package/dist/core/scene/kitRegistry.js +5 -0
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/layerOps.js +82 -0
- package/dist/core/scene/layerOps.js.map +1 -0
- package/dist/core/scene/layoutPass.js +14 -12
- package/dist/core/scene/layoutPass.js.map +1 -1
- package/dist/core/scene/lockGuard.js +9 -3
- package/dist/core/scene/lockGuard.js.map +1 -1
- package/dist/core/scene/nestedHit.js +5 -5
- package/dist/core/scene/nestedHit.js.map +1 -1
- package/dist/core/scene/nodeFlags.js +8 -2
- package/dist/core/scene/nodeFlags.js.map +1 -1
- package/dist/core/scene/nodeGates.js +31 -2
- package/dist/core/scene/nodeGates.js.map +1 -1
- package/dist/core/scene/pathEffects/effects.js +22 -3
- package/dist/core/scene/pathEffects/effects.js.map +1 -1
- package/dist/core/scene/pathEffects/textAlong.js +16 -3
- package/dist/core/scene/pathEffects/textAlong.js.map +1 -1
- package/dist/core/scene/pathEffects/textAlongBands.js +65 -0
- package/dist/core/scene/pathEffects/textAlongBands.js.map +1 -0
- package/dist/core/scene/reconcileSpecs.js +1 -1
- package/dist/core/scene/reconcileSpecs.js.map +1 -1
- package/dist/core/scene/scene.js +11 -5
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/sceneEdits.js +5 -2
- package/dist/core/scene/sceneEdits.js.map +1 -1
- package/dist/core/scene/scenePoseAdapter.js +10 -1
- package/dist/core/scene/scenePoseAdapter.js.map +1 -1
- package/dist/core/scene/scrollOffsets.js +93 -0
- package/dist/core/scene/scrollOffsets.js.map +1 -0
- package/dist/core/scene/serialization.js +15 -5
- package/dist/core/scene/serialization.js.map +1 -1
- package/dist/core/scene/statedFields.js +2 -1
- package/dist/core/scene/statedFields.js.map +1 -1
- package/dist/core/scene/strategyView.js +35 -0
- package/dist/core/scene/strategyView.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js +2 -0
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/core/scene/worldSave.js +87 -0
- package/dist/core/scene/worldSave.js.map +1 -0
- package/dist/core/selection/selectionTarget.js +1 -1
- package/dist/core/selection/selectionTarget.js.map +1 -1
- package/dist/{cornerHandles-CNCb7aTY.d.ts → cornerHandles-DpmQM_z5.d.ts} +1 -1
- package/dist/{depSchema-B1RS-IJx.d.ts → depSchema-C3BwxitB.d.ts} +130 -104
- package/dist/devtools-protocol.d.ts +2 -2
- package/dist/devtools.d.ts +2 -2
- package/dist/{features → domains}/chrome-caps/buildChromeCtx.js +1 -1
- package/dist/domains/chrome-caps/buildChromeCtx.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/conditions.js +1 -1
- package/dist/domains/chrome-caps/conditions.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/defaults.js +1 -1
- package/dist/domains/chrome-caps/defaults.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/resolve.js +1 -1
- package/dist/domains/chrome-caps/resolve.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/useChromeCaps.js +1 -1
- package/dist/domains/chrome-caps/useChromeCaps.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/useHoverTracking.js +1 -1
- package/dist/domains/chrome-caps/useHoverTracking.js.map +1 -0
- package/dist/{features → domains}/ingestion/contentHandlers.js +20 -3
- package/dist/domains/ingestion/contentHandlers.js.map +1 -0
- package/dist/domains/ingestion/ghost.js +56 -0
- package/dist/domains/ingestion/ghost.js.map +1 -0
- package/dist/{features → domains}/ingestion/imageHandler.js +1 -1
- package/dist/domains/ingestion/imageHandler.js.map +1 -0
- package/dist/domains/ingestion/kitHandlers.js +22 -0
- package/dist/domains/ingestion/kitHandlers.js.map +1 -0
- package/dist/{features → domains}/ingestion/openFilePicker.js +1 -1
- package/dist/domains/ingestion/openFilePicker.js.map +1 -0
- package/dist/{features → domains}/ingestion/placement.js +1 -1
- package/dist/domains/ingestion/placement.js.map +1 -0
- package/dist/{features → domains}/ingestion/svgHandler.js +1 -1
- package/dist/domains/ingestion/svgHandler.js.map +1 -0
- package/dist/{features → domains}/ingestion/textHandler.js +9 -7
- package/dist/domains/ingestion/textHandler.js.map +1 -0
- package/dist/{features → domains}/ingestion/useCanvasIngestion.js +13 -15
- package/dist/domains/ingestion/useCanvasIngestion.js.map +1 -0
- package/dist/{features → domains}/ingestion/weaselJsonHandler.js +1 -1
- package/dist/domains/ingestion/weaselJsonHandler.js.map +1 -0
- package/dist/{features → domains}/longPress/longPressFeedback.js +1 -1
- package/dist/domains/longPress/longPressFeedback.js.map +1 -0
- package/dist/{features → domains}/measure/actionIds.js +1 -1
- package/dist/domains/measure/actionIds.js.map +1 -0
- package/dist/{features → domains}/measure/measureCommands.js +1 -1
- package/dist/domains/measure/measureCommands.js.map +1 -0
- package/dist/{features → domains}/measure/measureContribution.js +1 -1
- package/dist/domains/measure/measureContribution.js.map +1 -0
- package/dist/{features → domains}/measure/measureEdits.js +1 -1
- package/dist/domains/measure/measureEdits.js.map +1 -0
- package/dist/{features → domains}/measure/measureGestures.js +4 -7
- package/dist/domains/measure/measureGestures.js.map +1 -0
- package/dist/{features → domains}/measure/measureHit.js +1 -1
- package/dist/domains/measure/measureHit.js.map +1 -0
- package/dist/{features → domains}/measure/measureLayer.js +1 -1
- package/dist/domains/measure/measureLayer.js.map +1 -0
- package/dist/{features → domains}/measure/measurement.js +1 -1
- package/dist/domains/measure/measurement.js.map +1 -0
- package/dist/{features → domains}/measure/measurements.js +2 -2
- package/dist/domains/measure/measurements.js.map +1 -0
- package/dist/{features → domains}/modes/modeLayers.js +1 -1
- package/dist/domains/modes/modeLayers.js.map +1 -0
- package/dist/{features → domains}/modes/modeTriggers.js +3 -6
- package/dist/domains/modes/modeTriggers.js.map +1 -0
- package/dist/{features → domains}/pathEffects/effectSources.js +1 -1
- package/dist/domains/pathEffects/effectSources.js.map +1 -0
- package/dist/{features → domains}/pathEffects/index.js +1 -1
- package/dist/domains/pathEffects/index.js.map +1 -0
- package/dist/{features → domains}/pathEffects/readSource.js +1 -1
- package/dist/domains/pathEffects/readSource.js.map +1 -0
- package/dist/{features → domains}/pathEffects/textItems.js +11 -2
- package/dist/domains/pathEffects/textItems.js.map +1 -0
- package/dist/{features → domains}/pathEffects/useEffectSourcesLayer.js +1 -1
- package/dist/domains/pathEffects/useEffectSourcesLayer.js.map +1 -0
- package/dist/{features → domains}/paths/anchorData.js +1 -1
- package/dist/domains/paths/anchorData.js.map +1 -0
- package/dist/{features → domains}/paths/anchorMarkers.js +9 -7
- package/dist/domains/paths/anchorMarkers.js.map +1 -0
- package/dist/{features → domains}/paths/editablePath.js +1 -1
- package/dist/domains/paths/editablePath.js.map +1 -0
- package/dist/{features → domains}/paths/index.js +3 -3
- package/dist/{features → domains}/paths/originProjection.js +1 -1
- package/dist/domains/paths/originProjection.js.map +1 -0
- package/dist/{features → domains}/paths/pathEditExtension.js +1 -1
- package/dist/domains/paths/pathEditExtension.js.map +1 -0
- package/dist/{features → domains}/paths/pathEditState.js +1 -1
- package/dist/domains/paths/pathEditState.js.map +1 -0
- package/dist/{features → domains}/paths/pathEditingOverlayLayer.js +1 -1
- package/dist/domains/paths/pathEditingOverlayLayer.js.map +1 -0
- package/dist/{features → domains}/paths/pathLayer.js +1 -1
- package/dist/domains/paths/pathLayer.js.map +1 -0
- package/dist/{features → domains}/paths/pathRepresentation.js +1 -1
- package/dist/domains/paths/pathRepresentation.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/glyphs.js +1 -1
- package/dist/domains/paths/pathSnap/glyphs.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/pathSnapSource.js +1 -1
- package/dist/domains/paths/pathSnap/pathSnapSource.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/sceneIndex.js +1 -1
- package/dist/domains/paths/pathSnap/sceneIndex.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/snapGeometry.js +3 -2
- package/dist/domains/paths/pathSnap/snapGeometry.js.map +1 -0
- package/dist/{features → domains}/paths/penPreviewLayer.js +1 -1
- package/dist/domains/paths/penPreviewLayer.js.map +1 -0
- package/dist/{features → domains}/paths/shapeGenerator.js +6 -4
- package/dist/domains/paths/shapeGenerator.js.map +1 -0
- package/dist/{features → domains}/paths/strokeOutline.js +1 -1
- package/dist/domains/paths/strokeOutline.js.map +1 -0
- package/dist/{features → domains}/paths/useAnchorFields.js +1 -1
- package/dist/domains/paths/useAnchorFields.js.map +1 -0
- package/dist/{features → domains}/paths/usePathEdit.js +1 -1
- package/dist/domains/paths/usePathEdit.js.map +1 -0
- package/dist/{features → domains}/poseRun/usePoseRun.js +1 -1
- package/dist/domains/poseRun/usePoseRun.js.map +1 -0
- package/dist/{features → domains}/selection/enteredGroupFrame.js +49 -10
- package/dist/domains/selection/enteredGroupFrame.js.map +1 -0
- package/dist/{features → domains}/selection/enteredGroupScope.js +1 -1
- package/dist/domains/selection/enteredGroupScope.js.map +1 -0
- package/dist/domains/selection/enteredOperands.js +29 -0
- package/dist/domains/selection/enteredOperands.js.map +1 -0
- package/dist/{features → domains}/selection/handleCommands.js +1 -1
- package/dist/domains/selection/handleCommands.js.map +1 -0
- package/dist/{features → domains}/selection/overlay.js +1 -1
- package/dist/domains/selection/overlay.js.map +1 -0
- package/dist/{features → domains}/selection/selectionPose.js +1 -1
- package/dist/domains/selection/selectionPose.js.map +1 -0
- package/dist/{features → domains}/snap/angle2d.js +1 -1
- package/dist/domains/snap/angle2d.js.map +1 -0
- package/dist/{features → domains}/snap/debugCandidates.js +1 -1
- package/dist/domains/snap/debugCandidates.js.map +1 -0
- package/dist/{features → domains}/snap/gather.js +1 -1
- package/dist/domains/snap/gather.js.map +1 -0
- package/dist/{features → domains}/snap/inputSubspace.js +1 -1
- package/dist/domains/snap/inputSubspace.js.map +1 -0
- package/dist/{features → domains}/snap/lazySession.js +1 -1
- package/dist/domains/snap/lazySession.js.map +1 -0
- package/dist/{features → domains}/snap/marks.js +1 -1
- package/dist/domains/snap/marks.js.map +1 -0
- package/dist/{features → domains}/snap/outcomeStore.js +1 -1
- package/dist/domains/snap/outcomeStore.js.map +1 -0
- package/dist/{features → domains}/snap/pointSnap.js +1 -1
- package/dist/domains/snap/pointSnap.js.map +1 -0
- package/dist/{features → domains}/snap/probes2d.js +1 -1
- package/dist/domains/snap/probes2d.js.map +1 -0
- package/dist/{features → domains}/snap/readoutDraw.js +1 -1
- package/dist/domains/snap/readoutDraw.js.map +1 -0
- package/dist/{features → domains}/snap/readoutLayer.js +1 -1
- package/dist/domains/snap/readoutLayer.js.map +1 -0
- package/dist/{features → domains}/snap/registry.js +1 -1
- package/dist/domains/snap/registry.js.map +1 -0
- package/dist/{features → domains}/snap/resizeBox.js +1 -1
- package/dist/domains/snap/resizeBox.js.map +1 -0
- package/dist/{features → domains}/snap/roles.js +1 -1
- package/dist/domains/snap/roles.js.map +1 -0
- package/dist/{features → domains}/snap/rotationSource.js +1 -1
- package/dist/domains/snap/rotationSource.js.map +1 -0
- package/dist/{features → domains}/snap/sceneReads.js +24 -3
- package/dist/domains/snap/sceneReads.js.map +1 -0
- package/dist/{features → domains}/snap/sceneSession2d.js +3 -2
- package/dist/domains/snap/sceneSession2d.js.map +1 -0
- package/dist/{features → domains}/snap/screen.js +1 -1
- package/dist/domains/snap/screen.js.map +1 -0
- package/dist/{features → domains}/snap/session2d.js +3 -2
- package/dist/domains/snap/session2d.js.map +1 -0
- package/dist/{features → domains}/snap/spanSnap.js +1 -1
- package/dist/domains/snap/spanSnap.js.map +1 -0
- package/dist/{features → domains}/snap/stepAngles.js +1 -1
- package/dist/domains/snap/stepAngles.js.map +1 -0
- package/dist/{features → domains}/text/editingDrawOne.js +4 -4
- package/dist/domains/text/editingDrawOne.js.map +1 -0
- package/dist/domains/text/hostTextEditors.js +24 -0
- package/dist/domains/text/hostTextEditors.js.map +1 -0
- package/dist/{features → domains}/text/index.js +1 -1
- package/dist/{features → domains}/text/sceneTextDrafts.js +1 -1
- package/dist/domains/text/sceneTextDrafts.js.map +1 -0
- package/dist/{features → domains}/text/textHitAt.js +18 -2
- package/dist/domains/text/textHitAt.js.map +1 -0
- package/dist/{features → domains}/text/textLayer.js +1 -1
- package/dist/domains/text/textLayer.js.map +1 -0
- package/dist/{features → domains}/text/useSceneTextEdit.js +11 -3
- package/dist/domains/text/useSceneTextEdit.js.map +1 -0
- package/dist/{features → domains}/viewports/aim.js +1 -1
- package/dist/domains/viewports/aim.js.map +1 -0
- package/dist/{features → domains}/viewports/viewMarker.js +23 -5
- package/dist/domains/viewports/viewMarker.js.map +1 -0
- package/dist/{features → domains}/viewports/viewMarkerDrag.js +3 -3
- package/dist/domains/viewports/viewMarkerDrag.js.map +1 -0
- package/dist/{features → domains}/viewports/viewResolver.js +1 -1
- package/dist/domains/viewports/viewResolver.js.map +1 -0
- package/dist/{features → domains}/viewports/viewportLayer.js +3 -2
- package/dist/domains/viewports/viewportLayer.js.map +1 -0
- package/dist/icons.table.js +1 -1
- package/dist/icons.table.js.map +1 -1
- package/dist/icons.table.json +106 -6
- package/dist/index.d.ts +1445 -1053
- package/dist/index.js +90 -77
- package/dist/interactions/actions/admitsChild.js +6 -2
- package/dist/interactions/actions/admitsChild.js.map +1 -1
- package/dist/interactions/actions/align/align.js +1 -4
- package/dist/interactions/actions/align/align.js.map +1 -1
- package/dist/interactions/actions/booleans/booleanGroupAction.js +66 -0
- package/dist/interactions/actions/booleans/booleanGroupAction.js.map +1 -0
- package/dist/interactions/actions/booleans/keymap.js +52 -0
- package/dist/interactions/actions/booleans/keymap.js.map +1 -0
- package/dist/interactions/actions/booleans/operands.js +1 -1
- package/dist/interactions/actions/booleans/pieces.js +4 -11
- package/dist/interactions/actions/booleans/pieces.js.map +1 -1
- package/dist/interactions/actions/clipboard/action.js +3 -7
- package/dist/interactions/actions/clipboard/action.js.map +1 -1
- package/dist/interactions/actions/clipboard/clipboardOps.js +1 -1
- package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
- package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +6 -6
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/containerScroll.js +170 -0
- package/dist/interactions/actions/defaults/containerScroll.js.map +1 -0
- package/dist/interactions/actions/defaults/contextMenu.js +2 -5
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js +1 -1
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +1 -3
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterGroup.js +7 -12
- package/dist/interactions/actions/defaults/enterGroup.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js +4 -8
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +12 -24
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +18 -10
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +3 -3
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudgeSteps.js +3 -21
- package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -1
- package/dist/interactions/actions/defaults/pathEditMarks.js.map +1 -1
- package/dist/interactions/actions/defaults/selectDeep.js +2 -2
- package/dist/interactions/actions/defaults/selectDeep.js.map +1 -1
- package/dist/interactions/actions/distribute/distribute.js.map +1 -1
- package/dist/interactions/actions/dropVerdict.js +9 -0
- package/dist/interactions/actions/dropVerdict.js.map +1 -0
- package/dist/interactions/actions/edit-anchors/action.js +2 -2
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/extensionDrags.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/geometry.js +8 -6
- package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/handleDrag.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +3 -2
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/geometryProjection.js +8 -3
- package/dist/interactions/actions/geometryProjection.js.map +1 -1
- package/dist/interactions/actions/gestureFrames.js +1 -0
- package/dist/interactions/actions/gestureFrames.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +6 -2
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/frame.js +1 -1
- package/dist/interactions/actions/insert/frame.js.map +1 -1
- package/dist/interactions/actions/insert/placeAction.js +6 -8
- package/dist/interactions/actions/insert/placeAction.js.map +1 -1
- package/dist/interactions/actions/insert/session.js +2 -2
- package/dist/interactions/actions/insert/session.js.map +1 -1
- package/dist/interactions/actions/move/action.js +6 -15
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/layoutDrop.js +12 -2
- package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/reparentOnDrop.js +29 -10
- package/dist/interactions/actions/move/behaviors/reparentOnDrop.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +42 -6
- package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
- package/dist/interactions/actions/move/capture.js +4 -8
- package/dist/interactions/actions/move/capture.js.map +1 -1
- package/dist/interactions/actions/move/commit/index.js.map +1 -1
- package/dist/interactions/actions/move/commit/layoutDrop.js +4 -3
- package/dist/interactions/actions/move/commit/layoutDrop.js.map +1 -1
- package/dist/interactions/actions/move/commit/layoutRelease.js +15 -9
- package/dist/interactions/actions/move/commit/layoutRelease.js.map +1 -1
- package/dist/interactions/actions/move/commit/reparent.js +19 -13
- package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
- package/dist/interactions/actions/move/delta.js +51 -5
- package/dist/interactions/actions/move/delta.js.map +1 -1
- package/dist/interactions/actions/move/frame.js +8 -4
- package/dist/interactions/actions/move/frame.js.map +1 -1
- package/dist/interactions/actions/move/layout/container.js +19 -9
- package/dist/interactions/actions/move/layout/container.js.map +1 -1
- package/dist/interactions/actions/move/layout/dragged.js +2 -2
- package/dist/interactions/actions/move/layout/dragged.js.map +1 -1
- package/dist/interactions/actions/move/layout/dropPass.js +36 -18
- package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
- package/dist/interactions/actions/move/layout/exits.js +2 -2
- package/dist/interactions/actions/move/layout/exits.js.map +1 -1
- package/dist/interactions/actions/move/layout/glide.js +5 -5
- package/dist/interactions/actions/move/layout/glide.js.map +1 -1
- package/dist/interactions/actions/move/layout/view.js +55 -0
- package/dist/interactions/actions/move/layout/view.js.map +1 -0
- package/dist/interactions/actions/move/refusal.js +52 -0
- package/dist/interactions/actions/move/refusal.js.map +1 -0
- package/dist/interactions/actions/move/session.js +9 -8
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/move/translate.js +10 -4
- package/dist/interactions/actions/move/translate.js.map +1 -1
- package/dist/interactions/actions/moveNodes.js +21 -5
- package/dist/interactions/actions/moveNodes.js.map +1 -1
- package/dist/interactions/actions/pathCommands/pathNodes.js +3 -3
- package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
- package/dist/interactions/actions/planeInput.js +14 -13
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/actions/poseFrame.js +21 -8
- package/dist/interactions/actions/poseFrame.js.map +1 -1
- package/dist/interactions/actions/resize/action.js +1 -1
- package/dist/interactions/actions/resize/action.js.map +1 -1
- package/dist/interactions/actions/resize/aspect.js +1 -1
- package/dist/interactions/actions/resize/aspect.js.map +1 -1
- package/dist/interactions/actions/resize/commit.js +10 -1
- package/dist/interactions/actions/resize/commit.js.map +1 -1
- package/dist/interactions/actions/resize/frame.js +3 -2
- package/dist/interactions/actions/resize/frame.js.map +1 -1
- package/dist/interactions/actions/resize/held.js +44 -0
- package/dist/interactions/actions/resize/held.js.map +1 -0
- package/dist/interactions/actions/resize/session.js +3 -1
- package/dist/interactions/actions/resize/session.js.map +1 -1
- package/dist/interactions/actions/resolveOverlays.js +9 -0
- package/dist/interactions/actions/resolveOverlays.js.map +1 -1
- package/dist/interactions/actions/resolvePreviews.js +23 -7
- package/dist/interactions/actions/resolvePreviews.js.map +1 -1
- package/dist/interactions/actions/rotate/action.js +1 -1
- package/dist/interactions/actions/rotate/action.js.map +1 -1
- package/dist/interactions/actions/rotate/frame.js +1 -1
- package/dist/interactions/actions/rotate/frame.js.map +1 -1
- package/dist/interactions/actions/rotate/session.js +2 -2
- package/dist/interactions/actions/rotate/session.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +2 -4
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/spatialReference.js +4 -9
- package/dist/interactions/actions/spatialReference.js.map +1 -1
- package/dist/interactions/gestures/dragRadial/dragRadial.js.map +1 -1
- package/dist/interactions/gestures/dragRect/dragRect.js.map +1 -1
- package/dist/kitSettingsSchema.js +2 -2
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/{math-C_elShP_.d.ts → math-B_unOnF4.d.ts} +2 -2
- package/dist/math.d.ts +3 -3
- package/dist/move.d.ts +6 -6
- package/dist/{options-ClEm3SEl.d.ts → options-DAjkhbcs.d.ts} +5 -5
- package/dist/{poseDescriptor-DN2yfnq_.d.ts → poseDescriptor-CvSxVhCd.d.ts} +17 -4
- package/dist/resize.d.ts +6 -6
- package/dist/style.css +14 -0
- package/dist/test-seams.d.ts +11 -15
- package/dist/test-seams.js +1 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js +3 -6
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/measure/useMeasureTool.js +2 -2
- package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
- package/dist/tools/builtin/pen/penHandle.js +2 -2
- package/dist/tools/builtin/pen/penHandle.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +13 -19
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/select/groupEntry.js +1 -1
- package/dist/tools/builtin/select/groupEntry.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +1 -1
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +7 -7
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +5 -8
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +1 -1
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/{types-49uYJN3j.d.ts → types-A_sNJYRd.d.ts} +8 -0
- package/dist/{types-BEdRIhNc.d.ts → types-DW14eu9B.d.ts} +131 -36
- package/dist/{types-p2alu-R1.d.ts → types-VCtfchp-.d.ts} +4 -6
- package/dist/util/color.js +12 -14
- package/dist/util/color.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -24
- package/dist/canvas/poseCascade.js +0 -65
- package/dist/canvas/poseCascade.js.map +0 -1
- package/dist/features/chrome-caps/buildChromeCtx.js.map +0 -1
- package/dist/features/chrome-caps/conditions.js.map +0 -1
- package/dist/features/chrome-caps/defaults.js.map +0 -1
- package/dist/features/chrome-caps/resolve.js.map +0 -1
- package/dist/features/chrome-caps/useChromeCaps.js.map +0 -1
- package/dist/features/chrome-caps/useHoverTracking.js.map +0 -1
- package/dist/features/ingestion/contentHandlers.js.map +0 -1
- package/dist/features/ingestion/imageHandler.js.map +0 -1
- package/dist/features/ingestion/openFilePicker.js.map +0 -1
- package/dist/features/ingestion/placement.js.map +0 -1
- package/dist/features/ingestion/registerKitHandlers.js +0 -43
- package/dist/features/ingestion/registerKitHandlers.js.map +0 -1
- package/dist/features/ingestion/svgHandler.js.map +0 -1
- package/dist/features/ingestion/textHandler.js.map +0 -1
- package/dist/features/ingestion/useCanvasIngestion.js.map +0 -1
- package/dist/features/ingestion/weaselJsonHandler.js.map +0 -1
- package/dist/features/longPress/longPressFeedback.js.map +0 -1
- package/dist/features/measure/actionIds.js.map +0 -1
- package/dist/features/measure/measureCommands.js.map +0 -1
- package/dist/features/measure/measureContribution.js.map +0 -1
- package/dist/features/measure/measureEdits.js.map +0 -1
- package/dist/features/measure/measureGestures.js.map +0 -1
- package/dist/features/measure/measureHit.js.map +0 -1
- package/dist/features/measure/measureLayer.js.map +0 -1
- package/dist/features/measure/measurement.js.map +0 -1
- package/dist/features/measure/measurements.js.map +0 -1
- package/dist/features/modes/modeLayers.js.map +0 -1
- package/dist/features/modes/modeTriggers.js.map +0 -1
- package/dist/features/pathEffects/effectSources.js.map +0 -1
- package/dist/features/pathEffects/index.js.map +0 -1
- package/dist/features/pathEffects/readSource.js.map +0 -1
- package/dist/features/pathEffects/textItems.js.map +0 -1
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +0 -1
- package/dist/features/paths/anchorData.js.map +0 -1
- package/dist/features/paths/anchorMarkers.js.map +0 -1
- package/dist/features/paths/editablePath.js.map +0 -1
- package/dist/features/paths/originProjection.js.map +0 -1
- package/dist/features/paths/pathEditExtension.js.map +0 -1
- package/dist/features/paths/pathEditState.js.map +0 -1
- package/dist/features/paths/pathEditingOverlayLayer.js.map +0 -1
- package/dist/features/paths/pathLayer.js.map +0 -1
- package/dist/features/paths/pathRepresentation.js.map +0 -1
- package/dist/features/paths/pathSnap/glyphs.js.map +0 -1
- package/dist/features/paths/pathSnap/pathSnapSource.js.map +0 -1
- package/dist/features/paths/pathSnap/sceneIndex.js.map +0 -1
- package/dist/features/paths/pathSnap/snapGeometry.js.map +0 -1
- package/dist/features/paths/penPreviewLayer.js.map +0 -1
- package/dist/features/paths/shapeGenerator.js.map +0 -1
- package/dist/features/paths/strokeOutline.js.map +0 -1
- package/dist/features/paths/useAnchorFields.js.map +0 -1
- package/dist/features/paths/usePathEdit.js.map +0 -1
- package/dist/features/poseRun/usePoseRun.js.map +0 -1
- package/dist/features/selection/enteredGroupFrame.js.map +0 -1
- package/dist/features/selection/enteredGroupScope.js.map +0 -1
- package/dist/features/selection/handleCommands.js.map +0 -1
- package/dist/features/selection/overlay.js.map +0 -1
- package/dist/features/selection/selectionPose.js.map +0 -1
- package/dist/features/snap/angle2d.js.map +0 -1
- package/dist/features/snap/debugCandidates.js.map +0 -1
- package/dist/features/snap/gather.js.map +0 -1
- package/dist/features/snap/inputSubspace.js.map +0 -1
- package/dist/features/snap/lazySession.js.map +0 -1
- package/dist/features/snap/marks.js.map +0 -1
- package/dist/features/snap/outcomeStore.js.map +0 -1
- package/dist/features/snap/pointSnap.js.map +0 -1
- package/dist/features/snap/probes2d.js.map +0 -1
- package/dist/features/snap/readoutDraw.js.map +0 -1
- package/dist/features/snap/readoutLayer.js.map +0 -1
- package/dist/features/snap/registry.js.map +0 -1
- package/dist/features/snap/resizeBox.js.map +0 -1
- package/dist/features/snap/roles.js.map +0 -1
- package/dist/features/snap/rotationSource.js.map +0 -1
- package/dist/features/snap/sceneReads.js.map +0 -1
- package/dist/features/snap/sceneSession2d.js.map +0 -1
- package/dist/features/snap/screen.js.map +0 -1
- package/dist/features/snap/session2d.js.map +0 -1
- package/dist/features/snap/spanSnap.js.map +0 -1
- package/dist/features/snap/stepAngles.js.map +0 -1
- package/dist/features/text/editingDrawOne.js.map +0 -1
- package/dist/features/text/sceneTextDrafts.js.map +0 -1
- package/dist/features/text/textHitAt.js.map +0 -1
- package/dist/features/text/textLayer.js.map +0 -1
- package/dist/features/text/useSceneTextEdit.js.map +0 -1
- package/dist/features/viewports/aim.js.map +0 -1
- package/dist/features/viewports/viewMarker.js.map +0 -1
- package/dist/features/viewports/viewMarkerDrag.js.map +0 -1
- package/dist/features/viewports/viewResolver.js.map +0 -1
- package/dist/features/viewports/viewportLayer.js.map +0 -1
- /package/dist/{features → domains}/chrome-caps/index.js +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pathEditState.js","names":[],"sources":["../../../src/features/paths/pathEditState.ts"],"sourcesContent":["/**\n * Which path is being anchor-edited, which of its anchors are selected, and\n * the anchor marquee in flight: one object every reader shares — the\n * `editAnchors` dep, the anchor overlay, the selection overlay's suppressed\n * set, the chrome-caps rule context, and the select tool's shift-click lock.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { PathEditHost } from 'canvas/SceneCanvas/pathEditHost';\nimport type { HandleLocks } from 'interactions/actions/edit-anchors/params';\nimport type { PathEditExtension, PathEditMark } from './pathEditExtension';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { effectSourceFor } from 'features/pathEffects/effectSources';\n\n/** A world-coord rect. */\nexport interface AnchorMarquee {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * What decides whether a stored edit target is still one: its node exists and\n * is selected, and the mode permits anchor editing. Each is read at the\n * moment of the question.\n */\nexport interface PathEditSource {\n /** Whether `id` names a node that exists and is selected. Absent means\n * every id does. */\n isSelectedNode?(id: string): boolean;\n /** A host that owns the id; the state then keeps none of its own. */\n host?(): PathEditHost | undefined;\n /**\n * Whether anchor editing is permitted right now — whether the active mode\n * allows the `edits-anchors` capability.\n *\n * **Omit this when no mode registry is wired.** Absent means \"no modality,\n * so nothing revokes edit mode\"; a predicate built from an empty capability\n * set would revoke edit mode instantly.\n */\n isAllowed?(): boolean;\n /** The extensions it starts with; `setExtensions` replaces them. */\n extensions?: readonly PathEditExtension[];\n}\n\nexport interface PathEditState {\n /**\n * The node whose anchors are editable now, or `''`: the stored id, or the\n * host's, while `PathEditSource` still lets it stand. Masked rather than\n * cleared: a host's mode machine can leave path-edit mode without running\n * `exitPathEdit`, and nothing re-renders when it does, so there is no\n * moment to write the id back.\n */\n getEditingId(): string;\n /** `getEditingId()` as the set the selection overlay skips. */\n getEditingIds(): ReadonlySet<string>;\n setEditingId(id: string | null): void;\n /** Selected anchors (flat indices) of the path being edited. */\n getSelectedAnchors(): ReadonlySet<number>;\n setSelectedAnchors(next: ReadonlySet<number>): void;\n /** The in-flight anchor-marquee rect, in the edited path's world. */\n getMarquee(): AnchorMarquee | null;\n setMarquee(rect: AnchorMarquee | null): void;\n /** The extension mark selected in place of anchors, or null. */\n getSelectedMark(): PathEditMark | null;\n setSelectedMark(mark: PathEditMark | null): void;\n /** The canvas's path-edit extensions, in order. */\n getExtensions(): readonly PathEditExtension[];\n /** Replace the extensions; the overlay repaints when they change. */\n setExtensions(next: readonly PathEditExtension[]): void;\n /** The handle locks the `handleLock.*` toggles set; null until one is\n * pressed. Not tied to the edited path, so they outlast a change of it. */\n getHandleLocks(): HandleLocks | null;\n setHandleLocks(next: HandleLocks): void;\n /**\n * The path-edit tool in use, or null for plain anchor editing. Path edit has\n * a set of named tools, one active at a time, each changing what a plain\n * drag does: `'simplifyBrush'` brushes the path simpler. A tool belongs to\n * the open path; with none open, or a different one, there is none.\n */\n getTool(): string | null;\n /** Switch tool, or pass null to go back to plain anchor editing. Ignored\n * with no path open. */\n setTool(id: string | null): void;\n /** Notified when a write changes what the anchor overlay paints. A host\n * that owns the id announces its own changes. */\n subscribe(listener: () => void): () => void;\n}\n\nconst NO_IDS: ReadonlySet<string> = new Set();\nconst NO_EXTENSIONS: readonly PathEditExtension[] = [];\nconst NO_ANCHORS: ReadonlySet<number> = new Set();\n\n/** Whether `id` names a node `scene` holds and `selection` includes, or the path a selected effect hides and is edited\n * through. */\nexport function isSelectedNode(\n scene: Pick<Scene<unknown, string, unknown>, 'get'>,\n selection: { readonly current: readonly string[] },\n id: string,\n): boolean {\n if (scene.get(id as NodeId) == null) return false;\n return selection.current.includes(id)\n || selection.current.some((sel) => effectSourceFor(scene, sel as NodeId, 'path') === id);\n}\n\nexport function createPathEditState(source: PathEditSource = {}): PathEditState {\n let ownId = '';\n let selectedAnchors = NO_ANCHORS;\n let marquee: AnchorMarquee | null = null;\n let handleLocks: HandleLocks | null = null;\n let ownTool: { id: string; on: string } | null = null;\n let idSet = NO_IDS;\n let selectedMark: PathEditMark | null = null;\n let extensions = source.extensions ?? NO_EXTENSIONS;\n const changed = createEmitter();\n const notify = changed.emit;\n\n const next = (id: string | null): string => id ?? '';\n const getEditingId = (): string => {\n const host = source.host?.();\n const id = host ? host.editingId() ?? '' : ownId;\n if (!id) return '';\n if (source.isAllowed && !source.isAllowed()) return '';\n if (source.isSelectedNode && !source.isSelectedNode(id)) return '';\n return id;\n };\n\n return {\n getEditingId,\n getEditingIds() {\n const id = getEditingId();\n if (!id) return NO_IDS;\n if (!idSet.has(id)) idSet = new Set([id]);\n return idSet;\n },\n setEditingId(id) {\n const host = source.host?.();\n if (next(id) !== getEditingId()) ownTool = null;\n if (host) { host.setEditingId(id); return; }\n if (next(id) === ownId) return;\n ownId = next(id);\n notify();\n },\n getSelectedAnchors: () => {\n const host = source.host?.();\n return host?.selectedAnchors && host.setSelectedAnchors ? host.selectedAnchors() : selectedAnchors;\n },\n setSelectedAnchors(next) {\n const host = source.host?.();\n if (host?.selectedAnchors && host.setSelectedAnchors) { host.setSelectedAnchors(next); return; }\n selectedAnchors = next;\n notify();\n },\n getMarquee: () => marquee,\n setMarquee(rect) {\n marquee = rect;\n notify();\n },\n getSelectedMark: () => selectedMark,\n setSelectedMark(mark) {\n if (mark?.owner === selectedMark?.owner && mark?.index === selectedMark?.index) return;\n selectedMark = mark;\n notify();\n },\n getExtensions: () => extensions,\n setExtensions(next) {\n if (next === extensions) return;\n extensions = next;\n notify();\n },\n getHandleLocks: () => handleLocks,\n setHandleLocks(next) {\n handleLocks = { angle: next.angle, length: next.length };\n notify();\n },\n getTool() {\n const on = getEditingId();\n const host = source.host?.();\n if (host?.tool) return on ? host.tool() : null;\n // Dropped on the read that finds its path gone: a host can close the\n // path without telling this state.\n if (ownTool && ownTool.on !== on) ownTool = null;\n return ownTool?.id ?? null;\n },\n setTool(id) {\n const host = source.host?.();\n if (host?.tool && host.setTool) { host.setTool(id); return; }\n const on = getEditingId();\n const tool = id && on ? { id, on } : null;\n if (tool?.id === ownTool?.id && tool?.on === ownTool?.on) return;\n ownTool = tool;\n notify();\n },\n subscribe: changed.subscribe,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pathEditingOverlayLayer.js","names":[],"sources":["../../../src/features/paths/pathEditingOverlayLayer.ts"],"sourcesContent":["/**\n * Screen-space render layer for path-editing chrome — the open path's\n * outline, anchor markers, tangent lines, and control-point dots for a\n * polygon path that is the current edit target, under them whatever marks\n * its path-edit extensions draw. Marker sizes stay constant regardless of zoom.\n */\n\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport {\n type AnchorSet, type Path, type PolygonPath, type View, pathToAnchors, rectMarkerPath,\n} from '@weasel-js/geom';\nimport type { GesturePreviewSource } from 'canvas/gestureBounds';\nimport { chromeStateFrom, previewSourcesFrom, isVisibleFrom } from 'canvas/drawEnvelope';\nimport { paintsLayer } from 'core/selection/chromeState';\nimport {\n anchorMarkerCommands, DEFAULT_OVERLAY_STYLE, worldToScreen, type PathEditingOverlayStyle,\n} from './anchorMarkers';\nimport type { PathEditExtension, PathEditMark } from './pathEditExtension';\nimport type { AnchorMarkerShape } from './pathRepresentation';\n\nexport type { PathEditingOverlayStyle } from './anchorMarkers';\n\n/** Options for `createPathEditingOverlayLayer`. Everything is read per frame,\n * so the layer follows a changing edit target without being rebuilt. */\nexport interface CreatePathEditingOverlayLayerOptions {\n /** Returns the id of the polygon currently in anchor-edit mode, or null\n * when no node is being edited. */\n getEditingId(): string | null;\n /**\n * Returns the pose for an id, or null if the node has been deleted.\n * Non-polygon poses are tolerated (the layer no-ops on them).\n *\n * `previews` are the drawing view's in-flight preview surfaces, so an\n * anchor dragged in one panel does not move in the others. Resolve against\n * them before falling back to the committed pose.\n *\n * `view` is the drawing view's camera. Answer in its world: a node whose\n * layer is a parallax plane stores its path in the plane's.\n */\n getPose(id: string, previews: readonly GesturePreviewSource[], view: View): Path | null;\n /** The marker of each anchor, by flat index, for a node whose path was\n * drawn from a form (`PathRepresentation.anchorMarkers`); null, or an\n * anchor with none, draws a square. Resolve against `previews` as\n * `getPose` does. */\n getAnchorMarkers?(id: string, previews: readonly GesturePreviewSource[]): readonly (AnchorMarkerShape | null)[] | null;\n /** Flat indices of the selected anchors. Selected anchors render\n * filled; unselected ones hollow. */\n getSelectedAnchors?(): ReadonlySet<number>;\n /** In-flight anchor-marquee rect in world coords, or null. Drawn as a\n * rubber band while `marqueeAnchorsAction` is dragging. */\n getMarquee?(view: View): { x: number; y: number; width: number; height: number } | null;\n /** The edited node's committed pose and data, which extensions' marks read. */\n getNode?(id: string): { pose: unknown; data: unknown; layer?: string } | null;\n /** The path-edit extensions whose marks are drawn, in order. */\n getExtensions?(): readonly PathEditExtension[];\n /** The selected extension mark, or null. */\n getSelectedMark?(): PathEditMark | null;\n /** Optional styling overrides. */\n style?: PathEditingOverlayStyle;\n}\n\nconst EMPTY_SELECTION: ReadonlySet<number> = new Set();\nconst ALWAYS = (): boolean => true;\n\n/** Render layer that draws the outline and the anchors and control handles\n * of the path currently being anchor-edited. Draws nothing when no path is\n * being edited. */\nexport function createPathEditingOverlayLayer(\n opts: CreatePathEditingOverlayLayerOptions,\n): RenderLayer<unknown> {\n const style = { ...DEFAULT_OVERLAY_STYLE, ...(opts.style ?? {}) };\n\n return {\n id: 'path-editing-overlay',\n label: 'Path editing',\n space: 'screen',\n draw: (data, view) => {\n const id = opts.getEditingId();\n if (!id) return [];\n // Chrome-caps gate off the envelope, so paint and the anchor hit-test in\n // `affordanceAt` consult the same rule — otherwise a consumer that hides\n // the chrome still gets grabbable invisible anchors, or vice versa.\n const isVisible = isVisibleFrom(data);\n if (isVisible && !isVisible('path-edit.anchors')) return [];\n const node = opts.getNode?.(id);\n if (!paintsLayer(chromeStateFrom(data), node?.layer)) return [];\n const previews = previewSourcesFrom(data);\n const pose = opts.getPose(id, previews, view);\n if (!pose || pose.kind !== 'polygon') return [];\n const markers = opts.getAnchorMarkers?.(id, previews) ?? null;\n\n const selected = opts.getSelectedAnchors?.() ?? EMPTY_SELECTION;\n // Extensions' marks under the markers, which are what a press reaches first.\n const out: DrawCommand[] = [];\n if (node) {\n const mark = opts.getSelectedMark?.() ?? null;\n const anchors = pathToAnchors(pose as PolygonPath) as AnchorSet;\n const toScreen = (p: { x: number; y: number }) => worldToScreen(p, view);\n for (const ext of opts.getExtensions?.() ?? []) {\n if (!ext.marks) continue;\n out.push(...ext.marks({\n id, node, path: pose as PolygonPath, anchors, view, toScreen,\n isVisible: isVisible ?? ALWAYS,\n selected: mark?.owner === ext.id ? mark.index : null,\n }));\n }\n }\n out.push(...anchorMarkerCommands(pose as PolygonPath, view, selected, style, markers));\n\n // Marquee rubber band, on top of everything.\n const marquee = opts.getMarquee?.(view);\n if (marquee) {\n const m = worldToScreen(marquee, view);\n const m2 = worldToScreen({ x: marquee.x + marquee.width, y: marquee.y + marquee.height }, view);\n out.push({\n kind: 'path',\n path: rectMarkerPath(m.x, m.y, m2.x - m.x, m2.y - m.y),\n fill: { paint: { kind: 'solid', color: style.marqueeFill } },\n stroke: { paint: { kind: 'solid', color: style.marqueeStroke }, width: 1 },\n });\n }\n\n return out;\n },\n };\n}\n"],"mappings":";;;;;;AA8DA,IAAM,kCAAuC,IAAI,IAAI;AACrD,IAAM,eAAwB;;;;AAK9B,SAAgB,8BACd,MACsB;CACtB,MAAM,QAAQ;EAAE,GAAG;EAAuB,GAAI,KAAK,SAAS,CAAC;CAAG;CAEhE,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,OAAO,MAAM,SAAS;GACpB,MAAM,KAAK,KAAK,aAAa;GAC7B,IAAI,CAAC,IAAI,OAAO,CAAC;GAIjB,MAAM,YAAY,cAAc,IAAI;GACpC,IAAI,aAAa,CAAC,UAAU,mBAAmB,GAAG,OAAO,CAAC;GAC1D,MAAM,OAAO,KAAK,UAAU,EAAE;GAC9B,IAAI,CAAC,YAAY,gBAAgB,IAAI,GAAG,MAAM,KAAK,GAAG,OAAO,CAAC;GAC9D,MAAM,WAAW,mBAAmB,IAAI;GACxC,MAAM,OAAO,KAAK,QAAQ,IAAI,UAAU,IAAI;GAC5C,IAAI,CAAC,QAAQ,KAAK,SAAS,WAAW,OAAO,CAAC;GAC9C,MAAM,UAAU,KAAK,mBAAmB,IAAI,QAAQ,KAAK;GAEzD,MAAM,WAAW,KAAK,qBAAqB,KAAK;GAEhD,MAAM,MAAqB,CAAC;GAC5B,IAAI,MAAM;IACR,MAAM,OAAO,KAAK,kBAAkB,KAAK;IACzC,MAAM,UAAU,cAAc,IAAmB;IACjD,MAAM,YAAY,MAAgC,cAAc,GAAG,IAAI;IACvE,KAAK,MAAM,OAAO,KAAK,gBAAgB,KAAK,CAAC,GAAG;KAC9C,IAAI,CAAC,IAAI,OAAO;KAChB,IAAI,KAAK,GAAG,IAAI,MAAM;MACpB;MAAI;MAAM,MAAM;MAAqB;MAAS;MAAM;MACpD,WAAW,aAAa;MACxB,UAAU,MAAM,UAAU,IAAI,KAAK,KAAK,QAAQ;KAClD,CAAC,CAAC;IACJ;GACF;GACA,IAAI,KAAK,GAAG,qBAAqB,MAAqB,MAAM,UAAU,OAAO,OAAO,CAAC;GAGrF,MAAM,UAAU,KAAK,aAAa,IAAI;GACtC,IAAI,SAAS;IACX,MAAM,IAAI,cAAc,SAAS,IAAI;IACrC,MAAM,KAAK,cAAc;KAAE,GAAG,QAAQ,IAAI,QAAQ;KAAO,GAAG,QAAQ,IAAI,QAAQ;IAAO,GAAG,IAAI;IAC9F,IAAI,KAAK;KACP,MAAM;KACN,MAAM,eAAe,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,CAAC;KACrD,MAAM,EAAE,OAAO;MAAE,MAAM;MAAS,OAAO,MAAM;KAAY,EAAE;KAC3D,QAAQ;MAAE,OAAO;OAAE,MAAM;OAAS,OAAO,MAAM;MAAc;MAAG,OAAO;KAAE;IAC3E,CAAC;GACH;GAEA,OAAO;EACT;CACF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pathLayer.js","names":[],"sources":["../../../src/features/paths/pathLayer.ts"],"sourcesContent":["/**\n * `RenderLayer` that fills/strokes `Path` instances. Mirrors the shape of\n * `createTextLayer`: caller hands over a `getNodes()` enumerator and a\n * `getPath(node)` lookup; the layer iterates and renders. Fill and\n * stroke are looked up per node so consumers can keep the path geometry\n * separate from the visual style record (the typical scene-graph layout).\n */\n\nimport { type DrawCommand } from '@weasel-js/renderer';\nimport { type CommandStroke, type CommandFill, type VertexWidth } from '@weasel-js/paint';\nimport type { RenderLayer } from 'core/layers/render';\nimport { type Path, countPathAnchors } from '@weasel-js/geom';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport { isDev } from '@weasel-js/react';\n\nconst PLACEHOLDER_FILL: CommandFill = { paint: { color: '#ffffff' } };\nconst PLACEHOLDER_STROKE: CommandStroke = { paint: { color: '#ffffff' }, width: 1 };\n\n\n/** Options for `createPathLayer`. */\nexport interface CreatePathLayerOpts<T> {\n id?: string;\n label?: string;\n getNodes: () => readonly T[];\n getPath: (node: T) => Path;\n /** Per-node fill. Return `null`/`undefined` to skip filling. */\n getFill?: (node: T) => CommandFill | null | undefined;\n /** Per-node stroke. Return `null`/`undefined` to skip stroking. */\n getStroke?: (node: T) => CommandStroke | null | undefined;\n /** Optional per-node hide hook (e.g., suppress while editing). */\n isHidden?: (node: T) => boolean;\n /**\n * Per-node fill vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `PathDrawCommand.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getFill` returns null/undefined, a white placeholder fill is\n * synthesized so the renderer's per-vertex shader path activates.\n */\n getVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `Stroke.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getStroke` returns null/undefined, a white 1px placeholder stroke\n * is synthesized.\n */\n getStrokeVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-width array (one `VertexWidth` per anchor: a\n * width, or a `{ left, right }` pair).\n * Length must be `countPathAnchors(getPath(node))`. The tessellator\n * interpolates half-widths along each segment for tapered strokes.\n * When set and `getStroke` returns null/undefined, a 1px placeholder\n * stroke is synthesized so the per-anchor widths take effect.\n */\n getStrokeVertexWidths?: (node: T) => VertexWidth[] | null | undefined;\n /**\n * Optional color override registry, typically `animator.colorOverrides`.\n * When set, the renderer consults it before falling back to\n * `getVertexColors` / `getStrokeVertexColors`. Function-form overrides\n * receive the base color array and the current animation timestamp.\n */\n colorOverrides?: ColorOverrideRegistry;\n /** Clock used to timestamp function-form color overrides. Defaults to\n * `performance.now`. Override in tests. */\n now?: () => number;\n}\n\n/** Build a `RenderLayer` that fills/strokes `Path` instances enumerated from a node list. */\nexport function createPathLayer<T>(opts: CreatePathLayerOpts<T>): RenderLayer<unknown> {\n const {\n id = 'paths', label = 'Paths',\n getNodes, getPath, getFill, getStroke, isHidden,\n getVertexColors, getStrokeVertexColors, getStrokeVertexWidths,\n colorOverrides,\n now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n } = opts;\n const warned = new Set<string>();\n const dev = isDev();\n\n return {\n id,\n label,\n draw: () => {\n const children: DrawCommand[] = [];\n const nodes = getNodes();\n for (let idx = 0; idx < nodes.length; idx++) {\n const node = nodes[idx];\n if (isHidden?.(node)) continue;\n const path = getPath(node);\n const fillFromHook = getFill?.(node);\n const strokeFromHook = getStroke?.(node);\n const baseVColors = getVertexColors?.(node);\n const baseStrokeVColors = getStrokeVertexColors?.(node);\n const strokeVWidths = getStrokeVertexWidths?.(node);\n\n const nodeId = (node as { id?: string }).id ?? String(idx);\n const tMs = colorOverrides ? now() : 0;\n\n const vColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'fill', baseVColors ?? undefined, tMs)\n : baseVColors;\n const strokeVColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'stroke', baseStrokeVColors ?? undefined, tMs)\n : baseStrokeVColors;\n\n const nodeKey = nodeId;\n const anchorCount = countPathAnchors(path);\n const expectedLen = 4 * anchorCount;\n\n let useVColors: readonly number[] | null = null;\n if (vColors != null) {\n if (vColors.length === expectedLen) {\n useVColors = vColors;\n } else if (dev) {\n const key = `${id}:${nodeKey}:fill`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: fill vertexColors length ${vColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n let useStrokeVColors: readonly number[] | null = null;\n if (strokeVColors != null) {\n if (strokeVColors.length === expectedLen) {\n useStrokeVColors = strokeVColors;\n } else if (dev) {\n const key = `${id}:${nodeKey}:stroke`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexColors length ${strokeVColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n // Synthesize placeholder only when colors validate (useVColors != null),\n // not on any raw hook return. Mismatched-length arrays drop both the\n // colors and placeholder so the dev signal (console warning) isn't duplicated.\n const fill: CommandFill | undefined =\n fillFromHook != null ? fillFromHook\n : (useVColors != null ? PLACEHOLDER_FILL : undefined);\n\n let useStrokeVWidths: VertexWidth[] | null = null;\n if (strokeVWidths != null) {\n if (strokeVWidths.length === anchorCount) {\n useStrokeVWidths = strokeVWidths;\n } else if (dev) {\n const key = `${id}:${nodeKey}:strokeWidths`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexWidths length ${strokeVWidths.length}, expected ${anchorCount}; dropping`,\n );\n }\n }\n }\n\n const baseStroke: CommandStroke | undefined =\n strokeFromHook != null ? strokeFromHook\n : (useStrokeVColors != null || useStrokeVWidths != null ? PLACEHOLDER_STROKE : undefined);\n\n const stroke: CommandStroke | undefined =\n baseStroke != null\n ? {\n ...baseStroke,\n ...(useStrokeVColors != null ? { vertexColors: useStrokeVColors as number[] } : {}),\n ...(useStrokeVWidths != null ? { vertexWidths: useStrokeVWidths } : {}),\n }\n : baseStroke;\n\n if (fill == null && stroke == null) continue;\n\n children.push({\n kind: 'path',\n path,\n ...(fill != null ? { fill } : {}),\n ...(stroke != null ? { stroke } : {}),\n ...(useVColors != null ? { vertexColors: useVColors as number[] } : {}),\n });\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return children;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pathRepresentation.js","names":[],"sources":["../../../src/features/paths/pathRepresentation.ts"],"sourcesContent":["/**\n * A path drawn from another form the node keeps beside it, such as a curve\n * solved through the points it passes. The node's path stays the geometry\n * every reader draws and hit-tests; `data.representation` holds the form,\n * named by its `kind`. A preset that knows a kind brings a\n * {@link PathRepresentation} for it (`PathEditExtension.representation`),\n * through which path edit shows and edits the form. Where none is on, path\n * edit edits the drawn path, and the edit drops the form.\n */\nimport type { ActionDeps, OngoingHandle } from '@weasel-js/routing';\nimport type { PolygonPath } from '@weasel-js/geom';\nimport type { PathEditExtension } from './pathEditExtension';\n\n/** What `data.representation` holds: a form named by `kind`, with whatever\n * that kind keeps. */\nexport interface PathForm {\n readonly kind: string;\n readonly [field: string]: unknown;\n}\n\ntype Point = { x: number; y: number };\n\n/** The outline of an anchor's marker, in screen space: `size` px across at\n * `at`, with the anchor's neighbors along its subpath, null past an open\n * end. */\nexport type AnchorMarkerShape = (at: Point, size: number, prev: Point | null, next: Point | null) => PolygonPath;\n\n/** How path edit reads and writes one kind of {@link PathForm}. */\nexport interface PathRepresentation {\n /** The `kind` of the forms it reads. */\n readonly kind: string;\n /** What path edit shows and moves for a node holding `form`, whose drawn\n * path in world space is `path`. Null when `form` does not describe\n * `path`: path edit then edits `path`, and the edit drops the form. */\n editPath(path: PolygonPath, form: PathForm): PolygonPath | null;\n /**\n * The drawn path an edit makes, and the form to keep beside it: `edited` is\n * an edit of what {@link editPath} gave, `path` the drawn path it was given\n * from. Null when `form` does not describe `path`; the edit is then stored\n * as drawn, without the form. Runs on every commit of an edit and every\n * frame of one in flight.\n */\n store(path: PolygonPath, form: PathForm, edited: PolygonPath): { path: PolygonPath; form: PathForm } | null;\n /** The marker of each anchor of the edit path, by flat index. An anchor\n * with none draws path edit's square. */\n anchorMarkers?(form: PathForm): readonly (AnchorMarkerShape | null)[];\n /**\n * A press at world point `at` on the drawn outline of the node in path\n * edit: the drag it starts, or null when it starts none there. A\n * representation that has this takes every segment press, in place of\n * Bend segment.\n */\n segmentPress?(deps: ActionDeps, at: Point): (() => OngoingHandle) | null;\n}\n\n/** The form `data` holds, or null. */\nexport function pathFormOf(data: unknown): PathForm | null {\n const form = (data as { representation?: unknown } | null | undefined)?.representation;\n return form && typeof form === 'object' && typeof (form as { kind?: unknown }).kind === 'string'\n ? (form as PathForm)\n : null;\n}\n\n/** `data` without its form: its path as plain geometry. */\nexport function withoutPathForm<T>(data: T): T {\n if (data === null || typeof data !== 'object' || !('representation' in data)) return data;\n const { representation: _dropped, ...rest } = data as Record<string, unknown>;\n void _dropped;\n return rest as T;\n}\n\n/** The form `data` holds, and the representation among `extensions` that\n * reads it; null when either is missing. */\nexport function representationOf(\n extensions: readonly PathEditExtension[] | undefined,\n data: unknown,\n): { representation: PathRepresentation; form: PathForm } | null {\n const form = pathFormOf(data);\n if (!form || !extensions) return null;\n const representation = extensions.find((e) => e.representation?.kind === form.kind)?.representation;\n return representation ? { representation, form } : null;\n}\n"],"mappings":";;AAwDA,SAAgB,WAAW,MAAgC;CACzD,MAAM,OAAQ,MAA0D;CACxE,OAAO,QAAQ,OAAO,SAAS,YAAY,OAAQ,KAA4B,SAAS,WACnF,OACD;AACN;;AAGA,SAAgB,gBAAmB,MAAY;CAC7C,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,EAAE,oBAAoB,OAAO,OAAO;CACrF,MAAM,EAAE,gBAAgB,UAAU,GAAG,SAAS;CAE9C,OAAO;AACT;;;AAIA,SAAgB,iBACd,YACA,MAC+D;CAC/D,MAAM,OAAO,WAAW,IAAI;CAC5B,IAAI,CAAC,QAAQ,CAAC,YAAY,OAAO;CACjC,MAAM,iBAAiB,WAAW,MAAM,MAAM,EAAE,gBAAgB,SAAS,KAAK,IAAI,CAAC,EAAE;CACrF,OAAO,iBAAiB;EAAE;EAAgB;CAAK,IAAI;AACrD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"glyphs.js","names":[],"sources":["../../../../src/features/paths/pathSnap/glyphs.ts"],"sourcesContent":["import type { PathSnapKind } from '@weasel-js/geom';\n\n/** What the snap readout calls each kind of path target. */\nexport const PATH_SNAP_LABELS: Readonly<Record<PathSnapKind, string>> = {\n anchor: 'anchor',\n center: 'center',\n intersection: 'intersection',\n tangent: 'tangent',\n perpendicular: 'perpendicular',\n midpoint: 'midpoint',\n extremum: 'extremum',\n quadrant: 'quadrant',\n nearest: 'on path',\n};\n\n/** The glyph for `kind` centered on (x, y), `r` from center to edge, as SVG path data. */\nexport function pathSnapGlyph(kind: PathSnapKind, x: number, y: number, r: number): string {\n const L = x - r, R = x + r, T = y - r, B = y + r;\n const ring = (cx: number, cy: number, rc: number) => {\n const k = rc * 0.5523;\n return `M${cx} ${cy - rc} C${cx + k} ${cy - rc} ${cx + rc} ${cy - k} ${cx + rc} ${cy}`\n + ` C${cx + rc} ${cy + k} ${cx + k} ${cy + rc} ${cx} ${cy + rc}`\n + ` C${cx - k} ${cy + rc} ${cx - rc} ${cy + k} ${cx - rc} ${cy}`\n + ` C${cx - rc} ${cy - k} ${cx - k} ${cy - rc} ${cx} ${cy - rc} Z`;\n };\n switch (kind) {\n case 'anchor': return `M${L} ${T} H${R} V${B} H${L} Z`;\n case 'center': return `${ring(x, y, r)} M${L} ${y} H${R} M${x} ${T} V${B}`;\n case 'intersection': return `M${L} ${T} L${R} ${B} M${R} ${T} L${L} ${B}`;\n case 'midpoint': return `M${x} ${T} L${R} ${B} L${L} ${B} Z`;\n case 'extremum': return `M${x} ${T} L${R} ${y} L${x} ${B} L${L} ${y} Z`;\n case 'quadrant': return ring(x, y, r);\n case 'perpendicular': return `M${L} ${T} V${B} H${R} M${L} ${y} H${x} V${B}`;\n case 'nearest': return `M${L} ${T} H${R} L${L} ${B} H${R} Z`;\n case 'tangent': {\n const rc = (3 * r) / 4;\n return `${ring(x, y + r / 4, rc)} M${L} ${y + r / 4 - rc} H${R}`;\n }\n }\n}\n"],"mappings":";;AAGA,IAAa,mBAA2D;CACtE,QAAQ;CACR,QAAQ;CACR,cAAc;CACd,SAAS;CACT,eAAe;CACf,UAAU;CACV,UAAU;CACV,UAAU;CACV,SAAS;AACX;;AAGA,SAAgB,cAAc,MAAoB,GAAW,GAAW,GAAmB;CACzF,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI;CAC/C,MAAM,QAAQ,IAAY,IAAY,OAAe;EACnD,MAAM,IAAI,KAAK;EACf,OAAO,IAAI,GAAG,GAAG,KAAK,GAAG,IAAI,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,OACzE,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,KAAK,OACrD,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,OACrD,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,KAAK,GAAG;CACnE;CACA,QAAQ,MAAR;EACE,KAAK,UAAU,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;EACnD,KAAK,UAAU,OAAO,GAAG,KAAK,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI;EACvE,KAAK,gBAAgB,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;EACtE,KAAK,YAAY,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;EACzD,KAAK,YAAY,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;EACpE,KAAK,YAAY,OAAO,KAAK,GAAG,GAAG,CAAC;EACpC,KAAK,iBAAiB,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;EACzE,KAAK,WAAW,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;EACzD,KAAK,WAAW;GACd,MAAM,KAAM,IAAI,IAAK;GACrB,OAAO,GAAG,KAAK,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,IAAI,GAAG,IAAI;EAC/D;CACF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pathSnapSource.js","names":[],"sources":["../../../../src/features/paths/pathSnap/pathSnapSource.ts"],"sourcesContent":["import { findPathSnap, findPathSnaps, PATH_SNAP_KINDS, type PathSnapKind } from '@weasel-js/geom';\nimport type { Scene } from 'core/scene/types';\nimport { DEFAULT_SNAP_PX } from 'features/snap/session2d';\nimport type { SnapSource } from 'features/snap/types';\nimport { PATH_SNAP_LABELS } from './glyphs';\nimport { sceneSnapIndex } from './sceneIndex';\n\ntype Point = { x: number; y: number };\n\n/** Options for `pathSnapSource`. */\nexport interface PathSnapSourceOptions {\n /** The kinds offered. Default: every kind. */\n only?: readonly PathSnapKind[];\n /** Kinds to turn on or off. A kind not named is on. */\n kinds?: Partial<Record<PathSnapKind, boolean>>;\n /** Overrides the canvas's reach for path targets, in screen px. */\n reachPx?: number;\n}\n\n/**\n * The scene's paths as snap targets: a point for every enabled kind of\n * target in range of a pointer or geometry probe. `nearest` is offered only\n * when no other kind is in range of that probe. Tangent and perpendicular\n * targets need the query's `origin`.\n */\nexport function pathSnapSource(opts: PathSnapSourceOptions = {}): SnapSource {\n const kinds = (opts.only ?? PATH_SNAP_KINDS).filter((k) => opts.kinds?.[k] !== false);\n return {\n id: 'path-snap',\n roles: ['pointer', 'geometry'],\n ...(opts.reachPx !== undefined ? { reachPx: opts.reachPx } : {}),\n *collect(q) {\n if (!q.scene) return;\n const { ids, index } = sceneSnapIndex(q.scene, q.also);\n const exclude = new Set(q.exclude);\n const include = q.include ? new Set(q.include) : null;\n const radius = q.reachWorld(opts.reachPx ?? q.reachPx);\n const skip = (i: number) => {\n const id = ids[i];\n if (id === null) return false;\n return exclude.has(id) || (include ? !include.has(id) : false);\n };\n for (const p of q.probes) {\n const hits = findPathSnaps(index, p.at, { radius, kinds, skip, ...(q.origin ? { origin: q.origin } : {}) });\n for (const h of hits) {\n const at = { x: h.x, y: h.y };\n yield {\n probe: p.id,\n locus: { at, dirs: [] },\n mark: { kind: 'point', at, glyph: h.kind, label: PATH_SNAP_LABELS[h.kind] },\n };\n }\n }\n },\n };\n}\n\n/** The point of the nodes `ids`' own geometry nearest `press`, within\n * `reachPx` screen px, or null: where a move's `geometry` probe sits. Leaves\n * no mark. */\nexport function ownGeometryAt(\n scene: Pick<Scene<unknown, string, unknown>, 'nodes' | 'layers' | 'getVersion'>,\n press: Point,\n ids: readonly string[],\n scale: { x: number; y: number },\n reachPx = DEFAULT_SNAP_PX,\n): Point | null {\n const { ids: owners, index } = sceneSnapIndex(scene);\n const own = new Set(ids);\n const hit = findPathSnap(index, press, {\n radius: reachPx,\n scale,\n skip: (i) => {\n const id = owners[i];\n return id !== null && !own.has(id);\n },\n });\n return hit ? { x: hit.x, y: hit.y } : null;\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,SAAgB,eAAe,OAA8B,CAAC,GAAe;CAC3E,MAAM,SAAS,KAAK,QAAQ,gBAAA,CAAiB,QAAQ,MAAM,KAAK,QAAQ,OAAO,KAAK;CACpF,OAAO;EACL,IAAI;EACJ,OAAO,CAAC,WAAW,UAAU;EAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;EAC9D,CAAC,QAAQ,GAAG;GACV,IAAI,CAAC,EAAE,OAAO;GACd,MAAM,EAAE,KAAK,UAAU,eAAe,EAAE,OAAO,EAAE,IAAI;GACrD,MAAM,UAAU,IAAI,IAAI,EAAE,OAAO;GACjC,MAAM,UAAU,EAAE,UAAU,IAAI,IAAI,EAAE,OAAO,IAAI;GACjD,MAAM,SAAS,EAAE,WAAW,KAAK,WAAW,EAAE,OAAO;GACrD,MAAM,QAAQ,MAAc;IAC1B,MAAM,KAAK,IAAI;IACf,IAAI,OAAO,MAAM,OAAO;IACxB,OAAO,QAAQ,IAAI,EAAE,MAAM,UAAU,CAAC,QAAQ,IAAI,EAAE,IAAI;GAC1D;GACA,KAAK,MAAM,KAAK,EAAE,QAAQ;IACxB,MAAM,OAAO,cAAc,OAAO,EAAE,IAAI;KAAE;KAAQ;KAAO;KAAM,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;IAAG,CAAC;IAC1G,KAAK,MAAM,KAAK,MAAM;KACpB,MAAM,KAAK;MAAE,GAAG,EAAE;MAAG,GAAG,EAAE;KAAE;KAC5B,MAAM;MACJ,OAAO,EAAE;MACT,OAAO;OAAE;OAAI,MAAM,CAAC;MAAE;MACtB,MAAM;OAAE,MAAM;OAAS;OAAI,OAAO,EAAE;OAAM,OAAO,iBAAiB,EAAE;MAAM;KAC5E;IACF;GACF;EACF;CACF;AACF;;;;AAKA,SAAgB,cACd,OACA,OACA,KACA,OACA,UAAU,iBACI;CACd,MAAM,EAAE,KAAK,QAAQ,UAAU,eAAe,KAAK;CACnD,MAAM,MAAM,IAAI,IAAI,GAAG;CACvB,MAAM,MAAM,aAAa,OAAO,OAAO;EACrC,QAAQ;EACR;EACA,OAAO,MAAM;GACX,MAAM,KAAK,OAAO;GAClB,OAAO,OAAO,QAAQ,CAAC,IAAI,IAAI,EAAE;EACnC;CACF,CAAC;CACD,OAAO,MAAM;EAAE,GAAG,IAAI;EAAG,GAAG,IAAI;CAAE,IAAI;AACxC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sceneIndex.js","names":[],"sources":["../../../../src/features/paths/pathSnap/sceneIndex.ts"],"sourcesContent":["import {\n createPathSnapIndex, type Path, type PathSnapIndex, type PathSnapTarget,\n} from '@weasel-js/geom';\nimport type { Scene } from 'core/scene/types';\nimport { isAnnotationLayer } from 'core/scene/annotation';\nimport { nodeGates } from 'core/scene/nodeGates';\nimport type { SnapGeometry } from 'interactions/actions/snapDep';\nimport { nodeSnapGeometry } from './snapGeometry';\n\ntype SnapScene = Pick<Scene<unknown, string, unknown>, 'nodes' | 'layers' | 'getVersion'>;\n\n/** A scene's paths indexed for snapping; `ids[i]` is the node path `i` came\n * from, or null for a path of the query's own (`also`). */\nexport interface SceneSnapIndex {\n ids: (string | null)[];\n index: PathSnapIndex;\n}\n\n/** Indexes every leaf of `scene` with geometry (`nodeSnapGeometry`), but\n * hidden ones (`nodeGates`) and those on an annotation layer, once per scene\n * version, and again when a query brings geometry of its own. The scene\n * alone and the scene with the last `also` are cached apart, so a query\n * without geometry does not evict a gesture's. */\nexport function createSceneSnapIndexer(scene: SnapScene): (also?: SnapGeometry) => SceneSnapIndex {\n type Slot = { version: number; layers: unknown; also: SnapGeometry | undefined; built: SceneSnapIndex };\n const slots: { bare: Slot | null; also: Slot | null } = { bare: null, also: null };\n return (also) => {\n const version = scene.getVersion();\n const layers = scene.layers;\n const key = also ? 'also' : 'bare';\n const cached = slots[key];\n if (cached?.version === version && cached.layers === layers && cached.also === also) return cached.built;\n const notes = new Set(layers.filter(isAnnotationLayer).map((l) => l.id));\n const gates = nodeGates({ layers, get: (id) => scene.nodes.get(id) });\n const ids: (string | null)[] = [];\n const paths: Path[] = [];\n const extra: PathSnapTarget[] = [];\n for (const node of scene.nodes.values()) {\n if (node.kind !== 'mark' || notes.has(node.layer) || gates.hidden(node.id)) continue;\n const g = nodeSnapGeometry(node);\n if (!g) continue;\n for (const p of g.points) extra.push({ ...p, paths: [paths.length] });\n ids.push(node.id);\n paths.push(g.path);\n }\n for (const path of also?.paths ?? []) { ids.push(null); paths.push(path); }\n if (also?.anchors) {\n ids.push(null);\n for (const p of also.anchors) extra.push({ kind: 'anchor', x: p.x, y: p.y, paths: [paths.length] });\n paths.push({ kind: 'polygon', commands: new Uint8Array(), coords: new Float32Array(), fillRule: 'nonzero' });\n }\n const built = { ids, index: createPathSnapIndex(paths, extra) };\n slots[key] = { version, layers, also, built };\n return built;\n };\n}\n\nconst indexers = new WeakMap<object, ReturnType<typeof createSceneSnapIndexer>>();\n\n/** {@link createSceneSnapIndexer}, one per scene, so every source over a\n * scene shares its index. */\nexport function sceneSnapIndex(scene: SnapScene, also?: SnapGeometry): SceneSnapIndex {\n let indexer = indexers.get(scene);\n if (!indexer) indexers.set(scene, (indexer = createSceneSnapIndexer(scene)));\n return indexer(also);\n}\n"],"mappings":";;;;;;;;;;;AAuBA,SAAgB,uBAAuB,OAA2D;CAEhG,MAAM,QAAkD;EAAE,MAAM;EAAM,MAAM;CAAK;CACjF,QAAQ,SAAS;EACf,MAAM,UAAU,MAAM,WAAW;EACjC,MAAM,SAAS,MAAM;EACrB,MAAM,MAAM,OAAO,SAAS;EAC5B,MAAM,SAAS,MAAM;EACrB,IAAI,QAAQ,YAAY,WAAW,OAAO,WAAW,UAAU,OAAO,SAAS,MAAM,OAAO,OAAO;EACnG,MAAM,QAAQ,IAAI,IAAI,OAAO,OAAO,iBAAiB,CAAC,CAAC,KAAK,MAAM,EAAE,EAAE,CAAC;EACvE,MAAM,QAAQ,UAAU;GAAE;GAAQ,MAAM,OAAO,MAAM,MAAM,IAAI,EAAE;EAAE,CAAC;EACpE,MAAM,MAAyB,CAAC;EAChC,MAAM,QAAgB,CAAC;EACvB,MAAM,QAA0B,CAAC;EACjC,KAAK,MAAM,QAAQ,MAAM,MAAM,OAAO,GAAG;GACvC,IAAI,KAAK,SAAS,UAAU,MAAM,IAAI,KAAK,KAAK,KAAK,MAAM,OAAO,KAAK,EAAE,GAAG;GAC5E,MAAM,IAAI,iBAAiB,IAAI;GAC/B,IAAI,CAAC,GAAG;GACR,KAAK,MAAM,KAAK,EAAE,QAAQ,MAAM,KAAK;IAAE,GAAG;IAAG,OAAO,CAAC,MAAM,MAAM;GAAE,CAAC;GACpE,IAAI,KAAK,KAAK,EAAE;GAChB,MAAM,KAAK,EAAE,IAAI;EACnB;EACA,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;GAAE,IAAI,KAAK,IAAI;GAAG,MAAM,KAAK,IAAI;EAAG;EAC1E,IAAI,MAAM,SAAS;GACjB,IAAI,KAAK,IAAI;GACb,KAAK,MAAM,KAAK,KAAK,SAAS,MAAM,KAAK;IAAE,MAAM;IAAU,GAAG,EAAE;IAAG,GAAG,EAAE;IAAG,OAAO,CAAC,MAAM,MAAM;GAAE,CAAC;GAClG,MAAM,KAAK;IAAE,MAAM;IAAW,0BAAU,IAAI,WAAW;IAAG,wBAAQ,IAAI,aAAa;IAAG,UAAU;GAAU,CAAC;EAC7G;EACA,MAAM,QAAQ;GAAE;GAAK,OAAO,oBAAoB,OAAO,KAAK;EAAE;EAC9D,MAAM,OAAO;GAAE;GAAS;GAAQ;GAAM;EAAM;EAC5C,OAAO;CACT;AACF;AAEA,IAAM,2BAAW,IAAI,QAA2D;;;AAIhF,SAAgB,eAAe,OAAkB,MAAqC;CACpF,IAAI,UAAU,SAAS,IAAI,KAAK;CAChC,IAAI,CAAC,SAAS,SAAS,IAAI,OAAQ,UAAU,uBAAuB,KAAK,CAAE;CAC3E,OAAO,QAAQ,IAAI;AACrB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"snapGeometry.js","names":[],"sources":["../../../../src/features/paths/pathSnap/snapGeometry.ts"],"sourcesContent":["import type { Path, PathSnapTarget } from '@weasel-js/geom';\nimport { isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport { poseOutline } from 'core/geometry/regionHit';\nimport type { Node } from 'core/scene/types';\nimport { findNodeShape } from 'canvas/NodeShape';\nimport { nodeOutline } from '../strokeOutline';\n\n/** What a node offers path snapping: the outline it is drawn with, in world\n * coords, and any point of its own that the outline does not carry. */\nexport interface NodeSnapGeometry {\n path: Path;\n /** Targets beside the outline's, with `paths` left for the index to fill. */\n points: Omit<PathSnapTarget, 'paths'>[];\n}\n\n/**\n * The geometry `node` snaps to. A painter that `snapsToBox` — text, an image\n * — gives its pose box through the pose's frame, and the box's center. Any\n * other node gives its `nodeOutline`. Null for a node with neither.\n */\nexport function nodeSnapGeometry(node: Node<unknown, string, unknown>): NodeSnapGeometry | null {\n const pose = node.pose;\n if (findNodeShape(node)?.snapsToBox && isRectPose(pose)) {\n const path = poseOutline(pose, pose);\n const c = boxCenter(path);\n return { path, points: c ? [{ kind: 'center', ...c }] : [] };\n }\n const path = nodeOutline(node);\n return path ? { path, points: [] } : null;\n}\n\n/** The center of a box outline: a rect, or the four corners a turned one has. */\nfunction boxCenter(path: Path): { x: number; y: number } | null {\n if (path.kind === 'rect') return { x: path.x + path.width / 2, y: path.y + path.height / 2 };\n const c = path.coords;\n if (c.length < 8) return null;\n return { x: (c[0] + c[4]) / 2, y: (c[1] + c[5]) / 2 };\n}\n"],"mappings":";;;;;;;;;;;AAoBA,SAAgB,iBAAiB,MAA+D;CAC9F,MAAM,OAAO,KAAK;CAClB,IAAI,cAAc,IAAI,CAAC,EAAE,cAAc,WAAW,IAAI,GAAG;EACvD,MAAM,OAAO,YAAY,MAAM,IAAI;EACnC,MAAM,IAAI,UAAU,IAAI;EACxB,OAAO;GAAE;GAAM,QAAQ,IAAI,CAAC;IAAE,MAAM;IAAU,GAAG;GAAE,CAAC,IAAI,CAAC;EAAE;CAC7D;CACA,MAAM,OAAO,YAAY,IAAI;CAC7B,OAAO,OAAO;EAAE;EAAM,QAAQ,CAAC;CAAE,IAAI;AACvC;;AAGA,SAAS,UAAU,MAA6C;CAC9D,IAAI,KAAK,SAAS,QAAQ,OAAO;EAAE,GAAG,KAAK,IAAI,KAAK,QAAQ;EAAG,GAAG,KAAK,IAAI,KAAK,SAAS;CAAE;CAC3F,MAAM,IAAI,KAAK;CACf,IAAI,EAAE,SAAS,GAAG,OAAO;CACzB,OAAO;EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;EAAG,IAAI,EAAE,KAAK,EAAE,MAAM;CAAE;AACtD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"penPreviewLayer.js","names":[],"sources":["../../../src/features/paths/penPreviewLayer.ts"],"sourcesContent":["/**\n * Screen-space preview layer for `usePenTool`. Reads the pen's state each\n * frame and renders the in-progress path: finished\n * subpaths (faded), the current subpath (bright + anchor dots + handles),\n * a rubber-band segment from the latest anchor to the cursor, and a\n * close-hint ring on the first anchor when the tool flags it as hot.\n *\n * Runs in screen space so anchor/handle sizes stay constant under zoom.\n * All world-space coords go through `worldToScreen` via the layer's `view`\n * argument.\n */\n\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { KitSettingsStore } from '@weasel-js/react';\nimport type { PenScratch, PenAnchor, PenSubpath } from 'tools/builtin/pen';\nimport { PATH_C, PATH_L, PATH_M, type PolygonPath, anchorsToPath, circlePath } from '@weasel-js/geom';\n\n/** Appearance of the pen tool's in-progress path. */\nexport interface PenPreviewStyle {\n anchorFill?: string;\n anchorStroke?: string;\n handleStroke?: string;\n rubberBandStroke?: string;\n closeHintFill?: string;\n finishedSubpathStroke?: string;\n}\n\n/** Options for `createPenPreviewLayer`. */\nexport interface CreatePenPreviewLayerOptions {\n /** The pen's state. One object for the pen's life, changed in place, so\n * the layer reads the same one on every frame. */\n state: PenScratch;\n style?: PenPreviewStyle;\n /** The kit settings whose `targets.pen*RadiusPx` size the dots, read each frame. */\n kitSettings?: () => KitSettingsStore;\n}\n\nconst DEFAULT_STYLE: Required<PenPreviewStyle> = {\n anchorFill: '#d4c4a8',\n anchorStroke: '#d4c4a8',\n handleStroke: '#d4c4a8',\n rubberBandStroke: '#d4c4a8',\n closeHintFill: '#d4c4a8',\n finishedSubpathStroke: '#8a7a5e',\n};\n\nexport const ANCHOR_RADIUS_PX = 4;\nexport const HANDLE_DOT_RADIUS_PX = 3;\nexport const CLOSE_HINT_RADIUS_PX = 8;\n\n/** How near the pen's first anchor, in screen px, a click closes the subpath. */\nexport const PEN_CLOSE_HIT_PX = 8;\n\n/** The pen's reach in screen px: its own option, else the kit setting, else the constant. */\nexport const penReachOf = (o: { closeHitRadius?: number; kitSettings?: KitSettingsStore }) => ({\n close: o.closeHitRadius ?? o.kitSettings?.get('targets.penCloseHitPx') ?? PEN_CLOSE_HIT_PX,\n});\n\nconst radiiOf = (kit: KitSettingsStore | undefined) => ({\n anchor: kit?.get('targets.penAnchorRadiusPx') ?? ANCHOR_RADIUS_PX,\n handleDot: kit?.get('targets.penHandleDotRadiusPx') ?? HANDLE_DOT_RADIUS_PX,\n closeHint: kit?.get('targets.penCloseHintRadiusPx') ?? CLOSE_HINT_RADIUS_PX,\n});\n\nfunction w2s(wx: number, wy: number, view: { x: number; y: number; scale: { x: number; y: number } }): [number, number] {\n return [(wx - view.x) * view.scale.x, (wy - view.y) * view.scale.y];\n}\n\n// approximateCircle has moved to `./markers.ts` as `circlePath`. Re-exported\n// locally so we don't rename callers in this file.\nfunction approximateCircle(cx: number, cy: number, r: number): PolygonPath {\n return circlePath(cx, cy, r);\n}\n\n/** A pen subpath as a screen-space `PolygonPath`, segmented by the same rule\n * the pen commits with. */\nfunction subpathToPath(\n sp: PenSubpath,\n view: { x: number; y: number; scale: { x: number; y: number } },\n): PolygonPath | null {\n if (sp.anchors.length === 0) return null;\n const pt = (p: { x: number; y: number }) => {\n const [x, y] = w2s(p.x, p.y, view);\n return { x, y };\n };\n const anchors = sp.anchors.map((a: PenAnchor) => ({\n ...pt(a),\n ...(a.inHandle ? { inHandle: pt(a.inHandle) } : {}),\n ...(a.outHandle ? { outHandle: pt(a.outHandle) } : {}),\n }));\n return anchorsToPath([anchors], [sp.closed]);\n}\n\n/** Render layer that draws the pen tool's path as it is being placed —\n * committed anchors, the rubber band to the cursor, and the close hint. Draws\n * nothing when the pen is idle. */\nexport function createPenPreviewLayer(\n opts: CreatePenPreviewLayerOptions,\n): RenderLayer<unknown> {\n const style = { ...DEFAULT_STYLE, ...(opts.style ?? {}) };\n const s = opts.state;\n\n return {\n id: 'penPreview',\n label: 'Pen preview',\n space: 'screen',\n draw: (_data, view) => {\n const out: DrawCommand[] = [];\n const r = radiiOf(opts.kitSettings?.());\n // (1) Finished subpaths — faded outline.\n for (const sp of s.finishedSubpaths) {\n const path = subpathToPath(sp, view);\n if (path === null) continue;\n out.push({\n kind: 'path',\n path,\n stroke: { paint: { kind: 'solid', color: style.finishedSubpathStroke }, width: 1 },\n });\n }\n\n const cur = s.current;\n if (!cur && s.cursor === null) return out;\n\n // (2) Current subpath — bright outline.\n if (cur) {\n const path = subpathToPath(cur, view);\n if (path !== null) {\n out.push({\n kind: 'path',\n path,\n stroke: { paint: { kind: 'solid', color: style.rubberBandStroke }, width: 1 },\n });\n }\n }\n\n // (3) Rubber-band from latest anchor to cursor.\n if (cur && cur.anchors.length > 0 && s.cursor && s.draggingHandleAt === null) {\n const last = cur.anchors[cur.anchors.length - 1];\n const out2 = last.outHandle;\n const [sx, sy] = w2s(last.x, last.y, view);\n const [cx, cy] = w2s(s.cursor.x, s.cursor.y, view);\n let path: PolygonPath;\n if (out2) {\n const [c1x, c1y] = w2s(out2.x, out2.y, view);\n path = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_C]),\n coords: new Float32Array([sx, sy, c1x, c1y, cx, cy, cx, cy]),\n fillRule: 'nonzero',\n };\n } else {\n path = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L]),\n coords: new Float32Array([sx, sy, cx, cy]),\n fillRule: 'nonzero',\n };\n }\n out.push({\n kind: 'path',\n path,\n stroke: { paint: { kind: 'solid', color: style.rubberBandStroke }, width: 1 },\n });\n }\n\n // (4) Anchor dots + handles.\n if (cur) {\n for (let i = 0; i < cur.anchors.length; i++) {\n const a = cur.anchors[i];\n const [ax, ay] = w2s(a.x, a.y, view);\n out.push({\n kind: 'path',\n path: approximateCircle(ax, ay, r.anchor),\n fill: { paint: { kind: 'solid', color: style.anchorFill } },\n stroke: { paint: { kind: 'solid', color: style.anchorStroke }, width: 1 },\n });\n // Handle line + dot (only the latest anchor, per spec).\n if (i === cur.anchors.length - 1 && a.outHandle) {\n const [hx, hy] = w2s(a.outHandle.x, a.outHandle.y, view);\n const linePath: PolygonPath = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L]),\n coords: new Float32Array([ax, ay, hx, hy]),\n fillRule: 'nonzero',\n };\n out.push({\n kind: 'path',\n path: linePath,\n stroke: { paint: { kind: 'solid', color: style.handleStroke }, width: 1 },\n });\n out.push({\n kind: 'path',\n path: approximateCircle(hx, hy, r.handleDot),\n fill: { paint: { kind: 'solid', color: style.handleStroke } },\n });\n const inSide = a.inHandle;\n if (inSide) {\n const [mx, my] = w2s(inSide.x, inSide.y, view);\n const inLine: PolygonPath = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L]),\n coords: new Float32Array([ax, ay, mx, my]),\n fillRule: 'nonzero',\n };\n out.push({\n kind: 'path',\n path: inLine,\n stroke: { paint: { kind: 'solid', color: style.handleStroke }, width: 1 },\n });\n out.push({\n kind: 'path',\n path: approximateCircle(mx, my, r.handleDot),\n fill: { paint: { kind: 'solid', color: style.handleStroke } },\n });\n }\n }\n }\n\n // (5) Close-hint ring on first anchor.\n if (s.closeHintActive && cur.anchors.length >= 3) {\n const first = cur.anchors[0];\n const [fx, fy] = w2s(first.x, first.y, view);\n out.push({\n kind: 'path',\n path: approximateCircle(fx, fy, r.closeHint),\n stroke: { paint: { kind: 'solid', color: style.closeHintFill }, width: 1.5 },\n });\n }\n }\n\n return out;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shapeGenerator.js","names":[],"sources":["../../../src/features/paths/shapeGenerator.ts"],"sourcesContent":["/**\n * The parameters a polygon or star was drawn with, kept on the node beside the\n * geometry they made (`data.generator` beside `data.path`), so the shape stays\n * editable as a polygon or a star after it is drawn.\n *\n * `data.path` stays the geometry every reader uses; the generator only says how\n * to make it again. Editing the path itself turns the shape into plain\n * geometry: the generator is dropped, since it could no longer reproduce what\n * is drawn. A generator whose path no longer matches it (a writer that changed\n * the path without dropping it) is treated the same way.\n */\nimport {\n boundsOfPath,\n boxToBox,\n poseFrameOf,\n regularPolygonPath,\n starPath,\n transformPath,\n worldEditToStorage,\n type PathInWorldPose,\n type PolygonPath,\n} from '@weasel-js/geom';\n\n/** A regular polygon's parameters. `rotation` is the angle of its first\n * vertex, in radians, inside the pose's own rotation. */\nexport interface PolygonGenerator {\n kind: 'polygon';\n sides: number;\n rotation: number;\n}\n\n/** A star's parameters. `innerRadiusRatio` is the inner radius over the outer\n * one; `rotation` is the angle of its first point, in radians, inside the\n * pose's own rotation. */\nexport interface StarGenerator {\n kind: 'star';\n points: number;\n innerRadiusRatio: number;\n rotation: number;\n}\n\n/** The parameters a node's `data.path` was generated from. */\nexport type ShapeGenerator = PolygonGenerator | StarGenerator;\n\n/** The range of a polygon's side count and a star's point count. */\nexport const SHAPE_COUNT_RANGE = { min: 3, max: 32 } as const;\n\n/** The range of a star's `innerRadiusRatio`. */\nexport const STAR_RATIO_RANGE = { min: 0.01, max: 1 } as const;\n\nconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n\n/** Whether `v` is a well-formed {@link ShapeGenerator}. */\nexport function isShapeGenerator(v: unknown): v is ShapeGenerator {\n if (v === null || typeof v !== 'object') return false;\n const g = v as Record<string, unknown>;\n if (!finite(g.rotation)) return false;\n if (g.kind === 'polygon') return finite(g.sides);\n if (g.kind === 'star') return finite(g.points) && finite(g.innerRadiusRatio);\n return false;\n}\n\n/** The generator a node's `data` holds, or `null`. */\nexport function shapeGeneratorOf(data: unknown): ShapeGenerator | null {\n const g = (data as { generator?: unknown } | null | undefined)?.generator;\n return isShapeGenerator(g) ? g : null;\n}\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\nconst count = (v: number): number => clamp(Math.round(v), SHAPE_COUNT_RANGE.min, SHAPE_COUNT_RANGE.max);\n\n/** `g` with its counts rounded and every field held in range. */\nexport function clampShapeGenerator(g: ShapeGenerator): ShapeGenerator {\n if (g.kind === 'polygon') return { kind: 'polygon', sides: count(g.sides), rotation: g.rotation };\n return {\n kind: 'star',\n points: count(g.points),\n innerRadiusRatio: clamp(g.innerRadiusRatio, STAR_RATIO_RANGE.min, STAR_RATIO_RANGE.max),\n rotation: g.rotation,\n };\n}\n\n/** The shape `g` makes, on a circle of `radius` around `center`. */\nexport function shapeGeneratorPath(\n g: ShapeGenerator,\n center: { x: number; y: number } = { x: 0, y: 0 },\n radius = 1,\n): PolygonPath {\n const c = clampShapeGenerator(g);\n return c.kind === 'polygon'\n ? regularPolygonPath(center, radius, c.sides, c.rotation)\n : starPath(center, radius, c.points, radius * c.innerRadiusRatio, c.rotation);\n}\n\n/** `data` without its generator: the shape as plain geometry. */\nexport function withoutShapeGenerator<T>(data: T): T {\n if (data === null || typeof data !== 'object' || !('generator' in data)) return data;\n const { generator: _dropped, ...rest } = data as Record<string, unknown>;\n void _dropped;\n return rest as T;\n}\n\n/** Whether `path` is what `g` makes, up to the box-to-box map storage applies. */\nfunction pathMatches(path: unknown, g: ShapeGenerator): boolean {\n const p = path as PolygonPath | undefined;\n if (p?.kind !== 'polygon') return false;\n const unit = shapeGeneratorPath(g);\n if (p.coords.length !== unit.coords.length) return false;\n const u = boundsOfPath(unit);\n const b = boundsOfPath(p);\n const m = boxToBox(u.x, u.y, u.width, u.height, b.x, b.y, b.width, b.height);\n // Paths store float32 coordinates, so the slack scales with how far they are from the origin.\n const tol = 1e-4 * Math.max(1, Math.abs(b.x) + b.width, Math.abs(b.y) + b.height);\n for (let i = 0; i + 1 < unit.coords.length; i += 2) {\n const x = m[0] * unit.coords[i] + m[4];\n const y = m[3] * unit.coords[i + 1] + m[5];\n if (Math.abs(x - p.coords[i]) > tol || Math.abs(y - p.coords[i + 1]) > tol) return false;\n }\n return true;\n}\n\n/** A node's two halves, as a regeneration reads and writes them. */\nexport interface GeneratedShape<P extends PathInWorldPose = PathInWorldPose> {\n pose: P;\n data: unknown;\n}\n\n/**\n * `node` made again from `next`, in place: the new shape takes the box the old\n * one's circle had, under the same rotation and transform, so a star given\n * more points keeps its center and its size. Returns `null` when the node holds\n * no generator, or its path no longer matches the one it holds.\n */\nexport function regenerateShape<P extends PathInWorldPose>(\n node: GeneratedShape<P>,\n next: ShapeGenerator,\n): GeneratedShape<P> | null {\n const held = shapeGeneratorOf(node.data);\n if (!held || !pathMatches((node.data as { path?: unknown }).path, held)) return null;\n const g = clampShapeGenerator(next);\n const was = boundsOfPath(shapeGeneratorPath(held));\n const { pose } = node;\n // Where the unit circle of the held generator lands in the pose's local frame.\n const toLocal = boxToBox(was.x, was.y, was.width, was.height, pose.x, pose.y, pose.width, pose.height);\n const local = transformPath(shapeGeneratorPath(g), toLocal) as PolygonPath;\n const frame = poseFrameOf(pose);\n const world = frame ? (transformPath(local, frame) as PolygonPath) : local;\n const stored = worldEditToStorage(pose, world);\n return { pose: stored.pose, data: { ...(node.data as object), path: stored.path, generator: g } };\n}\n\n/**\n * What a write that left a node as `next`, from `prev`, stores: when the write\n * changed the generator, the shape made again from it. A generator written\n * onto a node that held none, or of another kind, or onto one whose path no\n * longer matches, is dropped, leaving the shape as plain geometry. Any other\n * write is stored as written. Fits `NodePropertiesEntry.settle`.\n */\nexport function settleShapeGenerator<N extends { pose: unknown; data: unknown }>(next: N, prev: N): N {\n const data = next.data as Record<string, unknown> | null;\n if (data === null || typeof data !== 'object' || !('generator' in data)) return next;\n const raw = data.generator;\n const held = shapeGeneratorOf(prev.data);\n if (held !== null && JSON.stringify(raw) === JSON.stringify(held)) return next;\n const regenerated = held !== null && isShapeGenerator(raw) && raw.kind === held.kind\n ? regenerateShape({ pose: next.pose as PathInWorldPose, data: { ...data, generator: held } }, raw)\n : null;\n return { ...next, ...(regenerated ?? { data: withoutShapeGenerator(next.data) }) };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"strokeOutline.js","names":[],"sources":["../../../src/features/paths/strokeOutline.ts"],"sourcesContent":["import type { Path } from '@weasel-js/geom';\nimport { isSilhouettePose } from 'core/geometry/regionHit';\nimport type { Node } from 'core/scene/types';\nimport { findShapeSilhouette } from 'canvas/NodeShape';\n\n/** The outline `node` is drawn with, in world coords: its pose when the pose\n * is a polygon, else its painter's silhouette. Null for a node with neither. */\nexport function nodeOutline(node: Node<unknown, string, unknown>): Path | null {\n const pose = node.pose;\n if (isSilhouettePose(pose)) return pose;\n return findShapeSilhouette(node, pose);\n}\n"],"mappings":";;;;;;AAOA,SAAgB,YAAY,MAAmD;CAC7E,MAAM,OAAO,KAAK;CAClB,IAAI,iBAAiB,IAAI,GAAG,OAAO;CACnC,OAAO,oBAAoB,MAAM,IAAI;AACvC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useAnchorFields.js","names":[],"sources":["../../../src/features/paths/useAnchorFields.ts"],"sourcesContent":["/**\n * The anchor fields of the path being edited, for chrome beside the canvas:\n * a panel showing the selected anchors' numbers and writing typed ones back.\n */\nimport { useMemo, useSyncExternalStore } from 'react';\nimport type { AnchorField, AnchorFieldValues, NumberEntry } from '@weasel-js/geom';\nimport { useActionsRegistry, useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport { anchorFieldsOf } from 'interactions/actions/defaults/anchorFields';\n\n/** What {@link useAnchorFields} answers. */\nexport interface AnchorFieldsApi {\n /** Each field's value: a number, `null` where the selected anchors differ,\n * no key where the field does not apply. Empty outside path edit or with no\n * anchor selected. */\n values: AnchorFieldValues;\n /** Write `entry` into `field` of every selected anchor, as one undo step,\n * through the `setAnchorField` action. */\n write(field: AnchorField, entry: NumberEntry): void;\n}\n\nconst noSubscribe = (): (() => void) => () => {};\n\n/**\n * The selected anchors' fields in the canvas in scope — the one last pressed\n * in, from chrome above several — kept current as the path, the anchor\n * selection, or the edited path changes. Outside any canvas it answers `{}`\n * and writes nothing.\n */\nexport function useAnchorFields(): AnchorFieldsApi {\n const deps = useOptionalDepRegistry();\n const actions = useActionsRegistry();\n const snapshot = (): string => JSON.stringify(anchorFieldsOf(deps ? { editAnchors: deps.get('editAnchors') } : undefined));\n const json = useSyncExternalStore(deps?.subscribe ?? noSubscribe, snapshot, snapshot);\n return useMemo(() => ({\n values: JSON.parse(json) as AnchorFieldValues,\n write: (field, entry) => { actions?.trigger('setAnchorField', { field, ...entry }); },\n }), [json, actions]);\n}\n"],"mappings":";;;;;;;;;AAoBA,IAAM,0BAAwC,CAAC;;;;;;;AAQ/C,SAAgB,kBAAmC;CACjD,MAAM,OAAO,uBAAuB;CACpC,MAAM,UAAU,mBAAmB;CACnC,MAAM,iBAAyB,KAAK,UAAU,eAAe,OAAO,EAAE,aAAa,KAAK,IAAI,aAAa,EAAE,IAAI,MAAS,CAAC;CACzH,MAAM,OAAO,qBAAqB,MAAM,aAAa,aAAa,UAAU,QAAQ;CACpF,OAAO,eAAe;EACpB,QAAQ,KAAK,MAAM,IAAI;EACvB,QAAQ,OAAO,UAAU;GAAE,SAAS,QAAQ,kBAAkB;IAAE;IAAO,GAAG;GAAM,CAAC;EAAG;CACtF,IAAI,CAAC,MAAM,OAAO,CAAC;AACrB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"usePathEdit.js","names":[],"sources":["../../../src/features/paths/usePathEdit.ts"],"sourcesContent":["/**\n * The path-edit state and its anchor overlay for one canvas surface.\n */\nimport { useLatest } from '@weasel-js/react';\nimport { useEffect, useMemo, useState } from 'react';\nimport type { PathEditHost, PathEditOpening } from 'canvas/SceneCanvas/pathEditHost';\nimport type { RenderLayer } from 'core/layers/render';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { liveAnchorMarkers, liveEditablePath, type EditableShape } from './editablePath';\nimport { createPathEditingOverlayLayer } from './pathEditingOverlayLayer';\nimport type { PathEditingOverlaySlotConfig } from 'canvas/layerSlots';\nimport { createPathEditState, type PathEditState } from './pathEditState';\nimport type { PathEditExtension } from './pathEditExtension';\n\nexport interface UsePathEditStateOptions {\n /** `<SceneCanvas pathEdit>`. */\n host: PathEditHost | undefined;\n /** `<SceneCanvas defaultPathEdit>`: what the state starts holding. */\n opening?: PathEditOpening;\n /** See `PathEditSource.isAllowed`; absent when no mode registry is wired. */\n isAllowed: (() => boolean) | undefined;\n isSelectedNode: (id: string) => boolean;\n /** The path-edit extensions the canvas's presets bring. */\n extensions: readonly PathEditExtension[];\n}\n\n/** One state object for the life of the component, answering from the\n * options of the last committed render. */\nexport function usePathEditState(options: UsePathEditStateOptions): PathEditState {\n const live = useLatest(options);\n const [state] = useState(() => {\n const made = createPathEditState({\n host: () => live.current.host,\n isAllowed: () => live.current.isAllowed?.() ?? true,\n isSelectedNode: (id) => live.current.isSelectedNode(id),\n extensions: options.extensions,\n });\n const { opening } = options;\n if (opening) {\n made.setEditingId(opening.id);\n made.setSelectedAnchors(new Set(opening.anchors ?? []));\n }\n return made;\n });\n useEffect(() => state.setExtensions(options.extensions), [state, options.extensions]);\n return state;\n}\n\nexport interface PathEditOverlayInputs {\n /** The committed node for an id, or nothing once it is gone. */\n nodeOf: (id: string) => EditableShape | null | undefined;\n /** How `view`'s world maps into the parallax plane `id` is drawn through. */\n planeOfNode: (id: string, view: View) => PlaneMap | null;\n}\n\n/** The anchor overlay for `state`, drawn as `config` says. Rebuilt when\n * `config` changes, so pass a stable one; otherwise it reads `inputs` as of\n * the last commit and repaints itself when the state changes. */\nexport function usePathEditOverlayLayer(\n state: PathEditState,\n inputs: PathEditOverlayInputs,\n config?: PathEditingOverlaySlotConfig,\n): RenderLayer<unknown> {\n const live = useLatest(inputs);\n return useMemo<RenderLayer<unknown>>(() => ({\n ...createPathEditingOverlayLayer({\n getEditingId: () => state.getEditingId() || null,\n getPose: (id, previews, view) =>\n liveEditablePath(id, live.current.nodeOf(id), previews, live.current.planeOfNode(id, view), state.getExtensions()),\n getAnchorMarkers: (id, previews) => liveAnchorMarkers(id, live.current.nodeOf(id), previews, state.getExtensions()),\n getSelectedAnchors: state.getSelectedAnchors,\n getSelectedMark: state.getSelectedMark,\n getExtensions: state.getExtensions,\n getNode: (id) => live.current.nodeOf(id) ?? null,\n // Drawn by an action that ran in the edited path's plane.\n getMarquee: (view) => {\n const rect = state.getMarquee();\n const plane = rect ? live.current.planeOfNode(state.getEditingId(), view) : null;\n return rect && plane ? rectFromPlane(plane, rect) : rect;\n },\n ...(config?.style ? { style: config.style } : {}),\n }),\n subscribe: state.subscribe,\n }), [state, live, config]);\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,iBAAiB,SAAiD;CAChF,MAAM,OAAO,UAAU,OAAO;CAC9B,MAAM,CAAC,SAAS,eAAe;EAC7B,MAAM,OAAO,oBAAoB;GAC/B,YAAY,KAAK,QAAQ;GACzB,iBAAiB,KAAK,QAAQ,YAAY,KAAK;GAC/C,iBAAiB,OAAO,KAAK,QAAQ,eAAe,EAAE;GACtD,YAAY,QAAQ;EACtB,CAAC;EACD,MAAM,EAAE,YAAY;EACpB,IAAI,SAAS;GACX,KAAK,aAAa,QAAQ,EAAE;GAC5B,KAAK,mBAAmB,IAAI,IAAI,QAAQ,WAAW,CAAC,CAAC,CAAC;EACxD;EACA,OAAO;CACT,CAAC;CACD,gBAAgB,MAAM,cAAc,QAAQ,UAAU,GAAG,CAAC,OAAO,QAAQ,UAAU,CAAC;CACpF,OAAO;AACT;;;;AAYA,SAAgB,wBACd,OACA,QACA,QACsB;CACtB,MAAM,OAAO,UAAU,MAAM;CAC7B,OAAO,eAAqC;EAC1C,GAAG,8BAA8B;GAC/B,oBAAoB,MAAM,aAAa,KAAK;GAC5C,UAAU,IAAI,UAAU,SACtB,iBAAiB,IAAI,KAAK,QAAQ,OAAO,EAAE,GAAG,UAAU,KAAK,QAAQ,YAAY,IAAI,IAAI,GAAG,MAAM,cAAc,CAAC;GACnH,mBAAmB,IAAI,aAAa,kBAAkB,IAAI,KAAK,QAAQ,OAAO,EAAE,GAAG,UAAU,MAAM,cAAc,CAAC;GAClH,oBAAoB,MAAM;GAC1B,iBAAiB,MAAM;GACvB,eAAe,MAAM;GACrB,UAAU,OAAO,KAAK,QAAQ,OAAO,EAAE,KAAK;GAE5C,aAAa,SAAS;IACpB,MAAM,OAAO,MAAM,WAAW;IAC9B,MAAM,QAAQ,OAAO,KAAK,QAAQ,YAAY,MAAM,aAAa,GAAG,IAAI,IAAI;IAC5E,OAAO,QAAQ,QAAQ,cAAc,OAAO,IAAI,IAAI;GACtD;GACA,GAAI,QAAQ,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EACjD,CAAC;EACD,WAAW,MAAM;CACnB,IAAI;EAAC;EAAO;EAAM;CAAM,CAAC;AAC3B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"usePoseRun.js","names":[],"sources":["../../../src/features/poseRun/usePoseRun.ts"],"sourcesContent":["/**\n * A pose run: poses published per frame, committed once.\n *\n * The frames go to `scene.overrides` — the ephemeral table a drag publishes\n * to — so an edge follows a node the run is moving, and the whole run lands as\n * one undo entry when it settles.\n *\n * **A node another gesture owns is pinned, not driven.** The run publishes\n * through a hold of its own, so an override it did not put there belongs to\n * somebody else — a drag, a resize. It never writes that id, never commits it,\n * and hands it to `step` as pinned so the producer can hold its neighbors\n * against it.\n */\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useLatest, useVisibleRaf, type VisibleRaf } from '@weasel-js/react';\nimport type { NodeId, PoseHold, Scene } from '../../core/scene/types';\n\n/** What a producer is told before it is asked for a frame. */\nexport interface PoseRunCtx<TPose> {\n /** Nodes another gesture is moving, and the pose each is drawn at. The run\n * will not write these, so a producer holds them fixed. */\n pinned: ReadonlyMap<NodeId, TPose>;\n /** Frames this run has produced, starting at 0. */\n frame: number;\n}\n\n/** One frame of a run. */\nexport interface PoseRunStep<TPose> {\n /** Where the nodes it drives are this frame — the whole picture, not a\n * delta. An id the frame omits stops being published, which is how a node\n * leaves the run's hands. */\n poses: Iterable<readonly [NodeId, TPose]>;\n /** Whether the run is finished. The frame is published either way, and a\n * finished run commits. */\n done: boolean;\n}\n\n/** Options for `usePoseRun`. Read fresh each frame, so they may change mid-run. */\nexport interface UsePoseRunOptions<TPose> {\n scene: Scene<unknown, string, TPose>;\n /** Called once per frame while running, for that frame's poses. */\n step: (ctx: PoseRunCtx<TPose>) => PoseRunStep<TPose>;\n /** The undo entry's name. Default `'Layout'`. */\n label?: string;\n /** How many nodes the commit wrote. Fires after the ops land. */\n onCommit?: (moved: number) => void;\n /** Writes of the caller's own, made inside the commit's batch so they\n * share its undo entry. `finished` is false when `stop` cut the run short\n * of where `step` was taking it. */\n commitWith?: (finished: boolean) => void;\n /** Clock injection, for tests. Defaults to `requestAnimationFrame`. */\n requestFrame?: (cb: (t: number) => void) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/** Controls for a pose run. Stable for the life of the component. */\nexport interface PoseRun {\n /** Begin a new run, counting frames from 0. Idempotent while running. */\n start(): void;\n /** Finish now: commit what is published, then drop the overrides. */\n stop(): void;\n /** Abandon: drop the overrides and write nothing, like a canceled drag. */\n cancel(): void;\n isRunning(): boolean;\n}\n\n/**\n * Drive node poses frame by frame through `scene.overrides`, committing them as\n * one undo entry when `step` reports done or `stop` is called. Idle until\n * `start`; unmounting abandons the run without writing.\n */\nexport function usePoseRun<TPose>(opts: UsePoseRunOptions<TPose>): PoseRun {\n const optsRef = useLatest(opts);\n\n /** This frame's poses. */\n const previews = useRef(new Map<NodeId, TPose>());\n /** The hold the run publishes through, and the scene it was taken from. */\n const held = useRef<{ scene: Scene<unknown, string, TPose>; hold: PoseHold<TPose> } | null>(null);\n const running = useRef(false);\n const frame = useRef(0);\n const mounted = useRef(true);\n\n /** The hold on the scene the options name now. A run handed a different\n * scene mid-flight lets go of the old one's nodes. */\n const holdNow = useCallback((): PoseHold<TPose> => {\n const { scene } = optsRef.current;\n if (held.current?.scene !== scene) {\n held.current?.hold.drop();\n held.current = { scene, hold: scene.overrides.hold() };\n }\n return held.current.hold;\n }, [optsRef]);\n\n const release = useCallback((): void => {\n held.current?.hold.drop();\n previews.current.clear();\n }, []);\n\n const commit = (finished: boolean): void => {\n const scene = optsRef.current.scene;\n const also = optsRef.current.commitWith;\n let moved = 0;\n try {\n if (previews.current.size > 0 || also !== undefined) {\n scene.batch(optsRef.current.label ?? 'Layout', () => {\n for (const [id, pose] of previews.current) {\n if (scene.get(id) === undefined || scene.isLocked(id)) continue;\n scene.setPose(id, pose);\n moved++;\n }\n also?.(finished);\n });\n }\n } finally {\n release();\n }\n optsRef.current.onCommit?.(moved);\n };\n\n const halt = (loop: VisibleRaf): void => {\n running.current = false;\n loop.cancel();\n };\n\n const tick = (loop: VisibleRaf): void => {\n if (!mounted.current || !running.current) return;\n const hold = holdNow();\n const pinned = hold.others();\n const { poses, done } = optsRef.current.step({ pinned, frame: frame.current });\n frame.current++;\n previews.current.clear();\n for (const [id, pose] of poses) {\n if (pinned.has(id)) continue;\n previews.current.set(id, pose);\n }\n hold.set(previews.current);\n if (done) {\n halt(loop);\n commit(true);\n return;\n }\n loop.request();\n };\n\n // The gate defaults an absent clock to `requestAnimationFrame` itself, so\n // these pass straight through rather than being defaulted twice.\n const loop = useVisibleRaf((_time, loop) => { tick(loop); }, {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n });\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n loop.cancel();\n // Teardown is not a decision: an unmounted run abandons its frames\n // rather than writing them.\n release();\n };\n }, [loop, release]);\n\n return useMemo<PoseRun>(() => ({\n start() {\n if (running.current || !mounted.current) return;\n running.current = true;\n frame.current = 0;\n loop.request();\n },\n stop() {\n if (!running.current) return;\n halt(loop);\n commit(false);\n },\n cancel() {\n halt(loop);\n release();\n },\n isRunning: () => running.current,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }), [loop]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,WAAkB,MAAyC;CACzE,MAAM,UAAU,UAAU,IAAI;;CAG9B,MAAM,WAAW,uBAAO,IAAI,IAAmB,CAAC;;CAEhD,MAAM,OAAO,OAA+E,IAAI;CAChG,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,QAAQ,OAAO,CAAC;CACtB,MAAM,UAAU,OAAO,IAAI;;;CAI3B,MAAM,UAAU,kBAAmC;EACjD,MAAM,EAAE,UAAU,QAAQ;EAC1B,IAAI,KAAK,SAAS,UAAU,OAAO;GACjC,KAAK,SAAS,KAAK,KAAK;GACxB,KAAK,UAAU;IAAE;IAAO,MAAM,MAAM,UAAU,KAAK;GAAE;EACvD;EACA,OAAO,KAAK,QAAQ;CACtB,GAAG,CAAC,OAAO,CAAC;CAEZ,MAAM,UAAU,kBAAwB;EACtC,KAAK,SAAS,KAAK,KAAK;EACxB,SAAS,QAAQ,MAAM;CACzB,GAAG,CAAC,CAAC;CAEL,MAAM,UAAU,aAA4B;EAC1C,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,MAAM,OAAO,QAAQ,QAAQ;EAC7B,IAAI,QAAQ;EACZ,IAAI;GACF,IAAI,SAAS,QAAQ,OAAO,KAAK,SAAS,QACxC,MAAM,MAAM,QAAQ,QAAQ,SAAS,gBAAgB;IACnD,KAAK,MAAM,CAAC,IAAI,SAAS,SAAS,SAAS;KACzC,IAAI,MAAM,IAAI,EAAE,MAAM,UAAa,MAAM,SAAS,EAAE,GAAG;KACvD,MAAM,QAAQ,IAAI,IAAI;KACtB;IACF;IACA,OAAO,QAAQ;GACjB,CAAC;EAEL,UAAU;GACR,QAAQ;EACV;EACA,QAAQ,QAAQ,WAAW,KAAK;CAClC;CAEA,MAAM,QAAQ,SAA2B;EACvC,QAAQ,UAAU;EAClB,KAAK,OAAO;CACd;CAEA,MAAM,QAAQ,SAA2B;EACvC,IAAI,CAAC,QAAQ,WAAW,CAAC,QAAQ,SAAS;EAC1C,MAAM,OAAO,QAAQ;EACrB,MAAM,SAAS,KAAK,OAAO;EAC3B,MAAM,EAAE,OAAO,SAAS,QAAQ,QAAQ,KAAK;GAAE;GAAQ,OAAO,MAAM;EAAQ,CAAC;EAC7E,MAAM;EACN,SAAS,QAAQ,MAAM;EACvB,KAAK,MAAM,CAAC,IAAI,SAAS,OAAO;GAC9B,IAAI,OAAO,IAAI,EAAE,GAAG;GACpB,SAAS,QAAQ,IAAI,IAAI,IAAI;EAC/B;EACA,KAAK,IAAI,SAAS,OAAO;EACzB,IAAI,MAAM;GACR,KAAK,IAAI;GACT,OAAO,IAAI;GACX;EACF;EACA,KAAK,QAAQ;CACf;CAIA,MAAM,OAAO,eAAe,OAAO,SAAS;EAAE,KAAK,IAAI;CAAG,GAAG;EAC3D,cAAc,KAAK;EACnB,aAAa,KAAK;CACpB,CAAC;CAED,gBAAgB;EACd,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;GAClB,KAAK,OAAO;GAGZ,QAAQ;EACV;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;CAElB,OAAO,eAAwB;EAC7B,QAAQ;GACN,IAAI,QAAQ,WAAW,CAAC,QAAQ,SAAS;GACzC,QAAQ,UAAU;GAClB,MAAM,UAAU;GAChB,KAAK,QAAQ;EACf;EACA,OAAO;GACL,IAAI,CAAC,QAAQ,SAAS;GACtB,KAAK,IAAI;GACT,OAAO,KAAK;EACd;EACA,SAAS;GACP,KAAK,IAAI;GACT,QAAQ;EACV;EACA,iBAAiB,QAAQ;CAE3B,IAAI,CAAC,IAAI,CAAC;AACZ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"enteredGroupFrame.js","names":[],"sources":["../../../src/features/selection/enteredGroupFrame.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport type { EnteredGroupApi } from 'canvas/deps/enteredGroup';\nimport { rectPathFor } from './handleCommands';\n\n/** The theme token the frame reads its color from, set on the canvas or any element above it. */\nexport const ENTERED_FRAME_TOKEN = '--wzl-entered-group';\n/** The frame's color where nothing sets {@link ENTERED_FRAME_TOKEN}. */\nexport const ENTERED_FRAME_COLOR = '#a48bd4';\nconst FRAME_WIDTH = 1;\nconst FRAME_DASH = [6, 4];\n\n/**\n * The frame around the group a canvas is working inside: one dashed outline on the group's\n * box, in the selection chrome's own screen space, repainting when the entered group changes.\n */\nexport function createEnteredGroupFrameLayer(opts: {\n entered: EnteredGroupApi;\n /** The box to frame, in world units. */\n boundsOf: (id: string) => Bounds | null;\n /** The frame's color, read at each paint. Default {@link ENTERED_FRAME_COLOR}. */\n color?: () => string;\n /** Hears that `color` may answer differently: a theme or mode switch. */\n colorChanged?: (fn: () => void) => () => void;\n}): RenderLayer<unknown> {\n return {\n id: 'entered-group-frame',\n label: 'Entered group',\n space: 'screen',\n subscribe: (repaint) => {\n const offGroup = opts.entered.subscribe(repaint);\n const offColor = opts.colorChanged?.(repaint);\n return () => { offGroup(); offColor?.(); };\n },\n draw: (_data, view) => {\n const id = opts.entered.get();\n const b = id === null ? null : opts.boundsOf(id);\n if (b === null) return [];\n const [x, y] = worldToScreen(b.x, b.y, viewToTransform(view));\n return [{\n kind: 'path',\n path: rectPathFor(x, y, b.width * view.scale.x, b.height * view.scale.y),\n stroke: {\n paint: { kind: 'solid', color: opts.color?.() ?? ENTERED_FRAME_COLOR },\n width: FRAME_WIDTH,\n dash: FRAME_DASH,\n },\n }];\n },\n };\n}\n\nexport function useEnteredGroupFrameLayer(\n entered: EnteredGroupApi,\n boundsOf: (id: string) => Bounds | null,\n color: { read: () => string; changed: (fn: () => void) => () => void },\n): RenderLayer<unknown> {\n return useMemo(\n () => createEnteredGroupFrameLayer({ entered, boundsOf, color: color.read, colorChanged: color.changed }),\n [entered, boundsOf, color],\n );\n}\n"],"mappings":";;;;;;;AASA,IAAa,sBAAsB;;AAEnC,IAAa,sBAAsB;AACnC,IAAM,cAAc;AACpB,IAAM,aAAa,CAAC,GAAG,CAAC;;;;;AAMxB,SAAgB,6BAA6B,MAQpB;CACvB,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,YAAY,YAAY;GACtB,MAAM,WAAW,KAAK,QAAQ,UAAU,OAAO;GAC/C,MAAM,WAAW,KAAK,eAAe,OAAO;GAC5C,aAAa;IAAE,SAAS;IAAG,WAAW;GAAG;EAC3C;EACA,OAAO,OAAO,SAAS;GACrB,MAAM,KAAK,KAAK,QAAQ,IAAI;GAC5B,MAAM,IAAI,OAAO,OAAO,OAAO,KAAK,SAAS,EAAE;GAC/C,IAAI,MAAM,MAAM,OAAO,CAAC;GACxB,MAAM,CAAC,GAAG,KAAK,cAAc,EAAE,GAAG,EAAE,GAAG,gBAAgB,IAAI,CAAC;GAC5D,OAAO,CAAC;IACN,MAAM;IACN,MAAM,YAAY,GAAG,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC;IACvE,QAAQ;KACN,OAAO;MAAE,MAAM;MAAS,OAAO,KAAK,QAAQ,KAAK;KAAoB;KACrE,OAAO;KACP,MAAM;IACR;GACF,CAAC;EACH;CACF;AACF;AAEA,SAAgB,0BACd,SACA,UACA,OACsB;CACtB,OAAO,cACC,6BAA6B;EAAE;EAAS;EAAU,OAAO,MAAM;EAAM,cAAc,MAAM;CAAQ,CAAC,GACxG;EAAC;EAAS;EAAU;CAAK,CAC3B;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"enteredGroupScope.js","names":[],"sources":["../../../src/features/selection/enteredGroupScope.ts"],"sourcesContent":["import { OUT_OF_SCOPE_ALPHA, type NodeScope } from '@weasel-js/modes';\nimport { createEmitter } from '@weasel-js/registry';\nimport { reachableInGroup, type EnteredGroupApi } from 'canvas/deps/enteredGroup';\nimport type { Scene } from 'core/scene/types';\n\n/**\n * The entered group as a scope: everything outside its subtree dims and stops taking hits, and\n * so does the group itself, whose empty box is empty canvas while it is entered. With nothing\n * entered every node is lit and live. Compose `alphaFor` into `<SceneCanvas alphaFor>` and the\n * scope into `redrawOn` to show it.\n */\nexport function createEnteredGroupScope(opts: {\n entered: EnteredGroupApi;\n scene: Pick<Scene<unknown, string, unknown>, 'ancestorsOf'>;\n /** Alpha for a node outside the entered group. Default `OUT_OF_SCOPE_ALPHA`. */\n dimAlpha?: number;\n}): NodeScope {\n const { entered, scene } = opts;\n const dim = opts.dimAlpha ?? OUT_OF_SCOPE_ALPHA;\n const listeners = createEmitter();\n let version = 0;\n let stop: (() => void) | null = null;\n\n return {\n alphaFor: (id) => {\n const group = entered.get();\n return id === group || reachableInGroup(scene, group, id) ? 1 : dim;\n },\n isPointerInteractive: (id) => reachableInGroup(scene, entered.get(), id),\n getVersion: () => version,\n subscribe(fn) {\n if (stop === null) stop = entered.subscribe(() => { version++; listeners.emit(); });\n const off = listeners.subscribe(fn);\n return () => {\n off();\n if (listeners.size === 0) { stop?.(); stop = null; }\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,wBAAwB,MAK1B;CACZ,MAAM,EAAE,SAAS,UAAU;CAC3B,MAAM,MAAM,KAAK,YAAY;CAC7B,MAAM,YAAY,cAAc;CAChC,IAAI,UAAU;CACd,IAAI,OAA4B;CAEhC,OAAO;EACL,WAAW,OAAO;GAChB,MAAM,QAAQ,QAAQ,IAAI;GAC1B,OAAO,OAAO,SAAS,iBAAiB,OAAO,OAAO,EAAE,IAAI,IAAI;EAClE;EACA,uBAAuB,OAAO,iBAAiB,OAAO,QAAQ,IAAI,GAAG,EAAE;EACvE,kBAAkB;EAClB,UAAU,IAAI;GACZ,IAAI,SAAS,MAAM,OAAO,QAAQ,gBAAgB;IAAE;IAAW,UAAU,KAAK;GAAG,CAAC;GAClF,MAAM,MAAM,UAAU,UAAU,EAAE;GAClC,aAAa;IACX,IAAI;IACJ,IAAI,UAAU,SAAS,GAAG;KAAE,OAAO;KAAG,OAAO;IAAM;GACrD;EACF;CACF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handleCommands.js","names":[],"sources":["../../../src/features/selection/handleCommands.ts"],"sourcesContent":["/**\n * The selection overlay's resize handles: their style, where the kit puts\n * them, and the commands that paint them.\n */\n\nimport type { DrawCommand, GlMat3 } from '@weasel-js/renderer';\nimport { alignedStrokeRect, type CommandStroke, type CommandFill } from '@weasel-js/paint';\nimport { resolveStrokeWidth } from '@weasel-js/stroke';\nimport { screenAngleOf } from '@weasel-js/geom';\nimport { angleOf, localToWorld, targetTransformOf } from 'core/geometry/targetFrame';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport { HANDLE_BASE_PX } from 'core/device/targets';\nimport {\n handlePoint, resizeHandlesFor, type ResizeHandleLayout,\n} from 'interactions/actions/resize/cornerHandles';\n\nexport type { ResizeHandleLayout };\n\nconst DEFAULT_HANDLE_FILL: CommandFill = { paint: { kind: 'solid', color: '#d4c4a8' } };\nconst DEFAULT_HANDLE_OUTLINE: CommandStroke = {\n paint: { kind: 'solid', color: '#1a130d' },\n width: 1,\n};\nconst DEFAULT_HANDLE_SIZE = HANDLE_BASE_PX;\n\n\n/** Handle visuals, as a consumer supplies them. */\nexport interface SelectionHandleStyle {\n size?: number;\n fill?: CommandFill;\n outline?: CommandStroke;\n}\n\nexport interface ResolvedHandles {\n size: number;\n fill: CommandFill;\n outline: CommandStroke;\n}\n\nexport function resolveHandles(opts?: SelectionHandleStyle): ResolvedHandles {\n return {\n size: opts?.size ?? DEFAULT_HANDLE_SIZE,\n fill: opts?.fill ?? DEFAULT_HANDLE_FILL,\n outline: opts?.outline ?? DEFAULT_HANDLE_OUTLINE,\n };\n}\n\n/** GL helper: emit a closed rect path (kind:'rect'). */\nexport function rectPathFor(x: number, y: number, width: number, height: number): { kind: 'rect'; x: number; y: number; width: number; height: number } {\n return { kind: 'rect', x, y, width, height };\n}\n\n/**\n * GL helper: build the matrix `translate(cx, cy) * rotate(θ) * translate(-cx, -cy)`\n * used to wrap rotated outline / handle commands.\n */\nfunction rotateAroundMat3(cx: number, cy: number, theta: number): GlMat3 {\n const c = Math.cos(theta);\n const s = Math.sin(theta);\n // Composed in column-major (matches mat3 helpers): T(cx,cy) · R(θ) · T(-cx,-cy).\n // After expansion: a=c, b=s, c'=-s, d=c, tx=cx - c*cx + s*cy, ty=cy - s*cx - c*cy.\n const tx = cx - c * cx + s * cy;\n const ty = cy - s * cx - c * cy;\n return new Float32Array([\n c, s, 0,\n -s, c, 0,\n tx, ty, 1,\n ]) as GlMat3;\n}\n\n/** GL helper: emit handle fill+stroke commands for one bounds entry. */\nexport function handleCommandsFor(\n ids: readonly string[],\n resolveBounds: (id: string) => Bounds | null,\n handles: ResolvedHandles,\n handlesOf: ((b: Bounds) => { x: number; y: number }[]) | undefined,\n layoutOpts: Partial<ResizeHandleLayout> | undefined,\n view: View,\n): DrawCommand[] {\n const layout: ResizeHandleLayout = { edges: true, hitRadiusPx: HANDLE_BASE_PX, ...layoutOpts };\n const out: DrawCommand[] = [];\n const half = handles.size / 2;\n const handleAlign = handles.outline.align ?? 'center';\n const handleWidth = resolveStrokeWidth(handles.outline.width ?? 1, 1);\n for (const id of ids) {\n const worldB = resolveBounds(id);\n if (!worldB) continue;\n // Framed in world, then projected: turning the projected point instead\n // lands somewhere else under non-uniform zoom.\n const xf = targetTransformOf(worldB);\n const t = viewToTransform(view);\n const angle = screenAngleOf(angleOf(xf), view.scale);\n const at = handlesOf\n ? handlesOf(worldB)\n : resizeHandlesFor(layout, worldB, xf, view.scale).map((c) => handlePoint(worldB, c));\n for (const hLocal of at) {\n const hWorld = localToWorld(xf, hLocal.x, hLocal.y);\n const [hsx, hsy] = worldToScreen(hWorld.x, hWorld.y, t);\n const center = { x: hsx, y: hsy };\n const baseRect = {\n x: center.x - half,\n y: center.y - half,\n width: handles.size,\n height: handles.size,\n };\n const sr = alignedStrokeRect(baseRect, handleAlign, handleWidth);\n const fillCmd: DrawCommand = {\n kind: 'path',\n path: rectPathFor(baseRect.x, baseRect.y, baseRect.width, baseRect.height),\n fill: handles.fill,\n };\n const strokeCmd: DrawCommand = {\n kind: 'path',\n path: rectPathFor(sr.x, sr.y, sr.width, sr.height),\n stroke: handles.outline,\n };\n if (angle === 0) {\n out.push(fillCmd, strokeCmd);\n } else {\n out.push({\n kind: 'group',\n transform: rotateAroundMat3(center.x, center.y, angle),\n children: [fillCmd, strokeCmd],\n });\n }\n }\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;AAqBA,IAAM,sBAAmC,EAAE,OAAO;CAAE,MAAM;CAAS,OAAO;AAAU,EAAE;AACtF,IAAM,yBAAwC;CAC5C,OAAO;EAAE,MAAM;EAAS,OAAO;CAAU;CACzC,OAAO;AACT;AACA,IAAM,sBAAsB;AAgB5B,SAAgB,eAAe,MAA8C;CAC3E,OAAO;EACL,MAAM,MAAM,QAAQ;EACpB,MAAM,MAAM,QAAQ;EACpB,SAAS,MAAM,WAAW;CAC5B;AACF;;AAGA,SAAgB,YAAY,GAAW,GAAW,OAAe,QAAuF;CACtJ,OAAO;EAAE,MAAM;EAAQ;EAAG;EAAG;EAAO;CAAO;AAC7C;;;;;AAMA,SAAS,iBAAiB,IAAY,IAAY,OAAuB;CACvE,MAAM,IAAI,KAAK,IAAI,KAAK;CACxB,MAAM,IAAI,KAAK,IAAI,KAAK;CAGxB,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI;CAC7B,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI;CAC7B,OAAO,IAAI,aAAa;EACtB;EAAG;EAAG;EACN,CAAC;EAAG;EAAG;EACP;EAAI;EAAI;CACV,CAAC;AACH;;AAGA,SAAgB,kBACd,KACA,eACA,SACA,WACA,YACA,MACe;CACf,MAAM,SAA6B;EAAE,OAAO;EAAM,aAAa;EAAgB,GAAG;CAAW;CAC7F,MAAM,MAAqB,CAAC;CAC5B,MAAM,OAAO,QAAQ,OAAO;CAC5B,MAAM,cAAc,QAAQ,QAAQ,SAAS;CAC7C,MAAM,cAAc,mBAAmB,QAAQ,QAAQ,SAAS,GAAG,CAAC;CACpE,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,SAAS,cAAc,EAAE;EAC/B,IAAI,CAAC,QAAQ;EAGb,MAAM,KAAK,kBAAkB,MAAM;EACnC,MAAM,IAAI,gBAAgB,IAAI;EAC9B,MAAM,QAAQ,cAAc,QAAQ,EAAE,GAAG,KAAK,KAAK;EACnD,MAAM,KAAK,YACP,UAAU,MAAM,IAChB,iBAAiB,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC,CAAC,KAAK,MAAM,YAAY,QAAQ,CAAC,CAAC;EACtF,KAAK,MAAM,UAAU,IAAI;GACvB,MAAM,SAAS,aAAa,IAAI,OAAO,GAAG,OAAO,CAAC;GAClD,MAAM,CAAC,KAAK,OAAO,cAAc,OAAO,GAAG,OAAO,GAAG,CAAC;GACtD,MAAM,SAAS;IAAE,GAAG;IAAK,GAAG;GAAI;GAChC,MAAM,WAAW;IACf,GAAG,OAAO,IAAI;IACd,GAAG,OAAO,IAAI;IACd,OAAO,QAAQ;IACf,QAAQ,QAAQ;GAClB;GACA,MAAM,KAAK,kBAAkB,UAAU,aAAa,WAAW;GAC/D,MAAM,UAAuB;IAC3B,MAAM;IACN,MAAM,YAAY,SAAS,GAAG,SAAS,GAAG,SAAS,OAAO,SAAS,MAAM;IACzE,MAAM,QAAQ;GAChB;GACA,MAAM,YAAyB;IAC7B,MAAM;IACN,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,MAAM;IACjD,QAAQ,QAAQ;GAClB;GACA,IAAI,UAAU,GACZ,IAAI,KAAK,SAAS,SAAS;QAE3B,IAAI,KAAK;IACP,MAAM;IACN,WAAW,iBAAiB,OAAO,GAAG,OAAO,GAAG,KAAK;IACrD,UAAU,CAAC,SAAS,SAAS;GAC/B,CAAC;EAEL;CACF;CACA,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.js","names":[],"sources":["../../../src/features/selection/overlay.ts"],"sourcesContent":["/**\n * Selection overlay primitives — render layers for drawing selection\n * outlines and resize handles.\n *\n * **Pose shape:** TPose is generic; callers pass `poseDescriptor` for poses\n * `AUTO_POSE_DESCRIPTOR` can't read. Container ids reduce via `unionAABB`\n * over the projected AABBs.\n */\n\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { mat3, type GlMat3 } from '@weasel-js/renderer';\nimport type { NodeId } from 'core/scene/types';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { DrawnBoundsScene } from 'canvas/drawnBounds';\nimport { containerAwareBoundsResolver } from './selectionPose';\nimport { targetCorners, targetTransformOf, type TargetTransform } from 'core/geometry/targetFrame';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { alignedStrokeRect, type CommandStroke } from '@weasel-js/paint';\nimport { resolveStrokeWidth } from '@weasel-js/stroke';\nimport {\n rotationHandle,\n DEFAULT_ROTATION_HANDLE_DISTANCE,\n} from 'interactions/actions/rotate/handle';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { ChromeState } from 'core/selection/chromeState';\nimport { MULTI_RESIZE_TARGET_ID } from 'core/selection/selectionTarget';\nimport type { View } from 'core/viewport/view';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport { PATH_L, PATH_M, PATH_Z, type PolygonPath } from '@weasel-js/geom';\nimport {\n handleCommandsFor, rectPathFor, resolveHandles, type ResizeHandleLayout, type ResolvedHandles, type SelectionHandleStyle,\n} from './handleCommands';\n\nexport type { SelectionHandleStyle };\n\nexport { composeSelectionPose, type ComposeSelectionPoseOpts } from './selectionPose';\n\n/** Project world AABB into screen-space AABB using the active view. */\nfunction projectBounds<B extends Bounds>(b: B, view: View): B {\n const t = viewToTransform(view);\n const [sx, sy] = worldToScreen(b.x, b.y, t);\n return { ...b, x: sx, y: sy, width: b.width * view.scale.x, height: b.height * view.scale.y };\n}\n\n\n/** Options every selection layer shares. `getSelection` and `getPose` are\n * declared on {@link SelectionOverlayLayerOpts}, which makes both optional —\n * omitted, they come off the draw envelope. */\ninterface SelectionLayerCommon<TPose> {\n /** How to read poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n /** Walk a container's direct children (e.g. `scene.childrenOf`). When\n * supplied with `isContainer`, any id that resolves to a container is\n * rendered using the union bounds of all its transitive leaves. */\n getChildren?: (id: string) => readonly string[];\n /** True when `id` is a structural container. */\n isContainer?: (id: string) => boolean;\n /** The nodes behind the ids `getPose` answers for. With it, a stroked\n * node's chrome sits on its stroke (`drawnBoundsOf`); without, on its\n * pose box. Ignored when bounds come off the draw envelope. */\n scene?: DrawnBoundsScene;\n}\n\n/** Options for `createSelectionOutlineLayer`. The overlay layer's options\n * minus the handle visuals — the two run the same body. */\nexport type SelectionOutlineLayerOpts<TPose> =\n Omit<SelectionOverlayLayerOpts<TPose>, 'handles' | 'handlesOf' | 'resizeHandles' | 'rotationHandle'>;\n\n/** Options for `createSelectionHandlesLayer`. The overlay layer's options\n * minus the outline stroke — the two run the same body. */\nexport type SelectionHandlesLayerOpts<TPose> =\n Omit<SelectionOverlayLayerOpts<TPose>, 'outline'>;\n\n/** Options for `createSelectionOverlayLayer`. */\nexport interface SelectionOverlayLayerOpts<TPose>\n extends SelectionLayerCommon<TPose> {\n /**\n * Which ids to draw chrome for. Omit to take them from the `ChromeState` on\n * the draw envelope, which is what makes one canvas's several views each\n * outline their own selection — the layer is shared, the envelope is not.\n */\n getSelection?: () => readonly NodeId[];\n /**\n * Resolve an id to the pose to draw chrome around. Return null to skip an\n * id. Omit to take bounds from the same envelope `getSelection` omitted\n * takes ids from.\n *\n * Takes `string` rather than `NodeId` because the container-aware resolver\n * walks expanded leaf ids via `getChildren`, which is generic over strings.\n */\n getPose?: (id: string) => TPose | null;\n outline?: CommandStroke & { pad?: number };\n /** Pass `false` to render outlines only. */\n handles?: SelectionHandleStyle | false;\n /** Where to paint handles, in the bounds' own frame. Replaces the kit's\n * layout, `resizeHandles` included. */\n handlesOf?: (bounds: Bounds) => { x: number; y: number }[];\n /** The layout the resize affordance hits by: its `hitRadiusPx`, never\n * `handles.size`, decides which edge handles are painted. Default: edges on,\n * at the kit's default hit radius. */\n resizeHandles?: Partial<ResizeHandleLayout>;\n /** Draw a rotate badge above the selection's top edge. `true` places it at\n * the default offset; `distance` sets the offset in screen pixels. Off by\n * default. */\n rotationHandle?:\n | boolean\n | {\n distance?: number;\n };\n /** Optional separate id list for the per-item outline pass, for a\n * multi-selection whose handle pass works against a synthetic union id\n * (e.g. `MULTI_RESIZE_TARGET_ID`) while each real member is outlined.\n * When omitted, the outline pass uses the handle pass's ids. */\n getOutlineIds?: () => readonly NodeId[];\n /** Optional set of ids to suppress entirely — both outline and handles\n * are skipped. */\n getSuppressedIds?: () => ReadonlySet<string>;\n}\n\nconst DEFAULT_OUTLINE: Required<Pick<CommandStroke, 'paint' | 'width'>> & { pad: number } = {\n paint: { kind: 'solid', color: '#f0e0a8' },\n width: 2,\n pad: 1,\n};\nfunction resolveOutlineStroke(opts?: CommandStroke & { pad?: number }): {\n stroke: CommandStroke;\n pad: number;\n} {\n if (!opts) {\n return {\n stroke: { paint: DEFAULT_OUTLINE.paint, width: DEFAULT_OUTLINE.width },\n pad: DEFAULT_OUTLINE.pad,\n };\n }\n return {\n stroke: {\n paint: opts.paint,\n width: opts.width ?? DEFAULT_OUTLINE.width,\n dash: opts.dash,\n cap: opts.cap,\n join: opts.join,\n align: opts.align,\n },\n pad: opts.pad ?? DEFAULT_OUTLINE.pad,\n };\n}\n\n/** GL helper: emit an outline command for one bounds entry. */\nfunction outlineCommandsFor(\n ids: readonly string[],\n resolveBounds: (id: string) => Bounds | null,\n stroke: CommandStroke,\n pad: number,\n view: View,\n): DrawCommand[] {\n const out: DrawCommand[] = [];\n const align = stroke.align ?? 'center';\n const width = resolveStrokeWidth(stroke.width ?? 1, 1);\n for (const id of ids) {\n const worldB = resolveBounds(id);\n if (!worldB) continue;\n const b = projectBounds(worldB, view);\n const padded = {\n x: b.x - pad,\n y: b.y - pad,\n width: b.width + pad * 2,\n height: b.height + pad * 2,\n };\n const xf = targetTransformOf(worldB);\n if (xf.identity) {\n const r = alignedStrokeRect(padded, align, width);\n out.push({ kind: 'path', path: rectPathFor(r.x, r.y, r.width, r.height), stroke });\n continue;\n }\n // Turned or skewed, the target lands on screen as a parallelogram, so the\n // outline is traced through its projected corners. `alignedStrokeRect`'s\n // shift is folded into the offset so the ribbon lands where it would.\n const shift = align === 'outer' ? width / 2 : align === 'inner' ? -width / 2 : 0;\n const q = screenCorners(worldB, xf, view, pad + shift);\n out.push({\n kind: 'path',\n path: {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),\n coords: new Float32Array([q[0].x, q[0].y, q[1].x, q[1].y, q[2].x, q[2].y, q[3].x, q[3].y]),\n fillRule: 'nonzero',\n },\n stroke: { ...stroke, align: 'center' },\n });\n }\n return out;\n}\n\n/**\n * A framed target's corners on screen, TL/TR/BR/BL, each pushed `pad` screen\n * pixels out from both edges that meet there.\n */\nfunction screenCorners(\n worldB: Bounds,\n xf: TargetTransform,\n view: View,\n pad: number,\n): { x: number; y: number }[] {\n const t = viewToTransform(view);\n const q = targetCorners(worldB, xf).map((w) => {\n const [x, y] = worldToScreen(w.x, w.y, t);\n return { x, y };\n });\n if (pad === 0) return q;\n const [ccx, ccy] = worldToScreen(worldB.x + worldB.width / 2, worldB.y + worldB.height / 2, t);\n const normal = (a: { x: number; y: number }, b: { x: number; y: number }) => {\n let nx = -(b.y - a.y), ny = b.x - a.x;\n const l = Math.hypot(nx, ny) || 1;\n nx /= l; ny /= l;\n const mx = (a.x + b.x) / 2 - ccx, my = (a.y + b.y) / 2 - ccy;\n return nx * mx + ny * my < 0 ? { x: -nx, y: -ny } : { x: nx, y: ny };\n };\n return q.map((c, i) => {\n const n1 = normal(q[(i + 3) % 4], c);\n const n2 = normal(c, q[(i + 1) % 4]);\n // The point `pad` off both edge lines.\n const k = pad / (1 + n1.x * n2.x + n1.y * n2.y);\n return { x: c.x + (n1.x + n2.x) * k, y: c.y + (n1.y + n2.y) * k };\n });\n}\n\n/** GL helper: emit a curved double-headed arrow, the rotate badge above the\n * rect. The arc opens *toward* the object (apex on top, mouth pointing\n * down), with an arrowhead at each endpoint pointing along the tangent. */\nfunction rotationHandleCommands(\n worldB: Bounds,\n handles: ResolvedHandles,\n distance: number,\n view: View,\n): DrawCommand[] {\n const hWorld = rotationHandle(worldB, distance, view.scale);\n const rotation = hWorld.angle;\n const t = viewToTransform(view);\n const [scx, scy] = worldToScreen(hWorld.cx, hWorld.cy, t);\n const size = handles.size;\n const arcRadius = size;\n const ARC_HALF_ANGLE = (Math.PI * 240) / 360 / 2; // 120°\n const N_SEGS = 18;\n const arcWidth = resolveStrokeWidth(handles.outline.width ?? 1, 1) * 1.5;\n const stroke: CommandStroke = {\n paint: handles.fill.paint,\n width: arcWidth,\n cap: 'round',\n join: 'round',\n };\n // The arc as a polyline in handle-local coords, centered on the handle\n // anchor: apex at -y, endpoints at +y near the rect side.\n const cmds: number[] = [PATH_M];\n const xs: number[] = [];\n const ys: number[] = [];\n for (let i = 0; i <= N_SEGS; i++) {\n const t = i / N_SEGS;\n const angle = ARC_HALF_ANGLE - t * ARC_HALF_ANGLE * 2;\n xs.push(arcRadius * Math.sin(angle));\n ys.push(-arcRadius * Math.cos(angle));\n if (i > 0) cmds.push(PATH_L);\n }\n const arcCoords: number[] = [];\n for (let i = 0; i < xs.length; i++) {\n arcCoords.push(xs[i], ys[i]);\n }\n const arcPath: PolygonPath = {\n kind: 'polygon',\n commands: new Uint8Array(cmds),\n coords: new Float32Array(arcCoords),\n fillRule: 'nonzero',\n };\n // Arrowheads: small filled triangles at each endpoint, pointing along\n // the tangent (away from the arc body).\n const headLen = size * 0.6;\n const headWidth = size * 0.4;\n const arrowHead = (endX: number, endY: number, tx: number, ty: number): PolygonPath => {\n // Normalize tangent.\n const len = Math.hypot(tx, ty) || 1;\n const ux = tx / len;\n const uy = ty / len;\n // Perpendicular (rotate tangent 90° CCW).\n const px = -uy;\n const py = ux;\n // Triangle: tip is endX+ux*headLen, base is endX (with +/- headWidth/2 along p).\n const tipX = endX + ux * headLen;\n const tipY = endY + uy * headLen;\n const baseLeftX = endX + px * (headWidth / 2);\n const baseLeftY = endY + py * (headWidth / 2);\n const baseRightX = endX - px * (headWidth / 2);\n const baseRightY = endY - py * (headWidth / 2);\n return {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L]),\n coords: new Float32Array([tipX, tipY, baseLeftX, baseLeftY, baseRightX, baseRightY, tipX, tipY]),\n fillRule: 'nonzero',\n };\n };\n // Endpoint A (right side, angle = +ARC_HALF_ANGLE). Arc point:\n // (sin(a)·R, -cos(a)·R). Tangent (d/da) = (cos(a)·R, sin(a)·R).\n const angleA = ARC_HALF_ANGLE;\n const endAX = arcRadius * Math.sin(angleA);\n const endAY = -arcRadius * Math.cos(angleA);\n const tangentAX = Math.cos(angleA);\n const tangentAY = Math.sin(angleA);\n // Endpoint B (left side, angle = -ARC_HALF_ANGLE): tangent in the\n // outward direction is the negation of the above.\n const angleB = -ARC_HALF_ANGLE;\n const endBX = arcRadius * Math.sin(angleB);\n const endBY = -arcRadius * Math.cos(angleB);\n const tangentBX = -Math.cos(angleB);\n const tangentBY = -Math.sin(angleB);\n const headA = arrowHead(endAX, endAY, tangentAX, tangentAY);\n const headB = arrowHead(endBX, endBY, tangentBX, tangentBY);\n // Build transform: translate(scx, scy) [* rotate(rotation)].\n let transform = mat3.translated(mat3.identity(), scx, scy);\n if (rotation !== 0) {\n const c = Math.cos(rotation);\n const s = Math.sin(rotation);\n const rot = new Float32Array([c, s, 0, -s, c, 0, 0, 0, 1]) as GlMat3;\n transform = mat3.multiply(transform, rot);\n }\n return [\n {\n kind: 'group',\n transform,\n children: [\n { kind: 'path', path: arcPath, stroke },\n { kind: 'path', path: headA, fill: handles.fill },\n { kind: 'path', path: headB, fill: handles.fill },\n ],\n },\n ];\n}\n\n/** Which passes a selection layer runs. The three exported factories differ\n * only in this — everything else they do is shared, so a fix to one is a fix\n * to all three. */\ninterface SelectionPasses {\n readonly id: string;\n readonly label: string;\n readonly outline: boolean;\n readonly handles: boolean;\n}\n\nfunction buildSelectionLayer<TPose>(\n opts: SelectionOverlayLayerOpts<TPose>,\n passes: SelectionPasses,\n): RenderLayer<unknown> {\n const { stroke, pad } = resolveOutlineStroke(opts.outline);\n const handlesEnabled = passes.handles && opts.handles !== false;\n const handles = handlesEnabled ? resolveHandles(opts.handles || undefined) : null;\n const { handlesOf, resizeHandles } = opts;\n const d = (opts.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n const getPose = opts.getPose;\n const poseBounds = getPose\n ? containerAwareBoundsResolver(getPose, d, opts.getChildren, opts.isContainer, opts.scene)\n : null;\n const rotationHandleDistance = passes.handles\n ? rotationBadgeOf(opts)?.distancePx ?? null\n : null;\n\n return {\n id: passes.id,\n label: passes.label,\n space: 'screen',\n draw: (data, view) => {\n // The synthetic multi-resize id resolves to `ChromeState.unionBounds`,\n // the same value the affordance hit-tester reads, so painted chrome\n // and its hit region stay in agreement. With no chromeState on the\n // draw envelope, fall through to the supplied resolver.\n const chromeState = asChromeState(data);\n const allIds = opts.getSelection\n ? opts.getSelection()\n : chromeSelectionIds(chromeState);\n if (allIds.length === 0) return [];\n const suppressed = opts.getSuppressedIds?.();\n const ids = suppressed && suppressed.size > 0\n ? allIds.filter((id) => !suppressed.has(id as unknown as string))\n : allIds;\n if (ids.length === 0) return [];\n // Chrome-caps: gate each pass on its canonical id. When no\n // `getIsVisible` is wired, every pass runs.\n const isVisible = asIsVisible(data);\n const showOutline = passes.outline && (isVisible ? isVisible('selection.outline') : true);\n const showHandles = isVisible ? isVisible('selection.resize-handles') : true;\n const showRotation = isVisible ? isVisible('selection.rotation-handle') : true;\n\n const resolveBounds = poseBounds\n ? (id: string) => poseBounds(id, view.scale)\n : (id: string) => chromeState?.boundsOf(id) ?? null;\n const resolveTargetBounds = (id: string): Bounds | null => {\n if (id === MULTI_RESIZE_TARGET_ID && chromeState?.unionBounds != null) {\n return chromeState.unionBounds;\n }\n return resolveBounds(id);\n };\n\n const out: DrawCommand[] = [];\n if (showOutline) {\n const outlineIdsRaw = opts.getOutlineIds\n ? opts.getOutlineIds()\n : (opts.getSelection ? ids : (chromeState?.selection ?? ids));\n const outlineIds = suppressed && suppressed.size > 0\n ? outlineIdsRaw.filter((id) => !suppressed.has(id as unknown as string))\n : outlineIdsRaw;\n for (const cmd of outlineCommandsFor(outlineIds, resolveTargetBounds, stroke, pad, view)) out.push(cmd);\n }\n if (!handles) return out;\n if (showHandles) {\n for (const cmd of handleCommandsFor(ids, resolveTargetBounds, handles, handlesOf, resizeHandles, view)) out.push(cmd);\n }\n if (showRotation && rotationHandleDistance !== null) {\n for (const id of ids) {\n const b = resolveTargetBounds(id);\n if (!b) continue;\n for (const cmd of rotationHandleCommands(b, handles, rotationHandleDistance, view)) {\n out.push(cmd);\n }\n }\n }\n return out;\n },\n };\n}\n\n/** Where the overlay paints its rotate badge and how big it is, in screen\n * pixels, or `null` when it paints none. The badge's grab region and its\n * slops halo read this too, so all three agree on it. */\nexport function rotationBadgeOf(\n opts: Pick<SelectionOverlayLayerOpts<unknown>, 'handles' | 'rotationHandle'>,\n): { distancePx: number; sizePx: number } | null {\n const r = opts.rotationHandle;\n if (!r || opts.handles === false) return null;\n return {\n distancePx: r === true ? DEFAULT_ROTATION_HANDLE_DISTANCE : r.distance ?? DEFAULT_ROTATION_HANDLE_DISTANCE,\n sizePx: resolveHandles(opts.handles || undefined).size,\n };\n}\n/**\n * `RenderLayer` that draws selection outlines only. Stack alongside\n * `createSelectionHandlesLayer` (or use `createSelectionOverlayLayer`, which\n * runs both passes in one layer) when both are wanted.\n */\nexport function createSelectionOutlineLayer<TPose>(\n opts: SelectionOutlineLayerOpts<TPose>,\n): RenderLayer<unknown> {\n return buildSelectionLayer(opts, {\n id: 'selection-outline',\n label: 'Selection outline',\n outline: true,\n handles: false,\n });\n}\n\n/**\n * `RenderLayer` that draws selection handles only. Stack on top of\n * `createSelectionOutlineLayer` (handles render on top of the outline).\n */\nexport function createSelectionHandlesLayer<TPose>(\n opts: SelectionHandlesLayerOpts<TPose>,\n): RenderLayer<unknown> {\n return buildSelectionLayer(opts, {\n id: 'selection-handles',\n label: 'Selection handles',\n outline: false,\n handles: true,\n });\n}\n\n/**\n * Draws outlines then handles in a single layer. Exactly equivalent to\n * stacking `createSelectionOutlineLayer` and `createSelectionHandlesLayer` —\n * all three run the same body. Pass `handles: false` to render outlines only.\n */\nexport function createSelectionOverlayLayer<TPose>(\n opts: SelectionOverlayLayerOpts<TPose>,\n): RenderLayer<unknown> {\n return buildSelectionLayer(opts, {\n id: 'selection-overlay',\n label: 'Selection',\n outline: true,\n handles: true,\n });\n}\n\n/** Pull a chrome-caps visibility predicate off the draw `data` envelope.\n * Returns `null` when the caller passed bare data (no `getIsVisible`\n * thunk) — the layer then renders unconditionally. */\nfunction asIsVisible(data: unknown): ((id: string) => boolean) | null {\n const maybe = data as { getIsVisible?: () => (id: string) => boolean };\n if (typeof maybe?.getIsVisible === 'function') return maybe.getIsVisible();\n return null;\n}\n\n/** The ids the handle pass works against when the layer takes its selection\n * from the envelope: the synthetic union id for a multi-selection, the\n * selection itself otherwise. */\nfunction chromeSelectionIds(state: ChromeState | null): readonly NodeId[] {\n if (!state) return [];\n if (state.multiActive) return [MULTI_RESIZE_TARGET_ID as NodeId];\n return state.selection;\n}\n\n/** Pull live `ChromeState` off the draw `data` envelope. Returns `null` for\n * callers that pass plain data with no `getChromeState` thunk — the layer\n * then resolves the multi-union via the supplied `getPose` resolver. */\nfunction asChromeState(data: unknown): ChromeState | null {\n const maybe = data as { getChromeState?: () => ChromeState };\n if (typeof maybe?.getChromeState === 'function') return maybe.getChromeState();\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"selectionPose.js","names":[],"sources":["../../../src/features/selection/selectionPose.ts"],"sourcesContent":["/**\n * The pose and box a selection's chrome is drawn around: precedence between\n * the in-flight overlays and the stored pose, a container's union over its\n * leaves, and the drawn box a scene supplies.\n */\nimport type { Scale2 } from '@weasel-js/geom';\nimport { drawnAABBOf, drawnBoundsOf, type DrawnBoundsScene } from 'canvas/drawnBounds';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { framedBounds, visualBoundsViaDescriptor, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport type { NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\n\n/**\n * Transitive leaf ids under `id`. A childless node is a leaf only if it is not\n * itself a container: an empty container contributes no bounds, and its own\n * stored pose is at its most stale when no children are left to have moved it.\n */\nfunction leavesOf(\n id: string,\n getChildren: (id: string) => readonly string[],\n isContainer: (id: string) => boolean,\n): string[] {\n if (!isContainer(id)) return [id];\n const out: string[] = [];\n const visit = (nid: string) => {\n const kids = getChildren(nid);\n if (kids.length === 0) { if (!isContainer(nid)) out.push(nid); return; }\n for (const k of kids) visit(k);\n };\n visit(id);\n return out;\n}\n\n/** How one id's pose becomes a box: the pose box, or with a scene the box the\n * node is drawn with. Each id is read as a leaf — the pose already says where\n * a container is, so its members' stored poses are not consulted again. */\nfunction boxReader<TPose>(\n d: PoseDescriptor<TPose>,\n scene: DrawnBoundsScene | undefined,\n): { framed(id: string, pose: TPose, scale?: number | Scale2): Bounds; aabb(id: string, pose: TPose, scale?: number | Scale2): Bounds } {\n if (!scene) {\n return {\n framed: (_id, pose) => framedBounds(pose, d),\n aabb: (_id, pose) => visualBoundsViaDescriptor(pose, d),\n };\n }\n const leaves: DrawnBoundsScene = {\n get: (id) => scene.get(id),\n ...(scene.derivedPathOf ? { derivedPathOf: (id: NodeId) => scene.derivedPathOf!(id) } : {}),\n };\n const opts = (scale: number | Scale2 | undefined) => ({\n descriptor: d as PoseDescriptor<unknown>,\n ...(scale !== undefined ? { scale } : {}),\n });\n return {\n framed: (id, pose, scale) => drawnBoundsOf(leaves, id as NodeId, pose, opts(scale)),\n aabb: (id, pose, scale) => drawnAABBOf(leaves, id as NodeId, pose, opts(scale)),\n };\n}\n\n/** Options for `composeSelectionPose`. */\nexport interface ComposeSelectionPoseOpts<TPose> {\n /** Move overlay; when present its `poses` map wins over everything else. */\n moveOverlay?: { poses: Map<string, TPose> } | null;\n /**\n * Resize overlay; consulted only when move overlay does not own the id.\n * For container resize, `leafPoses` (when present) maps each leaf id under\n * the container to its overlay pose. If absent the container falls back to\n * stored leaf poses.\n */\n resizeOverlay?: {\n id: string;\n currentPose: TPose;\n leafPoses?: Map<string, TPose>;\n } | null;\n /** Fallback pose lookup (typically the stored/committed pose). */\n getStoredPose: (id: string) => TPose;\n /** How to read poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n /** Walk a container's direct children (e.g. `scene.childrenOf`). With\n * `isContainer`, a selected container resolves to the union AABB of its\n * transitive leaf poses instead of its own stored pose. */\n getChildren?: (id: string) => readonly string[];\n /** True when `id` is a structural container. */\n isContainer?: (id: string) => boolean;\n /** The nodes behind the ids. With it, a container's union takes each leaf\n * as drawn, stroke included (`drawnBoundsOf`); without, its pose box. */\n scene?: DrawnBoundsScene;\n /** View scale a screen-pixel stroke is resolved at. Default 1. */\n viewScale?: () => number | Scale2;\n}\n\n/**\n * Build a pose resolver for a selection. Precedence per id:\n * move overlay > resize overlay > stored. When `getChildren`/`isContainer`\n * are supplied and the id resolves to a container, the resolver returns the\n * union AABB of all transitive leaf poses (each leaf still subject to the\n * precedence rules). Containers with no leaves resolve to `null`.\n */\nexport function composeSelectionPose<TPose>(\n opts: ComposeSelectionPoseOpts<TPose>,\n): (id: string) => TPose | null {\n const { moveOverlay, resizeOverlay, getStoredPose, getChildren, isContainer } = opts;\n const d = (opts.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n const boxes = boxReader(d, opts.scene);\n\n const selectionLeaves = (id: string): string[] =>\n getChildren && isContainer ? leavesOf(id, getChildren, isContainer) : [id];\n\n const resolveLeaf = (id: string): TPose => {\n const moved = moveOverlay?.poses.get(id);\n if (moved !== undefined) return moved;\n if (resizeOverlay && resizeOverlay.id === id) return resizeOverlay.currentPose;\n return getStoredPose(id);\n };\n\n return (id: string): TPose | null => {\n if (isContainer?.(id)) {\n const leaves = selectionLeaves(id);\n if (leaves.length === 0) return null;\n const containerResizeLeafPoses =\n resizeOverlay && resizeOverlay.id === id ? resizeOverlay.leafPoses : undefined;\n const scale = opts.viewScale?.();\n const leafBounds: Bounds[] = [];\n for (const leafId of leaves) {\n const pose = moveOverlay?.poses.get(leafId)\n ?? containerResizeLeafPoses?.get(leafId)\n ?? getStoredPose(leafId);\n leafBounds.push(boxes.aabb(leafId, pose, scale));\n }\n const u = unionAABB(leafBounds);\n if (u === null) return null;\n return d.fromBounds(u, getStoredPose(leaves[0]!));\n }\n return resolveLeaf(id);\n };\n}\n\n/**\n * Build a bounds resolver that handles container ids by computing the union\n * AABB of every leaf's bounds. Non-container ids pass through directly. When\n * `getChildren`/`isContainer` are omitted, every id is treated as a leaf.\n * With a scene, each leaf is boxed as drawn at the view's scale.\n */\nexport function containerAwareBoundsResolver<TPose>(\n getPose: (id: string) => TPose | null,\n d: PoseDescriptor<TPose>,\n getChildren: ((id: string) => readonly string[]) | undefined,\n isContainer: ((id: string) => boolean) | undefined,\n scene: DrawnBoundsScene | undefined,\n): (id: string, scale: Scale2) => Bounds | null {\n const boxes = boxReader(d, scene);\n const leaf = (id: string, scale: Scale2): Bounds | null => {\n const p = getPose(id);\n return p === null ? null : boxes.framed(id, p, scale);\n };\n if (getChildren === undefined || isContainer === undefined) return leaf;\n return (id, scale) => {\n if (!isContainer(id)) return leaf(id, scale);\n const leaves = leavesOf(id, getChildren, isContainer);\n if (leaves.length === 0) return null;\n const leafBounds: Bounds[] = [];\n for (const leafId of leaves) {\n const p = getPose(leafId);\n if (p !== null) leafBounds.push(boxes.aabb(leafId, p, scale));\n }\n if (leafBounds.length === 0) return null;\n return unionAABB(leafBounds);\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"angle2d.js","names":[],"sources":["../../../src/features/snap/angle2d.ts"],"sourcesContent":["/** A snap session's one-dimensional path: an angle about a pivot, snapped by\n * the same resolver as a point, with reach carried through the radius. */\nimport { resolveSnap, type SnapCandidate } from '@weasel-js/geom/nd';\nimport type { SnapMods } from 'interactions/actions/snapDep';\nimport { distinct, markFromPlane } from './marks';\nimport { screenOf } from './screen';\nimport { stepAngles } from './stepAngles';\nimport type { Session2dInputs } from './session2d';\nimport type { AngleCollect, SnapMark, SnapMarks, SnapSource } from './types';\n\nconst TURN = Math.PI * 2;\nconst PROBE = 'angle';\n/** A press near the pivot turns px reach into a wide angle; it reaches no\n * further than this. */\nconst MAX_REACH_RAD = Math.PI / 6;\n\n/** Where `theta` lands, and what it met. Writes the marks to the store. */\nexport function angleAt(\n inputs: Session2dInputs, sources: readonly SnapSource[], theta: number, mods: SnapMods,\n): { theta: number; marks: readonly SnapMark[] } {\n const { req, store } = inputs;\n // Screen px per radian, or 1 when the gesture gives its reach in radians.\n const scale = req.reachRad !== undefined ? 1 : pressRadiusPx(inputs);\n const reachOf = (s: SnapSource) => req.reachRad\n ?? Math.min(s.reachPx ?? inputs.reachPx, MAX_REACH_RAD * scale);\n const candidates = collectAngles(inputs, sources, theta, mods, reachOf);\n if (candidates.length === 0) {\n store.set([]);\n return { theta, marks: [] };\n }\n const res = resolveSnap({\n probes: [{ id: PROBE, at: [0], role: 'pointer' }],\n input: { at: [0], dirs: [[1]] },\n delta0: [theta],\n candidates,\n // No radius, no reach: only a required angle can win.\n toScreen: ([t]) => [scale > 0 ? t * scale : NaN],\n });\n const out = res.delta[0];\n const marks = distinct(res.satisfied.flatMap((c) => {\n const m = c.mark ?? rayMarks(inputs, out);\n return 'kind' in m ? [m] : m;\n }));\n const plane = req.plane?.() ?? null;\n store.set(plane ? marks.map((m) => markFromPlane(m, plane)) : marks);\n return { theta: out, marks };\n}\n\nfunction collectAngles(\n inputs: Session2dInputs, sources: readonly SnapSource[], theta: number, mods: SnapMods,\n reachOf: (s: SnapSource) => number,\n): SnapCandidate<SnapMarks>[] {\n const { req } = inputs;\n const out: SnapCandidate<SnapMarks>[] = [];\n const c = req.constrain;\n if (mods.constrain && typeof c === 'object' && c.step > 0) {\n for (const at of stepAngles(theta, c.step)) {\n out.push({ probe: PROBE, locus: { at: [at], dirs: [] }, source: 'constrain', order: -1, tier: 'target', reachPx: 0, required: true });\n }\n }\n const theta0 = req.theta0 ?? 0;\n if (mods.hold) {\n const at = theta0 + TURN * Math.round((theta - theta0) / TURN);\n out.push({ probe: PROBE, locus: { at: [at], dirs: [] }, source: 'hold', order: -1, tier: 'target', reachPx: 0, required: true, mark: [] });\n }\n if (mods.suspend) return out;\n const q: AngleCollect = {\n pivot: req.pivot ?? req.press,\n press: req.press,\n theta0,\n theta,\n ids: req.ids ?? [],\n exclude: inputs.exclude ?? req.ids ?? [],\n mods,\n ...(req.also ? { also: req.also } : {}),\n ...(inputs.scene ? { scene: inputs.scene } : {}),\n ...(inputs.rotationOf ? { rotationOf: inputs.rotationOf } : {}),\n };\n // Runs across sources and their yields: an earlier source, then an earlier\n // yield, wins a tie.\n let order = 0;\n sources.forEach((source) => {\n if (!source.angles) return;\n const reachPx = reachOf(source);\n for (const a of source.angles(q)) {\n // The whole turn of the angle nearest `theta`: 0 catches 359°.\n const at = a.angle + TURN * Math.round((theta - a.angle) / TURN);\n out.push({\n probe: PROBE, locus: { at: [at], dirs: [] }, source: source.id, order: order++, tier: source.tier ?? 'target', reachPx,\n ...(a.required ? { required: true } : {}),\n ...(a.mark ? { mark: a.mark } : {}),\n });\n }\n });\n return out;\n}\n\n/** The screen distance from the pivot to the press: what turns reach in px\n * into radians. */\nfunction pressRadiusPx(inputs: Session2dInputs): number {\n const { req } = inputs;\n const pivot = req.pivot ?? req.press;\n const toScreen = screenOf(inputs.view(), req.plane?.() ?? null);\n const [ax, ay] = toScreen([pivot.x, pivot.y]);\n const [bx, by] = toScreen([req.press.x, req.press.y]);\n return Math.hypot(bx - ax, by - ay);\n}\n\n/** A ray from the pivot out to the press radius, along where the pointer\n * lands at `theta`, labeled with `theta` in degrees. */\nfunction rayMarks(inputs: Session2dInputs, theta: number): SnapMark[] {\n const { req } = inputs;\n const pivot = req.pivot ?? req.press;\n const r = Math.hypot(req.press.x - pivot.x, req.press.y - pivot.y);\n const dir = Math.atan2(req.press.y - pivot.y, req.press.x - pivot.x) + theta - (req.theta0 ?? 0);\n const end = { x: pivot.x + r * Math.cos(dir), y: pivot.y + r * Math.sin(dir) };\n const deg = ((Math.round(theta * 180 / Math.PI) % 360) + 360) % 360;\n return [\n { kind: 'line', from: pivot, to: end },\n { kind: 'point', at: end, glyph: 'quadrant', label: `${deg}°` },\n ];\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debugCandidates.js","names":[],"sources":["../../../src/features/snap/debugCandidates.ts"],"sourcesContent":["import { closestOnFlat, type SnapCandidate } from '@weasel-js/geom/nd';\nimport type { DebugSink } from '@weasel-js/routing';\nimport { fromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { SnapProbeAt } from './types';\n\ntype Point = { x: number; y: number };\n\n/** Replaces the sink's snap records with one frame's candidates: where each\n * would put its probe, accepted when the result meets it. Camera world. */\nexport function recordSnapCandidates(\n debug: DebugSink,\n candidates: readonly SnapCandidate<unknown>[],\n satisfied: readonly SnapCandidate<unknown>[],\n probes: readonly SnapProbeAt[],\n delta0: Point,\n plane: PlaneMap | null,\n): void {\n debug.clearSnap();\n const met = new Set(satisfied);\n const at = new Map(probes.map((q) => [q.id, [q.at.x + delta0.x, q.at.y + delta0.y]]));\n for (const c of candidates) {\n const p = at.get(c.probe);\n if (!p) continue;\n const [x, y] = closestOnFlat(c.locus, p);\n debug.recordSnapCandidate(plane ? fromPlane(plane, { x, y }) : { x, y }, met.has(c));\n }\n}\n"],"mappings":";;;;;;AASA,SAAgB,qBACd,OACA,YACA,WACA,QACA,QACA,OACM;CACN,MAAM,UAAU;CAChB,MAAM,MAAM,IAAI,IAAI,SAAS;CAC7B,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,OAAO,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,KAAK,YAAY;EAC1B,MAAM,IAAI,GAAG,IAAI,EAAE,KAAK;EACxB,IAAI,CAAC,GAAG;EACR,MAAM,CAAC,GAAG,KAAK,cAAc,EAAE,OAAO,CAAC;EACvC,MAAM,oBAAoB,QAAQ,UAAU,OAAO;GAAE;GAAG;EAAE,CAAC,IAAI;GAAE;GAAG;EAAE,GAAG,IAAI,IAAI,CAAC,CAAC;CACrF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gather.js","names":[],"sources":["../../../src/features/snap/gather.ts"],"sourcesContent":["import type { SnapCandidate, Vec } from '@weasel-js/geom/nd';\nimport { distinct } from './marks';\nimport type { SnapCollect, SnapGesture, SnapMark, SnapProbeAt, SnapSource, SourceCandidate } from './types';\n\nconst DEFAULT_GESTURES: readonly SnapGesture[] = ['point', 'move', 'resize'];\n\n/** The sources that answer `gesture`: those naming it, and, for one naming none, point, move and resize. */\nexport function snapSourcesFor<S extends Pick<SnapSource<unknown>, 'gestures'>>(sources: readonly S[], gesture: SnapGesture): S[] {\n return sources.filter((s) => (s.gestures ?? DEFAULT_GESTURES).includes(gesture));\n}\n\n/** Throws if two of `sources` share an id: one would silently replace the other. */\nexport function assertSnapSourceIds(sources: readonly Pick<SnapSource<unknown>, 'id'>[]): void {\n const seen = new Set<string>();\n for (const { id } of sources) {\n if (seen.has(id)) {\n throw new Error(`[snap] two snap sources share the id '${id}'. Give one its own id, or drop one.`);\n }\n seen.add(id);\n }\n}\n\n/** What a candidate draws when the result meets it. */\nexport type SnapCandidateMark<P> = NonNullable<SourceCandidate<P>['mark']>;\n\n/** How near `origin` a point target counts as on it, relative to its magnitude. */\nconst ORIGIN_EPS = 1e-9;\n\n/** How `gatherSnapCandidates` reads a host's points. */\nexport interface GatherSnapOptions<P> {\n /** `p` as a resolver tuple. */\n toVec(p: P): Vec;\n /** The reach of a source that sets none, in screen px. */\n reachPx: number;\n /** Where the geometry being drawn starts: no point target on it is offered. */\n origin?: P;\n}\n\n/**\n * One frame's resolver candidates: each source is asked, through `ask`, about\n * the probes whose role it matches and the reach that applies to it (its\n * own `reachPx`, else `opts.reachPx`), and what it answers for a probe it was\n * not offered, or a point on `origin`, is dropped. Sources run in order, and\n * an earlier source, then an earlier yield, wins a full tie. The building\n * block of a host's own snap session over `resolveSnap`.\n */\nexport function gatherSnapCandidates<P>(\n sources: readonly SnapSource<P>[],\n probes: readonly SnapProbeAt<P>[],\n ask: (source: SnapSource<P>, offered: SnapProbeAt<P>[], reachPx: number) => SnapCollect<P>,\n opts: GatherSnapOptions<P>,\n): SnapCandidate<SnapCandidateMark<P>>[] {\n const origin = opts.origin === undefined ? null : opts.toVec(opts.origin);\n const out: SnapCandidate<SnapCandidateMark<P>>[] = [];\n let order = 0;\n for (const source of sources) {\n const offered = probes.filter((q) => source.roles.includes(q.role));\n if (offered.length === 0) continue;\n const ids = new Set(offered.map((q) => q.id));\n const reachPx = source.reachPx ?? opts.reachPx;\n for (const c of source.collect(ask(source, offered, reachPx))) {\n if (!ids.has(c.probe)) continue;\n const at = opts.toVec(c.locus.at);\n if (origin && c.locus.dirs.length === 0 && same(at, origin)) continue;\n out.push({\n probe: c.probe, locus: { at, dirs: c.locus.dirs.map(opts.toVec) },\n ...(c.locus.span ? { span: c.locus.span } : {}),\n source: source.id, order: order++, tier: source.tier ?? 'target', reachPx,\n ...(c.required ? { required: true } : {}),\n ...(c.mark ? { mark: c.mark } : {}),\n });\n }\n }\n return out;\n}\n\nfunction same(a: Vec, b: Vec): boolean {\n const eps = ORIGIN_EPS * Math.max(1, ...b.map(Math.abs));\n return a.every((v, i) => Math.abs(v - b[i]) <= eps);\n}\n\n/** The marks of the candidates a result meets, with `delta` the resolved\n * move, and repeats dropped so a target several probes meet is drawn once. */\nexport function resolvedSnapMarks<P>(\n satisfied: readonly { mark?: SnapCandidateMark<P> }[], delta: P,\n): SnapMark<P>[] {\n return distinct(satisfied.flatMap((c) => marksOf(c.mark, delta)));\n}\n\nfunction marksOf<P>(mark: SnapCandidateMark<P> | undefined, delta: P): readonly SnapMark<P>[] {\n if (!mark) return [];\n const m = typeof mark === 'function' ? mark(delta) : mark;\n return 'kind' in m ? [m as SnapMark<P>] : (m as readonly SnapMark<P>[]);\n}\n"],"mappings":";;;AAIA,IAAM,mBAA2C;CAAC;CAAS;CAAQ;AAAQ;;AAG3E,SAAgB,eAAgE,SAAuB,SAA2B;CAChI,OAAO,QAAQ,QAAQ,OAAO,EAAE,YAAY,iBAAA,CAAkB,SAAS,OAAO,CAAC;AACjF;;AAGA,SAAgB,oBAAoB,SAA2D;CAC7F,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,EAAE,QAAQ,SAAS;EAC5B,IAAI,KAAK,IAAI,EAAE,GACb,MAAM,IAAI,MAAM,yCAAyC,GAAG,qCAAqC;EAEnG,KAAK,IAAI,EAAE;CACb;AACF;;AAMA,IAAM,aAAa;;;;;;;;;AAoBnB,SAAgB,qBACd,SACA,QACA,KACA,MACuC;CACvC,MAAM,SAAS,KAAK,WAAW,SAAY,OAAO,KAAK,MAAM,KAAK,MAAM;CACxE,MAAM,MAA6C,CAAC;CACpD,IAAI,QAAQ;CACZ,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,MAAM,SAAS,EAAE,IAAI,CAAC;EAClE,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,EAAE,CAAC;EAC5C,MAAM,UAAU,OAAO,WAAW,KAAK;EACvC,KAAK,MAAM,KAAK,OAAO,QAAQ,IAAI,QAAQ,SAAS,OAAO,CAAC,GAAG;GAC7D,IAAI,CAAC,IAAI,IAAI,EAAE,KAAK,GAAG;GACvB,MAAM,KAAK,KAAK,MAAM,EAAE,MAAM,EAAE;GAChC,IAAI,UAAU,EAAE,MAAM,KAAK,WAAW,KAAK,KAAK,IAAI,MAAM,GAAG;GAC7D,IAAI,KAAK;IACP,OAAO,EAAE;IAAO,OAAO;KAAE;KAAI,MAAM,EAAE,MAAM,KAAK,IAAI,KAAK,KAAK;IAAE;IAChE,GAAI,EAAE,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,OAAO;IAAI,OAAO;IAAS,MAAM,OAAO,QAAQ;IAAU;IAClE,GAAI,EAAE,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;GACnC,CAAC;EACH;CACF;CACA,OAAO;AACT;AAEA,SAAS,KAAK,GAAQ,GAAiB;CACrC,MAAM,MAAM,aAAa,KAAK,IAAI,GAAG,GAAG,EAAE,IAAI,KAAK,GAAG,CAAC;CACvD,OAAO,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,IAAI,EAAE,EAAE,KAAK,GAAG;AACpD;;;AAIA,SAAgB,kBACd,WAAuD,OACxC;CACf,OAAO,SAAS,UAAU,SAAS,MAAM,QAAQ,EAAE,MAAM,KAAK,CAAC,CAAC;AAClE;AAEA,SAAS,QAAW,MAAwC,OAAkC;CAC5F,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,IAAI;CACrD,OAAO,UAAU,IAAI,CAAC,CAAgB,IAAK;AAC7C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inputSubspace.js","names":[],"sources":["../../../src/features/snap/inputSubspace.ts"],"sourcesContent":["import { stepLine, type Flat } from '@weasel-js/geom/nd';\nimport type { SnapBegin, SnapMods } from 'interactions/actions/snapDep';\nimport { diagonal, edgeAxis, type ResizeSubject } from './resizeBox';\nimport { SNAP_AXIS_STEP } from './roles';\n\ntype Point = { x: number; y: number };\n\nconst PLANE: Flat = { at: [0, 0], dirs: [[1, 0], [0, 1]] };\nconst AXES = [[1, 0], [0, 1]] as const;\n\n/** `H`: the subspace of δ = pointer − press that the gesture's result stays\n * in. `resize` is the box a resize drags, which an edge handle's axis and an\n * aspect lock are read from; without it they run along the ctx axes. */\nexport function inputFlat(req: SnapBegin, mods: SnapMods, delta0: Point, resize?: ResizeSubject | null): Flat {\n const h = req.handle;\n if (h && (h.x === 'free') !== (h.y === 'free')) {\n const axis = resize ? edgeAxis(resize)! : h.y === 'free' ? { x: 1, y: 0 } : { x: 0, y: 1 };\n return { at: [0, 0], dirs: [[axis.x, axis.y]] };\n }\n const c = req.constrain;\n if (!mods.constrain || c === undefined) return PLANE;\n if (c === 'axis') return stepLine([0, 0], [delta0.x, delta0.y], AXES, SNAP_AXIS_STEP);\n if (c === 'aspect') {\n if (!resize || !h) return PLANE;\n const d = diagonal(resize);\n return { at: [0, 0], dirs: [[d.x, d.y]] };\n }\n const origin = req.origin ?? req.press;\n return stepLine([origin.x - req.press.x, origin.y - req.press.y], [delta0.x, delta0.y], AXES, c.step);\n}\n"],"mappings":";;;;;AAOA,IAAM,QAAc;CAAE,IAAI,CAAC,GAAG,CAAC;CAAG,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAAE;AACzD,IAAM,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;;;;AAK5B,SAAgB,UAAU,KAAgB,MAAgB,QAAe,QAAqC;CAC5G,MAAM,IAAI,IAAI;CACd,IAAI,KAAM,EAAE,MAAM,YAAa,EAAE,MAAM,SAAS;EAC9C,MAAM,OAAO,SAAS,SAAS,MAAM,IAAK,EAAE,MAAM,SAAS;GAAE,GAAG;GAAG,GAAG;EAAE,IAAI;GAAE,GAAG;GAAG,GAAG;EAAE;EACzF,OAAO;GAAE,IAAI,CAAC,GAAG,CAAC;GAAG,MAAM,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;EAAE;CAChD;CACA,MAAM,IAAI,IAAI;CACd,IAAI,CAAC,KAAK,aAAa,MAAM,QAAW,OAAO;CAC/C,IAAI,MAAM,QAAQ,OAAO,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,MAAM,cAAc;CACpF,IAAI,MAAM,UAAU;EAClB,IAAI,CAAC,UAAU,CAAC,GAAG,OAAO;EAC1B,MAAM,IAAI,SAAS,MAAM;EACzB,OAAO;GAAE,IAAI,CAAC,GAAG,CAAC;GAAG,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;EAAE;CAC1C;CACA,MAAM,SAAS,IAAI,UAAU,IAAI;CACjC,OAAO,SAAS,CAAC,OAAO,IAAI,IAAI,MAAM,GAAG,OAAO,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,MAAM,EAAE,IAAI;AACtG"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lazySession.js","names":[],"sources":["../../../src/features/snap/lazySession.ts"],"sourcesContent":["import type { SnapBegin, SnapDep, SnapSession } from 'interactions/actions/snapDep';\n\n/** A session of `dep` that begins on its first `at` or `angle`, so a press\n * that never moves reads nothing of the scene. Null without a dep. */\nexport function lazySnapSession(dep: SnapDep | undefined, req: SnapBegin): SnapSession | null {\n if (!dep) return null;\n let begun: SnapSession | null = null;\n const session = () => (begun ??= dep.begin(req));\n return {\n at: (p, mods) => session().at(p, mods),\n angle: (theta, mods) => session().angle(theta, mods),\n end: () => begun?.end(),\n };\n}\n"],"mappings":";;;AAIA,SAAgB,gBAAgB,KAA0B,KAAoC;CAC5F,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,QAA4B;CAChC,MAAM,gBAAiB,UAAU,IAAI,MAAM,GAAG;CAC9C,OAAO;EACL,KAAK,GAAG,SAAS,QAAQ,CAAC,CAAC,GAAG,GAAG,IAAI;EACrC,QAAQ,OAAO,SAAS,QAAQ,CAAC,CAAC,MAAM,OAAO,IAAI;EACnD,WAAW,OAAO,IAAI;CACxB;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"marks.js","names":[],"sources":["../../../src/features/snap/marks.ts"],"sourcesContent":["import { fromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { SnapMark } from './types';\n\ntype Point = { x: number; y: number };\n\n/** `marks` with repeats dropped, so a target several probes meet is drawn once. */\nexport function distinct<P>(marks: readonly SnapMark<P>[]): SnapMark<P>[] {\n // A string key keeps this linear: comparing every pair field by field more\n // than doubled a crowded row's frame time (culling.test.ts, 2026-10-18).\n const seen = new Set<string>();\n return marks.filter((m) => {\n const key = JSON.stringify(m);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** `m` carried out of a plane into the camera's world, where the readout draws. */\nexport function markFromPlane(m: SnapMark, plane: PlaneMap): SnapMark {\n const pt = (p: Point) => fromPlane(plane, p);\n return m.kind === 'point' ? { ...m, at: pt(m.at) } : { ...m, from: pt(m.from), to: pt(m.to) };\n}\n"],"mappings":";;;;AAMA,SAAgB,SAAY,OAA8C;CAGxE,MAAM,uBAAO,IAAI,IAAY;CAC7B,OAAO,MAAM,QAAQ,MAAM;EACzB,MAAM,MAAM,KAAK,UAAU,CAAC;EAC5B,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;EAC1B,KAAK,IAAI,GAAG;EACZ,OAAO;CACT,CAAC;AACH;;AAGA,SAAgB,cAAc,GAAa,OAA2B;CACpE,MAAM,MAAM,MAAa,UAAU,OAAO,CAAC;CAC3C,OAAO,EAAE,SAAS,UAAU;EAAE,GAAG;EAAG,IAAI,GAAG,EAAE,EAAE;CAAE,IAAI;EAAE,GAAG;EAAG,MAAM,GAAG,EAAE,IAAI;EAAG,IAAI,GAAG,EAAE,EAAE;CAAE;AAC9F"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"outcomeStore.js","names":[],"sources":["../../../src/features/snap/outcomeStore.ts"],"sourcesContent":["import type { SnapMark } from './types';\n\ntype Point = { x: number; y: number };\n\n/** The marks of the snap in flight, for the readout to draw. */\nexport interface SnapOutcomeStore<P = Point> {\n get(): readonly SnapMark<P>[];\n set(marks: readonly SnapMark<P>[]): void;\n subscribe(listener: () => void): () => void;\n}\n\nconst NONE: readonly never[] = [];\n\n/** An empty outcome store, which notifies only when the marks change. */\nexport function createSnapOutcomeStore<P = Point>(): SnapOutcomeStore<P> {\n let marks: readonly SnapMark<P>[] = NONE;\n const listeners = new Set<() => void>();\n return {\n get: () => marks,\n set(next) {\n if (next.length === 0 && marks.length === 0) return;\n marks = next.length === 0 ? NONE : next;\n for (const l of [...listeners]) l();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n };\n}\n"],"mappings":";AAWA,IAAM,OAAyB,CAAC;;AAGhC,SAAgB,yBAAyD;CACvE,IAAI,QAAgC;CACpC,MAAM,4BAAY,IAAI,IAAgB;CACtC,OAAO;EACL,WAAW;EACX,IAAI,MAAM;GACR,IAAI,KAAK,WAAW,KAAK,MAAM,WAAW,GAAG;GAC7C,QAAQ,KAAK,WAAW,IAAI,OAAO;GACnC,KAAK,MAAM,KAAK,CAAC,GAAG,SAAS,GAAG,EAAE;EACpC;EACA,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IAAE,UAAU,OAAO,QAAQ;GAAG;EAC7C;CACF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pointSnap.js","names":[],"sources":["../../../src/features/snap/pointSnap.ts"],"sourcesContent":["import type { SnapBegin, SnapDep, SnapMods, SnapOutcome, SnapSession } from 'interactions/actions/snapDep';\nimport { inputSession } from './session2d';\n\ntype Point = { x: number; y: number };\n\nconst FREE: SnapMods = { suspend: false, constrain: false };\n\n/** A drawing tool's point snapping: the points it places one by one, and the\n * cursor it trails from the last of them. */\nexport interface PointSnap {\n /** Where a point placed at `p` lands, in a `point` session of its own\n * whose `origin` is the point placed before. */\n place(dep: SnapDep | undefined, p: Point, mods: SnapMods, origin?: Point): SnapOutcome;\n /** Where the cursor at `p` lands. One session runs while `dep` and\n * `origin` stay the same, and its marks stay drawn until {@link stop}. */\n trail(dep: SnapDep | undefined, p: Point, origin?: Point): Point;\n /** End the cursor's session, clearing its marks. */\n stop(): void;\n}\n\n/** A {@link PointSnap}. With no `snap` dep, or one that runs no sessions,\n * every point lands where it is. */\nexport function pointSnap(): PointSnap {\n let live: { session: SnapSession; dep: SnapDep | undefined; origin: Point | undefined } | null = null;\n const begin = (dep: SnapDep | undefined, req: SnapBegin) => dep?.begin(req) ?? inputSession(req);\n const request = (p: Point, origin: Point | undefined): SnapBegin => ({ gesture: 'point', press: p, ...(origin ? { origin } : {}) });\n const stop = () => {\n live?.session.end();\n live = null;\n };\n return {\n place(dep, p, mods, origin) {\n const session = begin(dep, request(p, origin));\n try {\n return session.at(p, mods);\n } finally {\n session.end();\n }\n },\n trail(dep, p, origin) {\n if (!live || live.dep !== dep || live.origin?.x !== origin?.x || live.origin?.y !== origin?.y) {\n stop();\n live = { session: begin(dep, request(p, origin)), dep, origin: origin && { ...origin } };\n }\n return live.session.at(p, FREE).point;\n },\n stop,\n };\n}\n"],"mappings":";;;AAKA,IAAM,OAAiB;CAAE,SAAS;CAAO,WAAW;AAAM;;;AAiB1D,SAAgB,YAAuB;CACrC,IAAI,OAA6F;CACjG,MAAM,SAAS,KAA0B,QAAmB,KAAK,MAAM,GAAG,KAAK,aAAa,GAAG;CAC/F,MAAM,WAAW,GAAU,YAA0C;EAAE,SAAS;EAAS,OAAO;EAAG,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAAG;CACjI,MAAM,aAAa;EACjB,MAAM,QAAQ,IAAI;EAClB,OAAO;CACT;CACA,OAAO;EACL,MAAM,KAAK,GAAG,MAAM,QAAQ;GAC1B,MAAM,UAAU,MAAM,KAAK,QAAQ,GAAG,MAAM,CAAC;GAC7C,IAAI;IACF,OAAO,QAAQ,GAAG,GAAG,IAAI;GAC3B,UAAU;IACR,QAAQ,IAAI;GACd;EACF;EACA,MAAM,KAAK,GAAG,QAAQ;GACpB,IAAI,CAAC,QAAQ,KAAK,QAAQ,OAAO,KAAK,QAAQ,MAAM,QAAQ,KAAK,KAAK,QAAQ,MAAM,QAAQ,GAAG;IAC7F,KAAK;IACL,OAAO;KAAE,SAAS,MAAM,KAAK,QAAQ,GAAG,MAAM,CAAC;KAAG;KAAK,QAAQ,UAAU,EAAE,GAAG,OAAO;IAAE;GACzF;GACA,OAAO,KAAK,QAAQ,GAAG,GAAG,IAAI,CAAC,CAAC;EAClC;EACA;CACF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"probes2d.js","names":[],"sources":["../../../src/features/snap/probes2d.ts"],"sourcesContent":["import { unionBounds } from 'core/geometry/unionBounds';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { SnapBegin } from 'interactions/actions/snapDep';\nimport type { SnapProbeAt } from './types';\nimport { resizeProbes, resizeSubject, type ResizeSubject } from './resizeBox';\n\ntype Point = { x: number; y: number };\n\n/** How a 2D session reads its subject at press. */\nexport interface ProbeReads {\n /** `id`'s world pose now, or undefined when it is gone. */\n pose(id: string): unknown;\n /** The axis-aligned box `id` is drawn with at `pose`, stroke included. */\n drawnBox(id: string, pose: unknown): Bounds;\n /** The point of `pose` a grid rounds: its `OriginProjection` origin. */\n origin(pose: unknown): Point;\n /** The point of the subject's own geometry nearest `press`, or null. */\n geometryAt?(press: Point, ids: readonly string[]): Point | null;\n}\n\n/** The union drawn box of the subjects, or null when none is left. */\nexport function subjectBox(ids: readonly string[], reads: ProbeReads): Bounds | null {\n const boxes: Bounds[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) boxes.push(reads.drawnBox(id, pose));\n }\n return unionBounds(boxes);\n}\n\n/** What a gesture drags, read once at press. */\nexport interface Subject2d {\n /** A move's subjects' union drawn box. */\n box: Bounds | null;\n /** A resize's box and frame: the one it names, else the subjects' drawn\n * box, axis-aligned. */\n resize: ResizeSubject | null;\n}\n\n/** The subject of `req`, null where the gesture has none or nothing is left. */\nexport function subjectOf(req: SnapBegin, reads: ProbeReads): Subject2d {\n if (req.gesture === 'move') return { box: subjectBox(req.ids ?? [], reads), resize: null };\n if (req.gesture !== 'resize' || !req.handle) return { box: null, resize: null };\n if (req.box) return { box: null, resize: resizeSubject(req.box, req.handle) };\n const bounds = subjectBox(req.ids ?? [], reads);\n return { box: null, resize: bounds ? resizeSubject({ bounds }, req.handle) : null };\n}\n\n/** The probes a gesture snaps, where they are at press. */\nexport function buildProbes(req: SnapBegin, reads: ProbeReads, subject = subjectOf(req, reads)): SnapProbeAt[] {\n const ids = req.ids ?? [];\n switch (req.gesture) {\n case 'point':\n return [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'resize':\n return subject.resize ? resizeProbes(subject.resize) : [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'move':\n return moveProbes(req.press, ids, reads, subject.box);\n case 'rotate':\n case 'handle':\n return [];\n }\n}\n\nfunction moveProbes(press: Point, ids: readonly string[], reads: ProbeReads, box: Bounds | null): SnapProbeAt[] {\n const out: SnapProbeAt[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) out.push({ id: `origin:${id}`, at: reads.origin(pose), role: 'origin' });\n }\n if (box) {\n out.push(\n { id: 'box.min', at: { x: box.x, y: box.y }, role: 'box' },\n { id: 'box.center', at: { x: box.x + box.width / 2, y: box.y + box.height / 2 }, role: 'box' },\n { id: 'box.max', at: { x: box.x + box.width, y: box.y + box.height }, role: 'box' },\n );\n }\n const geometry = reads.geometryAt?.(press, ids) ?? press;\n out.push({ id: 'geometry', at: { x: geometry.x, y: geometry.y }, role: 'geometry' });\n return out;\n}\n"],"mappings":";;;;;AAqBA,SAAgB,WAAW,KAAwB,OAAkC;CACnF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,MAAM,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;CAC7D;CACA,OAAO,YAAY,KAAK;AAC1B;;AAYA,SAAgB,UAAU,KAAgB,OAA8B;CACtE,IAAI,IAAI,YAAY,QAAQ,OAAO;EAAE,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;EAAG,QAAQ;CAAK;CACzF,IAAI,IAAI,YAAY,YAAY,CAAC,IAAI,QAAQ,OAAO;EAAE,KAAK;EAAM,QAAQ;CAAK;CAC9E,IAAI,IAAI,KAAK,OAAO;EAAE,KAAK;EAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,MAAM;CAAE;CAC5E,MAAM,SAAS,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;CAC9C,OAAO;EAAE,KAAK;EAAM,QAAQ,SAAS,cAAc,EAAE,OAAO,GAAG,IAAI,MAAM,IAAI;CAAK;AACpF;;AAGA,SAAgB,YAAY,KAAgB,OAAmB,UAAU,UAAU,KAAK,KAAK,GAAkB;CAC7G,MAAM,MAAM,IAAI,OAAO,CAAC;CACxB,QAAQ,IAAI,SAAZ;EACE,KAAK,SACH,OAAO,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3D,KAAK,UACH,OAAO,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3G,KAAK,QACH,OAAO,WAAW,IAAI,OAAO,KAAK,OAAO,QAAQ,GAAG;EACtD,KAAK;EACL,KAAK,UACH,OAAO,CAAC;CACZ;AACF;AAEA,SAAS,WAAW,OAAc,KAAwB,OAAmB,KAAmC;CAC9G,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,IAAI,KAAK;GAAE,IAAI,UAAU;GAAM,IAAI,MAAM,OAAO,IAAI;GAAG,MAAM;EAAS,CAAC;CACjG;CACA,IAAI,KACF,IAAI,KACF;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI;GAAG,GAAG,IAAI;EAAE;EAAG,MAAM;CAAM,GACzD;EAAE,IAAI;EAAc,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI,QAAQ;GAAG,GAAG,IAAI,IAAI,IAAI,SAAS;EAAE;EAAG,MAAM;CAAM,GAC7F;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI;GAAO,GAAG,IAAI,IAAI,IAAI;EAAO;EAAG,MAAM;CAAM,CACpF;CAEF,MAAM,WAAW,MAAM,aAAa,OAAO,GAAG,KAAK;CACnD,IAAI,KAAK;EAAE,IAAI;EAAY,IAAI;GAAE,GAAG,SAAS;GAAG,GAAG,SAAS;EAAE;EAAG,MAAM;CAAW,CAAC;CACnF,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"readoutDraw.js","names":[],"sources":["../../../src/features/snap/readoutDraw.ts"],"sourcesContent":["/**\n * The screen-space drawing the snap readout and the guides overlay share:\n * ticked segments, number pills, and the glyph-and-label of a point mark.\n */\nimport { PATH_L, PATH_M, pathFromD, rectPath, type PolygonPath, type PathSnapKind } from '@weasel-js/geom';\nimport { textCommand, textCommandFromRuns, type CommandStroke, type DrawCommand } from '@weasel-js/renderer';\nimport { pathSnapGlyph } from 'features/paths/pathSnap/glyphs';\n\nconst PILL_FONT = { fontSize: 10 };\n// No text measurer in a layer: a 10px digit advances about 6px.\nconst PILL_CHAR_W = 6;\nconst PILL_H = 14;\nconst PILL_PAD_X = 4;\n\n/** A straight segment from (x0, y0) to (x1, y1), with a tick of length `t`\n * across each end. Screen px. */\nexport function tickedSegment(x0: number, y0: number, x1: number, y1: number, t: number): PolygonPath {\n const h = t / 2;\n const len = Math.hypot(x1 - x0, y1 - y0);\n // Across the segment: the pixel axes when it runs along one, else its normal.\n const [nx, ny] = x0 === x1 ? [1, 0] : y0 === y1 || len === 0 ? [0, 1] : [-(y1 - y0) / len, (x1 - x0) / len];\n const ends = t <= 0 ? [] : [\n x0 - nx * h, y0 - ny * h, x0 + nx * h, y0 + ny * h,\n x1 - nx * h, y1 - ny * h, x1 + nx * h, y1 + ny * h,\n ];\n const coords = [x0, y0, x1, y1, ...ends];\n const commands: number[] = [];\n for (let i = 0; i < coords.length; i += 4) commands.push(PATH_M, PATH_L);\n return {\n kind: 'polygon',\n commands: new Uint8Array(commands),\n coords: new Float32Array(coords),\n fillRule: 'nonzero',\n };\n}\n\n/** `text` on a filled pill centered at (cx, cy), in white on `color`. */\nexport function pillCommands(cx: number, cy: number, text: string, color: string): DrawCommand[] {\n const w = text.length * PILL_CHAR_W + PILL_PAD_X * 2;\n const left = cx - w / 2;\n const top = cy - PILL_H / 2;\n return [\n { kind: 'path', path: rectPath(left, top, w, PILL_H), fill: { paint: { kind: 'solid', color } } },\n textCommandFromRuns(left + PILL_PAD_X, top + 2, [{ text, fill: { paint: { kind: 'solid', color: '#fff' } } }], PILL_FONT),\n ];\n}\n\n/** A ticked segment between two screen points, with `label` on a pill at its middle. */\nexport function measuredSegmentCommands(\n x0: number, y0: number, x1: number, y1: number,\n ticks: number, stroke: CommandStroke, label: string | null, color: string,\n): DrawCommand[] {\n const out: DrawCommand[] = [{ kind: 'path', path: tickedSegment(x0, y0, x1, y1, ticks), stroke }];\n if (label !== null) out.push(...pillCommands((x0 + x1) / 2, (y0 + y1) / 2, label, color));\n return out;\n}\n\n/** The glyph for `glyph` on (x, y), `r` from center to edge, with `label`\n * above and to its right. An unknown glyph name draws as `anchor`. */\nexport function glyphCommands(\n glyph: string, x: number, y: number, label: string | undefined, color: string, r = 5, fontSize = 11,\n): DrawCommand[] {\n const d = pathSnapGlyph(glyph as PathSnapKind, x, y, r) ?? pathSnapGlyph('anchor', x, y, r);\n const out: DrawCommand[] = [\n { kind: 'path', path: pathFromD(d), stroke: { paint: { color }, width: 1.5 } },\n ];\n if (label) {\n out.push(textCommand(x + r + 3, y - r - fontSize - 2, label, { fontSize }, undefined, undefined, undefined, {\n fill: { paint: { color } },\n }));\n }\n return out;\n}\n"],"mappings":";;;;;;;;;AAQA,IAAM,YAAY,EAAE,UAAU,GAAG;AAEjC,IAAM,cAAc;AACpB,IAAM,SAAS;AACf,IAAM,aAAa;;;AAInB,SAAgB,cAAc,IAAY,IAAY,IAAY,IAAY,GAAwB;CACpG,MAAM,IAAI,IAAI;CACd,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE;CAEvC,MAAM,CAAC,IAAI,MAAM,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,OAAO,MAAM,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,KAAK,MAAM,GAAG;CAK1G,MAAM,SAAS;EAAC;EAAI;EAAI;EAAI;EAAI,GAJnB,KAAK,IAAI,CAAC,IAAI;GACzB,KAAK,KAAK;GAAG,KAAK,KAAK;GAAG,KAAK,KAAK;GAAG,KAAK,KAAK;GACjD,KAAK,KAAK;GAAG,KAAK,KAAK;GAAG,KAAK,KAAK;GAAG,KAAK,KAAK;EACnD;CACuC;CACvC,MAAM,WAAqB,CAAC;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG,SAAS,KAAK,QAAQ,MAAM;CACvE,OAAO;EACL,MAAM;EACN,UAAU,IAAI,WAAW,QAAQ;EACjC,QAAQ,IAAI,aAAa,MAAM;EAC/B,UAAU;CACZ;AACF;;AAGA,SAAgB,aAAa,IAAY,IAAY,MAAc,OAA8B;CAC/F,MAAM,IAAI,KAAK,SAAS,cAAc;CACtC,MAAM,OAAO,KAAK,IAAI;CACtB,MAAM,MAAM,KAAK,SAAS;CAC1B,OAAO,CACL;EAAE,MAAM;EAAQ,MAAM,SAAS,MAAM,KAAK,GAAG,MAAM;EAAG,MAAM,EAAE,OAAO;GAAE,MAAM;GAAS;EAAM,EAAE;CAAE,GAChG,oBAAoB,OAAO,YAAY,MAAM,GAAG,CAAC;EAAE;EAAM,MAAM,EAAE,OAAO;GAAE,MAAM;GAAS,OAAO;EAAO,EAAE;CAAE,CAAC,GAAG,SAAS,CAC1H;AACF;;AAGA,SAAgB,wBACd,IAAY,IAAY,IAAY,IACpC,OAAe,QAAuB,OAAsB,OAC7C;CACf,MAAM,MAAqB,CAAC;EAAE,MAAM;EAAQ,MAAM,cAAc,IAAI,IAAI,IAAI,IAAI,KAAK;EAAG;CAAO,CAAC;CAChG,IAAI,UAAU,MAAM,IAAI,KAAK,GAAG,cAAc,KAAK,MAAM,IAAI,KAAK,MAAM,GAAG,OAAO,KAAK,CAAC;CACxF,OAAO;AACT;;;AAIA,SAAgB,cACd,OAAe,GAAW,GAAW,OAA2B,OAAe,IAAI,GAAG,WAAW,IAClF;CACf,MAAM,IAAI,cAAc,OAAuB,GAAG,GAAG,CAAC,KAAK,cAAc,UAAU,GAAG,GAAG,CAAC;CAC1F,MAAM,MAAqB,CACzB;EAAE,MAAM;EAAQ,MAAM,UAAU,CAAC;EAAG,QAAQ;GAAE,OAAO,EAAE,MAAM;GAAG,OAAO;EAAI;CAAE,CAC/E;CACA,IAAI,OACF,IAAI,KAAK,YAAY,IAAI,IAAI,GAAG,IAAI,IAAI,WAAW,GAAG,OAAO,EAAE,SAAS,GAAG,QAAW,QAAW,QAAW,EAC1G,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAC3B,CAAC,CAAC;CAEJ,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"readoutLayer.js","names":[],"sources":["../../../src/features/snap/readoutLayer.ts"],"sourcesContent":["/** The one layer that draws what a snap won: every mark in the outcome store. */\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport type { SnapOutcomeStore } from './outcomeStore';\nimport { glyphCommands, measuredSegmentCommands } from './readoutDraw';\nimport type { SnapReadoutStyle } from './types';\n\nconst TICK = 6;\n\n/** A screen-space layer drawing the marks in `store`, redrawn when they change. */\nexport function createSnapReadoutLayer(\n store: SnapOutcomeStore,\n style: SnapReadoutStyle = {},\n): RenderLayer<unknown> {\n const color = style.color ?? '#e8742c';\n const gapColor = style.gapColor ?? '#3aa0ff';\n const width = style.lineWidth ?? 1;\n const strokeOf = (c: string) => ({ paint: { kind: 'solid' as const, color: c }, width });\n return {\n id: 'snap-readout',\n label: 'Snap readout',\n space: 'screen',\n alwaysOn: true,\n subscribe: (repaint) => store.subscribe(repaint),\n draw: (_data, view) => {\n const marks = store.get();\n if (marks.length === 0) return [];\n const t = viewToTransform(view);\n const out: DrawCommand[] = [];\n for (const m of marks) {\n const measured = m.kind === 'gap' || m.kind === 'distance';\n const c = m.color ?? (measured ? gapColor : color);\n if (m.kind === 'point') {\n const [x, y] = worldToScreen(m.at.x, m.at.y, t);\n out.push(...glyphCommands(m.glyph, x, y, m.label, c));\n continue;\n }\n const [x0, y0] = worldToScreen(m.from.x, m.from.y, t);\n const [x1, y1] = worldToScreen(m.to.x, m.to.y, t);\n out.push(...measuredSegmentCommands(\n x0, y0, x1, y1, measured ? TICK : 0, strokeOf(c), measured ? String(Math.round(m.value)) : null, c,\n ));\n }\n return out;\n },\n };\n}\n"],"mappings":";;;;;AASA,IAAM,OAAO;;AAGb,SAAgB,uBACd,OACA,QAA0B,CAAC,GACL;CACtB,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,QAAQ,MAAM,aAAa;CACjC,MAAM,YAAY,OAAe;EAAE,OAAO;GAAE,MAAM;GAAkB,OAAO;EAAE;EAAG;CAAM;CACtF,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,UAAU;EACV,YAAY,YAAY,MAAM,UAAU,OAAO;EAC/C,OAAO,OAAO,SAAS;GACrB,MAAM,QAAQ,MAAM,IAAI;GACxB,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;GAChC,MAAM,IAAI,gBAAgB,IAAI;GAC9B,MAAM,MAAqB,CAAC;GAC5B,KAAK,MAAM,KAAK,OAAO;IACrB,MAAM,WAAW,EAAE,SAAS,SAAS,EAAE,SAAS;IAChD,MAAM,IAAI,EAAE,UAAU,WAAW,WAAW;IAC5C,IAAI,EAAE,SAAS,SAAS;KACtB,MAAM,CAAC,GAAG,KAAK,cAAc,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC;KAC9C,IAAI,KAAK,GAAG,cAAc,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,CAAC,CAAC;KACpD;IACF;IACA,MAAM,CAAC,IAAI,MAAM,cAAc,EAAE,KAAK,GAAG,EAAE,KAAK,GAAG,CAAC;IACpD,MAAM,CAAC,IAAI,MAAM,cAAc,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC;IAChD,IAAI,KAAK,GAAG,wBACV,IAAI,IAAI,IAAI,IAAI,WAAW,OAAO,GAAG,SAAS,CAAC,GAAG,WAAW,OAAO,KAAK,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CACnG,CAAC;GACH;GACA,OAAO;EACT;CACF;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","names":[],"sources":["../../../src/features/snap/registry.ts"],"sourcesContent":["import { createContext, useContext, useEffect } from 'react';\nimport type { SnapSource } from './types';\n\n/** The sources contributed to one canvas by {@link useSnapSource}, in mount order. */\nexport interface SnapSourceRegistry {\n list(): readonly SnapSource[];\n add(source: SnapSource): () => void;\n}\n\nexport function createSnapSourceRegistry(): SnapSourceRegistry {\n let sources: readonly SnapSource[] = [];\n return {\n list: () => sources,\n add(source) {\n sources = [...sources, source];\n return () => { sources = sources.filter((s) => s !== source); };\n },\n };\n}\n\nexport const SnapSourceRegistryContext = createContext<SnapSourceRegistry | null>(null);\n\n/** Adds `source` to the enclosing `<SceneCanvas>`'s snapping while mounted.\n * Pass a stable object: a new one each render re-registers it. */\nexport function useSnapSource(source: SnapSource): void {\n const registry = useContext(SnapSourceRegistryContext);\n if (!registry) throw new Error('[useSnapSource] call it under a <SceneCanvas>');\n useEffect(() => registry.add(source), [registry, source]);\n}\n"],"mappings":";;;AASA,SAAgB,2BAA+C;CAC7D,IAAI,UAAiC,CAAC;CACtC,OAAO;EACL,YAAY;EACZ,IAAI,QAAQ;GACV,UAAU,CAAC,GAAG,SAAS,MAAM;GAC7B,aAAa;IAAE,UAAU,QAAQ,QAAQ,MAAM,MAAM,MAAM;GAAG;EAChE;CACF;AACF;AAEA,IAAa,4BAA4B,cAAyC,IAAI;;;AAItF,SAAgB,cAAc,QAA0B;CACtD,MAAM,WAAW,WAAW,yBAAyB;CACrD,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,+CAA+C;CAC9E,gBAAgB,SAAS,IAAI,MAAM,GAAG,CAAC,UAAU,MAAM,CAAC;AAC1D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resizeBox.js","names":[],"sources":["../../../src/features/snap/resizeBox.ts"],"sourcesContent":["import type { ResizeAnchor } from '@weasel-js/routing';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { fixedCornerOf, movedEdges, movingPoint } from 'interactions/actions/resize/cornerHandles';\nimport type { SnapResizeBox } from 'interactions/actions/snapDep';\nimport type { SnapEdge, SnapEdges, SnapProbeAt } from './types';\n\ntype Point = { x: number; y: number };\n\nexport const EPS = 1e-9;\n\n/** A resize's box with its frame read out, in ctx coordinates. */\nexport interface ResizeSubject {\n /** In the box's own frame. */\n bounds: Bounds;\n handle: ResizeAnchor;\n at(p: Point): Point;\n /** Where the frame's unit axes point. */\n ex: Point;\n ey: Point;\n}\n\nexport function resizeSubject(box: SnapResizeBox, handle: ResizeAnchor): ResizeSubject {\n const at = box.toWorld ?? ((p: Point) => p);\n const o = at({ x: 0, y: 0 });\n const ax = at({ x: 1, y: 0 });\n const ay = at({ x: 0, y: 1 });\n return { bounds: box.bounds, handle, at, ex: { x: ax.x - o.x, y: ax.y - o.y }, ey: { x: ay.x - o.x, y: ay.y - o.y } };\n}\n\n/** The dragged corner or edge midpoint as `pointer`, and as `box` the dragged\n * corner or both ends of the dragged edge: the points a drag δ moves by δ. */\nexport function resizeProbes(s: ResizeSubject): SnapProbeAt[] {\n const { bounds: b, handle } = s;\n const out: SnapProbeAt[] = [{ id: 'pointer', at: s.at(movingPoint(b, handle)), role: 'pointer' }];\n const sides = (pin: ResizeAnchor['x'], lo: number, size: number): number[] =>\n pin === 'min' ? [lo + size] : pin === 'max' ? [lo] : [lo, lo + size];\n const xs = sides(handle.x, b.x, b.width);\n const ys = sides(handle.y, b.y, b.height);\n if (xs.length === 2 && ys.length === 2) return out;\n for (const x of xs) for (const y of ys) out.push({ id: `box.${out.length - 1}`, at: s.at({ x, y }), role: 'box' });\n return out;\n}\n\n/** The direction an edge handle's edge moves along; null for a corner. */\nexport function edgeAxis(s: ResizeSubject): Point | null {\n const { x, y } = s.handle;\n if ((x === 'free') === (y === 'free')) return null;\n return y === 'free' ? s.ex : s.ey;\n}\n\n/** From the fixed corner to the dragged one. */\nexport function diagonal(s: ResizeSubject): Point {\n const f = s.at(fixedCornerOf(s.bounds, s.handle));\n const m = s.at(movingPoint(s.bounds, s.handle));\n return { x: m.x - f.x, y: m.y - f.y };\n}\n\n/** The box in its own frame once the handle has moved by `delta`. */\nfunction movedBox(s: ResizeSubject, delta: Point): Bounds {\n const det = s.ex.x * s.ey.y - s.ey.x * s.ex.y;\n const local = Math.abs(det) < EPS\n ? { x: 0, y: 0 }\n : { x: (s.ey.y * delta.x - s.ey.x * delta.y) / det, y: (s.ex.x * delta.y - s.ex.y * delta.x) / det };\n return movedEdges(s.bounds, s.handle, local.x, local.y);\n}\n\n/** The box's axis-aligned extent once the handle has moved by `delta`. */\nexport function resizedBox(s: ResizeSubject, delta: Point): Bounds {\n const b = movedBox(s, delta);\n const corners = [\n s.at({ x: b.x, y: b.y }), s.at({ x: b.x + b.width, y: b.y }),\n s.at({ x: b.x, y: b.y + b.height }), s.at({ x: b.x + b.width, y: b.y + b.height }),\n ];\n const xs = corners.map((c) => c.x);\n const ys = corners.map((c) => c.y);\n const x = Math.min(...xs);\n const y = Math.min(...ys);\n return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };\n}\n\n/** The sides of {@link resizedBox} a drag of `delta` moves: the side across\n * from the pinned one, or the pinned side's once the drag has flipped the\n * box past it. A frame that turns or flips the box moves no side of its\n * extent alone. */\nexport function resizeEdges(s: ResizeSubject, delta: Point): SnapEdges {\n const square = Math.abs(s.ex.y) < EPS && Math.abs(s.ey.x) < EPS && s.ex.x > 0 && s.ey.y > 0;\n if (!square) return { x: null, y: null };\n const b = movedBox(s, delta);\n const moved = (pin: ResizeAnchor['x'], size: number): SnapEdge => {\n if (pin === 'free') return null;\n const far = pin === 'min' ? 'max' : 'min';\n return size >= 0 ? far : pin;\n };\n return { x: moved(s.handle.x, b.width), y: moved(s.handle.y, b.height) };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"roles.js","names":[],"sources":["../../../src/features/snap/roles.ts"],"sourcesContent":["import { roleReader, type ModifierRoles, type ModifierState } from '@weasel-js/routing';\nimport type { SnapMods } from 'interactions/actions/snapDep';\n\n/** The modifier roles a snapping action declares. */\nexport const SNAP_ROLES = {\n 'snap.suspend': { label: 'Suspend snapping', keys: ['ctrl'] },\n 'snap.constrain': { label: 'Constrain', keys: ['shift'] },\n} as const satisfies ModifierRoles;\n\n/** The angle step `snap.constrain` (Shift) holds a direction to: a line, a\n * measurement, a rotate, a symmetry axis. 15 degrees. */\nexport const SNAP_ANGLE_STEP = Math.PI / 12;\n\n/** The step an `'axis'` lock holds a move or an anchor drag to, and Shift a\n * pen handle: the axes and the diagonals. */\nexport const SNAP_AXIS_STEP = Math.PI / 4;\n\nconst held = roleReader(SNAP_ROLES);\n\n/** The snap roles `ctx` holds. */\nexport function snapMods(ctx: { roles?: Readonly<Record<string, boolean>>; modifiers: ModifierState }): SnapMods {\n return { suspend: held(ctx, 'snap.suspend'), constrain: held(ctx, 'snap.constrain') };\n}\n"],"mappings":";;;;AAIA,IAAa,aAAa;CACxB,gBAAgB;EAAE,OAAO;EAAoB,MAAM,CAAC,MAAM;CAAE;CAC5D,kBAAkB;EAAE,OAAO;EAAa,MAAM,CAAC,OAAO;CAAE;AAC1D;;;AAIA,IAAa,kBAAkB,KAAK,KAAK;;;AAIzC,IAAa,iBAAiB,KAAK,KAAK;AAExC,IAAM,OAAO,WAAW,UAAU;;AAGlC,SAAgB,SAAS,KAAwF;CAC/G,OAAO;EAAE,SAAS,KAAK,KAAK,cAAc;EAAG,WAAW,KAAK,KAAK,gBAAgB;CAAE;AACtF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rotationSource.js","names":[],"sources":["../../../src/features/snap/rotationSource.ts"],"sourcesContent":["import type { NodeId } from 'core/scene/types';\nimport type { AngleCandidate, AngleCollect, SnapSource } from './types';\n\nconst QUARTER = Math.PI / 2;\n\n/**\n * A rotate's angle targets: every quarter turn, and the world rotation of\n * every other visible node that can turn. Shift's step is the rotate's own.\n * Every `<SceneCanvas>` asks it, under the id `'rotation'`.\n */\nexport function rotationSnapSource(): SnapSource {\n let cached: { key: readonly unknown[]; angles: number[] } | null = null;\n const nodeAngles = (q: AngleCollect): readonly number[] => {\n const { scene, rotationOf } = q;\n if (!scene || !rotationOf) return [];\n const key = [scene, scene.getVersion(), rotationOf, q.exclude];\n if (cached && key.every((k, i) => k === cached!.key[i])) return cached.angles;\n const exclude = new Set(q.exclude);\n const angles: number[] = [];\n for (const id of scene.nodes.keys()) {\n if (exclude.has(id) || scene.isHidden(id as NodeId)) continue;\n const angle = rotationOf(id);\n if (angle !== undefined && Number.isFinite(angle)) angles.push(angle);\n }\n cached = { key, angles };\n return angles;\n };\n return {\n id: 'rotation',\n roles: [],\n gestures: ['rotate'],\n collect: () => [],\n *angles(q): Iterable<AngleCandidate> {\n for (let k = 0; k < 4; k++) yield { angle: k * QUARTER };\n for (const angle of nodeAngles(q)) yield { angle };\n },\n };\n}\n"],"mappings":";AAGA,IAAM,UAAU,KAAK,KAAK;;;;;;AAO1B,SAAgB,qBAAiC;CAC/C,IAAI,SAA+D;CACnE,MAAM,cAAc,MAAuC;EACzD,MAAM,EAAE,OAAO,eAAe;EAC9B,IAAI,CAAC,SAAS,CAAC,YAAY,OAAO,CAAC;EACnC,MAAM,MAAM;GAAC;GAAO,MAAM,WAAW;GAAG;GAAY,EAAE;EAAO;EAC7D,IAAI,UAAU,IAAI,OAAO,GAAG,MAAM,MAAM,OAAQ,IAAI,EAAE,GAAG,OAAO,OAAO;EACvE,MAAM,UAAU,IAAI,IAAI,EAAE,OAAO;EACjC,MAAM,SAAmB,CAAC;EAC1B,KAAK,MAAM,MAAM,MAAM,MAAM,KAAK,GAAG;GACnC,IAAI,QAAQ,IAAI,EAAE,KAAK,MAAM,SAAS,EAAY,GAAG;GACrD,MAAM,QAAQ,WAAW,EAAE;GAC3B,IAAI,UAAU,UAAa,OAAO,SAAS,KAAK,GAAG,OAAO,KAAK,KAAK;EACtE;EACA,SAAS;GAAE;GAAK;EAAO;EACvB,OAAO;CACT;CACA,OAAO;EACL,IAAI;EACJ,OAAO,CAAC;EACR,UAAU,CAAC,QAAQ;EACnB,eAAe,CAAC;EAChB,CAAC,OAAO,GAA6B;GACnC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,MAAM,EAAE,OAAO,IAAI,QAAQ;GACvD,KAAK,MAAM,SAAS,WAAW,CAAC,GAAG,MAAM,EAAE,MAAM;EACnD;CACF;AACF"}
|