@weasel-js/core 1.9.2 → 1.9.4
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 +393 -0
- package/README.md +2 -2
- package/dist/WeaselProvider.js +5 -5
- package/dist/affordances/composeAffordanceLayer.js +10 -20
- package/dist/affordances/composeAffordanceLayer.js.map +1 -1
- package/dist/affordances/cornerResize.js +25 -17
- package/dist/affordances/cornerResize.js.map +1 -1
- package/dist/affordances/hitAffordanceRegions.js +27 -20
- package/dist/affordances/hitAffordanceRegions.js.map +1 -1
- package/dist/affordances/rotationHandle.js +18 -18
- package/dist/affordances/rotationHandle.js.map +1 -1
- package/dist/animation/behaviors/momentum.js +93 -55
- package/dist/animation/behaviors/momentum.js.map +1 -1
- package/dist/animation/loop.js +10 -12
- package/dist/animation/loop.js.map +1 -1
- package/dist/animation/reflow.js +8 -8
- package/dist/animation/reflow.js.map +1 -1
- package/dist/animation/stagger.js +64 -56
- package/dist/animation/stagger.js.map +1 -1
- package/dist/animation/timeline/booking.js +62 -54
- package/dist/animation/timeline/booking.js.map +1 -1
- package/dist/animation/timeline/createTimeline.js +85 -76
- package/dist/animation/timeline/createTimeline.js.map +1 -1
- package/dist/animation/timeline/sampleTrack.js +13 -5
- package/dist/animation/timeline/sampleTrack.js.map +1 -1
- package/dist/animation/useAnimator.js +45 -35
- package/dist/animation/useAnimator.js.map +1 -1
- package/dist/animation/wrappers/animateLifecycle.js +31 -24
- package/dist/animation/wrappers/animateLifecycle.js.map +1 -1
- package/dist/animation/wrappers/animateOnSetPose.js +28 -26
- package/dist/animation/wrappers/animateOnSetPose.js.map +1 -1
- package/dist/animation/wrappers/instantPose.js +13 -0
- package/dist/animation/wrappers/instantPose.js.map +1 -0
- package/dist/{poseDescriptor-D-OH-tu5.d.ts → autoPoseDescriptor-Ct7yqivU.d.ts} +28 -3
- package/dist/barrel/chrome.js +16 -0
- package/dist/barrel/input.js +19 -0
- package/dist/barrel/interactions.js +42 -0
- package/dist/barrel/paint.js +18 -0
- package/dist/barrel/paths.js +19 -0
- package/dist/barrel/renderer.js +20 -0
- package/dist/barrel/routing.js +51 -0
- package/dist/barrel/text.js +18 -0
- package/dist/barrel/viewport.js +52 -0
- package/dist/builtins-DPcdghtB.d.ts +1223 -0
- package/dist/canvas/Canvas.js +363 -448
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +164 -165
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/CursorCoordsHud.js +37 -32
- package/dist/canvas/CursorCoordsHud.js.map +1 -1
- package/dist/canvas/DrawCanvas.js +49 -43
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/MinimapCanvas.js +26 -25
- package/dist/canvas/MinimapCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +59 -377
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/PickHud.js +50 -47
- package/dist/canvas/PickHud.js.map +1 -1
- package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js +167 -0
- package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js.map +1 -0
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +13 -13
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +67 -0
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -0
- package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js +16 -0
- package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js.map +1 -0
- package/dist/canvas/SceneCanvas/coordTrace.js +10 -0
- package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -0
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +146 -92
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +9 -8
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +1 -0
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/isDev.js +12 -0
- package/dist/canvas/SceneCanvas/isDev.js.map +1 -0
- package/dist/canvas/SceneCanvas/poseGeometry.js +29 -28
- package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
- package/dist/canvas/SceneCanvas/poseRegistrars.js +13 -0
- package/dist/canvas/SceneCanvas/poseRegistrars.js.map +1 -0
- package/dist/canvas/SceneCanvas/props.js +38 -0
- package/dist/canvas/SceneCanvas/props.js.map +1 -0
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js +21 -62
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +46 -48
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +33 -33
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +534 -788
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +45 -43
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js +91 -0
- package/dist/canvas/buildSceneLayer.js.map +1 -0
- package/dist/canvas/buildSceneTree.js +16 -19
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +68 -52
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/deps/changes.js +13 -0
- package/dist/canvas/deps/changes.js.map +1 -0
- package/dist/canvas/deps/editAnchors.js +28 -28
- package/dist/canvas/deps/insert.js +119 -91
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/slice.js +46 -45
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/deps/textEdit.js +10 -9
- package/dist/canvas/deps/textEdit.js.map +1 -1
- package/dist/canvas/derivedPath.js +46 -23
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/getNodeAtPoint.js +7 -7
- package/dist/canvas/getNodeAtPoint.js.map +1 -1
- package/dist/canvas/hud.module.js +1 -1
- package/dist/canvas/hud.module.js.map +1 -1
- package/dist/canvas/kitRequirements.js +2 -2
- package/dist/canvas/kitRequirements.js.map +1 -1
- package/dist/canvas/layerOrder.js +13 -6
- package/dist/canvas/layerOrder.js.map +1 -1
- package/dist/canvas/minimapMath.js +12 -10
- package/dist/canvas/minimapMath.js.map +1 -1
- package/dist/canvas/nodeShapes/derivedPainter.js +34 -0
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/imagePainter.js +87 -0
- package/dist/canvas/nodeShapes/imagePainter.js.map +1 -0
- package/dist/canvas/nodeShapes/memo.js +9 -0
- package/dist/canvas/nodeShapes/memo.js.map +1 -0
- package/dist/canvas/nodeShapes/nodePaint.js +57 -0
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -0
- package/dist/canvas/nodeShapes/pathPainter.js +38 -0
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/poseBox.js +20 -0
- package/dist/canvas/nodeShapes/poseBox.js.map +1 -0
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +38 -0
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/shapePainter.js +72 -0
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -0
- package/dist/canvas/nodeShapes/textPainter.js +55 -0
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/paintCull.js +10 -11
- package/dist/canvas/paintCull.js.map +1 -1
- package/dist/canvas/pickWalk.js +52 -63
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/poseRotation.js +11 -23
- package/dist/canvas/poseRotation.js.map +1 -1
- package/dist/canvas/renderDebugSnapshot.js +5 -5
- package/dist/canvas/renderSceneToPixels.js +3 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +66 -148
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +112 -0
- package/dist/canvas/sceneClipboard.js.map +1 -0
- package/dist/canvas/sceneSlotWiring.js +4 -4
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +39 -40
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/useCanvasRenderer.js +14 -6
- package/dist/canvas/useCanvasRenderer.js.map +1 -1
- package/dist/canvas/useCanvasView.js +58 -0
- package/dist/canvas/useCanvasView.js.map +1 -0
- package/dist/canvas/useFrameLoop.js +35 -26
- package/dist/canvas/useFrameLoop.js.map +1 -1
- package/dist/canvas/useSceneLayerList.js +21 -0
- package/dist/canvas/useSceneLayerList.js.map +1 -0
- package/dist/clipboard.d.ts +10 -6
- package/dist/clone.d.ts +4 -3
- package/dist/core/adapters/arrayAdapter.js +48 -39
- package/dist/core/adapters/arrayAdapter.js.map +1 -1
- package/dist/core/device/index.js +5 -4
- package/dist/core/device/useDeviceProfile.js +38 -44
- package/dist/core/device/useDeviceProfile.js.map +1 -1
- package/dist/core/device/watchDevicePixelRatio.js +17 -0
- package/dist/core/device/watchDevicePixelRatio.js.map +1 -0
- package/dist/core/fillInPoseFrame.js.map +1 -1
- package/dist/core/geometry/poseDescriptor.js +21 -19
- package/dist/core/geometry/poseDescriptor.js.map +1 -1
- package/dist/core/geometry/poseFrame.js +44 -0
- package/dist/core/geometry/poseFrame.js.map +1 -0
- package/dist/core/geometry/poseRotation.js.map +1 -1
- package/dist/core/geometry/regionHit.js +9 -10
- package/dist/core/geometry/regionHit.js.map +1 -1
- package/dist/core/geometry/unionBounds.js +31 -29
- package/dist/core/geometry/unionBounds.js.map +1 -1
- package/dist/core/isPlainObject.js +2 -11
- package/dist/core/ops/arrange.js +1 -0
- package/dist/core/ops/arrange.js.map +1 -1
- package/dist/core/ops/create.js +10 -5
- package/dist/core/ops/create.js.map +1 -1
- package/dist/core/ops/delete.js +12 -24
- package/dist/core/ops/delete.js.map +1 -1
- package/dist/core/ops/reorder/index.js +43 -40
- package/dist/core/ops/reorder/index.js.map +1 -1
- package/dist/core/ops/reorder/setChildOrder.js +34 -0
- package/dist/core/ops/reorder/setChildOrder.js.map +1 -0
- package/dist/core/paintKinds.js +5 -5
- package/dist/core/paintKinds.js.map +1 -1
- package/dist/core/scene/NodeProperties.js.map +1 -1
- package/dist/core/scene/appliedLog.js +37 -0
- package/dist/core/scene/appliedLog.js.map +1 -0
- package/dist/core/scene/arrivalWindow.js +101 -0
- package/dist/core/scene/arrivalWindow.js.map +1 -0
- package/dist/core/scene/derivedPath.js.map +1 -1
- package/dist/core/scene/historyFacade.js +84 -0
- package/dist/core/scene/historyFacade.js.map +1 -0
- package/dist/core/scene/kitOps.js +173 -0
- package/dist/core/scene/kitOps.js.map +1 -0
- package/dist/core/scene/layerStack.js +38 -0
- package/dist/core/scene/layerStack.js.map +1 -0
- package/dist/core/scene/lockGuard.js +37 -0
- package/dist/core/scene/lockGuard.js.map +1 -0
- package/dist/core/scene/nodeFnFields.js +5 -6
- package/dist/core/scene/nodeFnFields.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +45 -19
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/nodeStore.js +126 -0
- package/dist/core/scene/nodeStore.js.map +1 -0
- package/dist/core/scene/pickability.js +6 -0
- package/dist/core/scene/pickability.js.map +1 -0
- package/dist/core/scene/poseFeed.js +38 -38
- package/dist/core/scene/poseFeed.js.map +1 -1
- package/dist/core/scene/poseOverrides.js +34 -38
- package/dist/core/scene/poseOverrides.js.map +1 -1
- package/dist/core/scene/renderOrder.js +84 -0
- package/dist/core/scene/renderOrder.js.map +1 -0
- package/dist/core/scene/scene.js +289 -866
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/serialization.js +68 -0
- package/dist/core/scene/serialization.js.map +1 -0
- package/dist/core/scene/treeOrder.js +13 -0
- package/dist/core/scene/treeOrder.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/viewport/meanScale.js +1 -1
- package/dist/core/viewport/meanScale.js.map +1 -1
- package/dist/core/viewport/view.js +12 -12
- package/dist/core/viewport/view.js.map +1 -1
- package/dist/core/viewport/zoomBounds.js +10 -8
- package/dist/core/viewport/zoomBounds.js.map +1 -1
- package/dist/{cornerHandles-BCrDp_mH.d.ts → cornerHandles-CtCb8a3G.d.ts} +1 -1
- package/dist/debug/createDebugOverlayLayer.js +14 -14
- package/dist/debug/createDebugOverlayLayer.js.map +1 -1
- package/dist/debug/flag.js +2 -41
- package/dist/features/chrome-caps/useHoverTracking.js +22 -17
- package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
- package/dist/features/focus/gateLayer.js +3 -4
- package/dist/features/focus/gateLayer.js.map +1 -1
- package/dist/features/images/imageCache.js +19 -9
- package/dist/features/images/imageCache.js.map +1 -1
- package/dist/features/ingestion/contentHandlers.js +14 -11
- package/dist/features/ingestion/contentHandlers.js.map +1 -1
- package/dist/features/ingestion/index.js +1 -1
- package/dist/features/ingestion/registerKitHandlers.js +7 -7
- package/dist/features/ingestion/weaselJsonHandler.js +17 -14
- package/dist/features/ingestion/weaselJsonHandler.js.map +1 -1
- package/dist/features/meshPaint/lazyKind.js +201 -35
- package/dist/features/meshPaint/lazyKind.js.map +1 -1
- package/dist/features/meshPaint/meshPaint.js +82 -79
- package/dist/features/meshPaint/meshPaint.js.map +1 -1
- package/dist/features/minimap/createMinimapContribution.js +42 -42
- package/dist/features/minimap/layers.js +44 -62
- package/dist/features/minimap/layers.js.map +1 -1
- package/dist/features/parallax/createParallaxPlane.js +10 -14
- package/dist/features/parallax/createParallaxPlane.js.map +1 -1
- package/dist/features/paths/anchorEdits.js +19 -8
- package/dist/features/paths/anchorEdits.js.map +1 -1
- package/dist/features/paths/anchors.js +31 -26
- package/dist/features/paths/anchors.js.map +1 -1
- package/dist/features/paths/booleans.js +2 -2
- package/dist/features/paths/index.js +13 -11
- package/dist/features/paths/markerCommands.js +37 -36
- package/dist/features/paths/markerCommands.js.map +1 -1
- package/dist/features/paths/pathInWorld.js +19 -19
- package/dist/features/paths/pathInWorld.js.map +1 -1
- package/dist/features/paths/pathLayer.js.map +1 -1
- package/dist/features/paths/strokeToPath.js +40 -0
- package/dist/features/paths/strokeToPath.js.map +1 -0
- package/dist/features/paths/tessellate/stroke.js.map +1 -1
- package/dist/features/patterns/commandPaint.js +18 -0
- package/dist/features/patterns/commandPaint.js.map +1 -0
- package/dist/features/patterns/index.js +5 -17
- package/dist/features/patterns/index.js.map +1 -1
- package/dist/features/patterns/rasterizeTile.js +17 -0
- package/dist/features/patterns/rasterizeTile.js.map +1 -0
- package/dist/features/patterns/resolveSpec.js +52 -44
- package/dist/features/patterns/resolveSpec.js.map +1 -1
- package/dist/features/poseRun/usePoseRun.js +8 -3
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/selection/SelectionContext.js +5 -0
- package/dist/features/selection/SelectionContext.js.map +1 -1
- package/dist/features/selection/overlay.js +18 -18
- package/dist/features/selection/overlay.js.map +1 -1
- package/dist/features/text/domOffsets.js +82 -0
- package/dist/features/text/domOffsets.js.map +1 -0
- package/dist/features/text/domRuns.js +103 -131
- package/dist/features/text/domRuns.js.map +1 -1
- package/dist/features/text/hitTest.js +5 -2
- package/dist/features/text/hitTest.js.map +1 -1
- package/dist/features/text/isTextData.js +8 -0
- package/dist/features/text/isTextData.js.map +1 -0
- package/dist/features/text/overlayDirection.js +15 -0
- package/dist/features/text/overlayDirection.js.map +1 -0
- package/dist/features/text/runs/rangeStyle.js +32 -34
- package/dist/features/text/runs/rangeStyle.js.map +1 -1
- package/dist/features/text/textCommand.js +34 -33
- package/dist/features/text/textCommand.js.map +1 -1
- package/dist/features/text/textToPath.js +40 -40
- package/dist/features/text/textToPath.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +76 -82
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/text/useTextEdit.js +184 -160
- package/dist/features/text/useTextEdit.js.map +1 -1
- package/dist/features/tiling/createTiledLayer.js +24 -22
- package/dist/features/tiling/createTiledLayer.js.map +1 -1
- package/dist/index.d.ts +11010 -7700
- package/dist/index.js +316 -309
- package/dist/input/keyState.js +42 -43
- package/dist/input/keyState.js.map +1 -1
- package/dist/input/useKeyModifiers.js +11 -0
- package/dist/input/useKeyModifiers.js.map +1 -0
- package/dist/interactions/actions/align/align.js +8 -8
- package/dist/interactions/actions/clipboard/clipboardOps.js +34 -32
- package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
- package/dist/interactions/actions/clipboard/materializePaste.js +10 -7
- package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
- package/dist/interactions/actions/clipboard/wireFormat.js.map +1 -1
- package/dist/interactions/actions/clone/behaviors/cloneByAltDrag.js.map +1 -1
- package/dist/interactions/actions/defaultCommitAdapter.js +20 -18
- package/dist/interactions/actions/defaultCommitAdapter.js.map +1 -1
- package/dist/interactions/actions/defaults/align.js +9 -10
- package/dist/interactions/actions/defaults/align.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +59 -81
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/areaSelect.js +35 -38
- package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/clipboard.js +26 -10
- package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
- package/dist/interactions/actions/defaults/clone.js +44 -60
- package/dist/interactions/actions/defaults/clone.js.map +1 -1
- package/dist/interactions/actions/defaults/copySubtree.js +40 -0
- package/dist/interactions/actions/defaults/copySubtree.js.map +1 -0
- package/dist/interactions/actions/defaults/distribute.js +14 -13
- package/dist/interactions/actions/defaults/distribute.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +17 -57
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/editAnchors.js +18 -18
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/flip.js +52 -46
- package/dist/interactions/actions/defaults/flip.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +43 -48
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +35 -33
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insert.js +50 -51
- package/dist/interactions/actions/defaults/insert.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -7
- package/dist/interactions/actions/defaults/lassoSelect.js +67 -70
- package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/move.js +267 -249
- package/dist/interactions/actions/defaults/move.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +40 -68
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/nudgeSteps.js +37 -0
- package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -0
- package/dist/interactions/actions/defaults/reorder.js +2 -0
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/resize.js +104 -98
- package/dist/interactions/actions/defaults/resize.js.map +1 -1
- package/dist/interactions/actions/defaults/rotate.js +111 -110
- package/dist/interactions/actions/defaults/rotate.js.map +1 -1
- package/dist/interactions/actions/defaults/setFill.js +7 -7
- package/dist/interactions/actions/defaults/setFillOpacity.js +4 -4
- package/dist/interactions/actions/defaults/setStroke.js +12 -12
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +6 -6
- package/dist/interactions/actions/defaults/sliceOps.js +19 -19
- package/dist/interactions/actions/defaults/viewportDragPan.js +37 -26
- package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/geometry.js +28 -23
- package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
- package/dist/interactions/actions/flip/helpers.js +30 -17
- package/dist/interactions/actions/flip/helpers.js.map +1 -1
- package/dist/interactions/actions/hitSelection.js +8 -0
- package/dist/interactions/actions/hitSelection.js.map +1 -0
- package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js +14 -18
- package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js +5 -5
- package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +32 -21
- package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
- package/dist/interactions/actions/move/gestureAdapter.js +32 -15
- package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
- package/dist/interactions/actions/move/landedDescendants.js +33 -0
- package/dist/interactions/actions/move/landedDescendants.js.map +1 -0
- package/dist/interactions/actions/outlines/createOutlines.js +18 -18
- package/dist/interactions/actions/poseDescriptorDep.js +16 -4
- package/dist/interactions/actions/poseDescriptorDep.js.map +1 -1
- package/dist/interactions/actions/poseFrame.js +11 -6
- package/dist/interactions/actions/poseFrame.js.map +1 -1
- package/dist/interactions/actions/previewOverrides.js +5 -5
- package/dist/interactions/actions/previewOverrides.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +69 -0
- package/dist/interactions/actions/selectionSubtree.js.map +1 -0
- package/dist/interactions/actions/useStandardActions.js +66 -62
- package/dist/interactions/actions/useStandardActions.js.map +1 -1
- package/dist/interactions/gestures/dragGesture/dragGesture.js +4 -7
- package/dist/interactions/gestures/dragGesture/dragGesture.js.map +1 -1
- package/dist/interactions/gestures/dragGesture/dragPointOf.js +23 -0
- package/dist/interactions/gestures/dragGesture/dragPointOf.js.map +1 -0
- package/dist/interactions/gestures/handleDrag/handleDrag.js +21 -26
- package/dist/interactions/gestures/handleDrag/handleDrag.js.map +1 -1
- package/dist/interactions/gestures/pointerDrag/pointerDrag.js +8 -6
- package/dist/interactions/gestures/pointerDrag/pointerDrag.js.map +1 -1
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +8 -4
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
- package/dist/{math-Drrw4ewZ.d.ts → math-J-ACHyEg.d.ts} +1 -2
- package/dist/math.d.ts +2 -3
- package/dist/move.d.ts +12 -11
- package/dist/{originProjection-DEklpDc_.d.ts → originProjection-gHnOup4e.d.ts} +1 -1
- package/dist/{registerProgram-BMZRDgrh.d.ts → registerProgram-T9S_PfAX.d.ts} +18 -7
- package/dist/renderer/DrawCommand.js.map +1 -1
- package/dist/renderer/WeaselRenderer.js +75 -73
- package/dist/renderer/WeaselRenderer.js.map +1 -1
- package/dist/renderer/cache/GLImageCache.js +15 -5
- package/dist/renderer/cache/GLImageCache.js.map +1 -1
- package/dist/renderer/cache/GLMeshCache.js +23 -23
- package/dist/renderer/cache/GLMeshCache.js.map +1 -1
- package/dist/renderer/cache/GLTextureCache.js +18 -1
- package/dist/renderer/cache/GLTextureCache.js.map +1 -1
- package/dist/renderer/cache/cache.js.map +1 -1
- package/dist/renderer/cache/markerCommandCache.js.map +1 -1
- package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
- package/dist/renderer/cullDrawCommands.js +11 -11
- package/dist/renderer/draw/batch.js +133 -0
- package/dist/renderer/draw/batch.js.map +1 -0
- package/dist/renderer/draw/clip.js +89 -0
- package/dist/renderer/draw/clip.js.map +1 -0
- package/dist/renderer/draw/customShader.js +230 -0
- package/dist/renderer/draw/customShader.js.map +1 -0
- package/dist/renderer/draw/drawTriangles.js +8 -0
- package/dist/renderer/draw/drawTriangles.js.map +1 -0
- package/dist/renderer/draw/effects.js +28 -0
- package/dist/renderer/draw/effects.js.map +1 -0
- package/dist/renderer/draw/flushBatch.js +35 -0
- package/dist/renderer/draw/flushBatch.js.map +1 -0
- package/dist/renderer/draw/gradient.js +90 -0
- package/dist/renderer/draw/gradient.js.map +1 -0
- package/dist/renderer/draw/image.js +38 -0
- package/dist/renderer/draw/image.js.map +1 -0
- package/dist/renderer/draw/mesh.js +16 -0
- package/dist/renderer/draw/mesh.js.map +1 -0
- package/dist/renderer/draw/paintBind.js +70 -0
- package/dist/renderer/draw/paintBind.js.map +1 -0
- package/dist/renderer/draw/pathPaint.js +142 -0
- package/dist/renderer/draw/pathPaint.js.map +1 -0
- package/dist/renderer/draw/scratchPass.js +83 -0
- package/dist/renderer/draw/scratchPass.js.map +1 -0
- package/dist/renderer/draw/stencil.js +28 -0
- package/dist/renderer/draw/stencil.js.map +1 -0
- package/dist/renderer/draw/text.js +167 -0
- package/dist/renderer/draw/text.js.map +1 -0
- package/dist/renderer/draw/uniforms.js +77 -0
- package/dist/renderer/draw/uniforms.js.map +1 -0
- package/dist/renderer/draw.js +60 -1116
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/effects/EffectTargets.js +3 -0
- package/dist/renderer/effects/EffectTargets.js.map +1 -1
- package/dist/renderer/effects/composite.js +7 -5
- package/dist/renderer/effects/composite.js.map +1 -1
- package/dist/renderer/index.js +18 -17
- package/dist/renderer/math/color.js +63 -83
- package/dist/renderer/math/color.js.map +1 -1
- package/dist/renderer/math/mat3.js +15 -11
- package/dist/renderer/math/mat3.js.map +1 -1
- package/dist/renderer/math/viewToMat3.js +1 -1
- package/dist/renderer/math/viewToMat3.js.map +1 -1
- package/dist/renderer/shaders/ShaderProgram.js +13 -13
- package/dist/renderer/shaders/ShaderProgram.js.map +1 -1
- package/dist/renderer/shaders/glyphPaint.js +3 -3
- package/dist/renderer/shaders/glyphPaint.js.map +1 -1
- package/dist/renderer/shaders/gradFill.js +2 -1
- package/dist/renderer/shaders/gradFill.js.map +1 -1
- package/dist/renderer/shaders/pathFill.js +2 -1
- package/dist/renderer/shaders/pathFill.js.map +1 -1
- package/dist/renderer/shaders/patternFill.js +2 -1
- package/dist/renderer/shaders/patternFill.js.map +1 -1
- package/dist/renderer/shaders/registerProgram.js.map +1 -1
- package/dist/renderer/strokeLengths.js.map +1 -1
- package/dist/renderer/textures/registerTexture.js +25 -5
- package/dist/renderer/textures/registerTexture.js.map +1 -1
- package/dist/renderer.d.ts +16 -15
- package/dist/renderer.js +17 -16
- package/dist/resize.d.ts +5 -6
- package/dist/resize.js +5 -5
- package/dist/style.css +1 -1
- package/dist/test-hook/install.js +12 -6
- package/dist/test-hook/install.js.map +1 -1
- package/dist/{test-seams-CuIxe0xY.d.ts → test-seams-DM2B87-g.d.ts} +4 -4
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +1 -0
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +11 -11
- package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +1 -0
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +153 -103
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
- package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +13 -8
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +24 -23
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +51 -48
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/shared/pointerFollower.js +17 -0
- package/dist/tools/builtin/shared/pointerFollower.js.map +1 -0
- package/dist/tools/builtin/slice/useSliceTool.js +49 -54
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +22 -16
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +13 -2
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/tools/useKeybindings.js +14 -30
- package/dist/tools/useKeybindings.js.map +1 -1
- package/dist/{types-B_9g6hMM.d.ts → types-B4iJRHC3.d.ts} +5 -1
- package/dist/{types-A37hYkeR.d.ts → types-k-5Oqh-U.d.ts} +31 -7
- package/dist/util/color.js +3 -3
- package/dist/util/color.js.map +1 -1
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -18
- package/dist/autoPoseDescriptor-vCdt55xm.d.ts +0 -27
- package/dist/builtins-C-VgUlWI.d.ts +0 -4514
- package/dist/core/isPlainObject.js.map +0 -1
- package/dist/debug/flag.js.map +0 -1
- package/dist/interactions/actions/defaults/freshNodeId.js +0 -10
- package/dist/interactions/actions/defaults/freshNodeId.js.map +0 -1
- package/dist/renderer/shaders/imageFill.js +0 -14
- package/dist/renderer/shaders/imageFill.js.map +0 -1
- package/dist/tools/prefs.js +0 -47
- package/dist/tools/prefs.js.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { registerOpFactory as e } from "../registry.js";
|
|
2
2
|
import { bringForward as t, bringToFront as n, canBringForward as r, canSendBackward as i, moveToIndex as a, sendBackward as o, sendToBack as s } from "./algorithms.js";
|
|
3
|
+
import { createSetChildOrderOp as c } from "./setChildOrder.js";
|
|
3
4
|
//#region src/core/ops/reorder/index.ts
|
|
4
|
-
function
|
|
5
|
+
function l(e, t) {
|
|
5
6
|
if (e.length !== t.length) return !1;
|
|
6
7
|
for (let n = 0; n < e.length; n++) if (e[n] !== t[n]) return !1;
|
|
7
8
|
return !0;
|
|
8
9
|
}
|
|
9
|
-
function
|
|
10
|
+
function u(e) {
|
|
10
11
|
let { ids: t, direction: n, fn: r, label: i, prevOrders: a } = e, o = {
|
|
11
12
|
ids: t,
|
|
12
13
|
direction: n,
|
|
@@ -31,41 +32,41 @@ function l(e) {
|
|
|
31
32
|
let t = n.getParent ? n.getParent(e) : null, r = i.get(t) ?? [];
|
|
32
33
|
r.push(e), i.set(t, r);
|
|
33
34
|
}
|
|
34
|
-
let a = [],
|
|
35
|
+
let a = [], c = !1;
|
|
35
36
|
for (let [e, t] of i) {
|
|
36
37
|
let i = n.getChildren(e), o = r(i, t);
|
|
37
|
-
|
|
38
|
+
l(i, o) || (a.push({
|
|
38
39
|
parentId: e,
|
|
39
40
|
before: i.slice()
|
|
40
|
-
}), n.setChildOrder(e, o),
|
|
41
|
+
}), n.setChildOrder(e, o), c = !0);
|
|
41
42
|
}
|
|
42
43
|
return s = a, o.prevOrders = a.map((e) => ({
|
|
43
44
|
...e,
|
|
44
45
|
before: e.before.slice()
|
|
45
|
-
})),
|
|
46
|
+
})), c;
|
|
46
47
|
},
|
|
47
48
|
invert() {
|
|
48
49
|
let e = s;
|
|
49
50
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
51
|
+
...c({
|
|
52
|
+
orders: (e ?? []).map((e) => ({
|
|
53
|
+
parentId: e.parentId,
|
|
54
|
+
ids: e.before
|
|
55
|
+
})),
|
|
56
|
+
label: i
|
|
57
|
+
}),
|
|
58
|
+
invert: () => u({
|
|
59
|
+
ids: t,
|
|
60
|
+
direction: n,
|
|
61
|
+
fn: r,
|
|
62
|
+
label: i,
|
|
63
|
+
prevOrders: e ?? void 0
|
|
64
|
+
})
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
|
-
var
|
|
69
|
+
var d = {
|
|
69
70
|
forward: {
|
|
70
71
|
fn: t,
|
|
71
72
|
defaultLabel: "Bring forward"
|
|
@@ -83,9 +84,9 @@ var u = {
|
|
|
83
84
|
defaultLabel: "Send to back"
|
|
84
85
|
}
|
|
85
86
|
};
|
|
86
|
-
function
|
|
87
|
-
let { ids: t, direction: n, label: r, prevOrders: i } = e, { fn: a, defaultLabel: o } =
|
|
88
|
-
return
|
|
87
|
+
function f(e) {
|
|
88
|
+
let { ids: t, direction: n, label: r, prevOrders: i } = e, { fn: a, defaultLabel: o } = d[n];
|
|
89
|
+
return u({
|
|
89
90
|
ids: t,
|
|
90
91
|
direction: n,
|
|
91
92
|
fn: a,
|
|
@@ -93,8 +94,8 @@ function d(e) {
|
|
|
93
94
|
prevOrders: i
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
|
-
e("reorder", (e) =>
|
|
97
|
-
function
|
|
97
|
+
e("reorder", (e) => f(e));
|
|
98
|
+
function p(e) {
|
|
98
99
|
let { ids: t, parentId: n, index: r, label: i } = e, o = {
|
|
99
100
|
ids: t,
|
|
100
101
|
parentId: n,
|
|
@@ -117,24 +118,26 @@ function f(e) {
|
|
|
117
118
|
invert() {
|
|
118
119
|
let e = s;
|
|
119
120
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
...c({
|
|
122
|
+
orders: e ? [{
|
|
123
|
+
parentId: n,
|
|
124
|
+
ids: e
|
|
125
|
+
}] : [],
|
|
126
|
+
label: i
|
|
127
|
+
}),
|
|
128
|
+
invert: () => p({
|
|
129
|
+
ids: t,
|
|
130
|
+
parentId: n,
|
|
131
|
+
index: r,
|
|
132
|
+
label: i,
|
|
133
|
+
prevPositions: e ?? void 0
|
|
134
|
+
})
|
|
132
135
|
};
|
|
133
136
|
}
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
|
-
e("moveToIndex", (e) =>
|
|
139
|
+
e("moveToIndex", (e) => p(e));
|
|
137
140
|
//#endregion
|
|
138
|
-
export { r as canBringForward, i as canSendBackward,
|
|
141
|
+
export { r as canBringForward, i as canSendBackward, p as createMoveToIndexOp, f as createReorderOp, c as createSetChildOrderOp };
|
|
139
142
|
|
|
140
143
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["arraysEqual","a","b","i","createPartitionedReorderOp","args","argsForSerial","prevOrders","restore","adapter","byParent","id","ids","parent","list","snapshots","mutated","parentId","parentIds","before","after","fn","captured","entry","REORDER_DIRECTIONS","bringForward","sendBackward","bringToFront","sendToBack","createReorderOp","direction","label","defaultLabel","registerOpFactory","createMoveToIndexOp","eligible","current","moveToIndex","index","canBringForward","canSendBackward"],"sources":["../../../../src/core/ops/reorder/index.ts"],"sourcesContent":["import type { Op } from '../types';\nimport { registerOpFactory } from '../registry';\nimport {\n bringForward,\n sendBackward,\n bringToFront,\n sendToBack,\n moveToIndex,\n} from './algorithms';\n\nexport { canBringForward, canSendBackward } from './algorithms';\n\ninterface ReorderAdapter {\n getParent?(id: string): string | null;\n getChildren?(parentId: string | null): string[];\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\ntype ReorderFn = (list: string[], ids: string[]) => string[];\n\nfunction arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n\n/** One parent's child order before a reorder, kept so undo can restore it. */\nexport interface ReorderRestoreEntry {\n parentId: string | null;\n before: string[];\n}\n\n/** Arguments to {@link createReorderOp}: move `ids` in paint order, one step or to the front/back. */\nexport interface ReorderArgs {\n ids: string[];\n direction: ReorderDirection;\n label?: string;\n /** Pre-mutation child order per touched parent. Filled in on first apply;\n * arrives populated when the op is rebuilt from a snapshot. */\n prevOrders?: ReorderRestoreEntry[];\n}\n\n/**\n * Build an op that, on apply, partitions `ids` by their current parent,\n * runs `fn(currentChildren, idsForParent)` per parent, and writes the\n * result back via setChildOrder. Records before-state per parent so invert\n * is exact.\n *\n * `prevOrders` is that before-state, mirrored into the op's serialized args\n * on first apply. A deserialized op has never run its own `apply`, so its\n * invert has nothing to capture — reading it back from the args is what lets\n * a reorder survive a reload, the same trick `createMoveToIndexOp` uses.\n */\nfunction createPartitionedReorderOp(args: {\n ids: string[];\n direction: ReorderDirection;\n fn: ReorderFn;\n label?: string;\n prevOrders?: ReorderRestoreEntry[];\n}): Op {\n const { ids, direction, fn, label, prevOrders } = args;\n const argsForSerial: ReorderArgs = {\n ids,\n direction,\n label,\n prevOrders: prevOrders ? prevOrders.map((e) => ({ ...e, before: e.before.slice() })) : undefined,\n };\n let restore: ReorderRestoreEntry[] | null =\n prevOrders ? prevOrders.map((e) => ({ ...e, before: e.before.slice() })) : null;\n\n return {\n name: 'reorder',\n args: argsForSerial,\n label,\n apply(adapter) {\n const a = adapter as ReorderAdapter;\n if (!a.getChildren || !a.setChildOrder) return false; // graceful no-op\n // Partition ids by current parent.\n const byParent = new Map<string | null, string[]>();\n for (const id of ids) {\n const parent = a.getParent ? a.getParent(id) : null;\n const list = byParent.get(parent) ?? [];\n list.push(id);\n byParent.set(parent, list);\n }\n const snapshots: ReorderRestoreEntry[] = [];\n let mutated = false;\n for (const [parentId, parentIds] of byParent) {\n const before = a.getChildren(parentId);\n const after = fn(before, parentIds);\n if (arraysEqual(before, after)) continue; // already at the desired order\n snapshots.push({ parentId, before: before.slice() });\n a.setChildOrder(parentId, after);\n mutated = true;\n }\n restore = snapshots;\n argsForSerial.prevOrders = snapshots.map((e) => ({ ...e, before: e.before.slice() }));\n return mutated;\n },\n invert() {\n const captured = restore;\n return {\n label,\n apply(adapter) {\n if (!captured) return;\n const a = adapter as ReorderAdapter;\n if (!a.setChildOrder) return;\n for (const entry of captured) {\n a.setChildOrder(entry.parentId, entry.before.slice());\n }\n },\n invert() {\n // Inverting twice should reapply the original; we don't support\n // this round-trip beyond two levels — the kit's history layer\n // doesn't need it. Return a no-op for safety.\n return { apply() {}, invert() { return this; } };\n },\n };\n },\n };\n}\n\n/** Direction of a sibling z-order reorder. */\nexport type ReorderDirection = 'forward' | 'backward' | 'front' | 'back';\n\nconst REORDER_DIRECTIONS: Record<ReorderDirection, { fn: ReorderFn; defaultLabel: string }> = {\n forward: { fn: bringForward, defaultLabel: 'Bring forward' },\n backward: { fn: sendBackward, defaultLabel: 'Send backward' },\n front: { fn: bringToFront, defaultLabel: 'Bring to front' },\n back: { fn: sendToBack, defaultLabel: 'Send to back' },\n};\n\n/**\n * Op: shift each id within its parent's child order per `direction`:\n * - `'forward'` — bump one step toward the top\n * - `'backward'` — bump one step toward the bottom\n * - `'front'` — move to the top, preserving relative order\n * - `'back'` — move to the bottom, preserving relative order\n */\nexport function createReorderOp(args: ReorderArgs): Op {\n const { ids, direction, label, prevOrders } = args;\n const { fn, defaultLabel } = REORDER_DIRECTIONS[direction];\n return createPartitionedReorderOp({\n ids, direction, fn, label: label ?? defaultLabel, prevOrders,\n });\n}\n\nregisterOpFactory<ReorderArgs>('reorder', (args) => createReorderOp(args));\n\n/** Arguments to {@link createMoveToIndexOp}: move `ids` to `index` under `parentId`. */\nexport interface MoveToIndexArgs {\n ids: string[];\n parentId: string | null;\n index: number;\n label?: string;\n /** Optional pre-mutation child order for the parent. When present, the op\n * uses it as the invert target instead of capturing at apply time — this\n * is how a deserialized op (whose original `apply` already ran in a prior\n * session) restores its invert behavior across an IDB round-trip. */\n prevPositions?: string[];\n}\n\n/** Op: move all `ids` to a contiguous block starting at `index` within `parentId`'s child order. */\nexport function createMoveToIndexOp(args: MoveToIndexArgs): Op {\n const { ids, parentId, index, label } = args;\n // Outer args record we'll mutate so the captured `before` lands in the\n // serialized form. `prevPositions` arrives populated when reconstructed\n // from a snapshot; otherwise it's filled in on first apply.\n const argsForSerial: MoveToIndexArgs = {\n ids,\n parentId,\n index,\n label,\n prevPositions: args.prevPositions ? args.prevPositions.slice() : undefined,\n };\n let before: string[] | null = args.prevPositions ? args.prevPositions.slice() : null;\n\n return {\n name: 'moveToIndex',\n args: argsForSerial,\n label: label ?? 'Move to index',\n apply(adapter) {\n const a = adapter as ReorderAdapter;\n if (!a.getChildren || !a.setChildOrder) return;\n // Filter to ids whose current parent matches target parent.\n const eligible = ids.filter((id) => {\n const p = a.getParent ? a.getParent(id) : null;\n return p === parentId;\n });\n const current = a.getChildren(parentId);\n // Only overwrite `before` the first time we apply; redo (a second\n // apply) must not clobber the originally-captured order, otherwise\n // invert would replay the post-move state and undo nothing.\n if (before === null) {\n before = current.slice();\n argsForSerial.prevPositions = before.slice();\n }\n const after = moveToIndex(current, eligible, index);\n a.setChildOrder(parentId, after);\n },\n invert() {\n const captured = before;\n return {\n label,\n apply(adapter) {\n if (!captured) return;\n const a = adapter as ReorderAdapter;\n a.setChildOrder?.(parentId, captured.slice());\n },\n invert() { return { apply() {}, invert() { return this; } }; },\n };\n },\n };\n}\n\nregisterOpFactory<MoveToIndexArgs>('moveToIndex', (args) => createMoveToIndexOp(args));\n"],"mappings":";;;AAoBA,SAASA,EAAY,GAAsB,GAA+B;CACxE,IAAIC,EAAE,WAAWC,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAIC,IAAI,GAAGA,IAAIF,EAAE,QAAQ,KAAK,IAAIA,EAAEE,OAAOD,EAAEC,IAAI,OAAO;CAC7D,OAAO;AACT;AA6BA,SAASC,EAA2B,GAM7B;CACL,IAAM,EAAE,QAAK,cAAW,OAAI,UAAO,kBAAeC,GAC5CC,IAA6B;EACjC;EACA;EACA;EACA,YAAYC,IAAaA,EAAW,KAAK,OAAO;GAAE,GAAG;GAAG,QAAQ,EAAE,OAAO,MAAM;EAAE,EAAE,IAAI,KAAA;CACzF,GACIC,IACFD,IAAaA,EAAW,KAAK,OAAO;EAAE,GAAG;EAAG,QAAQ,EAAE,OAAO,MAAM;CAAE,EAAE,IAAI;CAE7E,OAAO;EACL,MAAM;EACN,MAAMD;EACN;EACA,MAAM,GAAS;GACb,IAAML,IAAIQ;GACV,IAAI,CAACR,EAAE,eAAe,CAACA,EAAE,eAAe,OAAO;GAE/C,IAAMS,oBAAW,IAAI,IAA6B;GAClD,KAAK,IAAMC,KAAMC,GAAK;IACpB,IAAMC,IAASZ,EAAE,YAAYA,EAAE,UAAUU,CAAE,IAAI,MACzCG,IAAOJ,EAAS,IAAIG,CAAM,KAAK,CAAC;IAEtC,AADAC,EAAK,KAAKH,CAAE,GACZD,EAAS,IAAIG,GAAQC,CAAI;GAC3B;GACA,IAAMC,IAAmC,CAAC,GACtCC,IAAU;GACd,KAAK,IAAM,CAACC,GAAUC,MAAcR,GAAU;IAC5C,IAAMS,IAASlB,EAAE,YAAYgB,CAAQ,GAC/BG,IAAQC,EAAGF,GAAQD,CAAS;IAClC,AAAIlB,EAAYmB,GAAQC,CAAK,MAC7BL,EAAU,KAAK;KAAE;KAAU,QAAQI,EAAO,MAAM;IAAE,CAAC,GACnDlB,EAAE,cAAcgB,GAAUG,CAAK,GAC/B,IAAU;GACZ;GAGA,OAFA,IAAUL,GACV,EAAc,aAAaA,EAAU,KAAK,OAAO;IAAE,GAAG;IAAG,QAAQ,EAAE,OAAO,MAAM;GAAE,EAAE,GAC7EC;EACT;EACA,SAAS;GACP,IAAMM,IAAWd;GACjB,OAAO;IACL;IACA,MAAM,GAAS;KACb,IAAI,CAACc,GAAU;KACf,IAAMrB,IAAIQ;KACV,IAAKR,EAAE,eACP,KAAK,IAAMsB,KAASD,GAClB,EAAE,cAAcC,EAAM,UAAUA,EAAM,OAAO,MAAM,CAAC;IAExD;IACA,SAAS;KAIP,OAAO;MAAE,QAAQ,CAAC;MAAG,SAAS;OAAE,OAAO;MAAM;KAAE;IACjD;GACF;EACF;CACF;AACF;AAKA,IAAMC,IAAwF;CAC5F,SAAU;EAAE,IAAIC;EAAe,cAAc;CAAgB;CAC7D,UAAU;EAAE,IAAIC;EAAe,cAAc;CAAgB;CAC7D,OAAU;EAAE,IAAIC;EAAe,cAAc;CAAiB;CAC9D,MAAU;EAAE,IAAIC;EAAe,cAAc;CAAe;AAC9D;AASA,SAAgBC,EAAgB,GAAuB;CACrD,IAAM,EAAE,QAAK,cAAW,UAAO,kBAAexB,GACxC,EAAE,OAAI,oBAAiBmB,EAAmBM;CAChD,OAAO1B,EAA2B;EAChC;EAAK;EAAW;EAAI,OAAO2B,KAASC;EAAc;CACpD,CAAC;AACH;AAEAC,EAA+B,YAAY,MAASJ,EAAgBxB,CAAI,CAAC;AAgBzE,SAAgB6B,EAAoB,GAA2B;CAC7D,IAAM,EAAE,QAAK,aAAU,UAAO,aAAU7B,GAIlCC,IAAiC;EACrC;EACA;EACA;EACA;EACA,eAAeD,EAAK,gBAAgBA,EAAK,cAAc,MAAM,IAAI,KAAA;CACnE,GACIc,IAA0Bd,EAAK,gBAAgBA,EAAK,cAAc,MAAM,IAAI;CAEhF,OAAO;EACL,MAAM;EACN,MAAMC;EACN,OAAOyB,KAAS;EAChB,MAAM,GAAS;GACb,IAAM9B,IAAIQ;GACV,IAAI,CAACR,EAAE,eAAe,CAACA,EAAE,eAAe;GAExC,IAAMkC,IAAWvB,EAAI,QAAQ,OACjBX,EAAE,YAAYA,EAAE,UAAUU,CAAE,IAAI,UAC7BM,CACd,GACKmB,IAAUnC,EAAE,YAAYgB,CAAQ;GAItC,AAAIE,MAAW,SACb,IAASiB,EAAQ,MAAM,GACvB,EAAc,gBAAgBjB,EAAO,MAAM;GAE7C,IAAMC,IAAQiB,EAAYD,GAASD,GAAUG,CAAK;GAClD,EAAE,cAAcrB,GAAUG,CAAK;EACjC;EACA,SAAS;GACP,IAAME,IAAWH;GACjB,OAAO;IACL;IACA,MAAM,GAAS;KACb,AAAKG,KAELb,EAAE,gBAAgBQ,GAAUK,EAAS,MAAM,CAAC;IAC9C;IACA,SAAS;KAAE,OAAO;MAAE,QAAQ,CAAC;MAAG,SAAS;OAAE,OAAO;MAAM;KAAE;IAAG;GAC/D;EACF;CACF;AACF;AAEAW,EAAmC,gBAAgB,MAASC,EAAoB7B,CAAI,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["arraysEqual","a","b","i","createPartitionedReorderOp","args","argsForSerial","prevOrders","restore","adapter","byParent","id","ids","parent","list","snapshots","mutated","parentId","parentIds","before","after","fn","captured","createSetChildOrderOp","REORDER_DIRECTIONS","bringForward","sendBackward","bringToFront","sendToBack","createReorderOp","direction","label","defaultLabel","registerOpFactory","createMoveToIndexOp","eligible","current","moveToIndex","index","canBringForward","canSendBackward"],"sources":["../../../../src/core/ops/reorder/index.ts"],"sourcesContent":["import type { Op } from '../types';\nimport { registerOpFactory } from '../registry';\nimport {\n bringForward,\n sendBackward,\n bringToFront,\n sendToBack,\n moveToIndex,\n} from './algorithms';\n\nimport { createSetChildOrderOp } from './setChildOrder';\n\nexport { canBringForward, canSendBackward } from './algorithms';\nexport { createSetChildOrderOp, type ChildOrder, type SetChildOrderArgs } from './setChildOrder';\n\ninterface ReorderAdapter {\n getParent?(id: string): string | null;\n getChildren?(parentId: string | null): string[];\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\ntype ReorderFn = (list: string[], ids: string[]) => string[];\n\nfunction arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n\n/** One parent's child order before a reorder, kept so undo can restore it. */\nexport interface ReorderRestoreEntry {\n parentId: string | null;\n before: string[];\n}\n\n/** Arguments to {@link createReorderOp}: move `ids` in paint order, one step or to the front/back. */\nexport interface ReorderArgs {\n ids: string[];\n direction: ReorderDirection;\n label?: string;\n /** Pre-mutation child order per touched parent. Filled in on first apply;\n * arrives populated when the op is rebuilt from a snapshot. */\n prevOrders?: ReorderRestoreEntry[];\n}\n\n/**\n * Build an op that, on apply, partitions `ids` by their current parent,\n * runs `fn(currentChildren, idsForParent)` per parent, and writes the\n * result back via setChildOrder. Records before-state per parent so invert\n * is exact.\n *\n * `prevOrders` is that before-state, mirrored into the op's serialized args\n * on first apply. A deserialized op has never run its own `apply`, so its\n * invert has nothing to capture — reading it back from the args is what lets\n * a reorder survive a reload, the same trick `createMoveToIndexOp` uses.\n */\nfunction createPartitionedReorderOp(args: {\n ids: string[];\n direction: ReorderDirection;\n fn: ReorderFn;\n label?: string;\n prevOrders?: ReorderRestoreEntry[];\n}): Op {\n const { ids, direction, fn, label, prevOrders } = args;\n const argsForSerial: ReorderArgs = {\n ids,\n direction,\n label,\n prevOrders: prevOrders ? prevOrders.map((e) => ({ ...e, before: e.before.slice() })) : undefined,\n };\n let restore: ReorderRestoreEntry[] | null =\n prevOrders ? prevOrders.map((e) => ({ ...e, before: e.before.slice() })) : null;\n\n return {\n name: 'reorder',\n args: argsForSerial,\n label,\n apply(adapter) {\n const a = adapter as ReorderAdapter;\n if (!a.getChildren || !a.setChildOrder) return false; // graceful no-op\n // Partition ids by current parent.\n const byParent = new Map<string | null, string[]>();\n for (const id of ids) {\n const parent = a.getParent ? a.getParent(id) : null;\n const list = byParent.get(parent) ?? [];\n list.push(id);\n byParent.set(parent, list);\n }\n const snapshots: ReorderRestoreEntry[] = [];\n let mutated = false;\n for (const [parentId, parentIds] of byParent) {\n const before = a.getChildren(parentId);\n const after = fn(before, parentIds);\n if (arraysEqual(before, after)) continue; // already at the desired order\n snapshots.push({ parentId, before: before.slice() });\n a.setChildOrder(parentId, after);\n mutated = true;\n }\n restore = snapshots;\n argsForSerial.prevOrders = snapshots.map((e) => ({ ...e, before: e.before.slice() }));\n return mutated;\n },\n invert() {\n const captured = restore;\n return {\n ...createSetChildOrderOp({\n orders: (captured ?? []).map((e) => ({ parentId: e.parentId, ids: e.before })),\n label,\n }),\n invert: () =>\n createPartitionedReorderOp({\n ids, direction, fn, label, prevOrders: captured ?? undefined,\n }),\n };\n },\n };\n}\n\n/** Direction of a sibling z-order reorder. */\nexport type ReorderDirection = 'forward' | 'backward' | 'front' | 'back';\n\nconst REORDER_DIRECTIONS: Record<ReorderDirection, { fn: ReorderFn; defaultLabel: string }> = {\n forward: { fn: bringForward, defaultLabel: 'Bring forward' },\n backward: { fn: sendBackward, defaultLabel: 'Send backward' },\n front: { fn: bringToFront, defaultLabel: 'Bring to front' },\n back: { fn: sendToBack, defaultLabel: 'Send to back' },\n};\n\n/**\n * Op: shift each id within its parent's child order per `direction`:\n * - `'forward'` — bump one step toward the top\n * - `'backward'` — bump one step toward the bottom\n * - `'front'` — move to the top, preserving relative order\n * - `'back'` — move to the bottom, preserving relative order\n */\nexport function createReorderOp(args: ReorderArgs): Op {\n const { ids, direction, label, prevOrders } = args;\n const { fn, defaultLabel } = REORDER_DIRECTIONS[direction];\n return createPartitionedReorderOp({\n ids, direction, fn, label: label ?? defaultLabel, prevOrders,\n });\n}\n\nregisterOpFactory<ReorderArgs>('reorder', (args) => createReorderOp(args));\n\n/** Arguments to {@link createMoveToIndexOp}: move `ids` to `index` under `parentId`. */\nexport interface MoveToIndexArgs {\n ids: string[];\n parentId: string | null;\n index: number;\n label?: string;\n /** Optional pre-mutation child order for the parent. When present, the op\n * uses it as the invert target instead of capturing at apply time — this\n * is how a deserialized op (whose original `apply` already ran in a prior\n * session) restores its invert behavior across an IDB round-trip. */\n prevPositions?: string[];\n}\n\n/** Op: move all `ids` to a contiguous block starting at `index` within `parentId`'s child order. */\nexport function createMoveToIndexOp(args: MoveToIndexArgs): Op {\n const { ids, parentId, index, label } = args;\n // Outer args record we'll mutate so the captured `before` lands in the\n // serialized form. `prevPositions` arrives populated when reconstructed\n // from a snapshot; otherwise it's filled in on first apply.\n const argsForSerial: MoveToIndexArgs = {\n ids,\n parentId,\n index,\n label,\n prevPositions: args.prevPositions ? args.prevPositions.slice() : undefined,\n };\n let before: string[] | null = args.prevPositions ? args.prevPositions.slice() : null;\n\n return {\n name: 'moveToIndex',\n args: argsForSerial,\n label: label ?? 'Move to index',\n apply(adapter) {\n const a = adapter as ReorderAdapter;\n if (!a.getChildren || !a.setChildOrder) return;\n // Filter to ids whose current parent matches target parent.\n const eligible = ids.filter((id) => {\n const p = a.getParent ? a.getParent(id) : null;\n return p === parentId;\n });\n const current = a.getChildren(parentId);\n // Only overwrite `before` the first time we apply; redo (a second\n // apply) must not clobber the originally-captured order, otherwise\n // invert would replay the post-move state and undo nothing.\n if (before === null) {\n before = current.slice();\n argsForSerial.prevPositions = before.slice();\n }\n const after = moveToIndex(current, eligible, index);\n a.setChildOrder(parentId, after);\n },\n invert() {\n const captured = before;\n return {\n ...createSetChildOrderOp({ orders: captured ? [{ parentId, ids: captured }] : [], label }),\n invert: () =>\n createMoveToIndexOp({\n ids, parentId, index, label, prevPositions: captured ?? undefined,\n }),\n };\n },\n };\n}\n\nregisterOpFactory<MoveToIndexArgs>('moveToIndex', (args) => createMoveToIndexOp(args));\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { registerOpFactory as e } from "../registry.js";
|
|
2
|
+
//#region src/core/ops/reorder/setChildOrder.ts
|
|
3
|
+
function t(e) {
|
|
4
|
+
let { orders: n, label: r } = e, i = {
|
|
5
|
+
orders: n,
|
|
6
|
+
label: r,
|
|
7
|
+
prevOrders: e.prevOrders
|
|
8
|
+
}, a = e.prevOrders ?? null;
|
|
9
|
+
return {
|
|
10
|
+
name: "setChildOrder",
|
|
11
|
+
args: i,
|
|
12
|
+
label: r,
|
|
13
|
+
apply(e) {
|
|
14
|
+
let t = e;
|
|
15
|
+
if (!t.setChildOrder) return !1;
|
|
16
|
+
let { getChildren: r } = t;
|
|
17
|
+
a === null && r && (a = n.map((e) => ({
|
|
18
|
+
parentId: e.parentId,
|
|
19
|
+
ids: r.call(t, e.parentId).slice()
|
|
20
|
+
})), i.prevOrders = a);
|
|
21
|
+
for (let e of n) t.setChildOrder(e.parentId, e.ids.slice());
|
|
22
|
+
},
|
|
23
|
+
invert: () => t({
|
|
24
|
+
orders: a ?? [],
|
|
25
|
+
label: r,
|
|
26
|
+
prevOrders: n
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
e("setChildOrder", (e) => t(e));
|
|
31
|
+
//#endregion
|
|
32
|
+
export { t as createSetChildOrderOp };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=setChildOrder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setChildOrder.js","names":["createSetChildOrderOp","args","argsForSerial","before","a","adapter","getChildren","orders","o","registerOpFactory"],"sources":["../../../../src/core/ops/reorder/setChildOrder.ts"],"sourcesContent":["import type { Op } from '../types';\nimport { registerOpFactory } from '../registry';\n\ninterface ChildOrderAdapter {\n getChildren?(parentId: string | null): string[];\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** One parent's children, in order. */\nexport interface ChildOrder {\n parentId: string | null;\n ids: string[];\n}\n\n/** Arguments to {@link createSetChildOrderOp}: the whole child order to write, per parent. */\nexport interface SetChildOrderArgs {\n orders: ChildOrder[];\n label?: string;\n /** Each parent's order before the write. Filled in on first apply;\n * arrives populated when the op is rebuilt from a snapshot. */\n prevOrders?: ChildOrder[];\n}\n\n/** Op: write each listed parent's whole child order; inverts to a write of the\n * orders it replaced. Where `createMoveToIndexOp` keeps the moved ids in their\n * current relative order, this states every position. */\nexport function createSetChildOrderOp(args: SetChildOrderArgs): Op {\n const { orders, label } = args;\n const argsForSerial: SetChildOrderArgs = { orders, label, prevOrders: args.prevOrders };\n let before: ChildOrder[] | null = args.prevOrders ?? null;\n return {\n name: 'setChildOrder',\n args: argsForSerial,\n label,\n apply(adapter) {\n const a = adapter as ChildOrderAdapter;\n if (!a.setChildOrder) return false;\n const { getChildren } = a;\n if (before === null && getChildren) {\n before = orders.map((o) => ({ parentId: o.parentId, ids: getChildren.call(a, o.parentId).slice() }));\n argsForSerial.prevOrders = before;\n }\n for (const o of orders) a.setChildOrder(o.parentId, o.ids.slice());\n return undefined;\n },\n invert: () => createSetChildOrderOp({ orders: before ?? [], label, prevOrders: orders }),\n };\n}\n\nregisterOpFactory<SetChildOrderArgs>('setChildOrder', (args) => createSetChildOrderOp(args));\n"],"mappings":";;AA0BA,SAAgBA,EAAsB,GAA6B;CACjE,IAAM,EAAE,WAAQ,aAAUC,GACpBC,IAAmC;EAAE;EAAQ;EAAO,YAAYD,EAAK;CAAW,GAClFE,IAA8BF,EAAK,cAAc;CACrD,OAAO;EACL,MAAM;EACN,MAAMC;EACN;EACA,MAAM,GAAS;GACb,IAAME,IAAIC;GACV,IAAI,CAACD,EAAE,eAAe,OAAO;GAC7B,IAAM,EAAE,mBAAgBA;GACxB,AAAID,MAAW,QAAQG,MACrB,IAASC,EAAO,KAAK,OAAO;IAAE,UAAUC,EAAE;IAAU,KAAKF,EAAY,KAAKF,GAAGI,EAAE,QAAQ,CAAC,CAAC,MAAM;GAAE,EAAE,GACnG,EAAc,aAAaL;GAE7B,KAAK,IAAMK,KAAKD,GAAQ,EAAE,cAAcC,EAAE,UAAUA,EAAE,IAAI,MAAM,CAAC;EAEnE;EACA,cAAcR,EAAsB;GAAE,QAAQG,KAAU,CAAC;GAAG;GAAO,YAAYI;EAAO,CAAC;CACzF;AACF;AAEAE,EAAqC,kBAAkB,MAAST,EAAsBC,CAAI,CAAC"}
|
package/dist/core/paintKinds.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { gradientForBounds as e, isGradientFill as t, withGradientKind as n } from "./gradient.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getProgramSource as r, registerProgram as i } from "../renderer/shaders/registerProgram.js";
|
|
3
|
+
import { bumpNodeMemoGeneration as a } from "./scene/nodeMemo.js";
|
|
4
4
|
import { createReflectable as o } from "@weasel-js/registry";
|
|
5
5
|
//#region src/core/paintKinds.ts
|
|
6
6
|
var s = [
|
|
@@ -60,7 +60,7 @@ var d = o();
|
|
|
60
60
|
function f() {
|
|
61
61
|
for (let e of s) d.push(e.id, e, { source: "kit" });
|
|
62
62
|
}
|
|
63
|
-
f(), d.subscribe(
|
|
63
|
+
f(), d.subscribe(a);
|
|
64
64
|
var p = d.reflection;
|
|
65
65
|
function m(e) {
|
|
66
66
|
return e;
|
|
@@ -75,8 +75,8 @@ function h(e) {
|
|
|
75
75
|
throw Error(`weasel registerPaintKind: kind "${e.id}" is missing ${t}. A kind that converts one frame direction and not the other drifts on the next resize; supply both or neither.`);
|
|
76
76
|
}
|
|
77
77
|
for (let [t, n] of Object.entries(e.programs ?? {})) {
|
|
78
|
-
let e =
|
|
79
|
-
(!e || e.vert !== n.vert || e.frag !== n.frag) &&
|
|
78
|
+
let e = r(t);
|
|
79
|
+
(!e || e.vert !== n.vert || e.frag !== n.frag) && i(t, n.vert, n.frag);
|
|
80
80
|
}
|
|
81
81
|
return d.push(e.id, e);
|
|
82
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paintKinds.js","names":["BUILTINS","paint","gradientKind","gradientForBounds","id","UNIT_BOX","twoStopRamp","color","stops","isWhite","KINDS","createReflectable","seedBuiltins","entry","bumpNodeMemoGeneration","paintKindRegistry","asPaint","registerPaintKind","missing","src","have","getProgramSource","registerProgram","getPaintKind","kind","LOADERS","startLoad","LOADS","LAZY_BUILTINS","seedLoaders","load","registerPaintKindLoader","running","done","err","isPaintKindKnown","warmPaintKinds","ids","kinds","loads","derivedList","from","list","entries","derive","listPaintKinds","allKinds","all","listGradientKinds","gradientKinds","switchGradientKind","paintKindOf","to","isGradientFill","withGradientKind","interpolate","opacity","fill","_resetPaintKindsForTests"],"sources":["../../src/core/paintKinds.ts"],"sourcesContent":["/**\n * The paint-kind registry — what makes `FillStyle` open.\n *\n * A consumer registers a sixth kind and it renders, converts between the\n * bounds and pose frames, and serializes, with no kit edits. The five built-in\n * kinds are registered here at module load so an editor's kind bar can\n * enumerate every kind through one list.\n *\n * Each kit layer consults this registry for a kind it does not recognize and\n * otherwise runs its own built-in branch. That split is not laziness: a built-in\n * kind's render slot lives in the GL renderer, its serialize slot lives in\n * `@weasel-js/svg`, and neither can be imported from here without inverting a\n * package dependency.\n */\n\nimport type {\n ColorSpace, FillStyle, GradientFill, GradientKind, GradientUnits, GradStop, TilePatternSpec,\n} from '@weasel-js/paint';\nimport { gradientForBounds, isGradientFill, withGradientKind } from './gradient';\nimport { bumpNodeMemoGeneration } from './scene/nodeMemo';\nimport type { ComponentType } from 'react';\nimport { createReflectable, type ReflectedEntry, type Reflection } from '@weasel-js/registry';\nimport type { FillPoseBox } from './fillInPoseFrame';\nimport type { GlMat3 } from '../renderer/math/mat3';\nimport type { ShaderProgram } from '../renderer/shaders/ShaderProgram';\nimport { getProgramSource, registerProgram, type ProgramSource } from '../renderer/shaders/registerProgram';\n\n/** A compiled GL program. A paint kind gets one from\n * {@link PaintBindContext.program} and hands it back from `bind`. */\nexport type PaintProgram = ShaderProgram;\n\n/**\n * `FillStyle`'s discriminant, open on the string the way `ChromeId` is: the\n * five kinds the kit ships, plus whatever a consumer registers.\n */\nexport type PaintKind = 'solid' | 'linear-gradient' | 'radial-gradient' | 'conic-gradient' | 'pattern' | (string & {});\n\n/** What a registered kind's editor renders. `PaintInput` in `@weasel-js/ui`\n * is the control that mounts it. */\nexport interface PaintKindEditorProps {\n value: FillStyle;\n onInput?(next: FillStyle): void;\n onChange(next: FillStyle): void;\n}\n\n/**\n * The lifetime a paint kind's GPU state belongs to: one renderer on one live\n * context. The object itself is the key — a kind caches textures in a\n * `WeakMap` on it, so two renderers sharing a context never trade them — and\n * `onRelease` is where it frees them.\n */\nexport interface PaintResources {\n /** Run `release` once, when this lifetime ends: the renderer is disposed, or\n * its context was lost and restored. Free every GL object made under it\n * there. */\n onRelease(release: () => void): void;\n}\n\n/**\n * The renderer surface a paint kind binds against, narrowed to what a paint\n * needs. The full `DrawContext` is not consumer surface.\n */\nexport interface PaintBindContext {\n readonly gl: WebGL2RenderingContext;\n /** The group alpha this draw inherits — multiply it into the paint's own. */\n readonly alpha: number;\n /**\n * The compiled program for a `registerProgram` id, compiled against this\n * renderer on first use. `null` when no source is registered under `id` or\n * compilation failed.\n */\n program(id: string): PaintProgram | null;\n /** Send `u_proj` and `u_model`, which every kit vertex shader takes. */\n setProjAndModel(program: PaintProgram): void;\n /**\n * `u_worldInv` for a paint declaring `units` — the inverse of the transform\n * from the paint's space to the frame the geometry arrives in. Paired with\n * the vertex shader's `v_world` varying this is *the* paint-space\n * convention; it is not gradient-specific.\n *\n * `null` when that space has no inverse — a transform or view that flattens\n * an axis. Nothing the paint draws there means anything, so `bind` should\n * return `null` and draw nothing.\n */\n spaceInverse(units: GradientUnits | undefined): GlMat3 | null;\n /**\n * The transform from the space `units` names to device pixels of the target\n * this draw lands in — screen pixels times the pixel ratio on screen, output\n * pixels in a headless render. It is what a paint baking a texture measures\n * its resolution against. `null` where `spaceInverse` is.\n */\n spaceToDevice(units: GradientUnits | undefined): GlMat3 | null;\n /** The largest texture side this context accepts, `MAX_TEXTURE_SIZE`. */\n readonly maxTextureSize: number;\n /** The lifetime GPU state made for this draw belongs to. Stable across the\n * frames of one renderer, so it is the key for a kind's texture cache. */\n readonly resources: PaintResources;\n /**\n * Bake a stop ramp into the frame's ramp atlas, bind that atlas to a texture\n * unit, and return the `v` the ramp's own row sits at — every ramp in a\n * frame shares one texture, so a paint must sample at the returned `v` and\n * not at a constant.\n *\n * `space` is the blend the stops are baked through, defaulting to `'rgb'`.\n * It is part of the atlas key, so the same stops under two spaces take two\n * rows rather than colliding.\n */\n bindRamp(stops: GradStop[], unit: number, space?: ColorSpace): number;\n}\n\n/**\n * One paint kind.\n *\n * `seed`, `label` and `colorOf` are the editor's slots and every kind has\n * them. The rest are optional because a kind may not need them — but\n * `inPoseFrame` and `toBoundsFrame` come as a pair or not at all: a kind that\n * converts one direction and not the other paints correctly once and then\n * drifts on the next resize.\n */\nexport interface PaintKindEntry {\n id: string;\n label: string;\n /** Glyph naming this kind in an editor's kind bar, as an icon name the UI\n * layer resolves — the same indirection `ToolPrefBase.icon` uses. A kind\n * without one is named by its `label`. */\n icon?: string;\n /** A paint of this kind seeded from a color — what an editor writes when a\n * consumer switches a solid to this kind. */\n seed(fromColor: string): FillStyle;\n /** The single color this paint shows, or `undefined` when it has none. */\n colorOf(paint: FillStyle): string | undefined;\n /** Editor slot. */\n Editor?: ComponentType<PaintKindEditorProps>;\n /**\n * The stop list this paint reads as. With `fromStops`, it is what makes a\n * kind a gradient: `listGradientKinds` returns the kinds carrying both, and\n * `switchGradientKind` converts between any two of them through the list.\n */\n stopsOf?(paint: FillStyle): GradStop[];\n /** A paint of this kind built from a stop list, in `bounds` units. The\n * converse of `stopsOf`; supply both or neither. */\n fromStops?(stops: GradStop[]): FillStyle;\n /**\n * Render slot: bind program, uniforms and textures for `fill` and return the\n * bound program; `null` declines the paint.\n *\n * Binding is split from drawing on purpose — a caller owning its own stencil\n * state (an inner/outer-aligned stroke, an even-odd fill) must issue its own\n * draw call, and the kit's draw wrapper would clobber that state. Shader\n * output must be premultiplied: `outColor = vec4(rgb * a, a)`.\n *\n * The geometry is not always the node's own. Text below the outline tier\n * paints by drawing a quad over its glyphs' screen box into an offscreen\n * buffer and masking that with glyph coverage, so a paint must come from\n * `v_world` through `spaceInverse`, not from where `a_position` happens to\n * lie.\n */\n bind?(ctx: PaintBindContext, fill: FillStyle): PaintProgram | null;\n /** Bounds frame → the frame the node is painted in. */\n inPoseFrame?(fill: FillStyle, box: FillPoseBox): FillStyle;\n /** The inverse. Required whenever `inPoseFrame` is supplied. */\n toBoundsFrame?(fill: FillStyle, box: FillPoseBox): FillStyle;\n /**\n * The `<defs>` entry backing a `url(#id)` reference.\n *\n * SVG has paint servers for two gradients and a pattern and nothing else, so\n * a kind beyond those has no element to target: write a foreign-namespaced\n * one under the `wzl:` prefix `@weasel-js/svg` declares on the root whenever\n * a document holds such a paint. That package emits the fallback color\n * beside the reference on its own, from `colorOf`.\n */\n toSvg?(id: string, fill: FillStyle): string;\n /**\n * Shader programs `bind` looks up through `ctx.program`, keyed by program\n * id; registering the kind registers them. Declaring them here rather than\n * calling `registerProgram` at import keeps a kind loaded by\n * {@link registerPaintKindLoader} free of core's registries.\n */\n programs?: Readonly<Record<string, ProgramSource>>;\n}\n\nconst BUILTINS: readonly PaintKindEntry[] = [\n {\n id: 'solid',\n label: 'Solid',\n icon: 'paintSolid',\n seed: (color) => ({ color }),\n colorOf: (paint) => ((paint.fill ?? 'solid') === 'solid'\n ? (paint as { color: string }).color\n : undefined),\n },\n gradientKind('linear-gradient', 'Linear', 'paintLinear'),\n gradientKind('radial-gradient', 'Radial', 'paintRadial'),\n gradientKind('conic-gradient', 'Conic', 'paintConic'),\n {\n id: 'pattern',\n label: 'Pattern',\n icon: 'paintPattern',\n seed: (color) => ({\n fill: 'pattern',\n pattern: { tile: 'hatch', color },\n units: 'bounds',\n }),\n colorOf: (paint) => (paint.fill === 'pattern'\n ? (paint.pattern as Partial<TilePatternSpec>).color\n : undefined),\n },\n];\n\nfunction gradientKind(id: GradientKind, label: string, icon: string): PaintKindEntry {\n return {\n id,\n label,\n icon,\n // `bounds` units make the seed independent of the node's actual size.\n seed: (color) => gradientForBounds(id, UNIT_BOX, twoStopRamp(color), 'bounds'),\n colorOf: (paint) => (paint as Partial<GradientFill>).stops?.[0]?.color,\n stopsOf: (paint) => (paint as GradientFill).stops,\n fromStops: (stops) => gradientForBounds(id, UNIT_BOX, stops, 'bounds'),\n };\n}\n\nconst UNIT_BOX = { x: 0, y: 0, width: 1, height: 1 };\n\n/** `color` to a contrasting end — white reads against any hue; against white\n * itself, black does. */\nfunction twoStopRamp(color: string): GradStop[] {\n const isWhite = color.slice(0, 7).toLowerCase() === '#ffffff';\n return [{ offset: 0, color }, { offset: 1, color: isWhite ? '#000000ff' : '#ffffffff' }];\n}\n\nconst KINDS = createReflectable<PaintKindEntry>();\n\nfunction seedBuiltins(): void {\n for (const entry of BUILTINS) KINDS.push(entry.id, entry, { source: 'kit' });\n}\nseedBuiltins();\n// `NodeShape`'s paint slot memoizes per node and resolves a fill's frame\n// inside it, so the kind set is ambient state that memo cannot see change.\nKINDS.subscribe(bumpNodeMemoGeneration);\n\n/** Every registered paint kind, with the overrides each displaced. */\nexport const paintKindRegistry: Reflection<PaintKindEntry> = KINDS.reflection;\n\n/**\n * A consumer's own paint, typed as a `FillStyle`.\n *\n * `FillStyle` stays a closed union: opening its discriminant would widen every\n * built-in member and break the narrowing the kit's own branches depend on.\n * A registered kind declares its own interface instead and passes it through\n * here — the kit reads only `fill` and hands the whole object back to that\n * kind's slots.\n */\nexport function asPaint<T extends { fill: string }>(paint: T): FillStyle {\n return paint as unknown as FillStyle;\n}\n\n/** Register a paint kind. Returns a disposer that removes it. Re-registering an\n * existing id is an override; disposing it uncovers whatever it displaced. */\nexport function registerPaintKind(entry: PaintKindEntry): () => void {\n if ((entry.stopsOf === undefined) !== (entry.fromStops === undefined)) {\n const missing = entry.stopsOf === undefined ? 'stopsOf' : 'fromStops';\n throw new Error(\n `weasel registerPaintKind: kind \"${entry.id}\" is missing ${missing}. A ` +\n 'gradient kind reads as a stop list and builds from one; supply both or neither.',\n );\n }\n if ((entry.inPoseFrame === undefined) !== (entry.toBoundsFrame === undefined)) {\n const missing = entry.inPoseFrame === undefined ? 'inPoseFrame' : 'toBoundsFrame';\n throw new Error(\n `weasel registerPaintKind: kind \"${entry.id}\" is missing ${missing}. A kind ` +\n 'that converts one frame direction and not the other drifts on the next ' +\n 'resize; supply both or neither.',\n );\n }\n for (const [id, src] of Object.entries(entry.programs ?? {})) {\n const have = getProgramSource(id);\n if (!have || have.vert !== src.vert || have.frag !== src.frag) registerProgram(id, src.vert, src.frag);\n }\n // Re-registering a built-in id is how a consumer closes a gap the kit leaves,\n // so disposing that override puts the built-in back rather than deleting the\n // kind.\n return KINDS.push(entry.id, entry);\n}\n\n/** The entry for `kind`, or `undefined`. A lookup that misses on a kind with\n * a loader starts that load; see {@link registerPaintKindLoader}. */\nexport function getPaintKind(kind: string | undefined): PaintKindEntry | undefined {\n const id = kind ?? 'solid';\n const entry = KINDS.get(id);\n if (!entry && LOADERS.has(id)) void startLoad(id).catch(() => {});\n return entry;\n}\n\n/** Resolves with the entry for a kind that is loaded on first use. */\nexport type PaintKindLoader = () => Promise<PaintKindEntry>;\n\nconst LOADERS = new Map<string, PaintKindLoader>();\n\n/** One load per kind, kept after it settles: a failed load is not retried on\n * every frame that meets the kind. */\nconst LOADS = new Map<string, Promise<void>>();\n\n// A built-in kind heavy enough to keep off a consumer's bundle until a paint\n// of it turns up. Importing the module statically still registers it at once.\nconst LAZY_BUILTINS: ReadonlyArray<readonly [string, PaintKindLoader]> = [\n ['mesh-gradient', async () => (await import('../features/meshPaint/lazyKind')).meshGradientKind],\n];\n\nfunction seedLoaders(): void {\n LOADERS.clear();\n LOADS.clear();\n for (const [id, load] of LAZY_BUILTINS) LOADERS.set(id, load);\n}\nseedLoaders();\n\n/**\n * Declare a paint kind that is loaded the first time something looks it up,\n * so its code stays out of the bundle until a paint of it appears — typically\n * `() => import('./myKind').then((m) => m.myKindEntry)`.\n *\n * Until it lands the kind is unregistered: the renderer draws nothing for its\n * paints, and every other reader treats it as unknown. Registering it fires\n * `paintKindRegistry`'s subscribers, which is what `<SceneCanvas>` repaints\n * on. Call {@link warmPaintKinds} to load ahead of the first frame instead.\n *\n * A kind that is already registered never calls its loader. Returns a\n * disposer that removes the loader.\n */\nexport function registerPaintKindLoader(id: string, load: PaintKindLoader): () => void {\n LOADERS.set(id, load);\n return () => {\n if (LOADERS.get(id) === load) LOADERS.delete(id);\n };\n}\n\nfunction startLoad(id: string): Promise<void> {\n const running = LOADS.get(id);\n if (running) return running;\n const load = LOADERS.get(id)!;\n const done = load().then(\n (entry) => {\n if (entry.id !== id) {\n throw new Error(`weasel: the loader for paint kind \"${id}\" resolved to \"${entry.id}\".`);\n }\n if (!KINDS.has(id)) registerPaintKind(entry);\n },\n ).catch((err: unknown) => {\n console.error(`weasel: paint kind \"${id}\" failed to load:`, err);\n throw err;\n });\n LOADS.set(id, done);\n return done;\n}\n\n/** Is `id` registered, or declared with a loader? */\nexport function isPaintKindKnown(id: string): boolean {\n return KINDS.has(id) || LOADERS.has(id);\n}\n\n/**\n * Load paint kinds ahead of their first use, so the first frame that meets\n * one draws it rather than a blank. With no list, loads every kind that has a\n * loader — the kit's lazily loaded built-ins (`mesh-gradient`) plus any a\n * consumer declared with {@link registerPaintKindLoader}.\n *\n * Resolves once every kind is registered; a kind already registered counts\n * as loaded. Rejects when a load fails, or for a kind that is neither\n * registered nor loadable.\n */\nexport function warmPaintKinds(kinds?: readonly string[]): Promise<void> {\n const ids = kinds ?? [...LOADERS.keys()];\n const loads: Promise<void>[] = [];\n for (const id of ids) {\n if (KINDS.has(id)) continue;\n if (!LOADERS.has(id)) {\n return Promise.reject(new Error(\n `weasel warmPaintKinds: \"${id}\" is neither registered nor loadable.`,\n ));\n }\n loads.push(startLoad(id));\n }\n return Promise.all(loads).then(() => undefined);\n}\n\n/** A list derived from the registry, recomputed only when it changes — so\n * its identity is a `useSyncExternalStore` snapshot. */\nfunction derivedList(\n derive: (all: readonly ReflectedEntry<PaintKindEntry>[]) => readonly PaintKindEntry[],\n): () => readonly PaintKindEntry[] {\n let from: readonly ReflectedEntry<PaintKindEntry>[] | undefined;\n let list: readonly PaintKindEntry[] = [];\n return () => {\n const entries = KINDS.entries();\n if (entries !== from) {\n from = entries;\n list = Object.freeze(derive(entries));\n }\n return list;\n };\n}\n\n/** Every registered kind, built-ins first, in registration order. The same\n * array until the registry changes. */\nexport function listPaintKinds(): readonly PaintKindEntry[] {\n return allKinds();\n}\nconst allKinds = derivedList((all) => all.map((e) => e.value));\n\n/** Every registered gradient kind — the ones that read as and build from a\n * stop list — in registration order. The same array until the registry\n * changes. */\nexport function listGradientKinds(): readonly PaintKindEntry[] {\n return gradientKinds();\n}\nconst gradientKinds = derivedList(\n (all) => all.map((e) => e.value).filter((entry) => entry.stopsOf && entry.fromStops),\n);\n\n/**\n * `paint` retargeted to gradient kind `kind`, or `undefined` when either side\n * is not a gradient kind.\n *\n * Between two of the stop gradients this is `withGradientKind`, which carries\n * the geometry. Any other pair goes through the stop list — `stopsOf` on the\n * way out, `fromStops` on the way in — keeping `interpolate` and `opacity`.\n * Lossy wherever the kinds hold different information; an editor that wants a\n * switch to be undoable should keep the original paint.\n */\nexport function switchGradientKind(paint: FillStyle, kind: PaintKind): FillStyle | undefined {\n const from = paintKindOf(paint);\n const to = getPaintKind(kind);\n if (!from?.stopsOf || !to?.fromStops) return undefined;\n if (from.id === to.id) return paint;\n if (isGradientFill(paint) && isGradientFill({ fill: kind } as FillStyle)) {\n return withGradientKind(paint, kind as GradientKind);\n }\n const { interpolate, opacity } = paint as { interpolate?: ColorSpace; opacity?: number };\n return {\n ...to.fromStops(from.stopsOf(paint)),\n ...(interpolate !== undefined ? { interpolate } : {}),\n ...(opacity !== undefined ? { opacity } : {}),\n } as FillStyle;\n}\n\n/** The registry entry for a paint, or `undefined` when its kind is unknown. */\nexport function paintKindOf(fill: FillStyle): PaintKindEntry | undefined {\n return getPaintKind(fill.fill ?? 'solid');\n}\n\n/** @internal Test helper — do not call from product code. */\nexport function _resetPaintKindsForTests(): void {\n KINDS.clear();\n seedBuiltins();\n seedLoaders();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"paintKinds.js","names":["BUILTINS","paint","gradientKind","gradientForBounds","id","UNIT_BOX","twoStopRamp","color","stops","isWhite","KINDS","createReflectable","seedBuiltins","entry","bumpNodeMemoGeneration","paintKindRegistry","asPaint","registerPaintKind","missing","src","have","getProgramSource","registerProgram","getPaintKind","kind","LOADERS","startLoad","LOADS","LAZY_BUILTINS","seedLoaders","load","registerPaintKindLoader","running","done","err","isPaintKindKnown","warmPaintKinds","ids","kinds","loads","derivedList","from","list","entries","derive","listPaintKinds","allKinds","all","listGradientKinds","gradientKinds","switchGradientKind","paintKindOf","to","isGradientFill","withGradientKind","interpolate","opacity","fill","_resetPaintKindsForTests"],"sources":["../../src/core/paintKinds.ts"],"sourcesContent":["/**\n * The paint-kind registry — what makes `FillStyle` open.\n *\n * A consumer registers a sixth kind and it renders, converts between the\n * bounds and pose frames, and serializes, with no kit edits. The five built-in\n * kinds are registered here at module load so an editor's kind bar can\n * enumerate every kind through one list.\n *\n * Each kit layer consults this registry for a kind it does not recognize and\n * otherwise runs its own built-in branch. That split is not laziness: a built-in\n * kind's render slot lives in the GL renderer, its serialize slot lives in\n * `@weasel-js/svg`, and neither can be imported from here without inverting a\n * package dependency.\n */\n\nimport type {\n ColorSpace, FillStyle, GradientFill, GradientKind, GradientUnits, GradStop, TilePatternSpec,\n} from '@weasel-js/paint';\nimport { gradientForBounds, isGradientFill, withGradientKind } from './gradient';\nimport { bumpNodeMemoGeneration } from './scene/nodeMemo';\nimport type { ComponentType } from 'react';\nimport { createReflectable, type ReflectedEntry, type Reflection } from '@weasel-js/registry';\nimport type { FillPoseBox } from './fillInPoseFrame';\nimport type { GlMat3 } from '../renderer/math/mat3';\nimport type { ShaderProgram } from '../renderer/shaders/ShaderProgram';\nimport { getProgramSource, registerProgram, type ProgramSource } from '../renderer/shaders/registerProgram';\n\n/** A compiled GL program. A paint kind gets one from\n * {@link PaintBindContext.program} and hands it back from `bind`. */\nexport type PaintProgram = ShaderProgram;\n\n/**\n * `FillStyle`'s discriminant, open on the string the way `ChromeId` is: the\n * five kinds the kit ships, plus whatever a consumer registers.\n */\nexport type PaintKind = 'solid' | 'linear-gradient' | 'radial-gradient' | 'conic-gradient' | 'pattern' | (string & {});\n\n/** What a registered kind's editor renders. `PaintInput` in `@weasel-js/ui`\n * is the control that mounts it. */\nexport interface PaintKindEditorProps {\n value: FillStyle;\n onInput?(next: FillStyle): void;\n onChange(next: FillStyle): void;\n}\n\n/**\n * The lifetime a paint kind's GPU state belongs to: one renderer on one live\n * context. The object itself is the key — a kind caches textures in a\n * `WeakMap` on it, so two renderers sharing a context never trade them — and\n * `onRelease` is where it frees them.\n */\nexport interface PaintResources {\n /** Run `release` once, when this lifetime ends: the renderer is disposed, or\n * its context was lost and restored. Free every GL object made under it\n * there. */\n onRelease(release: () => void): void;\n}\n\n/**\n * The renderer surface a paint kind binds against, narrowed to what a paint\n * needs. The full `DrawContext` is not consumer surface.\n */\nexport interface PaintBindContext {\n readonly gl: WebGL2RenderingContext;\n /** The group alpha this draw inherits — multiply it into the paint's own. */\n readonly alpha: number;\n /**\n * The compiled program for a `registerProgram` id, compiled against this\n * renderer on first use. `null` when no source is registered under `id` or\n * compilation failed.\n */\n program(id: string): PaintProgram | null;\n /** Send `u_proj` and `u_model`, which every kit vertex shader takes. */\n setProjAndModel(program: PaintProgram): void;\n /**\n * `u_worldInv` for a paint declaring `units` — the inverse of the transform\n * from the paint's space to the frame the geometry arrives in. Paired with\n * the vertex shader's `v_world` varying this is *the* paint-space\n * convention; it is not gradient-specific.\n *\n * `null` when that space has no inverse — a transform or view that flattens\n * an axis. Nothing the paint draws there means anything, so `bind` should\n * return `null` and draw nothing.\n */\n spaceInverse(units: GradientUnits | undefined): GlMat3 | null;\n /**\n * The transform from the space `units` names to device pixels of the target\n * this draw lands in — screen pixels times the pixel ratio on screen, output\n * pixels in a headless render. It is what a paint baking a texture measures\n * its resolution against. `null` where `spaceInverse` is.\n */\n spaceToDevice(units: GradientUnits | undefined): GlMat3 | null;\n /** The largest texture side this context accepts, `MAX_TEXTURE_SIZE`. */\n readonly maxTextureSize: number;\n /** The lifetime GPU state made for this draw belongs to. Stable across the\n * frames of one renderer, so it is the key for a kind's texture cache. */\n readonly resources: PaintResources;\n /**\n * Bake a stop ramp into the frame's ramp atlas, bind that atlas to a texture\n * unit, and return the `v` the ramp's own row sits at — every ramp in a\n * frame shares one texture, so a paint must sample at the returned `v` and\n * not at a constant.\n *\n * `space` is the blend the stops are baked through, defaulting to `'rgb'`.\n * It is part of the atlas key, so the same stops under two spaces take two\n * rows rather than colliding.\n */\n bindRamp(stops: GradStop[], unit: number, space?: ColorSpace): number;\n}\n\n/**\n * One paint kind.\n *\n * `seed`, `label` and `colorOf` are the editor's slots and every kind has\n * them. The rest are optional because a kind may not need them — but\n * `inPoseFrame` and `toBoundsFrame` come as a pair or not at all: a kind that\n * converts one direction and not the other paints correctly once and then\n * drifts on the next resize.\n */\nexport interface PaintKindEntry {\n id: string;\n label: string;\n /** Glyph naming this kind in an editor's kind bar, as an icon name the UI\n * layer resolves — the same indirection `PrefBase.icon` uses. A kind\n * without one is named by its `label`. */\n icon?: string;\n /** A paint of this kind seeded from a color — what an editor writes when a\n * consumer switches a solid to this kind. */\n seed(fromColor: string): FillStyle;\n /** The single color this paint shows, or `undefined` when it has none. */\n colorOf(paint: FillStyle): string | undefined;\n /** Editor slot. */\n Editor?: ComponentType<PaintKindEditorProps>;\n /**\n * The stop list this paint reads as. With `fromStops`, it is what makes a\n * kind a gradient: `listGradientKinds` returns the kinds carrying both, and\n * `switchGradientKind` converts between any two of them through the list.\n */\n stopsOf?(paint: FillStyle): GradStop[];\n /** A paint of this kind built from a stop list, in `bounds` units. The\n * converse of `stopsOf`; supply both or neither. */\n fromStops?(stops: GradStop[]): FillStyle;\n /**\n * Render slot: bind program, uniforms and textures for `fill` and return the\n * bound program; `null` declines the paint.\n *\n * Binding is split from drawing on purpose — a caller owning its own stencil\n * state (an inner/outer-aligned stroke, an even-odd fill) must issue its own\n * draw call, and the kit's draw wrapper would clobber that state. Shader\n * output must be premultiplied: `outColor = vec4(rgb * a, a)`.\n *\n * The geometry is not always the node's own. Text below the outline tier\n * paints by drawing a quad over its glyphs' screen box into an offscreen\n * buffer and masking that with glyph coverage, so a paint must come from\n * `v_world` through `spaceInverse`, not from where `a_position` happens to\n * lie.\n */\n bind?(ctx: PaintBindContext, fill: FillStyle): PaintProgram | null;\n /** Bounds frame → the frame the node is painted in. */\n inPoseFrame?(fill: FillStyle, box: FillPoseBox): FillStyle;\n /** The inverse. Required whenever `inPoseFrame` is supplied. */\n toBoundsFrame?(fill: FillStyle, box: FillPoseBox): FillStyle;\n /**\n * The `<defs>` entry backing a `url(#id)` reference.\n *\n * SVG has paint servers for two gradients and a pattern and nothing else, so\n * a kind beyond those has no element to target: write a foreign-namespaced\n * one under the `wzl:` prefix `@weasel-js/svg` declares on the root whenever\n * a document holds such a paint. That package emits the fallback color\n * beside the reference on its own, from `colorOf`.\n */\n toSvg?(id: string, fill: FillStyle): string;\n /**\n * Shader programs `bind` looks up through `ctx.program`, keyed by program\n * id; registering the kind registers them. Declaring them here rather than\n * calling `registerProgram` at import keeps a kind loaded by\n * {@link registerPaintKindLoader} free of core's registries.\n */\n programs?: Readonly<Record<string, ProgramSource>>;\n}\n\nconst BUILTINS: readonly PaintKindEntry[] = [\n {\n id: 'solid',\n label: 'Solid',\n icon: 'paintSolid',\n seed: (color) => ({ color }),\n colorOf: (paint) => ((paint.fill ?? 'solid') === 'solid'\n ? (paint as { color: string }).color\n : undefined),\n },\n gradientKind('linear-gradient', 'Linear', 'paintLinear'),\n gradientKind('radial-gradient', 'Radial', 'paintRadial'),\n gradientKind('conic-gradient', 'Conic', 'paintConic'),\n {\n id: 'pattern',\n label: 'Pattern',\n icon: 'paintPattern',\n seed: (color) => ({\n fill: 'pattern',\n pattern: { tile: 'hatch', color },\n units: 'bounds',\n }),\n colorOf: (paint) => (paint.fill === 'pattern'\n ? (paint.pattern as Partial<TilePatternSpec>).color\n : undefined),\n },\n];\n\nfunction gradientKind(id: GradientKind, label: string, icon: string): PaintKindEntry {\n return {\n id,\n label,\n icon,\n // `bounds` units make the seed independent of the node's actual size.\n seed: (color) => gradientForBounds(id, UNIT_BOX, twoStopRamp(color), 'bounds'),\n colorOf: (paint) => (paint as Partial<GradientFill>).stops?.[0]?.color,\n stopsOf: (paint) => (paint as GradientFill).stops,\n fromStops: (stops) => gradientForBounds(id, UNIT_BOX, stops, 'bounds'),\n };\n}\n\nconst UNIT_BOX = { x: 0, y: 0, width: 1, height: 1 };\n\n/** `color` to a contrasting end — white reads against any hue; against white\n * itself, black does. */\nfunction twoStopRamp(color: string): GradStop[] {\n const isWhite = color.slice(0, 7).toLowerCase() === '#ffffff';\n return [{ offset: 0, color }, { offset: 1, color: isWhite ? '#000000ff' : '#ffffffff' }];\n}\n\nconst KINDS = createReflectable<PaintKindEntry>();\n\nfunction seedBuiltins(): void {\n for (const entry of BUILTINS) KINDS.push(entry.id, entry, { source: 'kit' });\n}\nseedBuiltins();\n// `NodeShape`'s paint slot memoizes per node and resolves a fill's frame\n// inside it, so the kind set is ambient state that memo cannot see change.\nKINDS.subscribe(bumpNodeMemoGeneration);\n\n/** Every registered paint kind, with the overrides each displaced. */\nexport const paintKindRegistry: Reflection<PaintKindEntry> = KINDS.reflection;\n\n/**\n * A consumer's own paint, typed as a `FillStyle`.\n *\n * `FillStyle` stays a closed union: opening its discriminant would widen every\n * built-in member and break the narrowing the kit's own branches depend on.\n * A registered kind declares its own interface instead and passes it through\n * here — the kit reads only `fill` and hands the whole object back to that\n * kind's slots.\n */\nexport function asPaint<T extends { fill: string }>(paint: T): FillStyle {\n return paint as unknown as FillStyle;\n}\n\n/** Register a paint kind. Returns a disposer that removes it. Re-registering an\n * existing id is an override; disposing it uncovers whatever it displaced. */\nexport function registerPaintKind(entry: PaintKindEntry): () => void {\n if ((entry.stopsOf === undefined) !== (entry.fromStops === undefined)) {\n const missing = entry.stopsOf === undefined ? 'stopsOf' : 'fromStops';\n throw new Error(\n `weasel registerPaintKind: kind \"${entry.id}\" is missing ${missing}. A ` +\n 'gradient kind reads as a stop list and builds from one; supply both or neither.',\n );\n }\n if ((entry.inPoseFrame === undefined) !== (entry.toBoundsFrame === undefined)) {\n const missing = entry.inPoseFrame === undefined ? 'inPoseFrame' : 'toBoundsFrame';\n throw new Error(\n `weasel registerPaintKind: kind \"${entry.id}\" is missing ${missing}. A kind ` +\n 'that converts one frame direction and not the other drifts on the next ' +\n 'resize; supply both or neither.',\n );\n }\n for (const [id, src] of Object.entries(entry.programs ?? {})) {\n const have = getProgramSource(id);\n if (!have || have.vert !== src.vert || have.frag !== src.frag) registerProgram(id, src.vert, src.frag);\n }\n // Re-registering a built-in id is how a consumer closes a gap the kit leaves,\n // so disposing that override puts the built-in back rather than deleting the\n // kind.\n return KINDS.push(entry.id, entry);\n}\n\n/** The entry for `kind`, or `undefined`. A lookup that misses on a kind with\n * a loader starts that load; see {@link registerPaintKindLoader}. */\nexport function getPaintKind(kind: string | undefined): PaintKindEntry | undefined {\n const id = kind ?? 'solid';\n const entry = KINDS.get(id);\n if (!entry && LOADERS.has(id)) void startLoad(id).catch(() => {});\n return entry;\n}\n\n/** Resolves with the entry for a kind that is loaded on first use. */\nexport type PaintKindLoader = () => Promise<PaintKindEntry>;\n\nconst LOADERS = new Map<string, PaintKindLoader>();\n\n/** One load per kind, kept after it settles: a failed load is not retried on\n * every frame that meets the kind. */\nconst LOADS = new Map<string, Promise<void>>();\n\n// A built-in kind heavy enough to keep off a consumer's bundle until a paint\n// of it turns up. Importing the module statically still registers it at once.\nconst LAZY_BUILTINS: ReadonlyArray<readonly [string, PaintKindLoader]> = [\n ['mesh-gradient', async () => (await import('../features/meshPaint/lazyKind')).meshGradientKind],\n];\n\nfunction seedLoaders(): void {\n LOADERS.clear();\n LOADS.clear();\n for (const [id, load] of LAZY_BUILTINS) LOADERS.set(id, load);\n}\nseedLoaders();\n\n/**\n * Declare a paint kind that is loaded the first time something looks it up,\n * so its code stays out of the bundle until a paint of it appears — typically\n * `() => import('./myKind').then((m) => m.myKindEntry)`.\n *\n * Until it lands the kind is unregistered: the renderer draws nothing for its\n * paints, and every other reader treats it as unknown. Registering it fires\n * `paintKindRegistry`'s subscribers, which is what `<SceneCanvas>` repaints\n * on. Call {@link warmPaintKinds} to load ahead of the first frame instead.\n *\n * A kind that is already registered never calls its loader. Returns a\n * disposer that removes the loader.\n */\nexport function registerPaintKindLoader(id: string, load: PaintKindLoader): () => void {\n LOADERS.set(id, load);\n return () => {\n if (LOADERS.get(id) === load) LOADERS.delete(id);\n };\n}\n\nfunction startLoad(id: string): Promise<void> {\n const running = LOADS.get(id);\n if (running) return running;\n const load = LOADERS.get(id)!;\n const done = load().then(\n (entry) => {\n if (entry.id !== id) {\n throw new Error(`weasel: the loader for paint kind \"${id}\" resolved to \"${entry.id}\".`);\n }\n if (!KINDS.has(id)) registerPaintKind(entry);\n },\n ).catch((err: unknown) => {\n console.error(`weasel: paint kind \"${id}\" failed to load:`, err);\n throw err;\n });\n LOADS.set(id, done);\n return done;\n}\n\n/** Is `id` registered, or declared with a loader? */\nexport function isPaintKindKnown(id: string): boolean {\n return KINDS.has(id) || LOADERS.has(id);\n}\n\n/**\n * Load paint kinds ahead of their first use, so the first frame that meets\n * one draws it rather than a blank. With no list, loads every kind that has a\n * loader — the kit's lazily loaded built-ins (`mesh-gradient`) plus any a\n * consumer declared with {@link registerPaintKindLoader}.\n *\n * Resolves once every kind is registered; a kind already registered counts\n * as loaded. Rejects when a load fails, or for a kind that is neither\n * registered nor loadable.\n */\nexport function warmPaintKinds(kinds?: readonly string[]): Promise<void> {\n const ids = kinds ?? [...LOADERS.keys()];\n const loads: Promise<void>[] = [];\n for (const id of ids) {\n if (KINDS.has(id)) continue;\n if (!LOADERS.has(id)) {\n return Promise.reject(new Error(\n `weasel warmPaintKinds: \"${id}\" is neither registered nor loadable.`,\n ));\n }\n loads.push(startLoad(id));\n }\n return Promise.all(loads).then(() => undefined);\n}\n\n/** A list derived from the registry, recomputed only when it changes — so\n * its identity is a `useSyncExternalStore` snapshot. */\nfunction derivedList(\n derive: (all: readonly ReflectedEntry<PaintKindEntry>[]) => readonly PaintKindEntry[],\n): () => readonly PaintKindEntry[] {\n let from: readonly ReflectedEntry<PaintKindEntry>[] | undefined;\n let list: readonly PaintKindEntry[] = [];\n return () => {\n const entries = KINDS.entries();\n if (entries !== from) {\n from = entries;\n list = Object.freeze(derive(entries));\n }\n return list;\n };\n}\n\n/** Every registered kind, built-ins first, in registration order. The same\n * array until the registry changes. */\nexport function listPaintKinds(): readonly PaintKindEntry[] {\n return allKinds();\n}\nconst allKinds = derivedList((all) => all.map((e) => e.value));\n\n/** Every registered gradient kind — the ones that read as and build from a\n * stop list — in registration order. The same array until the registry\n * changes. */\nexport function listGradientKinds(): readonly PaintKindEntry[] {\n return gradientKinds();\n}\nconst gradientKinds = derivedList(\n (all) => all.map((e) => e.value).filter((entry) => entry.stopsOf && entry.fromStops),\n);\n\n/**\n * `paint` retargeted to gradient kind `kind`, or `undefined` when either side\n * is not a gradient kind.\n *\n * Between two of the stop gradients this is `withGradientKind`, which carries\n * the geometry. Any other pair goes through the stop list — `stopsOf` on the\n * way out, `fromStops` on the way in — keeping `interpolate` and `opacity`.\n * Lossy wherever the kinds hold different information; an editor that wants a\n * switch to be undoable should keep the original paint.\n */\nexport function switchGradientKind(paint: FillStyle, kind: PaintKind): FillStyle | undefined {\n const from = paintKindOf(paint);\n const to = getPaintKind(kind);\n if (!from?.stopsOf || !to?.fromStops) return undefined;\n if (from.id === to.id) return paint;\n if (isGradientFill(paint) && isGradientFill({ fill: kind } as FillStyle)) {\n return withGradientKind(paint, kind as GradientKind);\n }\n const { interpolate, opacity } = paint as { interpolate?: ColorSpace; opacity?: number };\n return {\n ...to.fromStops(from.stopsOf(paint)),\n ...(interpolate !== undefined ? { interpolate } : {}),\n ...(opacity !== undefined ? { opacity } : {}),\n } as FillStyle;\n}\n\n/** The registry entry for a paint, or `undefined` when its kind is unknown. */\nexport function paintKindOf(fill: FillStyle): PaintKindEntry | undefined {\n return getPaintKind(fill.fill ?? 'solid');\n}\n\n/** @internal Test helper — do not call from product code. */\nexport function _resetPaintKindsForTests(): void {\n KINDS.clear();\n seedBuiltins();\n seedLoaders();\n}\n"],"mappings":";;;;;AAqLA,IAAMA,IAAsC;CAC1C;EACE,IAAI;EACJ,OAAO;EACP,MAAM;EACN,OAAO,OAAW,EAAE,SAAM;EAC1B,UAAU,OAAYC,EAAM,QAAQ,aAAa,UAC5CA,EAA4B,QAC7B,KAAA;CACN;CACAC,EAAa,mBAAmB,UAAU,aAAa;CACvDA,EAAa,mBAAmB,UAAU,aAAa;CACvDA,EAAa,kBAAkB,SAAS,YAAY;CACpD;EACE,IAAI;EACJ,OAAO;EACP,MAAM;EACN,OAAO,OAAW;GAChB,MAAM;GACN,SAAS;IAAE,MAAM;IAAS;GAAM;GAChC,OAAO;EACT;EACA,UAAU,MAAWD,EAAM,SAAS,YAC/BA,EAAM,QAAqC,QAC5C,KAAA;CACN;AACF;AAEA,SAASC,EAAa,GAAkB,GAAe,GAA8B;CACnF,OAAO;EACL;EACA;EACA;EAEA,OAAO,MAAUC,EAAkBC,GAAIC,GAAUC,EAAYC,CAAK,GAAG,QAAQ;EAC7E,UAAU,MAAWN,EAAgC,QAAQ,EAAE,EAAE;EACjE,UAAU,MAAWA,EAAuB;EAC5C,YAAY,MAAUE,EAAkBC,GAAIC,GAAUG,GAAO,QAAQ;CACvE;AACF;AAEA,IAAMH,IAAW;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;CAAG,QAAQ;AAAE;AAInD,SAASC,EAAY,GAA2B;CAC9C,IAAMG,IAAUF,EAAM,MAAM,GAAG,CAAC,CAAC,CAAC,YAAY,MAAM;CACpD,OAAO,CAAC;EAAE,QAAQ;EAAG;CAAM,GAAG;EAAE,QAAQ;EAAG,OAAOE,IAAU,cAAc;CAAY,CAAC;AACzF;AAEA,IAAMC,IAAQC,EAAkC;AAEhD,SAASC,IAAqB;CAC5B,KAAK,IAAMC,KAASb,GAAU,EAAM,KAAKa,EAAM,IAAIA,GAAO,EAAE,QAAQ,MAAM,CAAC;AAC7E;AACAD,EAAa,GAGbF,EAAM,UAAUI,CAAsB;AAGtC,IAAaC,IAAgDL,EAAM;AAWnE,SAAgBM,EAAoC,GAAqB;CACvE,OAAOf;AACT;AAIA,SAAgBgB,EAAkB,GAAmC;CACnE,IAAKJ,EAAM,YAAY,KAAA,MAAgBA,EAAM,cAAc,KAAA,IAAY;EACrE,IAAMK,IAAUL,EAAM,YAAY,KAAA,IAAY,YAAY;EAC1D,MAAU,MACR,mCAAmCA,EAAM,GAAG,eAAeK,EAAQ,oFAErE;CACF;CACA,IAAKL,EAAM,gBAAgB,KAAA,MAAgBA,EAAM,kBAAkB,KAAA,IAAY;EAC7E,IAAMK,IAAUL,EAAM,gBAAgB,KAAA,IAAY,gBAAgB;EAClE,MAAU,MACR,mCAAmCA,EAAM,GAAG,eAAeK,EAAQ,gHAGrE;CACF;CACA,KAAK,IAAM,CAACd,GAAIe,MAAQ,OAAO,QAAQN,EAAM,YAAY,CAAC,CAAC,GAAG;EAC5D,IAAMO,IAAOC,EAAiBjB,CAAE;EAChC,CAAI,CAACgB,KAAQA,EAAK,SAASD,EAAI,QAAQC,EAAK,SAASD,EAAI,SAAMG,EAAgBlB,GAAIe,EAAI,MAAMA,EAAI,IAAI;CACvG;CAIA,OAAOT,EAAM,KAAKG,EAAM,IAAIA,CAAK;AACnC;AAIA,SAAgBU,EAAa,GAAsD;CACjF,IAAMnB,IAAKoB,KAAQ,SACbX,IAAQH,EAAM,IAAIN,CAAE;CAE1B,OADI,CAACS,KAASY,EAAQ,IAAIrB,CAAE,KAAGsB,EAAetB,CAAE,CAAC,CAAC,YAAY,CAAC,CAAC,GACzDS;AACT;AAKA,IAAMY,oBAAU,IAAI,IAA6B,GAI3CE,oBAAQ,IAAI,IAA2B,GAIvCC,IAAmE,CACvE,CAAC,iBAAiB,aAAa,MAAM,OAAO,qCAAA,CAAmC,gBAAgB,CACjG;AAEA,SAASC,IAAoB;CAE3B,AADAJ,EAAQ,MAAM,GACdE,EAAM,MAAM;CACZ,KAAK,IAAM,CAACvB,GAAI0B,MAASF,GAAe,EAAQ,IAAIxB,GAAI0B,CAAI;AAC9D;AACAD,EAAY;AAeZ,SAAgBE,EAAwB,GAAY,GAAmC;CAErF,OADAN,EAAQ,IAAIrB,GAAI0B,CAAI,SACP;EACX,AAAIL,EAAQ,IAAIrB,CAAE,MAAM0B,KAAML,EAAQ,OAAOrB,CAAE;CACjD;AACF;AAEA,SAASsB,EAAU,GAA2B;CAC5C,IAAMM,IAAUL,EAAM,IAAIvB,CAAE;CAC5B,IAAI4B,GAAS,OAAOA;CAEpB,IAAMC,IADOR,EAAQ,IAAIrB,CACZ,CAAA,CAAK,CAAC,CAAC,MACjB,MAAU;EACT,IAAIS,EAAM,OAAOT,GACf,MAAU,MAAM,sCAAsCA,EAAG,iBAAiBS,EAAM,GAAG,GAAG;EAExF,AAAKH,EAAM,IAAIN,CAAE,KAAGa,EAAkBJ,CAAK;CAC7C,CACF,CAAC,CAAC,OAAO,MAAiB;EAExB,MADA,QAAQ,MAAM,uBAAuBT,EAAG,oBAAoB8B,CAAG,GACzDA;CACR,CAAC;CAED,OADAP,EAAM,IAAIvB,GAAI6B,CAAI,GACXA;AACT;AAGA,SAAgBE,EAAiB,GAAqB;CACpD,OAAOzB,EAAM,IAAIN,CAAE,KAAKqB,EAAQ,IAAIrB,CAAE;AACxC;AAYA,SAAgBgC,EAAe,GAA0C;CACvE,IAAMC,IAAMC,KAAS,CAAC,GAAGb,EAAQ,KAAK,CAAC,GACjCc,IAAyB,CAAC;CAChC,KAAK,IAAMnC,KAAMiC,GACf,IAAI,GAAM,IAAIjC,CAAE,GAChB;MAAI,CAACqB,EAAQ,IAAIrB,CAAE,GACjB,OAAO,QAAQ,OAAO,gBAAI,MACxB,2BAA2BA,EAAG,sCAChC,CAAC;EAEH,EAAM,KAAKsB,EAAUtB,CAAE,CAAC;CAFrB;CAIL,OAAO,QAAQ,IAAImC,CAAK,CAAC,CAAC,WAAW,KAAA,CAAS;AAChD;AAIA,SAASC,EACP,GACiC;CACjC,IAAIC,GACAC,IAAkC,CAAC;CACvC,aAAa;EACX,IAAMC,IAAUjC,EAAM,QAAQ;EAK9B,OAJIiC,MAAYF,MACd,IAAOE,GACP,IAAO,OAAO,OAAOC,EAAOD,CAAO,CAAC,IAE/BD;CACT;AACF;AAIA,SAAgBG,IAA4C;CAC1D,OAAOC,EAAS;AAClB;AACA,IAAMA,IAAWN,GAAa,MAAQO,EAAI,KAAK,MAAM,EAAE,KAAK,CAAC;AAK7D,SAAgBC,IAA+C;CAC7D,OAAOC,EAAc;AACvB;AACA,IAAMA,IAAgBT,GACnB,MAAQO,EAAI,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,MAAUlC,EAAM,WAAWA,EAAM,SAAS,CACrF;AAYA,SAAgBqC,EAAmB,GAAkB,GAAwC;CAC3F,IAAMT,IAAOU,EAAYlD,CAAK,GACxBmD,IAAK7B,EAAaC,CAAI;CAC5B,IAAI,CAACiB,GAAM,WAAW,CAACW,GAAI,WAAW;CACtC,IAAIX,EAAK,OAAOW,EAAG,IAAI,OAAOnD;CAC9B,IAAIoD,EAAepD,CAAK,KAAKoD,EAAe,EAAE,MAAM7B,EAAK,CAAc,GACrE,OAAO8B,EAAiBrD,GAAOuB,CAAoB;CAErD,IAAM,EAAE,gBAAa,eAAYvB;CACjC,OAAO;EACL,GAAGmD,EAAG,UAAUX,EAAK,QAAQxC,CAAK,CAAC;EACnC,GAAIsD,MAAgB,KAAA,IAA8B,CAAC,IAAnB,EAAE,eAAY;EAC9C,GAAIC,MAAY,KAAA,IAA0B,CAAC,IAAf,EAAE,WAAQ;CACxC;AACF;AAGA,SAAgBL,EAAY,GAA6C;CACvE,OAAO5B,EAAakC,EAAK,QAAQ,OAAO;AAC1C;AAGA,SAAgBC,IAAiC;CAG/C,AAFAhD,EAAM,MAAM,GACZE,EAAa,GACbiB,EAAY;AACd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeProperties.js","names":["createNodeProperties","entries","byName","entry","name"],"sources":["../../../src/core/scene/NodeProperties.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"NodeProperties.js","names":["createNodeProperties","entries","byName","entry","name"],"sources":["../../../src/core/scene/NodeProperties.ts"],"sourcesContent":["import type { PrefSection } from '@weasel-js/prefs';\n\n/**\n * NodePropertiesEntry — one kind's entry in the **properties trait's**\n * registry: a declarative schema of the kind's editable properties.\n *\n * `schema` is a `PrefSection`, not the `PrefGroup` a preferences schema\n * is: a leaf's own key is its whole dotted node path (`pose.x`,\n * `data.fill`, `data.style.fontSize`) — a path of any depth rooted at\n * `pose` or `data` — so schema consumers (weasel-ui `SelectionPanel`)\n * can read/aggregate/write generically with no per-kind code. The\n * sections it nests are headings only; their keys do not contribute to\n * the node path.\n *\n * Kind names share the routing trait's vocabulary — an entry registered\n * as `'rect'` describes nodes `NodeRouting.classify` maps to `'rect'`.\n * See `docs/superpowers/specs/2026-07-20-selection-panel-design.md` and\n * the trait taxonomy in\n * `docs/superpowers/specs/2026-05-24-node-traits-reframe-design.md`.\n */\nexport interface NodePropertiesEntry {\n /** Kind name — same vocabulary as the routing trait. */\n name: string;\n /** Property schema for this kind. */\n schema: PrefSection;\n}\n\n/**\n * NodeProperties — the **properties trait's** registry. Each trait of a\n * node (shape, routing, properties, …) is its own registry; this one\n * answers \"what editable properties does this kind expose?\" Consumed by\n * weasel-ui's `<SelectionPanel>`.\n *\n * (Supersedes the speculative `NodePropertyRows` name in the traits\n * spec — the stored value is a schema, not render contributors.)\n */\nexport interface NodeProperties {\n /** Register a kind. Throws if a kind with this name is already\n * registered. */\n register(entry: NodePropertiesEntry): void;\n /** Lookup a kind's entry by name. */\n get(name: string): NodePropertiesEntry | undefined;\n /** Enumerate registered kinds in registration order. */\n list(): readonly NodePropertiesEntry[];\n}\n\n/** Build a registry of per-node-kind property descriptors, which schema-driven\n * property panels read to know what a node exposes. Registering a duplicate\n * kind name throws. */\nexport function createNodeProperties(): NodeProperties {\n const entries: NodePropertiesEntry[] = [];\n const byName = new Map<string, NodePropertiesEntry>();\n return {\n register(entry) {\n if (byName.has(entry.name)) {\n throw new Error(\n `createNodeProperties: duplicate kind name \"${entry.name}\"`,\n );\n }\n byName.set(entry.name, entry);\n entries.push(entry);\n },\n get(name) {\n return byName.get(name);\n },\n list() {\n return entries;\n },\n };\n}\n"],"mappings":";AAiDA,SAAgBA,IAAuC;CACrD,IAAMC,IAAiC,CAAC,GAClCC,oBAAS,IAAI,IAAiC;CACpD,OAAO;EACL,SAAS,GAAO;GACd,IAAIA,EAAO,IAAIC,EAAM,IAAI,GACvB,MAAU,MACR,8CAA8CA,EAAM,KAAK,EAC3D;GAGF,AADAD,EAAO,IAAIC,EAAM,MAAMA,CAAK,GAC5BF,EAAQ,KAAKE,CAAK;EACpB;EACA,IAAI,GAAM;GACR,OAAOD,EAAO,IAAIE,CAAI;EACxB;EACA,OAAO;GACL,OAAOH;EACT;CACF;AACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/core/scene/appliedLog.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
let t = null;
|
|
4
|
+
return {
|
|
5
|
+
atomically(n, r) {
|
|
6
|
+
let i = t, a = [], o = e.getSelection();
|
|
7
|
+
t = a;
|
|
8
|
+
try {
|
|
9
|
+
let e = n();
|
|
10
|
+
return i && !r() && i.push(...a), e;
|
|
11
|
+
} catch (n) {
|
|
12
|
+
throw t = null, e.lifted(() => {
|
|
13
|
+
for (let t = a.length - 1; t >= 0; t--) {
|
|
14
|
+
let { kind: n, payload: r, reverted: i } = a[t], o = e.registered.get(n);
|
|
15
|
+
(i ? o.apply : o.revert)(r);
|
|
16
|
+
}
|
|
17
|
+
}), e.setSelection(o), a.length > 0 && e.notify(), n;
|
|
18
|
+
} finally {
|
|
19
|
+
t = i;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
logApplied(e, n, r) {
|
|
23
|
+
t?.push(r === void 0 ? {
|
|
24
|
+
kind: e,
|
|
25
|
+
payload: n
|
|
26
|
+
} : {
|
|
27
|
+
kind: e,
|
|
28
|
+
payload: n,
|
|
29
|
+
reverted: r
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { e as createAppliedLog };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=appliedLog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliedLog.js","names":["createAppliedLog","applied","outer","log","selectionBefore","env","result","fn","ownsEntry","err","i","handler","kind","reverted","payload"],"sources":["../../../src/core/scene/appliedLog.ts"],"sourcesContent":["/**\n * The scene's atomic-revert log: every kit op applied since the innermost\n * `atomically` opened, so an edit that is refused partway can take back what\n * it already did.\n */\nimport type { NodeId, RegisteredOp } from './types';\n\n/** What the log needs from the scene it reverts. */\nexport interface AppliedLogEnv {\n /** The op table a logged kind is reverted through. */\n registered: ReadonlyMap<string, RegisteredOp<unknown>>;\n /** Run with the lock guard lifted: a revert restores a state that was legal. */\n lifted<T>(fn: () => T): T;\n getSelection(): readonly NodeId[];\n setSelection(ids: readonly NodeId[]): void;\n notify(): void;\n}\n\nexport interface AppliedLog {\n /**\n * Run `fn` as all-or-nothing: if it throws, every kit op it applied is\n * reverted, newest first, and the selection goes back to where it was.\n * The log is at kit-op grain rather than per external op, because one\n * external op can issue several scene mutations (a container move cascading\n * to its children) and fail halfway through its own.\n *\n * On success the ops join an enclosing log only when `ownsEntry` says no\n * history entry holds them: reverting ops an entry still claims would leave\n * undo replaying a change that is no longer there.\n */\n atomically<T>(fn: () => T, ownsEntry: () => boolean): T;\n /** Note a kit op that was just applied, or with `reverted`, just reverted.\n * Does nothing outside `atomically`. */\n logApplied(kind: string, payload: unknown, reverted?: boolean): void;\n}\n\nexport function createAppliedLog(env: AppliedLogEnv): AppliedLog {\n let applied: { kind: string; payload: unknown; reverted?: boolean }[] | null = null;\n\n return {\n atomically(fn, ownsEntry) {\n const outer = applied;\n const log: NonNullable<typeof applied> = [];\n const selectionBefore = env.getSelection();\n applied = log;\n try {\n const result = fn();\n if (outer && !ownsEntry()) outer.push(...log);\n return result;\n } catch (err) {\n applied = null;\n env.lifted(() => {\n for (let i = log.length - 1; i >= 0; i--) {\n const { kind, payload, reverted } = log[i];\n const handler = env.registered.get(kind)!;\n (reverted ? handler.apply : handler.revert)(payload);\n }\n });\n env.setSelection(selectionBefore);\n if (log.length > 0) env.notify();\n throw err;\n } finally {\n applied = outer;\n }\n },\n logApplied(kind, payload, reverted) {\n applied?.push(reverted === undefined ? { kind, payload } : { kind, payload, reverted });\n },\n };\n}\n"],"mappings":";AAoCA,SAAgBA,EAAiB,GAAgC;CAC/D,IAAIC,IAA2E;CAE/E,OAAO;EACL,WAAW,GAAI,GAAW;GACxB,IAAMC,IAAQD,GACRE,IAAmC,CAAC,GACpCC,IAAkBC,EAAI,aAAa;GACzC,IAAUF;GACV,IAAI;IACF,IAAMG,IAASC,EAAG;IAElB,OADIL,KAAS,CAACM,EAAU,KAAGN,EAAM,KAAK,GAAGC,CAAG,GACrCG;GACT,SAASG,GAAK;IAWZ,MAVA,IAAU,MACVJ,EAAI,aAAa;KACf,KAAK,IAAIK,IAAIP,EAAI,SAAS,GAAGO,KAAK,GAAG,KAAK;MACxC,IAAM,EAAE,SAAM,YAAS,gBAAaP,EAAIO,IAClCC,IAAUN,EAAI,WAAW,IAAIO,CAAI;MACvC,CAACC,IAAWF,EAAQ,QAAQA,EAAQ,OAAA,CAAQG,CAAO;KACrD;IACF,CAAC,GACDT,EAAI,aAAaD,CAAe,GAC5BD,EAAI,SAAS,KAAGE,EAAI,OAAO,GACzBI;GACR,UAAU;IACR,IAAUP;GACZ;EACF;EACA,WAAW,GAAM,GAAS,GAAU;GAClC,GAAS,KAAKW,MAAa,KAAA,IAAY;IAAE;IAAM;GAAQ,IAAI;IAAE;IAAM;IAAS;GAAS,CAAC;EACxF;CACF;AACF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createArrangeOp as e } from "../ops/arrange.js";
|
|
2
|
+
import { SceneArrivalRefused as t } from "./arrivals.js";
|
|
3
|
+
import { createEmitter as n } from "@weasel-js/registry";
|
|
4
|
+
//#region src/core/scene/arrivalWindow.ts
|
|
5
|
+
function r(r) {
|
|
6
|
+
let { nodes: i } = r, a = null, o = null, s = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set(), l = null, u = n();
|
|
7
|
+
function d() {
|
|
8
|
+
return a ?? r.declared();
|
|
9
|
+
}
|
|
10
|
+
function f(e = !1) {
|
|
11
|
+
return o !== null || !e && d() === null ? !1 : (o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set(), l = null, !0);
|
|
12
|
+
}
|
|
13
|
+
function p() {
|
|
14
|
+
let e = l;
|
|
15
|
+
l = null, e !== null && e.length !== 0 && u.emit(e);
|
|
16
|
+
}
|
|
17
|
+
function m() {
|
|
18
|
+
let e = o;
|
|
19
|
+
o = null;
|
|
20
|
+
let n = d();
|
|
21
|
+
if (e === null || n === null) return [];
|
|
22
|
+
let r = /* @__PURE__ */ new Map();
|
|
23
|
+
for (let [t, n] of e) {
|
|
24
|
+
let e = n.filter((e) => i.get(e)?.parent === t);
|
|
25
|
+
e.length > 0 && i.has(t) && r.set(t, e);
|
|
26
|
+
}
|
|
27
|
+
let a = /* @__PURE__ */ new Map();
|
|
28
|
+
for (let [e, t] of s) {
|
|
29
|
+
let n = t.filter((t) => i.get(t)?.parent !== e);
|
|
30
|
+
n.length > 0 && i.has(e) && a.set(e, n);
|
|
31
|
+
}
|
|
32
|
+
s = /* @__PURE__ */ new Map();
|
|
33
|
+
let u = new Set([...c].filter((e) => i.get(e)?.kind === "container"));
|
|
34
|
+
if (r.size === 0 && u.size === 0) return [];
|
|
35
|
+
let f = n(r, u, a);
|
|
36
|
+
if (f === null) throw new t(r);
|
|
37
|
+
let p = [];
|
|
38
|
+
for (let [e, t] of f) {
|
|
39
|
+
let n = i.get(e);
|
|
40
|
+
n && p.push({
|
|
41
|
+
id: e,
|
|
42
|
+
from: n.pose,
|
|
43
|
+
to: t
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return l = p, p;
|
|
47
|
+
}
|
|
48
|
+
function h() {
|
|
49
|
+
o = null, l = null;
|
|
50
|
+
}
|
|
51
|
+
function g(t) {
|
|
52
|
+
return f(!0) ? {
|
|
53
|
+
ops: [...t, e({}, m, r.writePose)],
|
|
54
|
+
owned: !0
|
|
55
|
+
} : {
|
|
56
|
+
ops: t,
|
|
57
|
+
owned: !1
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
setHandler(e) {
|
|
62
|
+
return a = e, () => {
|
|
63
|
+
a === e && (a = null);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
onReflow: u.subscribe,
|
|
67
|
+
noteArrival(e, t, n) {
|
|
68
|
+
if (o === null || t === null || t === n) return;
|
|
69
|
+
c.add(t);
|
|
70
|
+
let r = o.get(t);
|
|
71
|
+
r === void 0 ? o.set(t, [e]) : r.includes(e) || r.push(e);
|
|
72
|
+
},
|
|
73
|
+
noteDeparture(e, t, n) {
|
|
74
|
+
if (o === null || t === null || t === n || (c.add(t), o.get(t)?.includes(e))) return;
|
|
75
|
+
let r = s.get(t);
|
|
76
|
+
r === void 0 ? s.set(t, [e]) : r.includes(e) || r.push(e);
|
|
77
|
+
},
|
|
78
|
+
noteChange(e) {
|
|
79
|
+
o !== null && e !== null && c.add(e);
|
|
80
|
+
},
|
|
81
|
+
bareLayoutEdit: () => d() !== null && o === null && r.recordsBareEdits(),
|
|
82
|
+
open: f,
|
|
83
|
+
settle: m,
|
|
84
|
+
emitReflow: p,
|
|
85
|
+
abandon: h,
|
|
86
|
+
runEdit(e, n) {
|
|
87
|
+
let { ops: r, owned: i } = g(e);
|
|
88
|
+
try {
|
|
89
|
+
n(r), i && p();
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (!(e instanceof t)) throw e;
|
|
92
|
+
} finally {
|
|
93
|
+
i && h();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { r as createArrivalWindow };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=arrivalWindow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrivalWindow.js","names":["createArrivalWindow","env","installed","arrivals","departures","changed","reflowDue","reflowListeners","createEmitter","activeHandler","open","always","emitReflow","moves","settle","pending","handler","landed","parent","ids","still","nodes","id","left","gone","touched","poses","SceneArrivalRefused","out","to","node","abandon","withArrivalOps","ops","createArrangeOp","from","list","all","run","owned","err"],"sources":["../../../src/core/scene/arrivalWindow.ts"],"sourcesContent":["/**\n * The scene's arrival window: what a live edit did to containers, collected\n * while it runs and handed to a layout handler before it closes.\n *\n * The window is open while a live edit is collecting the nodes that joined a\n * container, the ones that left one, and every container whose children or\n * size it changed; whoever opened the window settles it before the edit\n * closes. Undo, redo and journal replays never open it: they restore an\n * arrangement that was already decided.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { Op } from 'core/ops/types';\nimport { createArrangeOp, type ArrangedPose } from 'core/ops/arrange';\nimport { SceneArrivalRefused } from './arrivals';\nimport type { LayoutMove, Node, NodeId, SceneArrivalHandler } from './types';\n\n/** What the window needs from the scene it serves. */\nexport interface ArrivalWindowEnv<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n /** The pass over the scene's declared layouts, or `null` while it has none.\n * An installed handler takes its place. */\n declared(): SceneArrivalHandler<TPose> | null;\n /** Whether a bare scene call would record an edit of its own right now:\n * no batch is open and no history engine is driving. */\n recordsBareEdits(): boolean;\n /** Write one arranged pose. Bound into the arrange op, since not every\n * adapter an edit is applied through can write poses. */\n writePose(id: string, pose: unknown): void;\n}\n\nexport interface ArrivalWindow<TPose> {\n /** Install the handler that settles arrivals. Returns its remover. */\n setHandler(handler: SceneArrivalHandler<TPose>): () => void;\n /** Hear the poses each live edit's settle wrote, after the edit lands. */\n onReflow(listener: (moves: readonly LayoutMove<TPose>[]) => void): () => void;\n noteArrival(id: NodeId, parent: NodeId | null, from: NodeId | null): void;\n noteDeparture(id: NodeId, from: NodeId | null, to: NodeId | null): void;\n /** A container's child set or order changed, or it changed size. */\n noteChange(id: NodeId | null): void;\n /** Whether a bare scene call has to run as a batch of its own, so the\n * layout pass it triggers lands in its undo step. */\n bareLayoutEdit(): boolean;\n /** Open the window when a handler wants one — or `always`, for an edit that\n * may give the scene its first layout partway through — and no enclosing\n * edit holds it already; true when this caller now owns it. */\n open(always?: boolean): boolean;\n /** Close the window and ask the handler what the arrivals get. Throws\n * `SceneArrivalRefused` when it refuses, which reverts the edit. */\n settle(): ArrangedPose[];\n /** Report what the settled edit wrote, now that it has landed. */\n emitReflow(): void;\n /** Close the window with nothing settled or reported: its owner's edit is\n * over, however it ended. */\n abandon(): void;\n /** Run an op-driven edit that may carry arrivals: a refusal reverts it and\n * is swallowed, the way a drop onto a full container is — nothing lands. */\n runEdit(ops: Op[], run: (ops: Op[]) => void): void;\n}\n\nexport function createArrivalWindow<TData, TLayer extends string, TPose>(\n env: ArrivalWindowEnv<TData, TLayer, TPose>,\n): ArrivalWindow<TPose> {\n const { nodes } = env;\n let installed: SceneArrivalHandler<TPose> | null = null;\n let arrivals: Map<NodeId, NodeId[]> | null = null;\n let departures = new Map<NodeId, NodeId[]>();\n let changed = new Set<NodeId>();\n /** What the last settle wrote, reported to `onReflow` once its edit lands. */\n let reflowDue: LayoutMove<TPose>[] | null = null;\n const reflowListeners = createEmitter<[moves: readonly LayoutMove<TPose>[]]>();\n\n function activeHandler(): SceneArrivalHandler<TPose> | null {\n return installed ?? env.declared();\n }\n\n function open(always = false): boolean {\n if (arrivals !== null || (!always && activeHandler() === null)) return false;\n arrivals = new Map();\n departures = new Map();\n changed = new Set();\n reflowDue = null;\n return true;\n }\n\n function emitReflow(): void {\n const moves = reflowDue;\n reflowDue = null;\n if (moves === null || moves.length === 0) return;\n reflowListeners.emit(moves);\n }\n\n function settle(): ArrangedPose[] {\n const pending = arrivals;\n arrivals = null;\n const handler = activeHandler();\n if (pending === null || handler === null) return [];\n // Only what is still there once the edit's other mutations have landed.\n const landed = new Map<NodeId, NodeId[]>();\n for (const [parent, ids] of pending) {\n const still = ids.filter((id) => nodes.get(id)?.parent === parent);\n if (still.length > 0 && nodes.has(parent)) landed.set(parent, still);\n }\n // Only what is gone from the container once the edit has landed: a child\n // that left and came back did not depart.\n const left = new Map<NodeId, NodeId[]>();\n for (const [parent, ids] of departures) {\n const gone = ids.filter((id) => nodes.get(id)?.parent !== parent);\n if (gone.length > 0 && nodes.has(parent)) left.set(parent, gone);\n }\n departures = new Map();\n const touched = new Set([...changed].filter((id) => nodes.get(id)?.kind === 'container'));\n if (landed.size === 0 && touched.size === 0) return [];\n const poses = handler(landed, touched, left);\n if (poses === null) throw new SceneArrivalRefused(landed);\n const out: ArrangedPose[] = [];\n for (const [id, to] of poses) {\n const node = nodes.get(id);\n if (node) out.push({ id, from: node.pose, to });\n }\n reflowDue = out as LayoutMove<TPose>[];\n return out;\n }\n\n function abandon(): void {\n arrivals = null;\n reflowDue = null;\n }\n\n /** `ops` plus the op that settles their arrivals, when this call owns the\n * window; `ops` unchanged otherwise. Opened even on a scene with no layout\n * yet: an edit's op list is fixed before it runs, and any op in it may give\n * the scene its first. The arrange op that settles nothing elides itself. */\n function withArrivalOps(ops: Op[]): { ops: Op[]; owned: boolean } {\n if (!open(true)) return { ops, owned: false };\n return { ops: [...ops, createArrangeOp({}, settle, env.writePose)], owned: true };\n }\n\n return {\n setHandler(handler) {\n installed = handler;\n return () => {\n if (installed === handler) installed = null;\n };\n },\n onReflow: reflowListeners.subscribe,\n noteArrival(id, parent, from) {\n if (arrivals === null || parent === null || parent === from) return;\n changed.add(parent);\n const list = arrivals.get(parent);\n if (list === undefined) arrivals.set(parent, [id]);\n else if (!list.includes(id)) list.push(id);\n },\n noteDeparture(id, from, to) {\n if (arrivals === null || from === null || from === to) return;\n changed.add(from);\n // One that joined earlier in this edit was never there to leave.\n if (arrivals.get(from)?.includes(id)) return;\n const list = departures.get(from);\n if (list === undefined) departures.set(from, [id]);\n else if (!list.includes(id)) list.push(id);\n },\n noteChange(id) {\n if (arrivals !== null && id !== null) changed.add(id);\n },\n bareLayoutEdit: () => activeHandler() !== null && arrivals === null && env.recordsBareEdits(),\n open,\n settle,\n emitReflow,\n abandon,\n runEdit(ops, run) {\n const { ops: all, owned } = withArrivalOps(ops);\n try {\n run(all);\n if (owned) emitReflow();\n } catch (err) {\n if (!(err instanceof SceneArrivalRefused)) throw err;\n } finally {\n if (owned) abandon();\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derivedPath.js","names":["SLOT","resolving","cycleHits","resolveDerivedPath","derivePath","node","ids","dependencyIdsOf","childrenOf","hitsBefore","computed","record","nodeMemo","memoSlot","draw","depOf","sameDeps","dropPoseKeyedMemoSlots","deps","recordDeps","into"],"sources":["../../../src/core/scene/derivedPath.ts"],"sourcesContent":["/**\n * `resolveDerivedPath` — the path a node computes from its dependencies.\n *\n * It sits beside `derivedPose` rather than in `canvas/` because a *pose* can\n * now derive from a dependency's path: an edge label is positioned along the\n * route it labels, and `effectivePose` is what resolves it. The render walk's\n * wrapper stays in `canvas/derivedPath.ts`.\n *\n * Invalidation is both pushed and pulled. The scene pushes — dropping the\n * dependent's memo slot on the edits it performs — and on a memo hit this\n * module re-resolves the dependencies and compares their poses *by value*\n * against the ones the cached path was drawn from. The pull is what covers a\n * dependency that moved with no scene edit behind it: a lookup answering poses\n * of its own (`sceneDepLookup(scene, toPose)`), an ancestor's frame moving, a\n * dependency appearing or going away. Value, not reference: a pose override\n * mutates its buffer in place, so the reference never moves.\n *\n * The pull covers poses only. A derivation is handed each dependency's whole\n * node, so one reading `data` or `layer` still rides on the scene's push.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { dependencyIdsOf } from './dependents';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport { recordDeps, sameDeps, type DepRecord } from './depMemo';\nimport type { DerivedDep, DerivePathFn, NodeId } from './types';\n\nconst SLOT = 'kit:derivedPath';\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies it\n * without importing the full generic shape. */\nexport interface PathDerivingNode<TPose> {\n id: NodeId;\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** A cached path, with what it was drawn from. */\ninterface PathMemo<TPose> extends DepRecord<TPose> {\n path: Path | null;\n}\n\n/** Ids whose path is on the stack. A dependency graph is meant to be acyclic\n * and `dependsOn` is unvalidated; now that a path can read another path, a\n * cycle has to resolve to `null` at whichever node closes it rather than\n * overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires, so a value computed from a\n * fallback is not cached — it is an artifact of which node was asked first. */\nlet cycleHits = 0;\n\n/**\n * The path `node` computes from its dependencies, or `null` when it derives\n * from nothing (the normal case) or its `derivePath` has nothing to draw.\n *\n * `depOf` supplies each dependency — its node, the pose it is painted at, and\n * its own path; one it cannot resolve reaches `derivePath` as `undefined`.\n * `childrenOf` answers a node whose `dependsOn` is `'children'`.\n *\n * `memoSlot` separates a `depOf` that answers different poses than the scene's\n * own — the memo key cannot see which lookup filled it.\n */\nexport function resolveDerivedPath<TPose>(\n node: PathDerivingNode<TPose>,\n depOf: (id: NodeId) => DerivedDep<TPose> | undefined,\n childrenOf: (id: NodeId) => readonly NodeId[],\n memoSlot: string = SLOT,\n): Path | null {\n const derivePath = node.derivePath;\n if (derivePath === undefined) return null;\n const ids = dependencyIdsOf(node, childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n let computed = false;\n const record = nodeMemo<PathMemo<TPose>>(node, memoSlot, node.pose, () => {\n computed = true;\n return draw(node, derivePath, ids.map(depOf), { nodes: [], poses: [], path: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n draw(node, derivePath, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.path;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePath` against `deps` and record what it was run against. */\nfunction draw<TPose>(\n node: PathDerivingNode<TPose>,\n derivePath: NonNullable<PathDerivingNode<TPose>['derivePath']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PathMemo<TPose>,\n): PathMemo<TPose> {\n into.path = derivePath(node as never, deps);\n recordDeps(into, deps);\n return into;\n}\n\n"],"mappings":";;;;AA0BA,IAAMA,IAAO,mBAsBPC,oBAAY,IAAI,IAAY,GAI9BC,IAAY;AAahB,SAAgBC,EACd,GACA,GACA,GACA,
|
|
1
|
+
{"version":3,"file":"derivedPath.js","names":["SLOT","resolving","cycleHits","resolveDerivedPath","derivePath","node","ids","dependencyIdsOf","childrenOf","hitsBefore","computed","record","nodeMemo","memoSlot","draw","depOf","sameDeps","dropPoseKeyedMemoSlots","deps","recordDeps","into"],"sources":["../../../src/core/scene/derivedPath.ts"],"sourcesContent":["/**\n * `resolveDerivedPath` — the path a node computes from its dependencies.\n *\n * It sits beside `derivedPose` rather than in `canvas/` because a *pose* can\n * now derive from a dependency's path: an edge label is positioned along the\n * route it labels, and `effectivePose` is what resolves it. The render walk's\n * wrapper stays in `canvas/derivedPath.ts`.\n *\n * Invalidation is both pushed and pulled. The scene pushes — dropping the\n * dependent's memo slot on the edits it performs — and on a memo hit this\n * module re-resolves the dependencies and compares their poses *by value*\n * against the ones the cached path was drawn from. The pull is what covers a\n * dependency that moved with no scene edit behind it: a lookup answering poses\n * of its own (`sceneDepLookup(scene, toPose)`), an ancestor's frame moving, a\n * dependency appearing or going away. Value, not reference: a pose override\n * mutates its buffer in place, so the reference never moves.\n *\n * The pull covers poses only. A derivation is handed each dependency's whole\n * node, so one reading `data` or `layer` still rides on the scene's push.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { dependencyIdsOf } from './dependents';\nimport { dropPoseKeyedMemoSlots, nodeMemo, type NodeMemoScope } from './nodeMemo';\nimport { recordDeps, sameDeps, type DepRecord } from './depMemo';\nimport type { DerivedDep, DerivePathFn, NodeId } from './types';\n\nconst SLOT = 'kit:derivedPath';\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies it\n * without importing the full generic shape. */\nexport interface PathDerivingNode<TPose> {\n id: NodeId;\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** A cached path, with what it was drawn from. */\ninterface PathMemo<TPose> extends DepRecord<TPose> {\n path: Path | null;\n}\n\n/** Ids whose path is on the stack. A dependency graph is meant to be acyclic\n * and `dependsOn` is unvalidated; now that a path can read another path, a\n * cycle has to resolve to `null` at whichever node closes it rather than\n * overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires, so a value computed from a\n * fallback is not cached — it is an artifact of which node was asked first. */\nlet cycleHits = 0;\n\n/**\n * The path `node` computes from its dependencies, or `null` when it derives\n * from nothing (the normal case) or its `derivePath` has nothing to draw.\n *\n * `depOf` supplies each dependency — its node, the pose it is painted at, and\n * its own path; one it cannot resolve reaches `derivePath` as `undefined`.\n * `childrenOf` answers a node whose `dependsOn` is `'children'`.\n *\n * `memoSlot` separates a `depOf` that answers different poses than the scene's\n * own — the memo key cannot see which lookup filled it.\n */\nexport function resolveDerivedPath<TPose>(\n node: PathDerivingNode<TPose>,\n depOf: (id: NodeId) => DerivedDep<TPose> | undefined,\n childrenOf: (id: NodeId) => readonly NodeId[],\n memoSlot: string | NodeMemoScope = SLOT,\n): Path | null {\n const derivePath = node.derivePath;\n if (derivePath === undefined) return null;\n const ids = dependencyIdsOf(node, childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n let computed = false;\n const record = nodeMemo<PathMemo<TPose>>(node, memoSlot, node.pose, () => {\n computed = true;\n return draw(node, derivePath, ids.map(depOf), { nodes: [], poses: [], path: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n draw(node, derivePath, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.path;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePath` against `deps` and record what it was run against. */\nfunction draw<TPose>(\n node: PathDerivingNode<TPose>,\n derivePath: NonNullable<PathDerivingNode<TPose>['derivePath']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PathMemo<TPose>,\n): PathMemo<TPose> {\n into.path = derivePath(node as never, deps);\n recordDeps(into, deps);\n return into;\n}\n\n"],"mappings":";;;;AA0BA,IAAMA,IAAO,mBAsBPC,oBAAY,IAAI,IAAY,GAI9BC,IAAY;AAahB,SAAgBC,EACd,GACA,GACA,GACA,IAAmCH,GACtB;CACb,IAAMI,IAAaC,EAAK;CACxB,IAAID,MAAe,KAAA,GAAW,OAAO;CACrC,IAAME,IAAMC,EAAgBF,GAAMG,CAAU;CAC5C,IAAIF,EAAI,WAAW,GAAG,OAAO;CAC7B,IAAIL,EAAU,IAAII,EAAK,EAAE,GAEvB,OADA,KACO;CAET,EAAU,IAAIA,EAAK,EAAE;CACrB,IAAMI,IAAaP;CACnB,IAAI;EACF,IAAIQ,IAAW,IACTC,IAASC,EAA0BP,GAAMQ,GAAUR,EAAK,aAC5D,IAAW,IACJS,EAAKT,GAAMD,GAAYE,EAAI,IAAIS,CAAK,GAAG;GAAE,OAAO,CAAC;GAAG,OAAO,CAAC;GAAG,MAAM;EAAK,CAAC,EACnF;EAOD,OAJI,CAACL,KAAY,CAACM,EAASL,GAAQL,GAAKS,CAAK,KAC3CD,EAAKT,GAAMD,GAAYE,EAAI,IAAIS,CAAK,GAAGJ,CAAM,GAE3CT,MAAcO,KAAYQ,EAAuBZ,CAAI,GAClDM,EAAO;CAChB,UAAU;EACR,EAAU,OAAON,EAAK,EAAE;CAC1B;AACF;AAGA,SAASS,EACP,GACA,GACA,GACA,GACiB;CAGjB,OAFA,EAAK,OAAOV,EAAWC,GAAea,CAAI,GAC1CC,EAAWC,GAAMF,CAAI,GACdE;AACT"}
|