@weasel-js/core 1.11.1 → 1.13.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +275 -0
- package/dist/WeaselProvider.js +1 -1
- package/dist/affordances/pathAnchors.js +10 -6
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/animation/poseTarget.js +46 -15
- package/dist/animation/poseTarget.js.map +1 -1
- package/dist/animation/poseTransition.js +3 -3
- package/dist/animation/poseTransition.js.map +1 -1
- package/dist/animation/reflow.js +3 -6
- package/dist/animation/reflow.js.map +1 -1
- package/dist/animation/rig/bindRig.js +2 -2
- package/dist/animation/rig/bindRig.js.map +1 -1
- package/dist/animation/rig/skinBinding.js +2 -2
- package/dist/animation/rig/skinBinding.js.map +1 -1
- package/dist/barrel/canvas.js +5 -1
- package/dist/barrel/chrome.js +3 -1
- package/dist/barrel/paint.js +4 -4
- package/dist/barrel/paths.js +9 -4
- package/dist/barrel/renderer.js +3 -2
- package/dist/barrel/routing.js +4 -4
- package/dist/barrel/scene.js +7 -1
- package/dist/barrel/text.js +2 -2
- package/dist/barrel/viewport.js +6 -2
- package/dist/canvas/Canvas.js +2 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +23 -34
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/ModalityHud.js +1 -1
- package/dist/canvas/NodeShape.js +5 -1
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/scenePointQuery.js +27 -5
- package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +1 -0
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +10 -2
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +17 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -0
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +19 -4
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
- package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +7 -4
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +30 -12
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/ViewMarker.js +35 -0
- package/dist/canvas/ViewMarker.js.map +1 -0
- package/dist/canvas/accentColor.js +26 -3
- package/dist/canvas/accentColor.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +14 -20
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/copiesAbove.js +1 -1
- package/dist/canvas/copiesAbove.js.map +1 -1
- package/dist/canvas/defaultDrawOne.js +11 -6
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/deps/areaSelect.js +5 -2
- package/dist/canvas/deps/areaSelect.js.map +1 -1
- package/dist/canvas/deps/booleans.js +3 -2
- package/dist/canvas/deps/booleans.js.map +1 -1
- package/dist/canvas/deps/enteredGroup.js +81 -0
- package/dist/canvas/deps/enteredGroup.js.map +1 -0
- package/dist/canvas/deps/hitTestArea.js +28 -16
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/insert.js +10 -38
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/insertSite.js +39 -0
- package/dist/canvas/deps/insertSite.js.map +1 -0
- package/dist/canvas/deps/lassoSelect.js +6 -3
- package/dist/canvas/deps/lassoSelect.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +3 -7
- package/dist/canvas/deps/pathEditCommits.js.map +1 -1
- package/dist/canvas/deps/pathLeaf.js +1 -1
- package/dist/canvas/deps/pathLeaf.js.map +1 -1
- package/dist/canvas/deps/slice.js +2 -1
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/derivedPath.js +14 -6
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/doubleClickTarget.js +9 -11
- package/dist/canvas/doubleClickTarget.js.map +1 -1
- package/dist/canvas/drawnBounds.js +26 -9
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/inheritedPaints.js +55 -0
- package/dist/canvas/inheritedPaints.js.map +1 -0
- package/dist/canvas/nodeShapes/derivedPainter.js +15 -16
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/nodePaint.js +7 -34
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +7 -7
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +9 -9
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
- package/dist/canvas/nodeShapes/textPainter.js +13 -12
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
- package/dist/canvas/ownClip.js +24 -0
- package/dist/canvas/ownClip.js.map +1 -0
- package/dist/canvas/paintCull.js.map +1 -1
- package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
- package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
- package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
- package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
- package/dist/canvas/pickReach.js +3 -3
- package/dist/canvas/pickReach.js.map +1 -1
- package/dist/canvas/pickWalk.js +17 -91
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/poseCascade.js +1 -1
- package/dist/canvas/poseCascade.js.map +1 -1
- package/dist/canvas/publishPreviews.js +97 -0
- package/dist/canvas/publishPreviews.js.map +1 -0
- package/dist/canvas/renderSceneToPixels.js +1 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +7 -5
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +17 -10
- package/dist/canvas/sceneClipboard.js.map +1 -1
- package/dist/canvas/scenePickReads.js +37 -0
- package/dist/canvas/scenePickReads.js.map +1 -0
- package/dist/canvas/scenePickSource.js +74 -0
- package/dist/canvas/scenePickSource.js.map +1 -0
- package/dist/canvas/sceneSlotWiring.js +9 -3
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +9 -4
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/shownNodes.js +15 -0
- package/dist/canvas/shownNodes.js.map +1 -0
- package/dist/canvas/surfaceContribution.js +12 -8
- package/dist/canvas/surfaceContribution.js.map +1 -1
- package/dist/canvas/useCanvasView.js +4 -2
- package/dist/canvas/useCanvasView.js.map +1 -1
- package/dist/canvas/useLayerStack.js +6 -2
- package/dist/canvas/useLayerStack.js.map +1 -1
- package/dist/canvas/useSceneAdapter.js +5 -3
- package/dist/canvas/useSceneAdapter.js.map +1 -1
- package/dist/canvas/useViewAim.js +121 -0
- package/dist/canvas/useViewAim.js.map +1 -0
- package/dist/canvas/useViewNesting.js +3 -2
- package/dist/canvas/useViewNesting.js.map +1 -1
- package/dist/canvas/useViewRegistrySurface.js +4 -2
- package/dist/canvas/useViewRegistrySurface.js.map +1 -1
- package/dist/canvas/viewMarkers.js +63 -0
- package/dist/canvas/viewMarkers.js.map +1 -0
- package/dist/canvas/viewRegistry.js +27 -1
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/clipboard.d.ts +2 -2
- package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
- package/dist/clone.d.ts +3 -3
- package/dist/core/isPlainObject.js +2 -2
- package/dist/core/layers/layerGroup.js.map +1 -1
- package/dist/core/pages/pageEdits.js +2 -3
- package/dist/core/pages/pageEdits.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js +59 -2
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/scene/NodeRouting.js.map +1 -1
- package/dist/core/scene/arrivalWindow.js +1 -1
- package/dist/core/scene/arrivalWindow.js.map +1 -1
- package/dist/core/scene/clipNothing.js +10 -0
- package/dist/core/scene/clipNothing.js.map +1 -0
- package/dist/core/scene/declaredFieldSetters.js +44 -0
- package/dist/core/scene/declaredFieldSetters.js.map +1 -0
- package/dist/core/scene/depMemo.js +14 -3
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/dependents.js +9 -1
- package/dist/core/scene/dependents.js.map +1 -1
- package/dist/core/scene/derivedParts.js +10 -11
- package/dist/core/scene/derivedParts.js.map +1 -1
- package/dist/core/scene/documentOf.js +43 -0
- package/dist/core/scene/documentOf.js.map +1 -0
- package/dist/core/scene/effectivePose.js +44 -15
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/fieldEdits.js +17 -14
- package/dist/core/scene/fieldEdits.js.map +1 -1
- package/dist/core/scene/fontReads.js +37 -0
- package/dist/core/scene/fontReads.js.map +1 -0
- package/dist/core/scene/historyForm.js +4 -4
- package/dist/core/scene/historyForm.js.map +1 -1
- package/dist/core/scene/inheritedPaint.js +97 -0
- package/dist/core/scene/inheritedPaint.js.map +1 -0
- package/dist/core/scene/kitOps.js +3 -23
- package/dist/core/scene/kitOps.js.map +1 -1
- package/dist/core/scene/kitRegistry.js +5 -0
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/layoutPass.js +1 -2
- package/dist/core/scene/layoutPass.js.map +1 -1
- package/dist/core/scene/nodeAsShown.js +72 -0
- package/dist/core/scene/nodeAsShown.js.map +1 -0
- package/dist/core/scene/nodeFnFields.js +2 -10
- package/dist/core/scene/nodeFnFields.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +21 -8
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/nodeStore.js +2 -4
- package/dist/core/scene/nodeStore.js.map +1 -1
- package/dist/core/scene/pathEffects/blend.js.map +1 -1
- package/dist/core/scene/pathEffects/derive.js +10 -4
- package/dist/core/scene/pathEffects/derive.js.map +1 -1
- package/dist/core/scene/pathEffects/effects.js +3 -1
- package/dist/core/scene/pathEffects/effects.js.map +1 -1
- package/dist/core/scene/pathEffects/foundHidden.js +35 -0
- package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
- package/dist/core/scene/pathEffects/textAlong.js +95 -0
- package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
- package/dist/core/scene/plyStacks.js +89 -0
- package/dist/core/scene/plyStacks.js.map +1 -0
- package/dist/core/scene/poseHold.js +42 -24
- package/dist/core/scene/poseHold.js.map +1 -1
- package/dist/core/scene/poseOverrides.js +35 -38
- package/dist/core/scene/poseOverrides.js.map +1 -1
- package/dist/core/scene/posePlies.js +59 -0
- package/dist/core/scene/posePlies.js.map +1 -0
- package/dist/core/scene/reconcileSpecs.js +6 -15
- package/dist/core/scene/reconcileSpecs.js.map +1 -1
- package/dist/core/scene/renderOrder.js +10 -4
- package/dist/core/scene/renderOrder.js.map +1 -1
- package/dist/core/scene/sameValue.js +17 -0
- package/dist/core/scene/sameValue.js.map +1 -0
- package/dist/core/scene/scene.js +3 -3
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/sceneEdits.js +8 -0
- package/dist/core/scene/sceneEdits.js.map +1 -1
- package/dist/core/scene/sceneState.js +1 -2
- package/dist/core/scene/sceneState.js.map +1 -1
- package/dist/core/scene/serialization.js +45 -9
- package/dist/core/scene/serialization.js.map +1 -1
- package/dist/core/scene/staleReplay.js +5 -2
- package/dist/core/scene/staleReplay.js.map +1 -1
- package/dist/core/scene/statedFields.js +4 -2
- package/dist/core/scene/statedFields.js.map +1 -1
- package/dist/core/scene/treeEdits.js +2 -4
- package/dist/core/scene/treeEdits.js.map +1 -1
- package/dist/core/scene/treeOrder.js +3 -1
- package/dist/core/scene/treeOrder.js.map +1 -1
- package/dist/core/scene/treeReads.js +1 -2
- package/dist/core/scene/treeReads.js.map +1 -1
- package/dist/core/scene/treeShape.js +13 -0
- package/dist/core/scene/treeShape.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js +1 -1
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/{cornerHandles-BHuoMs1o.d.ts → cornerHandles-CNCb7aTY.d.ts} +3 -2
- package/dist/{depSchema-BpZNOv7q.d.ts → depSchema-B1RS-IJx.d.ts} +39 -18
- package/dist/devtools/handlers.js +1 -1
- package/dist/devtools/handlers.js.map +1 -1
- package/dist/devtools/protocol.js.map +1 -1
- package/dist/devtools/registry.js +4 -4
- package/dist/devtools/registry.js.map +1 -1
- package/dist/devtools-protocol.d.ts +4 -3
- package/dist/devtools.d.ts +3 -2
- package/dist/features/measure/measureContribution.js +4 -2
- package/dist/features/measure/measureContribution.js.map +1 -1
- package/dist/features/measure/measureHit.js +5 -3
- package/dist/features/measure/measureHit.js.map +1 -1
- package/dist/features/measure/measurement.js +1 -1
- package/dist/features/measure/measurement.js.map +1 -1
- package/dist/features/modes/modeLayers.js +14 -7
- package/dist/features/modes/modeLayers.js.map +1 -1
- package/dist/features/modes/modeTriggers.js +10 -6
- package/dist/features/modes/modeTriggers.js.map +1 -1
- package/dist/features/pathEffects/effectSources.js +69 -0
- package/dist/features/pathEffects/effectSources.js.map +1 -0
- package/dist/features/pathEffects/readSource.js +18 -2
- package/dist/features/pathEffects/readSource.js.map +1 -1
- package/dist/features/pathEffects/textItems.js +53 -0
- package/dist/features/pathEffects/textItems.js.map +1 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js +53 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
- package/dist/features/paths/editablePath.js +1 -1
- package/dist/features/paths/editablePath.js.map +1 -1
- package/dist/features/paths/pathEditState.js +5 -2
- package/dist/features/paths/pathEditState.js.map +1 -1
- package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
- package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
- package/dist/features/selection/enteredGroupFrame.js +65 -0
- package/dist/features/selection/enteredGroupFrame.js.map +1 -0
- package/dist/features/selection/enteredGroupScope.js +44 -0
- package/dist/features/selection/enteredGroupScope.js.map +1 -0
- package/dist/features/text/sceneTextDrafts.js +64 -0
- package/dist/features/text/sceneTextDrafts.js.map +1 -0
- package/dist/features/text/textHitAt.js +34 -3
- package/dist/features/text/textHitAt.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +23 -19
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/viewports/aim.js +105 -0
- package/dist/features/viewports/aim.js.map +1 -0
- package/dist/features/viewports/viewMarker.js +82 -0
- package/dist/features/viewports/viewMarker.js.map +1 -0
- package/dist/features/viewports/viewMarkerDrag.js +55 -0
- package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
- package/dist/index.d.ts +709 -258
- package/dist/index.js +33 -12
- package/dist/interactions/actions/admitsChild.js +11 -0
- package/dist/interactions/actions/admitsChild.js.map +1 -0
- package/dist/interactions/actions/booleans/booleans.js +2 -1
- package/dist/interactions/actions/booleans/booleans.js.map +1 -1
- package/dist/interactions/actions/booleans/operands.js +6 -5
- package/dist/interactions/actions/booleans/operands.js.map +1 -1
- package/dist/interactions/actions/clipboard/action.js +27 -17
- package/dist/interactions/actions/clipboard/action.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
- package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +7 -4
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/contextMenu.js +33 -11
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js +5 -2
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js +28 -14
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +11 -8
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterGroup.js +88 -0
- package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
- package/dist/interactions/actions/defaults/enterPathEdit.js +20 -10
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/escape.js +11 -8
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +26 -20
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +10 -4
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +8 -2
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
- package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js +24 -6
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js +15 -8
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/selectDeep.js +35 -0
- package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
- package/dist/interactions/actions/defaults/setFill.js +7 -3
- package/dist/interactions/actions/defaults/setFill.js.map +1 -1
- package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
- package/dist/interactions/actions/defaults/setStroke.js +12 -11
- package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js +7 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +19 -13
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/drawnBoxes.js +2 -1
- package/dist/interactions/actions/drawnBoxes.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +7 -4
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +8 -2
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +1 -0
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/parent.js +7 -5
- package/dist/interactions/actions/insert/parent.js.map +1 -1
- package/dist/interactions/actions/insert/placeAction.js +3 -2
- package/dist/interactions/actions/insert/placeAction.js.map +1 -1
- package/dist/interactions/actions/insert/repositionAction.js +2 -1
- package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
- package/dist/interactions/actions/insert/session.js +2 -2
- package/dist/interactions/actions/insert/session.js.map +1 -1
- package/dist/interactions/actions/move/action.js +7 -4
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/move/commit/reparent.js +1 -1
- package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
- package/dist/interactions/actions/move/gestureAdapter.js +6 -7
- package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
- package/dist/interactions/actions/move/layout/exits.js +1 -1
- package/dist/interactions/actions/move/layout/exits.js.map +1 -1
- package/dist/interactions/actions/move/session.js +4 -4
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
- package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
- package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
- package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
- package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
- package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
- package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
- package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
- package/dist/interactions/actions/resize/cornerHandles.js +2 -1
- package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +12 -7
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +8 -5
- package/dist/interactions/actions/transform-again/action.js.map +1 -1
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
- package/dist/json.d.ts +4 -0
- package/dist/json.js +3 -0
- package/dist/kitSettingsSchema.js +8 -1
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/{math-CrJULaZI.d.ts → math-C_elShP_.d.ts} +7 -1
- package/dist/math.d.ts +1 -1
- package/dist/math.js +2 -1
- package/dist/move.d.ts +4 -4
- package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
- package/dist/resize.d.ts +4 -4
- package/dist/test-seams.d.ts +6 -6
- package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
- package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +7 -4
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/image/useImageTool.js +1 -0
- package/dist/tools/builtin/image/useImageTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
- 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/measure/useMeasureTool.js +12 -5
- package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +14 -6
- 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 -4
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/pressTarget.js +22 -0
- package/dist/tools/builtin/pressTarget.js.map +1 -0
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/adapterPointQuery.js +6 -5
- package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
- package/dist/tools/builtin/select/collapseDeferred.js +40 -0
- package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
- package/dist/tools/builtin/select/groupEntry.js +82 -0
- package/dist/tools/builtin/select/groupEntry.js.map +1 -0
- package/dist/tools/builtin/select/pressDecision.js +32 -0
- package/dist/tools/builtin/select/pressDecision.js.map +1 -0
- package/dist/tools/builtin/select/selectionContributions.js +22 -13
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +74 -55
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +22 -8
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +13 -4
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/sticky/stickyNode.js +4 -1
- package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
- package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
- package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +3 -1
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
- package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
- package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
- package/dist/util/paint.js +1 -3
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +29 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcileSpecs.js","names":[],"sources":["../../../src/core/scene/reconcileSpecs.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"reconcileSpecs.js","names":["same"],"sources":["../../../src/core/scene/reconcileSpecs.ts"],"sourcesContent":["import { sameValue as same } from './sameValue';\nimport type { AddNodeSpec, NodeId, Scene } from './types';\n\n/**\n * Bring `scene` from `prev` to `next`: add what `next` gained, remove what it\n * lost, and write only the fields that changed between the two.\n *\n * The diff is spec against spec, not spec against scene, so whatever the scene\n * did since `prev` — a node dragged, an edge authored by a gesture — survives\n * unless `next` changes that same field. The writes are untracked: syncing to\n * outside data is not an undo step.\n *\n * Every spec needs an `id`, and `next` must be in an order `createScene` would\n * accept — parents and dependencies before what names them. A node whose\n * `kind` or derive functions change is removed and added again, along with\n * everything its removal takes with it, and stays selected if it was.\n */\nexport function reconcileSpecs<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n prev: readonly AddNodeSpec<TData, TLayer, TPose>[],\n next: readonly AddNodeSpec<TData, TLayer, TPose>[],\n): void {\n const prevById = byId(prev);\n const nextById = byId(next);\n const gone: NodeId[] = [];\n for (const [id, before] of prevById) {\n const after = nextById.get(id);\n if (after === undefined || needsReplace(before, after)) gone.push(id);\n }\n const removing = scene.removalClosure(gone.filter((id) => scene.get(id) !== undefined));\n const removed = new Set(removing);\n\n // A replaced node is the same node to whoever selected it.\n const selected = scene.getSelection();\n\n scene.untracked(() => {\n scene.removeMany(removing);\n for (const spec of next) {\n const id = spec.id!;\n const before = prevById.get(id);\n if (scene.get(id) === undefined || before === undefined || removed.has(id)) {\n scene.add(spec);\n continue;\n }\n // An unlock goes first and a lock last, so the writes between can land.\n if (before.locked === true && spec.locked !== true) scene.setLocked(id, false);\n const parent = spec.parent ?? null;\n if (parent !== (before.parent ?? null)) scene.move(id, parent);\n if (spec.layer !== before.layer) scene.setLayer(id, spec.layer);\n if (!same(spec.data, before.data)) scene.update(id, { data: spec.data });\n if (!same(spec.dependsOn, before.dependsOn)) scene.setDependsOn(id, spec.dependsOn);\n if (!same(spec.units, before.units)) scene.setUnits(id, spec.units ?? null);\n if (spec.clip !== before.clip) scene.setClip(id, spec.clip ?? null);\n if (!same(spec.pose, before.pose)) scene.setPose(id, spec.pose);\n if ((spec.hidden === true) !== (before.hidden === true)) scene.setHidden(id, spec.hidden === true);\n if (spec.locked === true && before.locked !== true) scene.setLocked(id, true);\n }\n if (selected.some((id) => removed.has(id))) {\n scene.setSelection(selected.filter((id) => scene.get(id) !== undefined));\n }\n });\n}\n\nfunction byId<T extends { id?: NodeId }>(specs: readonly T[]): Map<NodeId, T> {\n const out = new Map<NodeId, T>();\n for (const spec of specs) {\n if (spec.id === undefined) throw new Error('reconcileSpecs: every spec needs an id');\n if (out.has(spec.id)) throw new Error(`reconcileSpecs: id \"${spec.id}\" appears more than once`);\n out.set(spec.id, spec);\n }\n return out;\n}\n\nfunction needsReplace<TData, TLayer extends string, TPose>(\n a: AddNodeSpec<TData, TLayer, TPose>,\n b: AddNodeSpec<TData, TLayer, TPose>,\n): boolean {\n return a.kind !== b.kind\n || a.pickable !== b.pickable\n || a.derivePath !== b.derivePath\n || a.derivePose !== b.derivePose\n || a.clipFromPose !== b.clipFromPose\n || a.copiesFromPose !== b.copiesFromPose\n || a.layout !== b.layout;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,SAAgB,eACd,OACA,MACA,MACM;CACN,MAAM,WAAW,KAAK,IAAI;CAC1B,MAAM,WAAW,KAAK,IAAI;CAC1B,MAAM,OAAiB,CAAC;CACxB,KAAK,MAAM,CAAC,IAAI,WAAW,UAAU;EACnC,MAAM,QAAQ,SAAS,IAAI,EAAE;EAC7B,IAAI,UAAU,UAAa,aAAa,QAAQ,KAAK,GAAG,KAAK,KAAK,EAAE;CACtE;CACA,MAAM,WAAW,MAAM,eAAe,KAAK,QAAQ,OAAO,MAAM,IAAI,EAAE,MAAM,MAAS,CAAC;CACtF,MAAM,UAAU,IAAI,IAAI,QAAQ;CAGhC,MAAM,WAAW,MAAM,aAAa;CAEpC,MAAM,gBAAgB;EACpB,MAAM,WAAW,QAAQ;EACzB,KAAK,MAAM,QAAQ,MAAM;GACvB,MAAM,KAAK,KAAK;GAChB,MAAM,SAAS,SAAS,IAAI,EAAE;GAC9B,IAAI,MAAM,IAAI,EAAE,MAAM,UAAa,WAAW,UAAa,QAAQ,IAAI,EAAE,GAAG;IAC1E,MAAM,IAAI,IAAI;IACd;GACF;GAEA,IAAI,OAAO,WAAW,QAAQ,KAAK,WAAW,MAAM,MAAM,UAAU,IAAI,KAAK;GAC7E,MAAM,SAAS,KAAK,UAAU;GAC9B,IAAI,YAAY,OAAO,UAAU,OAAO,MAAM,KAAK,IAAI,MAAM;GAC7D,IAAI,KAAK,UAAU,OAAO,OAAO,MAAM,SAAS,IAAI,KAAK,KAAK;GAC9D,IAAI,CAACA,UAAK,KAAK,MAAM,OAAO,IAAI,GAAG,MAAM,OAAO,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;GACvE,IAAI,CAACA,UAAK,KAAK,WAAW,OAAO,SAAS,GAAG,MAAM,aAAa,IAAI,KAAK,SAAS;GAClF,IAAI,CAACA,UAAK,KAAK,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,IAAI,KAAK,SAAS,IAAI;GAC1E,IAAI,KAAK,SAAS,OAAO,MAAM,MAAM,QAAQ,IAAI,KAAK,QAAQ,IAAI;GAClE,IAAI,CAACA,UAAK,KAAK,MAAM,OAAO,IAAI,GAAG,MAAM,QAAQ,IAAI,KAAK,IAAI;GAC9D,IAAK,KAAK,WAAW,UAAW,OAAO,WAAW,OAAO,MAAM,UAAU,IAAI,KAAK,WAAW,IAAI;GACjG,IAAI,KAAK,WAAW,QAAQ,OAAO,WAAW,MAAM,MAAM,UAAU,IAAI,IAAI;EAC9E;EACA,IAAI,SAAS,MAAM,OAAO,QAAQ,IAAI,EAAE,CAAC,GACvC,MAAM,aAAa,SAAS,QAAQ,OAAO,MAAM,IAAI,EAAE,MAAM,MAAS,CAAC;CAE3E,CAAC;AACH;AAEA,SAAS,KAAgC,OAAqC;CAC5E,MAAM,sBAAM,IAAI,IAAe;CAC/B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,OAAO,QAAW,MAAM,IAAI,MAAM,wCAAwC;EACnF,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG,MAAM,IAAI,MAAM,uBAAuB,KAAK,GAAG,yBAAyB;EAC9F,IAAI,IAAI,KAAK,IAAI,IAAI;CACvB;CACA,OAAO;AACT;AAEA,SAAS,aACP,GACA,GACS;CACT,OAAO,EAAE,SAAS,EAAE,QACf,EAAE,aAAa,EAAE,YACjB,EAAE,eAAe,EAAE,cACnB,EAAE,eAAe,EAAE,cACnB,EAAE,iBAAiB,EAAE,gBACrB,EAAE,mBAAmB,EAAE,kBACvB,EAAE,WAAW,EAAE;AACtB"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { hasChildren } from "./treeShape.js";
|
|
2
|
+
|
|
1
3
|
//#region src/core/scene/renderOrder.ts
|
|
4
|
+
/**
|
|
5
|
+
* A scene's render order: its nodes layer-major, each layer in tree preorder,
|
|
6
|
+
* cached until the structure changes.
|
|
7
|
+
*/
|
|
2
8
|
function createRenderOrder(state) {
|
|
3
9
|
/**
|
|
4
10
|
* Layer-major DFS-preorder ids, and the same sequence as nodes. Four walks
|
|
@@ -49,7 +55,7 @@ function createRenderOrder(state) {
|
|
|
49
55
|
const node = state.nodes.get(id);
|
|
50
56
|
if (!node) continue;
|
|
51
57
|
if (node.layer === only) out.push(id);
|
|
52
|
-
if (node
|
|
58
|
+
if (hasChildren(node)) for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);
|
|
53
59
|
}
|
|
54
60
|
return out;
|
|
55
61
|
}
|
|
@@ -62,7 +68,7 @@ function createRenderOrder(state) {
|
|
|
62
68
|
const node = state.nodes.get(id);
|
|
63
69
|
if (!node) continue;
|
|
64
70
|
if (node.layer === only) out.push(node);
|
|
65
|
-
if (node
|
|
71
|
+
if (hasChildren(node)) for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);
|
|
66
72
|
}
|
|
67
73
|
return out;
|
|
68
74
|
}
|
|
@@ -77,7 +83,7 @@ function createRenderOrder(state) {
|
|
|
77
83
|
if (!node) continue;
|
|
78
84
|
const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];
|
|
79
85
|
if (bucket) bucket.push(id);
|
|
80
|
-
if (node
|
|
86
|
+
if (hasChildren(node)) for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);
|
|
81
87
|
}
|
|
82
88
|
const out = [];
|
|
83
89
|
for (const bucket of buckets) for (const id of bucket) out.push(id);
|
|
@@ -92,7 +98,7 @@ function createRenderOrder(state) {
|
|
|
92
98
|
if (!node) continue;
|
|
93
99
|
const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];
|
|
94
100
|
if (bucket) bucket.push(node);
|
|
95
|
-
if (node
|
|
101
|
+
if (hasChildren(node)) for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);
|
|
96
102
|
}
|
|
97
103
|
const out = [];
|
|
98
104
|
for (const bucket of buckets) for (const node of bucket) out.push(node);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderOrder.js","names":[],"sources":["../../../src/core/scene/renderOrder.ts"],"sourcesContent":["/**\n * A scene's render order: its nodes layer-major, each layer in tree preorder,\n * cached until the structure changes.\n */\nimport type { Node, NodeId } from './types';\n\n/** What the walks read of a scene's state. */\nexport interface RenderOrderState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n layers: { id: TLayer }[];\n layerIndex: Map<TLayer, number>;\n}\n\nexport interface RenderOrder<TData, TLayer extends string, TPose> {\n ids(): NodeId[];\n nodes(): Node<TData, TLayer, TPose>[];\n nodesOnLayer(layer: TLayer): readonly Node<TData, TLayer, TPose>[];\n /** Call on every structural edit; see the cache note below. */\n invalidate(): void;\n}\n\nexport function createRenderOrder<TData, TLayer extends string, TPose>(\n state: RenderOrderState<TData, TLayer, TPose>,\n): RenderOrder<TData, TLayer, TPose> {\n /**\n * Layer-major DFS-preorder ids, and the same sequence as nodes. Four walks\n * for two sequences, which wants explaining, because collapsing them is the\n * obvious cleanup and each collapse was measured to cost something:\n *\n * - ids vs nodes: projecting one from the other is a whole extra pass, worth\n * 20% of `renderOrder()` on a multi-layer scene.\n * - flat vs bucketed: a single layer needs no buckets and can compare the\n * layer id instead of indexing it, which is 1.4x. Behind a branch in one\n * function it also slows the bucketed path by 20% — the two shapes share\n * one optimized body, and whichever ran first wins it.\n *\n * All four emit the same order and `renderOrder.test.ts` holds them to it.\n */\n function walkIds(): NodeId[] {\n return state.layers.length === 1 ? renderOrderFlat() : renderOrderBucketed();\n }\n\n function walkNodes(): Node<TData, TLayer, TPose>[] {\n return state.layers.length === 1 ? renderOrderNodesFlat() : renderOrderNodesBucketed();\n }\n\n /**\n * Render order survives everything except a structural edit, so both walks\n * are cached against a counter the structural writers bump. Pose and data\n * edits leave order alone and must NOT invalidate — they fire per frame\n * during a drag, which is exactly when the cache pays.\n *\n * A missed bump is a silent wrong answer: nodes painted in the wrong z-order,\n * or unpickable. `renderOrder.test.ts` walks each structural op and asserts\n * the order moved.\n */\n let structureGeneration = 0;\n let orderCache: {\n gen: number;\n ids?: NodeId[];\n nodes?: Node<TData, TLayer, TPose>[];\n byLayer?: Map<TLayer, Node<TData, TLayer, TPose>[]>;\n } = { gen: 0 };\n\n function invalidate(): void {\n structureGeneration++;\n }\n\n const EMPTY_NODES: readonly Node<TData, TLayer, TPose>[] = [];\n\n function currentOrderCache(): typeof orderCache {\n if (orderCache.gen !== structureGeneration) {\n orderCache = { gen: structureGeneration };\n }\n return orderCache;\n }\n\n function renderOrderFlat(): NodeId[] {\n const only = state.layers[0].id;\n const out: NodeId[] = [];\n const stack: NodeId[] = [...state.roots].reverse();\n while (stack.length > 0) {\n const id = stack.pop()!;\n const node = state.nodes.get(id);\n if (!node) continue;\n if (node.layer === only) out.push(id);\n if (node
|
|
1
|
+
{"version":3,"file":"renderOrder.js","names":[],"sources":["../../../src/core/scene/renderOrder.ts"],"sourcesContent":["/**\n * A scene's render order: its nodes layer-major, each layer in tree preorder,\n * cached until the structure changes.\n */\nimport { hasChildren } from './treeShape';\nimport type { Node, NodeId } from './types';\n\n/** What the walks read of a scene's state. */\nexport interface RenderOrderState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n layers: { id: TLayer }[];\n layerIndex: Map<TLayer, number>;\n}\n\nexport interface RenderOrder<TData, TLayer extends string, TPose> {\n ids(): NodeId[];\n nodes(): Node<TData, TLayer, TPose>[];\n nodesOnLayer(layer: TLayer): readonly Node<TData, TLayer, TPose>[];\n /** Call on every structural edit; see the cache note below. */\n invalidate(): void;\n}\n\nexport function createRenderOrder<TData, TLayer extends string, TPose>(\n state: RenderOrderState<TData, TLayer, TPose>,\n): RenderOrder<TData, TLayer, TPose> {\n /**\n * Layer-major DFS-preorder ids, and the same sequence as nodes. Four walks\n * for two sequences, which wants explaining, because collapsing them is the\n * obvious cleanup and each collapse was measured to cost something:\n *\n * - ids vs nodes: projecting one from the other is a whole extra pass, worth\n * 20% of `renderOrder()` on a multi-layer scene.\n * - flat vs bucketed: a single layer needs no buckets and can compare the\n * layer id instead of indexing it, which is 1.4x. Behind a branch in one\n * function it also slows the bucketed path by 20% — the two shapes share\n * one optimized body, and whichever ran first wins it.\n *\n * All four emit the same order and `renderOrder.test.ts` holds them to it.\n */\n function walkIds(): NodeId[] {\n return state.layers.length === 1 ? renderOrderFlat() : renderOrderBucketed();\n }\n\n function walkNodes(): Node<TData, TLayer, TPose>[] {\n return state.layers.length === 1 ? renderOrderNodesFlat() : renderOrderNodesBucketed();\n }\n\n /**\n * Render order survives everything except a structural edit, so both walks\n * are cached against a counter the structural writers bump. Pose and data\n * edits leave order alone and must NOT invalidate — they fire per frame\n * during a drag, which is exactly when the cache pays.\n *\n * A missed bump is a silent wrong answer: nodes painted in the wrong z-order,\n * or unpickable. `renderOrder.test.ts` walks each structural op and asserts\n * the order moved.\n */\n let structureGeneration = 0;\n let orderCache: {\n gen: number;\n ids?: NodeId[];\n nodes?: Node<TData, TLayer, TPose>[];\n byLayer?: Map<TLayer, Node<TData, TLayer, TPose>[]>;\n } = { gen: 0 };\n\n function invalidate(): void {\n structureGeneration++;\n }\n\n const EMPTY_NODES: readonly Node<TData, TLayer, TPose>[] = [];\n\n function currentOrderCache(): typeof orderCache {\n if (orderCache.gen !== structureGeneration) {\n orderCache = { gen: structureGeneration };\n }\n return orderCache;\n }\n\n function renderOrderFlat(): NodeId[] {\n const only = state.layers[0].id;\n const out: NodeId[] = [];\n const stack: NodeId[] = [...state.roots].reverse();\n while (stack.length > 0) {\n const id = stack.pop()!;\n const node = state.nodes.get(id);\n if (!node) continue;\n if (node.layer === only) out.push(id);\n if (hasChildren(node)) {\n for (let i = node.children.length - 1; i >= 0; i--) {\n stack.push(node.children[i]);\n }\n }\n }\n return out;\n }\n\n function renderOrderNodesFlat(): Node<TData, TLayer, TPose>[] {\n const only = state.layers[0].id;\n const out: Node<TData, TLayer, TPose>[] = [];\n const stack: NodeId[] = [...state.roots].reverse();\n while (stack.length > 0) {\n const id = stack.pop()!;\n const node = state.nodes.get(id);\n if (!node) continue;\n if (node.layer === only) out.push(node);\n if (hasChildren(node)) {\n for (let i = node.children.length - 1; i >= 0; i--) {\n stack.push(node.children[i]);\n }\n }\n }\n return out;\n }\n\n /** One DFS of the tree, bucketed by layer: appending in preorder keeps each\n * bucket in preorder, so concatenating them is the layer-major sequence. */\n function renderOrderBucketed(): NodeId[] {\n const buckets: NodeId[][] = state.layers.map(() => []);\n const stack: NodeId[] = [...state.roots].reverse();\n while (stack.length > 0) {\n const id = stack.pop()!;\n const node = state.nodes.get(id);\n if (!node) continue;\n const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];\n if (bucket) bucket.push(id);\n if (hasChildren(node)) {\n for (let i = node.children.length - 1; i >= 0; i--) {\n stack.push(node.children[i]);\n }\n }\n }\n const out: NodeId[] = [];\n for (const bucket of buckets) {\n for (const id of bucket) out.push(id);\n }\n return out;\n }\n\n function renderOrderNodesBucketed(): Node<TData, TLayer, TPose>[] {\n const buckets: Node<TData, TLayer, TPose>[][] = state.layers.map(() => []);\n const stack: NodeId[] = [...state.roots].reverse();\n while (stack.length > 0) {\n const id = stack.pop()!;\n const node = state.nodes.get(id);\n if (!node) continue;\n const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];\n if (bucket) bucket.push(node);\n if (hasChildren(node)) {\n for (let i = node.children.length - 1; i >= 0; i--) {\n stack.push(node.children[i]);\n }\n }\n }\n const out: Node<TData, TLayer, TPose>[] = [];\n for (const bucket of buckets) {\n for (const node of bucket) out.push(node);\n }\n return out;\n }\n\n return {\n ids() {\n const c = currentOrderCache();\n return (c.ids ??= walkIds());\n },\n nodes() {\n const c = currentOrderCache();\n return (c.nodes ??= walkNodes());\n },\n nodesOnLayer(layer) {\n const c = currentOrderCache();\n let byLayer = c.byLayer;\n if (!byLayer) {\n byLayer = new Map();\n for (const node of (c.nodes ??= walkNodes())) {\n let bucket = byLayer.get(node.layer);\n if (!bucket) byLayer.set(node.layer, (bucket = []));\n bucket.push(node);\n }\n c.byLayer = byLayer;\n }\n return byLayer.get(layer) ?? EMPTY_NODES;\n },\n invalidate,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isPlainObject } from "../isPlainObject.js";
|
|
2
|
+
|
|
3
|
+
//#region src/core/scene/sameValue.ts
|
|
4
|
+
/** Structural equality over plain objects and arrays; anything else by identity. */
|
|
5
|
+
function sameValue(a, b) {
|
|
6
|
+
if (Object.is(a, b)) return true;
|
|
7
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));
|
|
8
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
9
|
+
const ka = Object.keys(a);
|
|
10
|
+
return ka.length === Object.keys(b).length && ka.every((k) => k in b && sameValue(a[k], b[k]));
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { sameValue };
|
|
17
|
+
//# sourceMappingURL=sameValue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sameValue.js","names":[],"sources":["../../../src/core/scene/sameValue.ts"],"sourcesContent":["import { isPlainObject } from '../isPlainObject';\n\n/** Structural equality over plain objects and arrays; anything else by identity. */\nexport function sameValue(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const ka = Object.keys(a);\n return ka.length === Object.keys(b).length && ka.every((k) => k in b && sameValue(a[k], b[k]));\n }\n return false;\n}\n"],"mappings":";;;;AAGA,SAAgB,UAAU,GAAY,GAAqB;CACzD,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAC5B,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GACrC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,UAAU,GAAG,EAAE,EAAE,CAAC;CAEtE,IAAI,cAAc,CAAC,KAAK,cAAc,CAAC,GAAG;EACxC,MAAM,KAAK,OAAO,KAAK,CAAC;EACxB,OAAO,GAAG,WAAW,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,GAAG,OAAO,MAAM,KAAK,KAAK,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;CAC/F;CACA,OAAO;AACT"}
|
package/dist/core/scene/scene.js
CHANGED
|
@@ -120,8 +120,8 @@ function buildScene(options, layers, initialNodes, initialDoc) {
|
|
|
120
120
|
overrides,
|
|
121
121
|
getSelection: selection.get,
|
|
122
122
|
setSelection: (ids) => selection.set([...ids]),
|
|
123
|
-
loadState(json) {
|
|
124
|
-
const specs = specsFromSerialized(json, registry);
|
|
123
|
+
loadState(json, opts) {
|
|
124
|
+
const specs = specsFromSerialized(json, registry, opts);
|
|
125
125
|
if (mode.open()) throw new Error("Scene: cannot loadState inside another edit — it replaces the state that edit would revert to");
|
|
126
126
|
overrides.clearAll();
|
|
127
127
|
doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);
|
|
@@ -145,7 +145,7 @@ function buildScene(options, layers, initialNodes, initialDoc) {
|
|
|
145
145
|
* keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).
|
|
146
146
|
* The scene starts with empty history. */
|
|
147
147
|
function sceneFromJSON(json, options = {}) {
|
|
148
|
-
return buildScene(options, json.systemLayers.map(layerFromSerialized), (registry) => specsFromSerialized(json, registry), json.doc);
|
|
148
|
+
return buildScene(options, json.systemLayers.map(layerFromSerialized), (registry) => specsFromSerialized(json, registry, options), json.doc);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene.js","names":[],"sources":["../../../src/core/scene/scene.ts"],"sourcesContent":["import { withKitRegistry } from './kitRegistry';\nimport { createPoseOverrides } from './poseOverrides';\nimport { createArrivalWindow } from './arrivalWindow';\nimport { documentReads, sceneMutators } from './sceneMutators';\nimport { createNotifier } from './notifier';\nimport type { SerializedDoc } from './docValues';\nimport { sceneChanges } from './sceneChanges';\nimport { createPoseWriteLog } from './poseWrites';\nimport { effectivePose } from './effectivePose';\nimport { createEditMode } from './editMode';\nimport { createSceneEdits } from './sceneEdits';\nimport { createSceneRecording } from './sceneRecording';\nimport { createSceneSelection } from './sceneSelection';\nimport { createSceneState } from './sceneState';\nimport { systemLayerRecord } from './layerStack';\nimport { layerFromSerialized, specsFromSerialized } from './serialization';\nimport { resolveLayoutFrame, runLayoutPass } from './layoutPass';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from './composeFrame';\nimport {\n asNodeId,\n type AddNodeSpec,\n type LayerRecord,\n type NodeId,\n type RegisteredOp,\n type Scene,\n type SceneRegistry,\n type SerializedScene,\n type UseSceneOptions,\n} from './types';\n\n/** A node still in `scene`, with the pose it is shown at. */\nfunction shownNode<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, id: NodeId) {\n const node = scene.get(id);\n return node && { node, pose: effectivePose(scene, node) };\n}\n\nlet defaultIdCounter = 0;\nconst defaultGenerateId = (): NodeId =>\n asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);\n\n/** Build a scene outside React. `useScene` is the usual entry point; reach for\n * this in tests, in headless rendering, or wherever a scene must outlive a\n * component. Throws if no system layers are declared. */\nexport function createScene<TData, TLayer extends string, TPose = import('./composePose').RectPose>(\n options: UseSceneOptions<TData, TLayer, TPose>,\n): Scene<TData, TLayer, TPose> {\n return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial, options.doc);\n}\n\ntype SceneConfig<TData, TLayer extends string, TPose> =\n Omit<UseSceneOptions<TData, TLayer, TPose>, 'systemLayers' | 'initial' | 'doc'>;\n\n/** A scene holding `layers`, the document values `initialDoc`, and the nodes\n * `initialNodes` answers with once it is handed the registry their functions\n * resolve against. */\nfunction buildScene<TData, TLayer extends string, TPose>(\n options: SceneConfig<TData, TLayer, TPose>,\n layers: readonly LayerRecord<TLayer>[],\n initialNodes: (registry: SceneRegistry<TPose, TData, TLayer>) => readonly AddNodeSpec<TData, TLayer, TPose>[] | undefined,\n initialDoc: SerializedDoc | undefined,\n): Scene<TData, TLayer, TPose> {\n if (layers.length === 0) throw new Error('Scene: systemLayers must declare at least one layer');\n\n // The kit's own registry entries sit under the consumer's, so a snapshot\n // carrying a kit-attached function (a grouped container's derived pose)\n // reloads in any scene, not only one whose consumer registered the key.\n const registry = withKitRegistry(options.registry ?? {});\n\n const doc = createSceneState<TData, TLayer, TPose>(registry, layers, () => arrivals.layoutDeclared(), initialDoc);\n const { state, locks, reads } = doc;\n const notifier = createNotifier();\n const mode = createEditMode();\n const poseWrites = createPoseWriteLog({ untracked: mode.untracked, changes: notifier, shown: (id) => shownNode(scene, id) });\n const writePose = (id: string, pose: unknown): void => poseWrites.layout(() => scene.setPose(asNodeId(id), pose as TPose));\n\n const layoutFrame = resolveLayoutFrame(options.layoutFrame);\n const arrivals = createArrivalWindow<TData, TLayer, TPose>({\n nodes: state.nodes,\n layoutPass: (arrived, touched, departed) =>\n runLayoutPass(scene, reads.layoutOf, arrived, touched, layoutFrame, departed),\n recordsBareEdits: () => !mode.open(),\n writePose,\n });\n\n const generateId = options.generateId ?? defaultGenerateId;\n const selection = createSceneSelection(locks.reachable);\n // A selection change is a scene change to every subscriber.\n selection.subscribe(notifier.notify);\n\n /** Ephemeral per-node overrides. Deliberately not part of `state`: not\n * document content, never reaches `toJSON`, and never touches history. */\n const overrides = createPoseOverrides<TPose>((id) => state.nodes.get(id), doc.store.invalidateDependents);\n\n const recording = createSceneRecording({\n notifier,\n mode,\n passAdapter: () => ({ ...sceneMutators(scene), ...documentReads(scene) }),\n stale: doc.stale,\n lifted: locks.lifted,\n selection,\n arrivals,\n writePose,\n poseWrites,\n config: options,\n });\n\n const edits = createSceneEdits<TData, TLayer, TPose>({\n state,\n store: doc.store,\n locks,\n layers: doc.layers,\n invalidateOrder: doc.order.invalidate,\n fnFields: doc.fnFields,\n ops: { ...recording.ops, register: poseWrites.reporting(recording.ops.register) },\n recorder: recording.recorder,\n arrivals,\n layoutFrame,\n generateId,\n narrowSelection: selection.narrow,\n clearOverride: overrides.clear,\n });\n for (const [kind, handler] of Object.entries(options.ops ?? {})) recording.ops.register(kind, handler);\n\n const scene: Scene<TData, TLayer, TPose> = {\n get nodes() { return state.nodes; },\n get roots() { return state.roots; },\n get layers() { return state.layers; },\n registry,\n\n ...reads,\n\n mintId: generateId,\n composition: options.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n\n ...edits.methods,\n ...recording.methods,\n\n unlocked: (fn) => locks.lifted(fn),\n\n setArrivalHandler: arrivals.setHandler,\n onReflow: arrivals.onReflow,\n onPoseWrite: poseWrites.subscribe,\n\n overrides,\n\n getSelection: selection.get,\n setSelection: (ids) => selection.set([...ids]),\n\n loadState(json) {\n // Validate + map up front (throws before we touch live state on a bad\n // version or unknown registry key).\n const specs = specsFromSerialized(json, registry);\n if (mode.open()) {\n throw new Error('Scene: cannot loadState inside another edit — it replaces the state that edit would revert to');\n }\n // Ephemeral, and every id is a new node after this.\n overrides.clearAll();\n doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);\n recording.clearHistory();\n poseWrites.quiet(() => edits.construct(specs));\n selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));\n notifier.notify();\n },\n\n ...sceneChanges(notifier, selection),\n };\n\n const initial = initialNodes(registry);\n if (initial) {\n poseWrites.quiet(() => edits.construct(initial));\n notifier.notify();\n }\n\n return scene;\n}\n\n/** Reconstruct a Scene from a JSON snapshot produced by `scene.toJSON()`.\n * Function fields (e.g., `clipFromPose`) are resolved by string key via the\n * registry passed in `options`. Throws on unknown version, unknown registry\n * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).\n * The scene starts with empty history. */\nexport function sceneFromJSON<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n options: {\n registry?: SceneRegistry<TPose, TData, TLayer>;\n historyLimit?: number;\n coalesceWindowMs?: number;\n generateId?: () => NodeId;\n ops?: Readonly<Record<string, RegisteredOp<unknown>>>;\n composition?: UseSceneOptions<TData, TLayer, TPose>['composition'];\n layoutFrame?: UseSceneOptions<TData, TLayer, TPose>['layoutFrame'];\n } = {},\n): Scene<TData, TLayer, TPose> {\n return buildScene(\n options,\n json.systemLayers.map(layerFromSerialized),\n (registry) => specsFromSerialized(json, registry),\n json.doc,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scene.js","names":[],"sources":["../../../src/core/scene/scene.ts"],"sourcesContent":["import { withKitRegistry } from './kitRegistry';\nimport { createPoseOverrides } from './poseOverrides';\nimport { createArrivalWindow } from './arrivalWindow';\nimport { documentReads, sceneMutators } from './sceneMutators';\nimport { createNotifier } from './notifier';\nimport type { SerializedDoc } from './docValues';\nimport { sceneChanges } from './sceneChanges';\nimport { createPoseWriteLog } from './poseWrites';\nimport { effectivePose } from './effectivePose';\nimport { createEditMode } from './editMode';\nimport { createSceneEdits } from './sceneEdits';\nimport { createSceneRecording } from './sceneRecording';\nimport { createSceneSelection } from './sceneSelection';\nimport { createSceneState } from './sceneState';\nimport { systemLayerRecord } from './layerStack';\nimport { layerFromSerialized, specsFromSerialized, type SpecsFromSerializedOptions } from './serialization';\nimport { resolveLayoutFrame, runLayoutPass } from './layoutPass';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from './composeFrame';\nimport {\n asNodeId,\n type AddNodeSpec,\n type LayerRecord,\n type NodeId,\n type RegisteredOp,\n type Scene,\n type SceneRegistry,\n type SerializedScene,\n type SerializedSceneV1,\n type UseSceneOptions,\n} from './types';\n\n/** A node still in `scene`, with the pose it is shown at. */\nfunction shownNode<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, id: NodeId) {\n const node = scene.get(id);\n return node && { node, pose: effectivePose(scene, node) };\n}\n\nlet defaultIdCounter = 0;\nconst defaultGenerateId = (): NodeId =>\n asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);\n\n/** Build a scene outside React. `useScene` is the usual entry point; reach for\n * this in tests, in headless rendering, or wherever a scene must outlive a\n * component. Throws if no system layers are declared. */\nexport function createScene<TData, TLayer extends string, TPose = import('./composePose').RectPose>(\n options: UseSceneOptions<TData, TLayer, TPose>,\n): Scene<TData, TLayer, TPose> {\n return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial, options.doc);\n}\n\ntype SceneConfig<TData, TLayer extends string, TPose> =\n Omit<UseSceneOptions<TData, TLayer, TPose>, 'systemLayers' | 'initial' | 'doc'>;\n\n/** A scene holding `layers`, the document values `initialDoc`, and the nodes\n * `initialNodes` answers with once it is handed the registry their functions\n * resolve against. */\nfunction buildScene<TData, TLayer extends string, TPose>(\n options: SceneConfig<TData, TLayer, TPose>,\n layers: readonly LayerRecord<TLayer>[],\n initialNodes: (registry: SceneRegistry<TPose, TData, TLayer>) => readonly AddNodeSpec<TData, TLayer, TPose>[] | undefined,\n initialDoc: SerializedDoc | undefined,\n): Scene<TData, TLayer, TPose> {\n if (layers.length === 0) throw new Error('Scene: systemLayers must declare at least one layer');\n\n // The kit's own registry entries sit under the consumer's, so a snapshot\n // carrying a kit-attached function (a grouped container's derived pose)\n // reloads in any scene, not only one whose consumer registered the key.\n const registry = withKitRegistry(options.registry ?? {});\n\n const doc = createSceneState<TData, TLayer, TPose>(registry, layers, () => arrivals.layoutDeclared(), initialDoc);\n const { state, locks, reads } = doc;\n const notifier = createNotifier();\n const mode = createEditMode();\n const poseWrites = createPoseWriteLog({ untracked: mode.untracked, changes: notifier, shown: (id) => shownNode(scene, id) });\n const writePose = (id: string, pose: unknown): void => poseWrites.layout(() => scene.setPose(asNodeId(id), pose as TPose));\n\n const layoutFrame = resolveLayoutFrame(options.layoutFrame);\n const arrivals = createArrivalWindow<TData, TLayer, TPose>({\n nodes: state.nodes,\n layoutPass: (arrived, touched, departed) =>\n runLayoutPass(scene, reads.layoutOf, arrived, touched, layoutFrame, departed),\n recordsBareEdits: () => !mode.open(),\n writePose,\n });\n\n const generateId = options.generateId ?? defaultGenerateId;\n const selection = createSceneSelection(locks.reachable);\n // A selection change is a scene change to every subscriber.\n selection.subscribe(notifier.notify);\n\n /** Ephemeral per-node overrides. Deliberately not part of `state`: not\n * document content, never reaches `toJSON`, and never touches history. */\n const overrides = createPoseOverrides<TPose, TData>((id) => state.nodes.get(id), doc.store.invalidateDependents);\n\n const recording = createSceneRecording({\n notifier,\n mode,\n passAdapter: () => ({ ...sceneMutators(scene), ...documentReads(scene) }),\n stale: doc.stale,\n lifted: locks.lifted,\n selection,\n arrivals,\n writePose,\n poseWrites,\n config: options,\n });\n\n const edits = createSceneEdits<TData, TLayer, TPose>({\n state,\n store: doc.store,\n locks,\n layers: doc.layers,\n invalidateOrder: doc.order.invalidate,\n fnFields: doc.fnFields,\n ops: { ...recording.ops, register: poseWrites.reporting(recording.ops.register) },\n recorder: recording.recorder,\n arrivals,\n layoutFrame,\n generateId,\n narrowSelection: selection.narrow,\n clearOverride: overrides.clear,\n });\n for (const [kind, handler] of Object.entries(options.ops ?? {})) recording.ops.register(kind, handler);\n\n const scene: Scene<TData, TLayer, TPose> = {\n get nodes() { return state.nodes; },\n get roots() { return state.roots; },\n get layers() { return state.layers; },\n registry,\n\n ...reads,\n\n mintId: generateId,\n composition: options.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n\n ...edits.methods,\n ...recording.methods,\n\n unlocked: (fn) => locks.lifted(fn),\n\n setArrivalHandler: arrivals.setHandler,\n onReflow: arrivals.onReflow,\n onPoseWrite: poseWrites.subscribe,\n\n overrides,\n\n getSelection: selection.get,\n setSelection: (ids) => selection.set([...ids]),\n\n loadState(json, opts) {\n // Validate + map up front (throws before we touch live state on a bad\n // version or unknown registry key).\n const specs = specsFromSerialized(json, registry, opts);\n if (mode.open()) {\n throw new Error('Scene: cannot loadState inside another edit — it replaces the state that edit would revert to');\n }\n // Ephemeral, and every id is a new node after this.\n overrides.clearAll();\n doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);\n recording.clearHistory();\n poseWrites.quiet(() => edits.construct(specs));\n selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));\n notifier.notify();\n },\n\n ...sceneChanges(notifier, selection),\n };\n\n const initial = initialNodes(registry);\n if (initial) {\n poseWrites.quiet(() => edits.construct(initial));\n notifier.notify();\n }\n\n return scene;\n}\n\n/** Reconstruct a Scene from a JSON snapshot produced by `scene.toJSON()`.\n * Function fields (e.g., `clipFromPose`) are resolved by string key via the\n * registry passed in `options`. Throws on unknown version, unknown registry\n * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).\n * The scene starts with empty history. */\nexport function sceneFromJSON<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose> | SerializedSceneV1<TData, TLayer, TPose>,\n options: {\n registry?: SceneRegistry<TPose, TData, TLayer>;\n historyLimit?: number;\n coalesceWindowMs?: number;\n generateId?: () => NodeId;\n ops?: Readonly<Record<string, RegisteredOp<unknown>>>;\n composition?: UseSceneOptions<TData, TLayer, TPose>['composition'];\n layoutFrame?: UseSceneOptions<TData, TLayer, TPose>['layoutFrame'];\n } & SpecsFromSerializedOptions<TData, TLayer, TPose> = {},\n): Scene<TData, TLayer, TPose> {\n return buildScene(\n options,\n json.systemLayers.map(layerFromSerialized),\n (registry) => specsFromSerialized(json, registry, options),\n json.doc,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSceneDoc } from "./docValues.js";
|
|
2
|
+
import { createDeclaredFieldSetters } from "./declaredFieldSetters.js";
|
|
2
3
|
import { createFieldEdits } from "./fieldEdits.js";
|
|
3
4
|
import { registerKitOps } from "./kitOps.js";
|
|
4
5
|
import { createNodeFlags } from "./nodeFlags.js";
|
|
@@ -56,6 +57,12 @@ function createSceneEdits(env) {
|
|
|
56
57
|
record: recorder.record,
|
|
57
58
|
narrowSelection: () => env.narrowSelection()
|
|
58
59
|
});
|
|
60
|
+
const declaredEdits = createDeclaredFieldSetters({
|
|
61
|
+
register: env.ops.register,
|
|
62
|
+
requireNode: store.requireNode,
|
|
63
|
+
refuse: locks.refuse,
|
|
64
|
+
record: recorder.record
|
|
65
|
+
});
|
|
59
66
|
const doc = createSceneDoc({
|
|
60
67
|
values: state.docValues,
|
|
61
68
|
register: env.ops.register,
|
|
@@ -68,6 +75,7 @@ function createSceneEdits(env) {
|
|
|
68
75
|
...fieldEdits,
|
|
69
76
|
...layerEdits,
|
|
70
77
|
...flagEdits,
|
|
78
|
+
...declaredEdits,
|
|
71
79
|
doc
|
|
72
80
|
}
|
|
73
81
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneEdits.js","names":[],"sources":["../../../src/core/scene/sceneEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its document — tree, node fields, layers, and document values — with\n * the kit ops they record.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { createSceneDoc, type SceneDocValues } from './docValues';\nimport { createFieldEdits, type FieldEdits } from './fieldEdits';\nimport { registerKitOps } from './kitOps';\nimport { createNodeFlags } from './nodeFlags';\nimport { createLayerEdits, type LayerEdits, type createLayerStack } from './layerStack';\nimport type { ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { OpTable } from './opTable';\nimport type { Recorder } from './recorder';\nimport { createTreeEdits, type TreeEdits } from './treeEdits';\nimport type { LayerRecord, Node, NodeId } from './types';\n\nexport interface SceneEditsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n layerIndex: Map<TLayer, number>;\n /** The scene's document values, which `doc` reads and records. */\n docValues: Map<string, unknown>;\n };\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<\n ReturnType<typeof createLayerStack<TLayer>>,\n 'requireLayerIndex' | 'rebuildLayerIndex' | 'assertSubtreeLayer'\n >;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n generateId(): NodeId;\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n clearOverride(id: NodeId): void;\n}\n\nexport interface SceneEdits<TData, TLayer extends string, TPose> {\n construct: TreeEdits<TData, TLayer, TPose>['construct'];\n methods: Omit<TreeEdits<TData, TLayer, TPose>, 'construct'>\n & FieldEdits<TData, TLayer, TPose>\n & LayerEdits<TData, TLayer, TPose>\n & ReturnType<typeof createNodeFlags<TData, TLayer, TPose>>\n & SceneDocValues;\n}\n\nexport function createSceneEdits<TData, TLayer extends string, TPose>(\n env: SceneEditsEnv<TData, TLayer, TPose>,\n): SceneEdits<TData, TLayer, TPose> {\n const { state, store, locks, layers, fnFields, recorder, arrivals } = env;\n\n const { layoutRefs } = registerKitOps<TData, TLayer, TPose>({\n state,\n register: env.ops.register,\n store,\n requireLayerIndex: layers.requireLayerIndex,\n rebuildLayerIndex: layers.rebuildLayerIndex,\n invalidateOrder: env.invalidateOrder,\n fnFields,\n narrowSelection: env.narrowSelection,\n clearOverride: env.clearOverride,\n });\n\n const { construct, ...treeEdits } = createTreeEdits<TData, TLayer, TPose>({\n nodes: state.nodes,\n generateId: env.generateId,\n store,\n locks,\n layers,\n fnFields,\n runOp: env.ops.run,\n recorder,\n arrivals,\n });\n\n const fieldEdits = createFieldEdits<TData, TLayer, TPose>({\n store,\n locks,\n layers,\n recorder,\n arrivals,\n layoutFrame: env.layoutFrame,\n layoutField: fnFields.find((f) => f.spec.field === 'layout')!,\n layoutRefs,\n });\n\n const layerEdits = createLayerEdits<TData, TLayer, TPose>({\n layers: state.layers,\n layerIndex: state.layerIndex,\n nodes: state.nodes,\n requireLayerIndex: layers.requireLayerIndex,\n record: recorder.record,\n lifted: locks.lifted,\n batch: recorder.batch,\n removeMany: treeEdits.removeMany,\n });\n\n const flagEdits = createNodeFlags<TData, TLayer, TPose>({\n register: env.ops.register,\n requireNode: store.requireNode,\n record: recorder.record,\n narrowSelection: () => env.narrowSelection(),\n });\n\n const doc = createSceneDoc({ values: state.docValues, register: env.ops.register, record: recorder.record });\n\n return { construct, methods: { ...treeEdits, ...fieldEdits, ...layerEdits, ...flagEdits, doc } };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"sceneEdits.js","names":[],"sources":["../../../src/core/scene/sceneEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its document — tree, node fields, layers, and document values — with\n * the kit ops they record.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { createSceneDoc, type SceneDocValues } from './docValues';\nimport { createDeclaredFieldSetters, type SceneDeclaredFieldSetters } from './declaredFieldSetters';\nimport { createFieldEdits, type FieldEdits } from './fieldEdits';\nimport { registerKitOps } from './kitOps';\nimport { createNodeFlags } from './nodeFlags';\nimport { createLayerEdits, type LayerEdits, type createLayerStack } from './layerStack';\nimport type { ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { OpTable } from './opTable';\nimport type { Recorder } from './recorder';\nimport { createTreeEdits, type TreeEdits } from './treeEdits';\nimport type { LayerRecord, Node, NodeId } from './types';\n\nexport interface SceneEditsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n layerIndex: Map<TLayer, number>;\n /** The scene's document values, which `doc` reads and records. */\n docValues: Map<string, unknown>;\n };\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<\n ReturnType<typeof createLayerStack<TLayer>>,\n 'requireLayerIndex' | 'rebuildLayerIndex' | 'assertSubtreeLayer'\n >;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n generateId(): NodeId;\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n clearOverride(id: NodeId): void;\n}\n\nexport interface SceneEdits<TData, TLayer extends string, TPose> {\n construct: TreeEdits<TData, TLayer, TPose>['construct'];\n methods: Omit<TreeEdits<TData, TLayer, TPose>, 'construct'>\n & FieldEdits<TData, TLayer, TPose>\n & LayerEdits<TData, TLayer, TPose>\n & ReturnType<typeof createNodeFlags<TData, TLayer, TPose>>\n & SceneDeclaredFieldSetters\n & SceneDocValues;\n}\n\nexport function createSceneEdits<TData, TLayer extends string, TPose>(\n env: SceneEditsEnv<TData, TLayer, TPose>,\n): SceneEdits<TData, TLayer, TPose> {\n const { state, store, locks, layers, fnFields, recorder, arrivals } = env;\n\n const { layoutRefs } = registerKitOps<TData, TLayer, TPose>({\n state,\n register: env.ops.register,\n store,\n requireLayerIndex: layers.requireLayerIndex,\n rebuildLayerIndex: layers.rebuildLayerIndex,\n invalidateOrder: env.invalidateOrder,\n fnFields,\n narrowSelection: env.narrowSelection,\n clearOverride: env.clearOverride,\n });\n\n const { construct, ...treeEdits } = createTreeEdits<TData, TLayer, TPose>({\n nodes: state.nodes,\n generateId: env.generateId,\n store,\n locks,\n layers,\n fnFields,\n runOp: env.ops.run,\n recorder,\n arrivals,\n });\n\n const fieldEdits = createFieldEdits<TData, TLayer, TPose>({\n store,\n locks,\n layers,\n recorder,\n arrivals,\n layoutFrame: env.layoutFrame,\n layoutField: fnFields.find((f) => f.spec.field === 'layout')!,\n layoutRefs,\n });\n\n const layerEdits = createLayerEdits<TData, TLayer, TPose>({\n layers: state.layers,\n layerIndex: state.layerIndex,\n nodes: state.nodes,\n requireLayerIndex: layers.requireLayerIndex,\n record: recorder.record,\n lifted: locks.lifted,\n batch: recorder.batch,\n removeMany: treeEdits.removeMany,\n });\n\n const flagEdits = createNodeFlags<TData, TLayer, TPose>({\n register: env.ops.register,\n requireNode: store.requireNode,\n record: recorder.record,\n narrowSelection: () => env.narrowSelection(),\n });\n\n const declaredEdits = createDeclaredFieldSetters<TData, TLayer, TPose>({\n register: env.ops.register,\n requireNode: store.requireNode,\n refuse: locks.refuse,\n record: recorder.record,\n });\n\n const doc = createSceneDoc({ values: state.docValues, register: env.ops.register, record: recorder.record });\n\n return { construct, methods: { ...treeEdits, ...fieldEdits, ...layerEdits, ...flagEdits, ...declaredEdits, doc } };\n}\n"],"mappings":";;;;;;;;;AAuDA,SAAgB,iBACd,KACkC;CAClC,MAAM,EAAE,OAAO,OAAO,OAAO,QAAQ,UAAU,UAAU,aAAa;CAEtE,MAAM,EAAE,eAAe,eAAqC;EAC1D;EACA,UAAU,IAAI,IAAI;EAClB;EACA,mBAAmB,OAAO;EAC1B,mBAAmB,OAAO;EAC1B,iBAAiB,IAAI;EACrB;EACA,iBAAiB,IAAI;EACrB,eAAe,IAAI;CACrB,CAAC;CAED,MAAM,EAAE,WAAW,GAAG,cAAc,gBAAsC;EACxE,OAAO,MAAM;EACb,YAAY,IAAI;EAChB;EACA;EACA;EACA;EACA,OAAO,IAAI,IAAI;EACf;EACA;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD;EACA;EACA;EACA;EACA;EACA,aAAa,IAAI;EACjB,aAAa,SAAS,MAAM,MAAM,EAAE,KAAK,UAAU,QAAQ;EAC3D;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,mBAAmB,OAAO;EAC1B,QAAQ,SAAS;EACjB,QAAQ,MAAM;EACd,OAAO,SAAS;EAChB,YAAY,UAAU;CACxB,CAAC;CAED,MAAM,YAAY,gBAAsC;EACtD,UAAU,IAAI,IAAI;EAClB,aAAa,MAAM;EACnB,QAAQ,SAAS;EACjB,uBAAuB,IAAI,gBAAgB;CAC7C,CAAC;CAED,MAAM,gBAAgB,2BAAiD;EACrE,UAAU,IAAI,IAAI;EAClB,aAAa,MAAM;EACnB,QAAQ,MAAM;EACd,QAAQ,SAAS;CACnB,CAAC;CAED,MAAM,MAAM,eAAe;EAAE,QAAQ,MAAM;EAAW,UAAU,IAAI,IAAI;EAAU,QAAQ,SAAS;CAAO,CAAC;CAE3G,OAAO;EAAE;EAAW,SAAS;GAAE,GAAG;GAAW,GAAG;GAAY,GAAG;GAAY,GAAG;GAAW,GAAG;GAAe;EAAI;CAAE;AACnH"}
|
|
@@ -41,8 +41,7 @@ function createSceneState(registry, initialLayers, layoutDeclared, initialDoc) {
|
|
|
41
41
|
isLocked: (id) => locks.isLocked(id),
|
|
42
42
|
isHidden: (id) => locks.isHidden(id),
|
|
43
43
|
layoutOf(id) {
|
|
44
|
-
|
|
45
|
-
return node?.kind === "container" ? node.layout ?? null : null;
|
|
44
|
+
return state.nodes.get(id)?.layout ?? null;
|
|
46
45
|
},
|
|
47
46
|
toJSON: () => serializeScene(state, fnFields, docValues.toJSON())
|
|
48
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneState.js","names":[],"sources":["../../../src/core/scene/sceneState.ts"],"sourcesContent":["/**\n * A scene's document state — its nodes, roots, and layers — with the modules\n * that read and write it directly.\n */\nimport { createDocStore, type SerializedDoc } from './docValues';\nimport { createLayerStack } from './layerStack';\nimport { createLockGuard } from './lockGuard';\nimport { createNodeFnFields } from './nodeFnFields';\nimport { createNodeStore } from './nodeStore';\nimport { createRenderOrder } from './renderOrder';\nimport { serializeScene } from './serialization';\nimport { createStaleReplay } from './staleReplay';\nimport { createTreeReads } from './treeReads';\nimport type { LayerRecord, Node, NodeId, Scene, SceneRegistry } from './types';\n\nexport function createSceneState<TData, TLayer extends string, TPose>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n initialLayers: readonly LayerRecord<TLayer>[],\n /** Called when a node is given a layout. */\n layoutDeclared: () => void,\n initialDoc?: SerializedDoc,\n) {\n const state = {\n nodes: new Map<NodeId, Node<TData, TLayer, TPose>>(),\n roots: [] as NodeId[],\n layers: [] as LayerRecord<TLayer>[],\n layerIndex: new Map<TLayer, number>(),\n docValues: new Map<string, unknown>(),\n };\n const order = createRenderOrder(state);\n /** The registry-keyed function fields, each with its key maps. A function\n * cannot travel through a serializable op payload, so an op carries its key. */\n const fnFields = createNodeFnFields<TPose>(registry, (field) => {\n if (field === 'layout') layoutDeclared();\n });\n const store = createNodeStore(state, order.invalidate, fnFields);\n const layers = createLayerStack(state, order.invalidate);\n layers.reset(initialLayers);\n const locks = createLockGuard(state);\n const docValues = createDocStore(state.docValues);\n docValues.reset(initialDoc);\n\n const reads: Pick<\n Scene<TData, TLayer, TPose>,\n | 'get' | 'incarnation' | 'childrenOf' | 'ancestorsOf' | 'renderOrder' | 'renderOrderNodes'\n | 'nodesOnLayer' | 'isLocked' | 'isHidden' | 'layoutOf' | 'toJSON'\n > = {\n ...createTreeReads(state.nodes),\n renderOrder: order.ids,\n renderOrderNodes: order.nodes,\n nodesOnLayer: order.nodesOnLayer,\n isLocked: (id) => locks.isLocked(id),\n isHidden: (id) => locks.isHidden(id),\n layoutOf(id) {\n const node = state.nodes.get(id);\n return node?.
|
|
1
|
+
{"version":3,"file":"sceneState.js","names":[],"sources":["../../../src/core/scene/sceneState.ts"],"sourcesContent":["/**\n * A scene's document state — its nodes, roots, and layers — with the modules\n * that read and write it directly.\n */\nimport { createDocStore, type SerializedDoc } from './docValues';\nimport { createLayerStack } from './layerStack';\nimport { createLockGuard } from './lockGuard';\nimport { createNodeFnFields } from './nodeFnFields';\nimport { createNodeStore } from './nodeStore';\nimport { createRenderOrder } from './renderOrder';\nimport { serializeScene } from './serialization';\nimport { createStaleReplay } from './staleReplay';\nimport { createTreeReads } from './treeReads';\nimport type { LayerRecord, Node, NodeId, Scene, SceneRegistry } from './types';\n\nexport function createSceneState<TData, TLayer extends string, TPose>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n initialLayers: readonly LayerRecord<TLayer>[],\n /** Called when a node is given a layout. */\n layoutDeclared: () => void,\n initialDoc?: SerializedDoc,\n) {\n const state = {\n nodes: new Map<NodeId, Node<TData, TLayer, TPose>>(),\n roots: [] as NodeId[],\n layers: [] as LayerRecord<TLayer>[],\n layerIndex: new Map<TLayer, number>(),\n docValues: new Map<string, unknown>(),\n };\n const order = createRenderOrder(state);\n /** The registry-keyed function fields, each with its key maps. A function\n * cannot travel through a serializable op payload, so an op carries its key. */\n const fnFields = createNodeFnFields<TPose>(registry, (field) => {\n if (field === 'layout') layoutDeclared();\n });\n const store = createNodeStore(state, order.invalidate, fnFields);\n const layers = createLayerStack(state, order.invalidate);\n layers.reset(initialLayers);\n const locks = createLockGuard(state);\n const docValues = createDocStore(state.docValues);\n docValues.reset(initialDoc);\n\n const reads: Pick<\n Scene<TData, TLayer, TPose>,\n | 'get' | 'incarnation' | 'childrenOf' | 'ancestorsOf' | 'renderOrder' | 'renderOrderNodes'\n | 'nodesOnLayer' | 'isLocked' | 'isHidden' | 'layoutOf' | 'toJSON'\n > = {\n ...createTreeReads(state.nodes),\n renderOrder: order.ids,\n renderOrderNodes: order.nodes,\n nodesOnLayer: order.nodesOnLayer,\n isLocked: (id) => locks.isLocked(id),\n isHidden: (id) => locks.isHidden(id),\n layoutOf(id) {\n const node = state.nodes.get(id);\n return node?.layout ?? null;\n },\n toJSON: () => serializeScene(state, fnFields, docValues.toJSON()),\n };\n\n return {\n state,\n order,\n locks,\n stale: createStaleReplay(state.nodes),\n fnFields,\n store,\n layers,\n reads,\n docValues,\n\n /** Empty the document and give it `records` as its layers and `values` as its document values. */\n reset(records: readonly LayerRecord<TLayer>[], values?: SerializedDoc): void {\n docValues.reset(values);\n store.dependents.clear();\n store.childDerived.clear();\n state.nodes.clear();\n state.roots.length = 0;\n layers.reset(records);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,iBACd,UACA,eAEA,gBACA,YACA;CACA,MAAM,QAAQ;EACZ,uBAAO,IAAI,IAAwC;EACnD,OAAO,CAAC;EACR,QAAQ,CAAC;EACT,4BAAY,IAAI,IAAoB;EACpC,2BAAW,IAAI,IAAqB;CACtC;CACA,MAAM,QAAQ,kBAAkB,KAAK;;;CAGrC,MAAM,WAAW,mBAA0B,WAAW,UAAU;EAC9D,IAAI,UAAU,UAAU,eAAe;CACzC,CAAC;CACD,MAAM,QAAQ,gBAAgB,OAAO,MAAM,YAAY,QAAQ;CAC/D,MAAM,SAAS,iBAAiB,OAAO,MAAM,UAAU;CACvD,OAAO,MAAM,aAAa;CAC1B,MAAM,QAAQ,gBAAgB,KAAK;CACnC,MAAM,YAAY,eAAe,MAAM,SAAS;CAChD,UAAU,MAAM,UAAU;CAE1B,MAAM,QAIF;EACF,GAAG,gBAAgB,MAAM,KAAK;EAC9B,aAAa,MAAM;EACnB,kBAAkB,MAAM;EACxB,cAAc,MAAM;EACpB,WAAW,OAAO,MAAM,SAAS,EAAE;EACnC,WAAW,OAAO,MAAM,SAAS,EAAE;EACnC,SAAS,IAAI;GAEX,OADa,MAAM,MAAM,IAAI,EACtB,CAAA,EAAM,UAAU;EACzB;EACA,cAAc,eAAe,OAAO,UAAU,UAAU,OAAO,CAAC;CAClE;CAEA,OAAO;EACL;EACA;EACA;EACA,OAAO,kBAAkB,MAAM,KAAK;EACpC;EACA;EACA;EACA;EACA;;EAGA,MAAM,SAAyC,QAA8B;GAC3E,UAAU,MAAM,MAAM;GACtB,MAAM,WAAW,MAAM;GACvB,MAAM,aAAa,MAAM;GACzB,MAAM,MAAM,MAAM;GAClB,MAAM,MAAM,SAAS;GACrB,OAAO,MAAM,OAAO;EACtB;CACF;AACF"}
|
|
@@ -8,7 +8,8 @@ import { treePreorder } from "./treeOrder.js";
|
|
|
8
8
|
*/
|
|
9
9
|
/** Whether `value` is a snapshot `specsFromSerialized` reads, as against a live scene. */
|
|
10
10
|
function isSerializedScene(value) {
|
|
11
|
-
|
|
11
|
+
const version = typeof value === "object" && value != null ? value.version : void 0;
|
|
12
|
+
return (version === 1 || version === 2) && Array.isArray(value.nodes);
|
|
12
13
|
}
|
|
13
14
|
/** One layer record from its snapshot form. A snapshot with no `kind` predates
|
|
14
15
|
* user layers, so everything in it was a system layer. */
|
|
@@ -49,14 +50,15 @@ function serializeScene(state, fnFields, doc) {
|
|
|
49
50
|
if (n.pickable === false) out.pickable = false;
|
|
50
51
|
if (n.hidden === true) out.hidden = true;
|
|
51
52
|
if (n.locked === true) out.locked = true;
|
|
52
|
-
if (n.
|
|
53
|
+
if (n.units !== void 0) out.units = n.units;
|
|
54
|
+
if (n.clip !== void 0) out.clip = n.clip;
|
|
53
55
|
Object.assign(out, fnKeysOf(fnFields, n, "jsonKey", (field) => {
|
|
54
56
|
throw new Error(`Scene.toJSON: node '${id}' has ${field.spec.field} but no matching registry key. The function must be registered via createScene's registry option to round-trip.`);
|
|
55
57
|
}));
|
|
56
58
|
nodes.push(out);
|
|
57
59
|
}
|
|
58
60
|
return {
|
|
59
|
-
version:
|
|
61
|
+
version: 2,
|
|
60
62
|
systemLayers: state.layers.map((l) => {
|
|
61
63
|
const layer = { id: l.id };
|
|
62
64
|
if (l.visible === false) layer.visible = false;
|
|
@@ -76,13 +78,23 @@ function serializeScene(state, fnFields, doc) {
|
|
|
76
78
|
/** Map a `SerializedScene` to construction specs. Shared by `sceneFromJSON`
|
|
77
79
|
* (new instance) and `Scene.loadState` (in-place). Validates version and
|
|
78
80
|
* resolves every `NODE_FN_FIELDS` key → function via the registry;
|
|
79
|
-
* throws on an unsupported version
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
* throws on an unsupported version, and on an unknown registry key unless
|
|
82
|
+
* `opts.unknownKeys` is `'skip'`. */
|
|
83
|
+
function specsFromSerialized(json, registry, opts = {}) {
|
|
84
|
+
let entries;
|
|
85
|
+
if (json.version === 1) entries = json.nodes.map((n) => ({
|
|
86
|
+
n,
|
|
87
|
+
kind: nodeKindOf(1, n, opts.classifyV1)
|
|
88
|
+
}));
|
|
89
|
+
else if (json.version === 2) entries = json.nodes.map((n) => ({
|
|
90
|
+
n,
|
|
91
|
+
kind: nodeKindOf(2, n)
|
|
92
|
+
}));
|
|
93
|
+
else throw new Error(`Scene: unsupported version ${String(Reflect.get(json, "version"))}; versions 1 and 2 are supported`);
|
|
94
|
+
return entries.map(({ n, kind }) => {
|
|
83
95
|
const spec = {
|
|
84
96
|
id: n.id,
|
|
85
|
-
kind
|
|
97
|
+
kind,
|
|
86
98
|
layer: n.layer,
|
|
87
99
|
pose: n.pose,
|
|
88
100
|
data: n.data
|
|
@@ -92,19 +104,43 @@ function specsFromSerialized(json, registry) {
|
|
|
92
104
|
if (n.hidden === true) spec.hidden = true;
|
|
93
105
|
if (n.locked === true) spec.locked = true;
|
|
94
106
|
if (n.units !== void 0) spec.units = n.units;
|
|
107
|
+
if (n.clip !== void 0) spec.clip = n.clip;
|
|
95
108
|
if (n.dependsOn !== void 0) spec.dependsOn = n.dependsOn === "children" ? "children" : n.dependsOn;
|
|
96
109
|
const serialized = n;
|
|
97
110
|
for (const field of NODE_FN_FIELDS) {
|
|
98
111
|
const key = serialized[field.jsonKey];
|
|
99
112
|
if (key === void 0) continue;
|
|
100
113
|
const fn = registry[field.field]?.[key];
|
|
114
|
+
if (!fn && opts.unknownKeys === "skip") {
|
|
115
|
+
opts.onSkip?.({
|
|
116
|
+
id: spec.id,
|
|
117
|
+
field: field.field,
|
|
118
|
+
key
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
101
122
|
if (!fn) throw new Error(`Scene: unknown ${field.field} key '${key}'. Register a function with this key in the registry option.`);
|
|
102
123
|
spec[field.field] = fn;
|
|
103
124
|
}
|
|
125
|
+
if (clipsAsV1(json.version, spec)) spec.clip = "outline";
|
|
104
126
|
return spec;
|
|
105
127
|
});
|
|
106
128
|
}
|
|
129
|
+
/** Version 1 clipped a container with no clip function to its outline, which
|
|
130
|
+
* is what `clip: 'outline'` draws on a group. One a classifier makes a mark
|
|
131
|
+
* gets no clip, since which painter claims it is not known here; one whose
|
|
132
|
+
* clip key was skipped loads as it would have without it. */
|
|
133
|
+
function clipsAsV1(version, spec) {
|
|
134
|
+
return version === 1 && spec.kind === "group" && spec.clipFromPose === void 0;
|
|
135
|
+
}
|
|
136
|
+
/** The kind a saved node loads as: its own on version 2; on version 1 a `leaf` is a
|
|
137
|
+
* mark and a `container` goes to `classify`, or is a group without one. */
|
|
138
|
+
function nodeKindOf(version, n, classify) {
|
|
139
|
+
if (version === 2) return n.kind;
|
|
140
|
+
if (n.kind === "leaf") return "mark";
|
|
141
|
+
return classify ? classify(n) : "group";
|
|
142
|
+
}
|
|
107
143
|
|
|
108
144
|
//#endregion
|
|
109
|
-
export { isSerializedScene, layerFromSerialized, serializeScene, specsFromSerialized };
|
|
145
|
+
export { isSerializedScene, layerFromSerialized, nodeKindOf, serializeScene, specsFromSerialized };
|
|
110
146
|
//# sourceMappingURL=serialization.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.js","names":[],"sources":["../../../src/core/scene/serialization.ts"],"sourcesContent":["/**\n * A scene to and from its JSON snapshot: the walk that writes one, and the\n * two readers that turn one back into layer records and node specs.\n */\nimport {\n NODE_FN_FIELDS, fnKeysOf, type FnBearingNode, type NodeFnField,\n} from './nodeFnFields';\nimport type { SerializedDoc } from './docValues';\nimport { treePreorder } from './treeOrder';\nimport type {\n AddNodeSpec, LayerRecord, Node, NodeId, SceneRegistry, SerializedLayer, SerializedNode, SerializedScene,\n} from './types';\n\n/** Whether `value` is a snapshot `specsFromSerialized` reads, as against a live scene. */\nexport function isSerializedScene<TData, TLayer extends string, TPose>(\n value: unknown,\n): value is SerializedScene<TData, TLayer, TPose> {\n return typeof value === 'object' && value != null\n && (value as { version?: unknown }).version === 1\n && Array.isArray((value as { nodes?: unknown }).nodes);\n}\n\n/** What a snapshot is written from. */\nexport interface SerializableState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n layers: LayerRecord<TLayer>[];\n}\n\n/** One layer record from its snapshot form. A snapshot with no `kind` predates\n * user layers, so everything in it was a system layer. */\nexport function layerFromSerialized<TLayer extends string>(\n spec: SerializedLayer<TLayer>,\n): LayerRecord<TLayer> {\n const base = {\n id: spec.id,\n visible: spec.visible ?? true,\n locked: spec.locked ?? false,\n ...(spec.parallax ? { parallax: spec.parallax } : {}),\n ...(spec.annotation ? { annotation: true } : {}),\n };\n return spec.kind === 'user'\n ? { ...base, kind: 'user', name: spec.name ?? spec.id }\n : { ...base, kind: 'system' };\n}\n\n/** The snapshot of `state`: layers in order, nodes in tree preorder, and the\n * document values when there are any. Throws\n * when a node carries a function the registry has no key for. */\nexport function serializeScene<TData, TLayer extends string, TPose>(\n state: SerializableState<TData, TLayer, TPose>,\n fnFields: readonly NodeFnField[],\n doc?: SerializedDoc,\n): SerializedScene<TData, TLayer, TPose> {\n const nodes: SerializedNode<TData, TLayer, TPose>[] = [];\n for (const n of treePreorder(state.roots, state.nodes)) {\n const id = n.id;\n const out: SerializedNode<TData, TLayer, TPose> = {\n id,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent != null) out.parent = n.parent;\n if (n.dependsOn === 'children') out.dependsOn = 'children';\n else if (n.dependsOn && n.dependsOn.length > 0) out.dependsOn = n.dependsOn;\n if (n.pickable === false) out.pickable = false;\n if (n.hidden === true) out.hidden = true;\n if (n.locked === true) out.locked = true;\n if (n.kind === 'container' && n.units !== undefined) out.units = n.units;\n Object.assign(\n out,\n fnKeysOf(fnFields, n as unknown as FnBearingNode, 'jsonKey', (field) => {\n throw new Error(\n `Scene.toJSON: node '${id}' has ${field.spec.field} but no matching registry key. ` +\n `The function must be registered via createScene's registry option to round-trip.`\n );\n }),\n );\n nodes.push(out);\n }\n const systemLayers = state.layers.map((l) => {\n const layer: SerializedLayer<TLayer> = { id: l.id };\n if (l.visible === false) layer.visible = false;\n if (l.locked === true) layer.locked = true;\n if (l.parallax) layer.parallax = l.parallax;\n if (l.annotation) layer.annotation = true;\n if (l.kind === 'user') {\n layer.kind = 'user';\n layer.name = l.name;\n }\n return layer;\n });\n return { version: 1, systemLayers, nodes, ...(doc ? { doc } : {}) };\n}\n\n/** Map a `SerializedScene` to construction specs. Shared by `sceneFromJSON`\n * (new instance) and `Scene.loadState` (in-place). Validates version and\n * resolves every `NODE_FN_FIELDS` key → function via the registry;\n * throws on an unsupported version or an unknown registry key. */\nexport function specsFromSerialized<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n registry: SceneRegistry<TPose, TData, TLayer>,\n): AddNodeSpec<TData, TLayer, TPose>[] {\n if (json.version !== 1) {\n throw new Error(`Scene: unsupported version ${json.version}; only v1 supported`);\n }\n return json.nodes.map((n) => {\n const spec: AddNodeSpec<TData, TLayer, TPose> = {\n id: n.id as NodeId,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent !== undefined) spec.parent = n.parent as NodeId;\n if (n.pickable !== undefined) spec.pickable = n.pickable;\n if (n.hidden === true) spec.hidden = true;\n if (n.locked === true) spec.locked = true;\n if (n.units !== undefined) spec.units = n.units;\n if (n.dependsOn !== undefined) {\n spec.dependsOn = n.dependsOn === 'children'\n ? 'children'\n : (n.dependsOn as readonly NodeId[]);\n }\n const serialized = n as unknown as Record<string, string | undefined>;\n for (const field of NODE_FN_FIELDS) {\n const key = serialized[field.jsonKey];\n if (key === undefined) continue;\n const fn = (registry[field.field] as Record<string, unknown> | undefined)?.[key];\n if (!fn) {\n throw new Error(\n `Scene: unknown ${field.field} key '${key}'. ` +\n `Register a function with this key in the registry option.`,\n );\n }\n (spec as unknown as Record<string, unknown>)[field.field] = fn;\n }\n return spec;\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serialization.js","names":[],"sources":["../../../src/core/scene/serialization.ts"],"sourcesContent":["/**\n * A scene to and from its JSON snapshot: the walk that writes one, and the\n * two readers that turn one back into layer records and node specs.\n */\nimport {\n NODE_FN_FIELDS, fnKeysOf, type FnBearingNode, type NodeFnField,\n} from './nodeFnFields';\nimport type { SerializedDoc } from './docValues';\nimport { treePreorder } from './treeOrder';\nimport type {\n AddNodeSpec, LayerRecord, Node, NodeId, SceneRegistry, SerializedLayer, SerializedNode, SerializedNodeV1,\n SerializedScene, SerializedSceneV1,\n} from './types';\n\n/** Whether `value` is a snapshot `specsFromSerialized` reads, as against a live scene. */\nexport function isSerializedScene<TData, TLayer extends string, TPose>(\n value: unknown,\n): value is SerializedScene<TData, TLayer, TPose> | SerializedSceneV1<TData, TLayer, TPose> {\n const version = typeof value === 'object' && value != null\n ? (value as { version?: unknown }).version : undefined;\n return (version === 1 || version === 2)\n && Array.isArray((value as { nodes?: unknown }).nodes);\n}\n\n/** What a snapshot is written from. */\nexport interface SerializableState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n layers: LayerRecord<TLayer>[];\n}\n\n/** One layer record from its snapshot form. A snapshot with no `kind` predates\n * user layers, so everything in it was a system layer. */\nexport function layerFromSerialized<TLayer extends string>(\n spec: SerializedLayer<TLayer>,\n): LayerRecord<TLayer> {\n const base = {\n id: spec.id,\n visible: spec.visible ?? true,\n locked: spec.locked ?? false,\n ...(spec.parallax ? { parallax: spec.parallax } : {}),\n ...(spec.annotation ? { annotation: true } : {}),\n };\n return spec.kind === 'user'\n ? { ...base, kind: 'user', name: spec.name ?? spec.id }\n : { ...base, kind: 'system' };\n}\n\n/** The snapshot of `state`: layers in order, nodes in tree preorder, and the\n * document values when there are any. Throws\n * when a node carries a function the registry has no key for. */\nexport function serializeScene<TData, TLayer extends string, TPose>(\n state: SerializableState<TData, TLayer, TPose>,\n fnFields: readonly NodeFnField[],\n doc?: SerializedDoc,\n): SerializedScene<TData, TLayer, TPose> {\n const nodes: SerializedNode<TData, TLayer, TPose>[] = [];\n for (const n of treePreorder(state.roots, state.nodes)) {\n const id = n.id;\n const out: SerializedNode<TData, TLayer, TPose> = {\n id,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent != null) out.parent = n.parent;\n if (n.dependsOn === 'children') out.dependsOn = 'children';\n else if (n.dependsOn && n.dependsOn.length > 0) out.dependsOn = n.dependsOn;\n if (n.pickable === false) out.pickable = false;\n if (n.hidden === true) out.hidden = true;\n if (n.locked === true) out.locked = true;\n if (n.units !== undefined) out.units = n.units;\n if (n.clip !== undefined) out.clip = n.clip;\n Object.assign(\n out,\n fnKeysOf(fnFields, n as unknown as FnBearingNode, 'jsonKey', (field) => {\n throw new Error(\n `Scene.toJSON: node '${id}' has ${field.spec.field} but no matching registry key. ` +\n `The function must be registered via createScene's registry option to round-trip.`\n );\n }),\n );\n nodes.push(out);\n }\n const systemLayers = state.layers.map((l) => {\n const layer: SerializedLayer<TLayer> = { id: l.id };\n if (l.visible === false) layer.visible = false;\n if (l.locked === true) layer.locked = true;\n if (l.parallax) layer.parallax = l.parallax;\n if (l.annotation) layer.annotation = true;\n if (l.kind === 'user') {\n layer.kind = 'user';\n layer.name = l.name;\n }\n return layer;\n });\n return { version: 2, systemLayers, nodes, ...(doc ? { doc } : {}) };\n}\n\n/** Decides what a version 1 `container` becomes; `leaf` is always a mark. */\nexport type V1Classifier<TData, TLayer extends string, TPose> =\n (node: SerializedNodeV1<TData, TLayer, TPose>) => 'mark' | 'group';\n\n/** Options for {@link specsFromSerialized}. */\nexport interface SpecsFromSerializedOptions<TData, TLayer extends string, TPose> {\n /** What a version 1 `container` loads as; see {@link V1Classifier}. */\n classifyV1?: V1Classifier<TData, TLayer, TPose>;\n /** `'skip'` loads a node whose registry key is unknown without that\n * function, for a save that outlived the code that registered it.\n * Default `'throw'`. */\n unknownKeys?: 'throw' | 'skip';\n /** Hears each key `unknownKeys: 'skip'` leaves out, as it is left out. */\n onSkip?: (skipped: SkippedKey) => void;\n}\n\n/** A registry key a load left out: the node, the function field it named,\n * and the key. */\nexport interface SkippedKey {\n id: NodeId;\n field: keyof SceneRegistry<unknown>;\n key: string;\n}\n\n/** Map a `SerializedScene` to construction specs. Shared by `sceneFromJSON`\n * (new instance) and `Scene.loadState` (in-place). Validates version and\n * resolves every `NODE_FN_FIELDS` key → function via the registry;\n * throws on an unsupported version, and on an unknown registry key unless\n * `opts.unknownKeys` is `'skip'`. */\nexport function specsFromSerialized<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose> | SerializedSceneV1<TData, TLayer, TPose>,\n registry: SceneRegistry<TPose, TData, TLayer>,\n opts: SpecsFromSerializedOptions<TData, TLayer, TPose> = {},\n): AddNodeSpec<TData, TLayer, TPose>[] {\n type Entry = { n: Omit<SerializedNode<TData, TLayer, TPose>, 'kind'>; kind: 'mark' | 'group' };\n let entries: Entry[];\n if (json.version === 1) {\n entries = json.nodes.map((n) => ({ n, kind: nodeKindOf(1, n, opts.classifyV1) }));\n } else if (json.version === 2) {\n entries = json.nodes.map((n) => ({ n, kind: nodeKindOf(2, n) }));\n } else {\n throw new Error(`Scene: unsupported version ${String(Reflect.get(json, 'version'))}; versions 1 and 2 are supported`);\n }\n return entries.map(({ n, kind }) => {\n const spec: AddNodeSpec<TData, TLayer, TPose> = {\n id: n.id as NodeId,\n kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent !== undefined) spec.parent = n.parent as NodeId;\n if (n.pickable !== undefined) spec.pickable = n.pickable;\n if (n.hidden === true) spec.hidden = true;\n if (n.locked === true) spec.locked = true;\n if (n.units !== undefined) spec.units = n.units;\n if (n.clip !== undefined) spec.clip = n.clip;\n if (n.dependsOn !== undefined) {\n spec.dependsOn = n.dependsOn === 'children'\n ? 'children'\n : (n.dependsOn as readonly NodeId[]);\n }\n const serialized = n as unknown as Record<string, string | undefined>;\n for (const field of NODE_FN_FIELDS) {\n const key = serialized[field.jsonKey];\n if (key === undefined) continue;\n const fn = (registry[field.field] as Record<string, unknown> | undefined)?.[key];\n if (!fn && opts.unknownKeys === 'skip') {\n opts.onSkip?.({ id: spec.id!, field: field.field, key });\n continue;\n }\n if (!fn) {\n throw new Error(\n `Scene: unknown ${field.field} key '${key}'. ` +\n `Register a function with this key in the registry option.`,\n );\n }\n (spec as unknown as Record<string, unknown>)[field.field] = fn;\n }\n if (clipsAsV1(json.version, spec)) spec.clip = 'outline';\n return spec;\n });\n}\n\n/** Version 1 clipped a container with no clip function to its outline, which\n * is what `clip: 'outline'` draws on a group. One a classifier makes a mark\n * gets no clip, since which painter claims it is not known here; one whose\n * clip key was skipped loads as it would have without it. */\nfunction clipsAsV1(version: 1 | 2, spec: { kind: string; clipFromPose?: unknown }): boolean {\n return version === 1 && spec.kind === 'group' && spec.clipFromPose === undefined;\n}\n\n/** The kind a saved node loads as: its own on version 2; on version 1 a `leaf` is a\n * mark and a `container` goes to `classify`, or is a group without one. */\nexport function nodeKindOf<TData, TLayer extends string, TPose>(\n version: 1 | 2,\n n: SerializedNode<TData, TLayer, TPose> | SerializedNodeV1<TData, TLayer, TPose>,\n classify?: V1Classifier<TData, TLayer, TPose>,\n): 'mark' | 'group' {\n if (version === 2) return (n as SerializedNode<TData, TLayer, TPose>).kind;\n if (n.kind === 'leaf') return 'mark';\n return classify ? classify(n as SerializedNodeV1<TData, TLayer, TPose>) : 'group';\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hasChildren } from "./treeShape.js";
|
|
2
|
+
|
|
1
3
|
//#region src/core/scene/staleReplay.ts
|
|
2
4
|
/** A scene edit named a node the scene does not hold, or one it already does. */
|
|
3
5
|
var SceneMembershipError = class extends Error {};
|
|
@@ -8,7 +10,7 @@ function createStaleReplay(nodes) {
|
|
|
8
10
|
const holds = (id) => id === null || nodes.has(id);
|
|
9
11
|
const childless = (id, within) => {
|
|
10
12
|
const node = nodes.get(id);
|
|
11
|
-
return node
|
|
13
|
+
return node === void 0 || !hasChildren(node) || node.children.every((c) => within?.has(c) === true);
|
|
12
14
|
};
|
|
13
15
|
function stale(kind, payload, forward) {
|
|
14
16
|
switch (kind) {
|
|
@@ -31,7 +33,8 @@ function createStaleReplay(nodes) {
|
|
|
31
33
|
case "kit:setLayer":
|
|
32
34
|
case "kit:setDependsOn":
|
|
33
35
|
case "kit:setLayout":
|
|
34
|
-
case "kit:setUnits":
|
|
36
|
+
case "kit:setUnits":
|
|
37
|
+
case "kit:setClip": return !nodes.has(payload.id);
|
|
35
38
|
default: return false;
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staleReplay.js","names":[],"sources":["../../../src/core/scene/staleReplay.ts"],"sourcesContent":["/**\n * What a replay does with an op whose node is not where its entry left it.\n *\n * An untracked edit changes the tree without the history stack hearing of it,\n * so an entry can name a node that edit removed, or add one it already put\n * back. Replaying such an op is skipped rather than refused: a refusal leaves\n * the entry on top of its stack, and every entry beneath it out of reach. The\n * inverse of a skipped op is skipped too, so undo then redo changes nothing.\n */\nimport type { RemoveSnapshot } from './kitOps';\nimport type { Node, NodeId } from './types';\n\n/** A scene edit named a node the scene does not hold, or one it already does. */\nexport class SceneMembershipError extends Error {}\n\nexport interface StaleReplay {\n /** Run a step that replays recorded ops. */\n during<T>(step: () => T): T;\n /** Whether replaying this kit op would address a node that is gone, or\n * restore one that is already there. Always false outside `during`. */\n skips(kind: string, payload: unknown, forward: boolean): boolean;\n}\n\nexport function createStaleReplay<TData, TLayer extends string, TPose>(\n nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>,\n): StaleReplay {\n let replaying = 0;\n /** Kit payloads whose last replay was skipped, by the direction it ran in. */\n const skipped = new WeakMap<object, boolean>();\n const holds = (id: NodeId | null): boolean => id === null || nodes.has(id);\n const childless = (id: NodeId, within?: ReadonlySet<NodeId>): boolean => {\n const node = nodes.get(id);\n return node
|
|
1
|
+
{"version":3,"file":"staleReplay.js","names":[],"sources":["../../../src/core/scene/staleReplay.ts"],"sourcesContent":["/**\n * What a replay does with an op whose node is not where its entry left it.\n *\n * An untracked edit changes the tree without the history stack hearing of it,\n * so an entry can name a node that edit removed, or add one it already put\n * back. Replaying such an op is skipped rather than refused: a refusal leaves\n * the entry on top of its stack, and every entry beneath it out of reach. The\n * inverse of a skipped op is skipped too, so undo then redo changes nothing.\n */\nimport type { RemoveSnapshot } from './kitOps';\nimport { hasChildren } from './treeShape';\nimport type { Node, NodeId } from './types';\n\n/** A scene edit named a node the scene does not hold, or one it already does. */\nexport class SceneMembershipError extends Error {}\n\nexport interface StaleReplay {\n /** Run a step that replays recorded ops. */\n during<T>(step: () => T): T;\n /** Whether replaying this kit op would address a node that is gone, or\n * restore one that is already there. Always false outside `during`. */\n skips(kind: string, payload: unknown, forward: boolean): boolean;\n}\n\nexport function createStaleReplay<TData, TLayer extends string, TPose>(\n nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>,\n): StaleReplay {\n let replaying = 0;\n /** Kit payloads whose last replay was skipped, by the direction it ran in. */\n const skipped = new WeakMap<object, boolean>();\n const holds = (id: NodeId | null): boolean => id === null || nodes.has(id);\n const childless = (id: NodeId, within?: ReadonlySet<NodeId>): boolean => {\n const node = nodes.get(id);\n return node === undefined || !hasChildren(node) || node.children.every((c) => within?.has(c) === true);\n };\n\n function stale(kind: string, payload: unknown, forward: boolean): boolean {\n switch (kind) {\n case 'kit:add': {\n const p = payload as { id: NodeId; parent: NodeId | null };\n return forward ? nodes.has(p.id) || !holds(p.parent) : !nodes.has(p.id) || !childless(p.id);\n }\n case 'kit:remove': {\n const p = payload as RemoveSnapshot<TData, TLayer, TPose>;\n if (!forward) return p.nodes.some((n) => nodes.has(n.id)) || !p.detached.every((d) => holds(d.parent));\n const taken = new Set(p.nodes.map((n) => n.id));\n return !p.nodes.every((n) => nodes.has(n.id) && childless(n.id, taken));\n }\n case 'kit:move': {\n const p = payload as { id: NodeId; fromParent: NodeId | null; toParent: NodeId | null };\n return !nodes.has(p.id) || !holds(forward ? p.toParent : p.fromParent);\n }\n case 'kit:setPose':\n case 'kit:setData':\n case 'kit:setLayer':\n case 'kit:setDependsOn':\n case 'kit:setLayout':\n case 'kit:setUnits':\n case 'kit:setClip':\n return !nodes.has((payload as { id: NodeId }).id);\n default:\n return false;\n }\n }\n\n function skips(kind: string, payload: unknown, forward: boolean): boolean {\n if (replaying === 0 || typeof payload !== 'object' || payload === null) return false;\n if (skipped.get(payload) === !forward) return skipped.delete(payload);\n if (!stale(kind, payload, forward)) return false;\n skipped.set(payload, forward);\n return true;\n }\n\n return {\n during(step) {\n replaying++;\n try {\n return step();\n } finally {\n replaying--;\n }\n },\n skips,\n };\n}\n"],"mappings":";;;;AAcA,IAAa,uBAAb,cAA0C,MAAM,CAAC;AAUjD,SAAgB,kBACd,OACa;CACb,IAAI,YAAY;;CAEhB,MAAM,0BAAU,IAAI,QAAyB;CAC7C,MAAM,SAAS,OAA+B,OAAO,QAAQ,MAAM,IAAI,EAAE;CACzE,MAAM,aAAa,IAAY,WAA0C;EACvE,MAAM,OAAO,MAAM,IAAI,EAAE;EACzB,OAAO,SAAS,UAAa,CAAC,YAAY,IAAI,KAAK,KAAK,SAAS,OAAO,MAAM,QAAQ,IAAI,CAAC,MAAM,IAAI;CACvG;CAEA,SAAS,MAAM,MAAc,SAAkB,SAA2B;EACxE,QAAQ,MAAR;GACE,KAAK,WAAW;IACd,MAAM,IAAI;IACV,OAAO,UAAU,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE;GAC5F;GACA,KAAK,cAAc;IACjB,MAAM,IAAI;IACV,IAAI,CAAC,SAAS,OAAO,EAAE,MAAM,MAAM,MAAM,MAAM,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,OAAO,MAAM,MAAM,EAAE,MAAM,CAAC;IACrG,MAAM,QAAQ,IAAI,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,EAAE,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,OAAO,MAAM,MAAM,IAAI,EAAE,EAAE,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC;GACxE;GACA,KAAK,YAAY;IACf,MAAM,IAAI;IACV,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,MAAM,UAAU,EAAE,WAAW,EAAE,UAAU;GACvE;GACA,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK,eACH,OAAO,CAAC,MAAM,IAAK,QAA2B,EAAE;GAClD,SACE,OAAO;EACX;CACF;CAEA,SAAS,MAAM,MAAc,SAAkB,SAA2B;EACxE,IAAI,cAAc,KAAK,OAAO,YAAY,YAAY,YAAY,MAAM,OAAO;EAC/E,IAAI,QAAQ,IAAI,OAAO,MAAM,CAAC,SAAS,OAAO,QAAQ,OAAO,OAAO;EACpE,IAAI,CAAC,MAAM,MAAM,SAAS,OAAO,GAAG,OAAO;EAC3C,QAAQ,IAAI,SAAS,OAAO;EAC5B,OAAO;CACT;CAEA,OAAO;EACL,OAAO,MAAM;GACX;GACA,IAAI;IACF,OAAO,KAAK;GACd,UAAU;IACR;GACF;EACF;EACA;CACF;AACF"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
//#region src/core/scene/statedFields.ts
|
|
2
2
|
/** The plain optional fields of a node or spec as a spread for `scene.add`:
|
|
3
3
|
* each present only when stated, so an unstated one keeps the scene's
|
|
4
|
-
* default
|
|
4
|
+
* default, and a flag states only `true`. What a copy of a node carries over
|
|
5
|
+
* besides its kind, layer, pose, data and functions. */
|
|
5
6
|
var statedFields = (n) => ({
|
|
6
7
|
...n.pickable !== void 0 ? { pickable: n.pickable } : {},
|
|
7
8
|
...n.hidden === true ? { hidden: true } : {},
|
|
8
9
|
...n.locked === true ? { locked: true } : {},
|
|
9
|
-
...n.
|
|
10
|
+
...n.units !== void 0 ? { units: n.units } : {},
|
|
11
|
+
...n.clip !== void 0 ? { clip: n.clip } : {}
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statedFields.js","names":[],"sources":["../../../src/core/scene/statedFields.ts"],"sourcesContent":["import type { UnitScope } from '@weasel-js/quantity';\
|
|
1
|
+
{"version":3,"file":"statedFields.js","names":[],"sources":["../../../src/core/scene/statedFields.ts"],"sourcesContent":["import type { UnitScope } from '@weasel-js/quantity';\nimport type { NodeClip } from './types';\n\n/** The plain optional fields a node or an `AddNodeSpec` may state. */\nexport interface StatedNodeFields {\n pickable?: boolean;\n hidden?: boolean;\n locked?: boolean;\n units?: UnitScope;\n clip?: NodeClip;\n}\n\n/** The plain optional fields of a node or spec as a spread for `scene.add`:\n * each present only when stated, so an unstated one keeps the scene's\n * default, and a flag states only `true`. What a copy of a node carries over\n * besides its kind, layer, pose, data and functions. */\nexport const statedFields = (n: StatedNodeFields & { kind?: string }): StatedNodeFields => ({\n ...(n.pickable !== undefined ? { pickable: n.pickable } : {}),\n ...(n.hidden === true ? { hidden: true } : {}),\n ...(n.locked === true ? { locked: true } : {}),\n ...(n.units !== undefined ? { units: n.units } : {}),\n ...(n.clip !== undefined ? { clip: n.clip } : {}),\n});\n"],"mappings":";;;;;AAgBA,IAAa,gBAAgB,OAA+D;CAC1F,GAAI,EAAE,aAAa,SAAY,EAAE,UAAU,EAAE,SAAS,IAAI,CAAC;CAC3D,GAAI,EAAE,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C,GAAI,EAAE,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C,GAAI,EAAE,UAAU,SAAY,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;CAClD,GAAI,EAAE,SAAS,SAAY,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AACjD"}
|
|
@@ -8,7 +8,7 @@ import { SceneMembershipError } from "./staleReplay.js";
|
|
|
8
8
|
function removeSnapshot(ids, removing, store, fnFields) {
|
|
9
9
|
const nodes = ids.map((nid) => {
|
|
10
10
|
const n = store.requireNode(nid);
|
|
11
|
-
return n.
|
|
11
|
+
return n.children ? {
|
|
12
12
|
...n,
|
|
13
13
|
children: [...n.children]
|
|
14
14
|
} : { ...n };
|
|
@@ -40,7 +40,7 @@ function createTreeEdits(env) {
|
|
|
40
40
|
const { noteArrival, noteDeparture, noteChange } = env.arrivals;
|
|
41
41
|
/** Check that `spec` can be placed, and answer with the `kit:add` payload
|
|
42
42
|
* that places it. Throws on an id collision, an unknown or locked layer,
|
|
43
|
-
* a parent
|
|
43
|
+
* a locked parent, and a layer below the
|
|
44
44
|
* parent's. */
|
|
45
45
|
function addPayload(spec) {
|
|
46
46
|
const id = spec.id ?? env.generateId();
|
|
@@ -50,7 +50,6 @@ function createTreeEdits(env) {
|
|
|
50
50
|
const parent = spec.parent ?? null;
|
|
51
51
|
if (parent !== null) {
|
|
52
52
|
const p = requireNode(parent);
|
|
53
|
-
if (p.kind !== "container") throw new Error(`Scene: parent "${parent}" is not a container`);
|
|
54
53
|
assertSubtreeLayer(spec.id, spec.layer, parent, p.layer);
|
|
55
54
|
refuse(parent, "add under");
|
|
56
55
|
}
|
|
@@ -111,7 +110,6 @@ function createTreeEdits(env) {
|
|
|
111
110
|
if (parent !== null) {
|
|
112
111
|
refuse(parent, "move nodes into");
|
|
113
112
|
const p = requireNode(parent);
|
|
114
|
-
if (p.kind !== "container") throw new Error(`Scene: parent "${parent}" is not a container`);
|
|
115
113
|
assertSubtreeLayer(id, node.layer, parent, p.layer);
|
|
116
114
|
if (parent === id) throw new Error("Scene: cannot parent a node to itself");
|
|
117
115
|
const desc = [];
|