@weasel-js/core 1.14.1 → 1.15.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 +218 -0
- package/README.md +1 -1
- package/dist/affordances/pathAnchors.js +0 -1
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/barrel/paths.js +1 -1
- package/dist/barrel/renderer.js +2 -2
- package/dist/barrel/routing.js +6 -4
- package/dist/barrel/text.js +5 -3
- package/dist/canvas/Canvas.js +5 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/DrawCanvas.js +5 -1
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +4 -0
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/builtinToolSets.js +6 -3
- package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +1 -1
- package/dist/canvas/SceneCanvas/presets/paths.js +6 -6
- package/dist/canvas/SceneCanvas/presets/paths.js.map +1 -1
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +1 -1
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -0
- package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
- package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneChrome.js +17 -5
- package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -0
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +5 -4
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +5 -1
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/affordanceAt.js +0 -1
- package/dist/canvas/affordanceAt.js.map +1 -1
- package/dist/canvas/deps/chromeRules.js +37 -0
- package/dist/canvas/deps/chromeRules.js.map +1 -0
- package/dist/canvas/deps/editAnchors.js +0 -6
- package/dist/canvas/deps/editAnchors.js.map +1 -1
- package/dist/canvas/deps/hitTestArea.js +3 -1
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/modeDefaultTool.js +23 -0
- package/dist/canvas/deps/modeDefaultTool.js.map +1 -0
- package/dist/canvas/deps/pathEditCommits.js +1 -1
- package/dist/canvas/deps/selectionSnapshot.js +23 -0
- package/dist/canvas/deps/selectionSnapshot.js.map +1 -0
- package/dist/canvas/deps/snap.js +1 -0
- package/dist/canvas/deps/snap.js.map +1 -1
- package/dist/canvas/pickReach.js +12 -8
- package/dist/canvas/pickReach.js.map +1 -1
- package/dist/canvas/pickWalk.js +7 -5
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/scenePickSource.js +2 -2
- package/dist/canvas/scenePickSource.js.map +1 -1
- package/dist/canvas/useCanvasSurface.js +21 -3
- package/dist/canvas/useCanvasSurface.js.map +1 -1
- package/dist/canvas/useFrameLoop.js +6 -7
- package/dist/canvas/useFrameLoop.js.map +1 -1
- package/dist/canvas/useSurfaceSize.js +4 -3
- package/dist/canvas/useSurfaceSize.js.map +1 -1
- package/dist/core/geometry/regionHit.js +35 -16
- package/dist/core/geometry/regionHit.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js +9 -10
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/scene/editMode.js +4 -1
- package/dist/core/scene/editMode.js.map +1 -1
- package/dist/core/scene/historyFacade.js +7 -50
- package/dist/core/scene/historyFacade.js.map +1 -1
- package/dist/core/scene/lockGuard.js +1 -0
- package/dist/core/scene/lockGuard.js.map +1 -1
- package/dist/core/scene/nodeGates.js +43 -23
- package/dist/core/scene/nodeGates.js.map +1 -1
- package/dist/core/scene/opTable.js +8 -8
- package/dist/core/scene/opTable.js.map +1 -1
- package/dist/core/scene/outerOps.js +2 -2
- package/dist/core/scene/outerOps.js.map +1 -1
- package/dist/core/scene/poseRemovals.js +1 -0
- package/dist/core/scene/poseRemovals.js.map +1 -1
- package/dist/core/scene/poseWrites.js +16 -15
- package/dist/core/scene/poseWrites.js.map +1 -1
- package/dist/core/scene/sceneJournals.js +78 -0
- package/dist/core/scene/sceneJournals.js.map +1 -0
- package/dist/core/scene/sceneRecording.js +3 -2
- package/dist/core/scene/sceneRecording.js.map +1 -1
- package/dist/core/scene/statedFields.js +10 -8
- package/dist/core/scene/statedFields.js.map +1 -1
- package/dist/core/scene/treeEdits.js +4 -9
- package/dist/core/scene/treeEdits.js.map +1 -1
- package/dist/core/viewport/useCanvasSize.js +47 -26
- package/dist/core/viewport/useCanvasSize.js.map +1 -1
- package/dist/{depSchema-C3BwxitB.d.ts → depSchema-ChQPzEkW.d.ts} +56 -25
- package/dist/domains/chrome-caps/resolve.js +1 -0
- package/dist/domains/chrome-caps/resolve.js.map +1 -1
- package/dist/domains/chrome-caps/useChromeCaps.js +4 -2
- package/dist/domains/chrome-caps/useChromeCaps.js.map +1 -1
- package/dist/domains/paths/pathEditState.js +0 -24
- package/dist/domains/paths/pathEditState.js.map +1 -1
- package/dist/domains/snap/probes2d.js +5 -0
- package/dist/domains/snap/probes2d.js.map +1 -1
- package/dist/domains/snap/resizeBox.js +43 -22
- package/dist/domains/snap/resizeBox.js.map +1 -1
- package/dist/domains/snap/sceneReads.js +11 -1
- package/dist/domains/snap/sceneReads.js.map +1 -1
- package/dist/domains/snap/sceneSession2d.js +4 -2
- package/dist/domains/snap/sceneSession2d.js.map +1 -1
- package/dist/domains/snap/session2d.js +14 -7
- package/dist/domains/snap/session2d.js.map +1 -1
- package/dist/domains/text/index.js +2 -2
- package/dist/icons.table.js +1 -1
- package/dist/icons.table.js.map +1 -1
- package/dist/icons.table.json +5 -0
- package/dist/index.d.ts +114 -62
- package/dist/index.js +12 -8
- package/dist/interactions/actions/booleans/action.js +4 -2
- package/dist/interactions/actions/booleans/action.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +3 -6
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +1 -2
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js +17 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +3 -4
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +21 -2
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/params.js +43 -0
- package/dist/interactions/actions/insert/params.js.map +1 -0
- package/dist/interactions/actions/notices.js +17 -0
- package/dist/interactions/actions/notices.js.map +1 -0
- package/dist/interactions/actions/outlines/createOutlines.js +8 -6
- package/dist/interactions/actions/outlines/createOutlines.js.map +1 -1
- package/dist/interactions/actions/outlines/createOutlinesAction.js +13 -8
- package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
- package/dist/interactions/actions/outlines/outlineStrokeAction.js +3 -2
- package/dist/interactions/actions/outlines/outlineStrokeAction.js.map +1 -1
- package/dist/interactions/actions/pathCommands/pathNodes.js +1 -1
- package/dist/interactions/actions/testUtils.js +0 -7
- package/dist/interactions/actions/testUtils.js.map +1 -1
- package/dist/interactions/actions/useNotices.js +13 -0
- package/dist/interactions/actions/useNotices.js.map +1 -0
- package/dist/test-seams.d.ts +5 -12
- package/dist/tools/builtin/anchors/anchorsTool.js +31 -0
- package/dist/tools/builtin/anchors/anchorsTool.js.map +1 -0
- package/dist/tools/builtin/polygon/usePolygonTool.js +1 -1
- package/dist/tools/builtin/star/useStarTool.js +1 -1
- package/dist/tools/index.js +4 -2
- package/dist/tools/toolOptions.js +47 -0
- package/dist/tools/toolOptions.js.map +1 -0
- package/dist/version.js +1 -1
- package/package.json +25 -25
- package/dist/interactions/actions/defaults/pathEditTools.js +0 -60
- package/dist/interactions/actions/defaults/pathEditTools.js.map +0 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/core/scene/sceneJournals.ts
|
|
2
|
+
/** A journal drives adapter mutation itself, so its applies and undos
|
|
3
|
+
* re-enter scene mutation methods the way the facade's do. */
|
|
4
|
+
function wrapJournal(raw, env, open) {
|
|
5
|
+
const { replay, notify } = env;
|
|
6
|
+
const wrapped = {
|
|
7
|
+
targetId: raw.targetId,
|
|
8
|
+
forkedAtEntryId: raw.forkedAtEntryId,
|
|
9
|
+
applyBatch(ops, label) {
|
|
10
|
+
env.labeled(label, () => env.runArrivalEdit(ops, (all) => {
|
|
11
|
+
env.applying(() => raw.applyBatch(all.map(env.causing), label));
|
|
12
|
+
notify();
|
|
13
|
+
}));
|
|
14
|
+
},
|
|
15
|
+
undo() {
|
|
16
|
+
replay(() => raw.undo());
|
|
17
|
+
notify();
|
|
18
|
+
},
|
|
19
|
+
redo() {
|
|
20
|
+
replay(() => raw.redo());
|
|
21
|
+
notify();
|
|
22
|
+
},
|
|
23
|
+
canUndo: () => raw.canUndo(),
|
|
24
|
+
canRedo: () => raw.canRedo(),
|
|
25
|
+
entries: () => raw.entries(),
|
|
26
|
+
seal: () => raw.seal(),
|
|
27
|
+
observe: (listener) => raw.observe(listener),
|
|
28
|
+
beginJournal: (opts) => open.wrap(raw.beginJournal(opts)),
|
|
29
|
+
getVersion: () => raw.getVersion(),
|
|
30
|
+
subscribe: (listener) => raw.subscribe(listener),
|
|
31
|
+
commit(label) {
|
|
32
|
+
raw.commit(label);
|
|
33
|
+
open.close(wrapped);
|
|
34
|
+
notify();
|
|
35
|
+
},
|
|
36
|
+
cancel() {
|
|
37
|
+
replay(() => raw.cancel());
|
|
38
|
+
open.close(wrapped);
|
|
39
|
+
notify();
|
|
40
|
+
},
|
|
41
|
+
suspend: () => raw.suspend(),
|
|
42
|
+
isActive: () => raw.isActive()
|
|
43
|
+
};
|
|
44
|
+
return wrapped;
|
|
45
|
+
}
|
|
46
|
+
function createSceneJournals(env) {
|
|
47
|
+
/** Wrapped journal → the engine's own: the engine keys resumability off
|
|
48
|
+
* journal identity, which a wrapper would not match. */
|
|
49
|
+
const raws = /* @__PURE__ */ new WeakMap();
|
|
50
|
+
const opened = [];
|
|
51
|
+
const open = {
|
|
52
|
+
wrap(raw) {
|
|
53
|
+
const wrapped = wrapJournal(raw, env, open);
|
|
54
|
+
raws.set(wrapped, raw);
|
|
55
|
+
opened.push(wrapped);
|
|
56
|
+
return wrapped;
|
|
57
|
+
},
|
|
58
|
+
close(wrapped) {
|
|
59
|
+
const i = opened.indexOf(wrapped);
|
|
60
|
+
if (i >= 0) opened.splice(i, 1);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
begin(opts) {
|
|
65
|
+
const parent = env.activeJournal();
|
|
66
|
+
return open.wrap(parent ? (raws.get(parent) ?? parent).beginJournal(opts) : env.engine.beginJournal(opts));
|
|
67
|
+
},
|
|
68
|
+
resume: (journal) => env.engine.resumeJournal(raws.get(journal) ?? journal),
|
|
69
|
+
innermost() {
|
|
70
|
+
for (let i = opened.length - 1; i >= 0; i--) if (opened[i].isActive()) return opened[i];
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createSceneJournals };
|
|
78
|
+
//# sourceMappingURL=sceneJournals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneJournals.js","names":[],"sources":["../../../src/core/scene/sceneJournals.ts"],"sourcesContent":["/**\n * The journals opened on `scene.history`: each wrapped so its applies and\n * undos re-enter scene mutation the way the facade's do, and tracked while\n * open, so the scene's edits go to the innermost.\n */\nimport type { BeginJournalOptions, Journal } from '@weasel-js/history';\nimport type { SceneHistoryEnv } from './historyFacade';\n\ninterface OpenJournals {\n wrap(raw: Journal): Journal;\n close(wrapped: Journal): void;\n}\n\n/** A journal drives adapter mutation itself, so its applies and undos\n * re-enter scene mutation methods the way the facade's do. */\nfunction wrapJournal(raw: Journal, env: SceneHistoryEnv, open: OpenJournals): Journal {\n const { replay, notify } = env;\n const wrapped: Journal = {\n targetId: raw.targetId,\n forkedAtEntryId: raw.forkedAtEntryId,\n applyBatch(ops, label) {\n env.labeled(label, () => env.runArrivalEdit(ops, (all) => {\n env.applying(() => raw.applyBatch(all.map(env.causing), label));\n notify();\n }));\n },\n undo() {\n replay(() => raw.undo());\n notify();\n },\n redo() {\n replay(() => raw.redo());\n notify();\n },\n canUndo: () => raw.canUndo(),\n canRedo: () => raw.canRedo(),\n entries: () => raw.entries(),\n seal: () => raw.seal(),\n observe: (listener) => raw.observe(listener),\n beginJournal: (opts) => open.wrap(raw.beginJournal(opts)),\n getVersion: () => raw.getVersion(),\n subscribe: (listener) => raw.subscribe(listener),\n commit(label) {\n // Flushes the session's net ops to the parent stack — no apply, but\n // the parent's undo depth changes and listeners read that.\n raw.commit(label);\n open.close(wrapped);\n notify();\n },\n cancel() {\n replay(() => raw.cancel());\n open.close(wrapped);\n notify();\n },\n suspend: () => raw.suspend(),\n isActive: () => raw.isActive(),\n };\n return wrapped;\n}\n\nexport interface SceneJournals {\n /** Open a journal inside the one edits go to now, else on the engine: an\n * edit session started during a mode's lands in the mode's as one entry. */\n begin(opts: BeginJournalOptions): Journal;\n resume(journal: Journal): void;\n /** The innermost active journal opened here, or null. */\n innermost(): Journal | null;\n}\n\nexport function createSceneJournals(env: SceneHistoryEnv): SceneJournals {\n /** Wrapped journal → the engine's own: the engine keys resumability off\n * journal identity, which a wrapper would not match. */\n const raws = new WeakMap<Journal, Journal>();\n const opened: Journal[] = [];\n const open: OpenJournals = {\n wrap(raw) {\n const wrapped = wrapJournal(raw, env, open);\n raws.set(wrapped, raw);\n opened.push(wrapped);\n return wrapped;\n },\n close(wrapped) {\n const i = opened.indexOf(wrapped);\n if (i >= 0) opened.splice(i, 1);\n },\n };\n return {\n begin(opts) {\n const parent = env.activeJournal();\n return open.wrap(parent ? (raws.get(parent) ?? parent).beginJournal(opts) : env.engine.beginJournal(opts));\n },\n resume: (journal) => env.engine.resumeJournal(raws.get(journal) ?? journal),\n innermost() {\n for (let i = opened.length - 1; i >= 0; i--) if (opened[i]!.isActive()) return opened[i]!;\n return null;\n },\n };\n}\n"],"mappings":";;;AAeA,SAAS,YAAY,KAAc,KAAsB,MAA6B;CACpF,MAAM,EAAE,QAAQ,WAAW;CAC3B,MAAM,UAAmB;EACvB,UAAU,IAAI;EACd,iBAAiB,IAAI;EACrB,WAAW,KAAK,OAAO;GACrB,IAAI,QAAQ,aAAa,IAAI,eAAe,MAAM,QAAQ;IACxD,IAAI,eAAe,IAAI,WAAW,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,CAAC;IAC9D,OAAO;GACT,CAAC,CAAC;EACJ;EACA,OAAO;GACL,aAAa,IAAI,KAAK,CAAC;GACvB,OAAO;EACT;EACA,OAAO;GACL,aAAa,IAAI,KAAK,CAAC;GACvB,OAAO;EACT;EACA,eAAe,IAAI,QAAQ;EAC3B,eAAe,IAAI,QAAQ;EAC3B,eAAe,IAAI,QAAQ;EAC3B,YAAY,IAAI,KAAK;EACrB,UAAU,aAAa,IAAI,QAAQ,QAAQ;EAC3C,eAAe,SAAS,KAAK,KAAK,IAAI,aAAa,IAAI,CAAC;EACxD,kBAAkB,IAAI,WAAW;EACjC,YAAY,aAAa,IAAI,UAAU,QAAQ;EAC/C,OAAO,OAAO;GAGZ,IAAI,OAAO,KAAK;GAChB,KAAK,MAAM,OAAO;GAClB,OAAO;EACT;EACA,SAAS;GACP,aAAa,IAAI,OAAO,CAAC;GACzB,KAAK,MAAM,OAAO;GAClB,OAAO;EACT;EACA,eAAe,IAAI,QAAQ;EAC3B,gBAAgB,IAAI,SAAS;CAC/B;CACA,OAAO;AACT;AAWA,SAAgB,oBAAoB,KAAqC;;;CAGvE,MAAM,uBAAO,IAAI,QAA0B;CAC3C,MAAM,SAAoB,CAAC;CAC3B,MAAM,OAAqB;EACzB,KAAK,KAAK;GACR,MAAM,UAAU,YAAY,KAAK,KAAK,IAAI;GAC1C,KAAK,IAAI,SAAS,GAAG;GACrB,OAAO,KAAK,OAAO;GACnB,OAAO;EACT;EACA,MAAM,SAAS;GACb,MAAM,IAAI,OAAO,QAAQ,OAAO;GAChC,IAAI,KAAK,GAAG,OAAO,OAAO,GAAG,CAAC;EAChC;CACF;CACA,OAAO;EACL,MAAM,MAAM;GACV,MAAM,SAAS,IAAI,cAAc;GACjC,OAAO,KAAK,KAAK,UAAU,KAAK,IAAI,MAAM,KAAK,OAAA,CAAQ,aAAa,IAAI,IAAI,IAAI,OAAO,aAAa,IAAI,CAAC;EAC3G;EACA,SAAS,YAAY,IAAI,OAAO,cAAc,KAAK,IAAI,OAAO,KAAK,OAAO;EAC1E,YAAY;GACV,KAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK,IAAI,OAAO,EAAE,CAAE,SAAS,GAAG,OAAO,OAAO;GACtF,OAAO;EACT;CACF;AACF"}
|
|
@@ -39,7 +39,8 @@ function createSceneRecording(env) {
|
|
|
39
39
|
own: (name, args) => ops.handlers.has(name) ? ops.op(name, args) : null,
|
|
40
40
|
recorder: () => recorder,
|
|
41
41
|
driving: (step) => notifier.deferred(() => mode.during("engine", () => atomically(step, () => true))),
|
|
42
|
-
getActiveJournal: config.getActiveJournal ?? null
|
|
42
|
+
getActiveJournal: config.getActiveJournal ?? null,
|
|
43
|
+
openJournal: () => openJournal()
|
|
43
44
|
});
|
|
44
45
|
const engine = createHistory(void 0, {
|
|
45
46
|
selection: {
|
|
@@ -81,7 +82,7 @@ function createSceneRecording(env) {
|
|
|
81
82
|
function refuseUntracked(verb) {
|
|
82
83
|
if (mode.untracked()) throw new Error(`Scene: cannot ${verb} inside untracked() — it records`);
|
|
83
84
|
}
|
|
84
|
-
const { history, methods } = createSceneHistory({
|
|
85
|
+
const { history, methods, openJournal } = createSceneHistory({
|
|
85
86
|
engine,
|
|
86
87
|
applying: (step) => atomically(() => engineDriven(step), () => true),
|
|
87
88
|
causing: outer.causing,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneRecording.js","names":[],"sources":["../../../src/core/scene/sceneRecording.ts"],"sourcesContent":["/**\n * How a scene's edits reach its undo stack: the op table, the log that takes\n * back an edit refused partway, the engine, and the recorder between them.\n */\nimport type { Journal } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport { createHistory } from 'core/ops/createHistory';\nimport { createAppliedLog } from './appliedLog';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport type { EditMode } from './editMode';\nimport { createEditPasses } from './editPass';\nimport { createSceneHistory, type SceneHistoryMethods } from './historyFacade';\nimport type { Notifier } from './notifier';\nimport type { PoseWriteLog } from './poseWrites';\nimport { createOpTable, type OpTable } from './opTable';\nimport { createOuterOps } from './outerOps';\nimport { createRecorder, type Recorder, type RecorderEnv } from './recorder';\nimport type { SceneSelection } from './sceneSelection';\nimport type { StaleReplay } from './staleReplay';\nimport type { NodeId, RegisteredOp, Scene } from './types';\n\nexport interface SceneRecordingEnv {\n notifier: Notifier;\n mode: EditMode;\n stale: StaleReplay;\n /** Run with the lock guard lifted. */\n lifted<T>(fn: () => T): T;\n selection: Pick<SceneSelection, 'get' | 'set' | 'replace'>;\n arrivals: RecorderEnv['arrivals'] & Pick<ArrivalWindow<unknown>, 'runEdit'>;\n /** The adapter an edit pass's ops apply through. */\n passAdapter(): unknown;\n writePose(id: string, pose: unknown): void;\n poseWrites: Pick<PoseWriteLog<unknown>, 'labeled' | 'replaying'>;\n config: {\n getActiveJournal?: (() => Journal | null) | undefined;\n coalesceWindowMs?: number | undefined;\n historyLimit?: number | undefined;\n };\n}\n\nexport interface SceneRecording {\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Recorder;\n clearHistory(): void;\n methods: SceneHistoryMethods & Pick<\n Scene<unknown, string, unknown>,\n | 'registerOp' | 'recordOp' | 'applyBatch' | 'history' | 'batch' | 'untracked'\n | 'setActiveJournalAccessor' | 'setHistoryAdapter' | 'addEditPass'\n >;\n}\n\nexport function createSceneRecording(env: SceneRecordingEnv): SceneRecording {\n const { notifier, mode, stale, lifted, selection, arrivals, config, poseWrites } = env;\n const { notify } = notifier;\n const passes = createEditPasses({ adapter: env.passAdapter, untracked: mode.untracked });\n\n /** Run an op-driven edit with its arrivals settled and its passes run at\n * its end, in the same entry, when this call owns them. */\n const runEdit = (ops: Op[], run: (ops: Op[]) => void): boolean => arrivals.runEdit(ops, (all) => {\n if (!passes.open()) return run(all);\n try {\n run([...all, passes.op()]);\n } finally {\n passes.abandon();\n }\n });\n\n const ops = createOpTable({\n driven: mode.open,\n replayAlone: (step) => { replay(step); notify(); },\n skips: stale.skips,\n // Only a replay is logged: a first apply is recorded by the entry it\n // creates, and reverting it would leave that entry describing nothing.\n applied: (kind, payload, reverted) => { if (!mode.records()) logApplied(kind, payload, reverted); },\n });\n\n const outer = createOuterOps({\n own: (name, args) => (ops.handlers.has(name) ? ops.op(name, args) : null),\n recorder: () => recorder,\n // Deferred rather than muted, so a throwing op cannot strand subscribers\n // after earlier ops in the batch already mutated state.\n driving: (step) => notifier.deferred(() => mode.during('engine', () => atomically(step, () => true))),\n getActiveJournal: config.getActiveJournal ?? null,\n });\n\n // The scene's undo/redo engine. Its recorded ops close over their payloads,\n // so the engine's adapter argument is unused.\n const engine = createHistory(undefined, {\n selection: { get: selection.get, set: (ids) => selection.set(ids as readonly NodeId[]) },\n ...(config.coalesceWindowMs !== undefined ? { coalesceWindowMs: config.coalesceWindowMs } : {}),\n ...(config.historyLimit !== undefined ? { historyLimit: config.historyLimit } : {}),\n rebuildOp: outer.rebuild,\n });\n\n const log = createAppliedLog({\n registered: ops.handlers,\n lifted,\n getSelection: selection.get,\n setSelection: selection.replace,\n notify,\n });\n const { atomically, logApplied } = log;\n\n const recorder = createRecorder({\n notifier,\n mode,\n engine,\n runOp: ops.run,\n makeOp: ops.op,\n log,\n arrivals,\n passes,\n selection: selection.get,\n writePose: env.writePose,\n labeled: poseWrites.labeled,\n });\n const { record, engineDriven } = recorder;\n\n /** Run an engine step that replays recorded ops — undo, redo, goto, a\n * journal stepping. The engine inverts whole ops when one throws; this\n * also reverts the scene writes of the op that threw partway. */\n function replay(step: () => void): void {\n poseWrites.replaying(() => atomically(() => lifted(() => stale.during(() => engineDriven(step))), () => true));\n }\n\n function refuseUntracked(verb: string): void {\n if (mode.untracked()) throw new Error(`Scene: cannot ${verb} inside untracked() — it records`);\n }\n\n const { history, methods } = createSceneHistory({\n engine,\n applying: (step) => atomically(() => engineDriven(step), () => true),\n causing: outer.causing,\n replay,\n notify,\n refuseUntracked,\n runArrivalEdit: runEdit,\n labeled: (label, fn) => poseWrites.labeled(label, 'ops', fn),\n activeJournal: outer.activeJournal,\n });\n\n return {\n ops,\n recorder,\n clearHistory: () => engine.clear(),\n methods: {\n registerOp(kind, handler) {\n if (kind.startsWith('kit:')) {\n throw new Error(`Scene: op kind \"${kind}\" is reserved (kit:* prefix)`);\n }\n ops.register(kind, handler as RegisteredOp<unknown>);\n },\n recordOp(op) {\n record(op.kind, op.payload, op.kind);\n },\n applyBatch(batch, label, adapter) {\n refuseUntracked('applyBatch');\n return poseWrites.labeled(label, 'ops', () =>\n runEdit(batch, (all) => outer.apply(engine, all, label, adapter)));\n },\n history,\n ...methods,\n batch: recorder.batch,\n untracked: recorder.untracked,\n setActiveJournalAccessor: outer.setActiveJournalAccessor,\n setHistoryAdapter: outer.setHistoryAdapter,\n addEditPass: passes.add,\n },\n };\n}\n"],"mappings":";;;;;;;;;AAmDA,SAAgB,qBAAqB,KAAwC;CAC3E,MAAM,EAAE,UAAU,MAAM,OAAO,QAAQ,WAAW,UAAU,QAAQ,eAAe;CACnF,MAAM,EAAE,WAAW;CACnB,MAAM,SAAS,iBAAiB;EAAE,SAAS,IAAI;EAAa,WAAW,KAAK;CAAU,CAAC;;;CAIvF,MAAM,WAAW,KAAW,QAAsC,SAAS,QAAQ,MAAM,QAAQ;EAC/F,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO,IAAI,GAAG;EAClC,IAAI;GACF,IAAI,CAAC,GAAG,KAAK,OAAO,GAAG,CAAC,CAAC;EAC3B,UAAU;GACR,OAAO,QAAQ;EACjB;CACF,CAAC;CAED,MAAM,MAAM,cAAc;EACxB,QAAQ,KAAK;EACb,cAAc,SAAS;GAAE,OAAO,IAAI;GAAG,OAAO;EAAG;EACjD,OAAO,MAAM;EAGb,UAAU,MAAM,SAAS,aAAa;GAAE,IAAI,CAAC,KAAK,QAAQ,GAAG,WAAW,MAAM,SAAS,QAAQ;EAAG;CACpG,CAAC;CAED,MAAM,QAAQ,eAAe;EAC3B,MAAM,MAAM,SAAU,IAAI,SAAS,IAAI,IAAI,IAAI,IAAI,GAAG,MAAM,IAAI,IAAI;EACpE,gBAAgB;EAGhB,UAAU,SAAS,SAAS,eAAe,KAAK,OAAO,gBAAgB,WAAW,YAAY,IAAI,CAAC,CAAC;EACpG,kBAAkB,OAAO,oBAAoB;
|
|
1
|
+
{"version":3,"file":"sceneRecording.js","names":[],"sources":["../../../src/core/scene/sceneRecording.ts"],"sourcesContent":["/**\n * How a scene's edits reach its undo stack: the op table, the log that takes\n * back an edit refused partway, the engine, and the recorder between them.\n */\nimport type { Journal } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport { createHistory } from 'core/ops/createHistory';\nimport { createAppliedLog } from './appliedLog';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport type { EditMode } from './editMode';\nimport { createEditPasses } from './editPass';\nimport { createSceneHistory, type SceneHistoryMethods } from './historyFacade';\nimport type { Notifier } from './notifier';\nimport type { PoseWriteLog } from './poseWrites';\nimport { createOpTable, type OpTable } from './opTable';\nimport { createOuterOps } from './outerOps';\nimport { createRecorder, type Recorder, type RecorderEnv } from './recorder';\nimport type { SceneSelection } from './sceneSelection';\nimport type { StaleReplay } from './staleReplay';\nimport type { NodeId, RegisteredOp, Scene } from './types';\n\nexport interface SceneRecordingEnv {\n notifier: Notifier;\n mode: EditMode;\n stale: StaleReplay;\n /** Run with the lock guard lifted. */\n lifted<T>(fn: () => T): T;\n selection: Pick<SceneSelection, 'get' | 'set' | 'replace'>;\n arrivals: RecorderEnv['arrivals'] & Pick<ArrivalWindow<unknown>, 'runEdit'>;\n /** The adapter an edit pass's ops apply through. */\n passAdapter(): unknown;\n writePose(id: string, pose: unknown): void;\n poseWrites: Pick<PoseWriteLog<unknown>, 'labeled' | 'replaying'>;\n config: {\n getActiveJournal?: (() => Journal | null) | undefined;\n coalesceWindowMs?: number | undefined;\n historyLimit?: number | undefined;\n };\n}\n\nexport interface SceneRecording {\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Recorder;\n clearHistory(): void;\n methods: SceneHistoryMethods & Pick<\n Scene<unknown, string, unknown>,\n | 'registerOp' | 'recordOp' | 'applyBatch' | 'history' | 'batch' | 'untracked'\n | 'setActiveJournalAccessor' | 'setHistoryAdapter' | 'addEditPass'\n >;\n}\n\nexport function createSceneRecording(env: SceneRecordingEnv): SceneRecording {\n const { notifier, mode, stale, lifted, selection, arrivals, config, poseWrites } = env;\n const { notify } = notifier;\n const passes = createEditPasses({ adapter: env.passAdapter, untracked: mode.untracked });\n\n /** Run an op-driven edit with its arrivals settled and its passes run at\n * its end, in the same entry, when this call owns them. */\n const runEdit = (ops: Op[], run: (ops: Op[]) => void): boolean => arrivals.runEdit(ops, (all) => {\n if (!passes.open()) return run(all);\n try {\n run([...all, passes.op()]);\n } finally {\n passes.abandon();\n }\n });\n\n const ops = createOpTable({\n driven: mode.open,\n replayAlone: (step) => { replay(step); notify(); },\n skips: stale.skips,\n // Only a replay is logged: a first apply is recorded by the entry it\n // creates, and reverting it would leave that entry describing nothing.\n applied: (kind, payload, reverted) => { if (!mode.records()) logApplied(kind, payload, reverted); },\n });\n\n const outer = createOuterOps({\n own: (name, args) => (ops.handlers.has(name) ? ops.op(name, args) : null),\n recorder: () => recorder,\n // Deferred rather than muted, so a throwing op cannot strand subscribers\n // after earlier ops in the batch already mutated state.\n driving: (step) => notifier.deferred(() => mode.during('engine', () => atomically(step, () => true))),\n getActiveJournal: config.getActiveJournal ?? null, openJournal: () => openJournal(),\n });\n\n // The scene's undo/redo engine. Its recorded ops close over their payloads,\n // so the engine's adapter argument is unused.\n const engine = createHistory(undefined, {\n selection: { get: selection.get, set: (ids) => selection.set(ids as readonly NodeId[]) },\n ...(config.coalesceWindowMs !== undefined ? { coalesceWindowMs: config.coalesceWindowMs } : {}),\n ...(config.historyLimit !== undefined ? { historyLimit: config.historyLimit } : {}),\n rebuildOp: outer.rebuild,\n });\n\n const log = createAppliedLog({\n registered: ops.handlers,\n lifted,\n getSelection: selection.get,\n setSelection: selection.replace,\n notify,\n });\n const { atomically, logApplied } = log;\n\n const recorder = createRecorder({\n notifier,\n mode,\n engine,\n runOp: ops.run,\n makeOp: ops.op,\n log,\n arrivals,\n passes,\n selection: selection.get,\n writePose: env.writePose,\n labeled: poseWrites.labeled,\n });\n const { record, engineDriven } = recorder;\n\n /** Run an engine step that replays recorded ops — undo, redo, goto, a\n * journal stepping. The engine inverts whole ops when one throws; this\n * also reverts the scene writes of the op that threw partway. */\n function replay(step: () => void): void {\n poseWrites.replaying(() => atomically(() => lifted(() => stale.during(() => engineDriven(step))), () => true));\n }\n\n function refuseUntracked(verb: string): void {\n if (mode.untracked()) throw new Error(`Scene: cannot ${verb} inside untracked() — it records`);\n }\n\n const { history, methods, openJournal } = createSceneHistory({\n engine,\n applying: (step) => atomically(() => engineDriven(step), () => true),\n causing: outer.causing,\n replay,\n notify,\n refuseUntracked,\n runArrivalEdit: runEdit,\n labeled: (label, fn) => poseWrites.labeled(label, 'ops', fn),\n activeJournal: outer.activeJournal,\n });\n\n return {\n ops,\n recorder,\n clearHistory: () => engine.clear(),\n methods: {\n registerOp(kind, handler) {\n if (kind.startsWith('kit:')) {\n throw new Error(`Scene: op kind \"${kind}\" is reserved (kit:* prefix)`);\n }\n ops.register(kind, handler as RegisteredOp<unknown>);\n },\n recordOp(op) {\n record(op.kind, op.payload, op.kind);\n },\n applyBatch(batch, label, adapter) {\n refuseUntracked('applyBatch');\n return poseWrites.labeled(label, 'ops', () =>\n runEdit(batch, (all) => outer.apply(engine, all, label, adapter)));\n },\n history,\n ...methods,\n batch: recorder.batch,\n untracked: recorder.untracked,\n setActiveJournalAccessor: outer.setActiveJournalAccessor,\n setHistoryAdapter: outer.setHistoryAdapter,\n addEditPass: passes.add,\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
* each present only when stated, so an unstated one keeps the scene's
|
|
4
4
|
* default, and a flag states only `true`. What a copy of a node carries over
|
|
5
5
|
* besides its kind, layer, pose, data and functions. */
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
function statedFields(n) {
|
|
7
|
+
const out = {};
|
|
8
|
+
if (n.pickable !== void 0) out.pickable = n.pickable;
|
|
9
|
+
if (n.hidden === true) out.hidden = true;
|
|
10
|
+
if (n.locked === true) out.locked = true;
|
|
11
|
+
if (n.units !== void 0) out.units = n.units;
|
|
12
|
+
if (n.clip !== void 0) out.clip = n.clip;
|
|
13
|
+
if (n.consumesChildren === true) out.consumesChildren = true;
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
//#endregion
|
|
16
18
|
export { statedFields };
|
|
@@ -1 +1 @@
|
|
|
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 consumesChildren?: boolean;\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
|
|
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 consumesChildren?: boolean;\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 function statedFields(n: StatedNodeFields & { kind?: string }): StatedNodeFields {\n const out: StatedNodeFields = {};\n if (n.pickable !== undefined) out.pickable = n.pickable;\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 if (n.consumesChildren === true) out.consumesChildren = true;\n return out;\n}\n"],"mappings":";;;;;AAiBA,SAAgB,aAAa,GAA2D;CACtF,MAAM,MAAwB,CAAC;CAC/B,IAAI,EAAE,aAAa,QAAW,IAAI,WAAW,EAAE;CAC/C,IAAI,EAAE,WAAW,MAAM,IAAI,SAAS;CACpC,IAAI,EAAE,WAAW,MAAM,IAAI,SAAS;CACpC,IAAI,EAAE,UAAU,QAAW,IAAI,QAAQ,EAAE;CACzC,IAAI,EAAE,SAAS,QAAW,IAAI,OAAO,EAAE;CACvC,IAAI,EAAE,qBAAqB,MAAM,IAAI,mBAAmB;CACxD,OAAO;AACT"}
|
|
@@ -53,22 +53,17 @@ function createTreeEdits(env) {
|
|
|
53
53
|
assertSubtreeLayer(spec.id, spec.layer, parent, p.layer);
|
|
54
54
|
refuse(parent, "add under");
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
const payload = {
|
|
57
57
|
id,
|
|
58
58
|
kind: spec.kind,
|
|
59
59
|
layer: spec.layer,
|
|
60
60
|
pose: spec.pose,
|
|
61
61
|
data: spec.data,
|
|
62
62
|
parent,
|
|
63
|
-
index: spec.index ?? siblingsOf(parent).length
|
|
64
|
-
...spec.dependsOn !== void 0 ? { dependsOn: spec.dependsOn } : {},
|
|
65
|
-
...statedFields(spec),
|
|
66
|
-
...fnKeysOf(fnFields, {
|
|
67
|
-
...spec,
|
|
68
|
-
id,
|
|
69
|
-
kind: spec.kind
|
|
70
|
-
}, "opKey")
|
|
63
|
+
index: spec.index ?? siblingsOf(parent).length
|
|
71
64
|
};
|
|
65
|
+
if (spec.dependsOn !== void 0) payload.dependsOn = spec.dependsOn;
|
|
66
|
+
return Object.assign(payload, statedFields(spec), fnKeysOf(fnFields, spec, "opKey"));
|
|
72
67
|
}
|
|
73
68
|
function removeMany(rootIds) {
|
|
74
69
|
if (rootIds.length === 0) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"treeEdits.js","names":[],"sources":["../../../src/core/scene/treeEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its tree: adding and removing nodes, reparenting, and\n * reordering siblings. Each checks its arguments, then records one kit op.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport type { RemoveSnapshot } from './kitOps';\nimport type { createLayerStack } from './layerStack';\nimport type { LockGuard } from './lockGuard';\nimport { fnKeysOf, type FnBearingNode, type NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { Recorder } from './recorder';\nimport { SceneMembershipError } from './staleReplay';\nimport { statedFields } from './statedFields';\nimport type { AddNodeSpec, Node, NodeId, Scene } from './types';\n\nexport interface TreeEditsEnv<TData, TLayer extends string, TPose> {\n nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>;\n generateId(): NodeId;\n store: Pick<\n ReturnType<typeof createNodeStore<TData, TLayer, TPose>>,\n 'requireNode' | 'siblingsOf' | 'descendants' | 'removalClosure' | 'patchNodeFunctions'\n >;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<ReturnType<typeof createLayerStack<TLayer>>, 'requireLayerIndex' | 'assertSubtreeLayer'>;\n fnFields: readonly NodeFnField[];\n /** Run a registered op without recording it. */\n runOp(kind: string, payload: unknown): void;\n recorder: Pick<Recorder, 'record' | 'edit'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange'>;\n}\n\nexport interface TreeEdits<TData, TLayer extends string, TPose>\n extends Pick<Scene<TData, TLayer, TPose>, 'add' | 'remove' | 'removalClosure' | 'removeMany' | 'move' | 'reorder'> {\n /** Place nodes as the document a scene starts from, or is loaded with:\n * nothing is recorded, and no lock refuses. Specs list parents before\n * children, the order `toJSON()` emits. */\n construct(specs: readonly AddNodeSpec<TData, TLayer, TPose>[]): void;\n}\n\n/** What `kit:remove` carries for the nodes `ids`, read before any of them\n * goes. Throws on an id that is not in the scene. */\nfunction removeSnapshot<TData, TLayer extends string, TPose>(\n ids: readonly NodeId[],\n removing: ReadonlySet<NodeId>,\n store: Pick<TreeEditsEnv<TData, TLayer, TPose>['store'], 'requireNode' | 'siblingsOf'>,\n fnFields: readonly NodeFnField[],\n): RemoveSnapshot<TData, TLayer, TPose> {\n const nodes: Node<TData, TLayer, TPose>[] = ids.map((nid) => {\n const n = store.requireNode(nid);\n return n.children ? { ...n, children: [...n.children] } : { ...n };\n });\n // The rest is reachable through a snapshotted parent's cloned `children`.\n const detached = nodes\n .filter((n) => n.parent === null || !removing.has(n.parent))\n .map((n) => ({ id: n.id, parent: n.parent, index: store.siblingsOf(n.parent).indexOf(n.id) }))\n .sort((l, r) => l.index - r.index);\n const fnKeys = nodes.flatMap((n) => {\n const keys = fnKeysOf(fnFields, n as unknown as FnBearingNode, 'opKey');\n return Object.keys(keys).length === 0 ? [] : [{ id: n.id, ...keys }];\n });\n return { nodes, detached, ...(fnKeys.length > 0 ? { fnKeys } : {}) };\n}\n\nexport function createTreeEdits<TData, TLayer extends string, TPose>(\n env: TreeEditsEnv<TData, TLayer, TPose>,\n): TreeEdits<TData, TLayer, TPose> {\n const { nodes, fnFields } = env;\n const { requireNode, siblingsOf, descendants, removalClosure, patchNodeFunctions } = env.store;\n const { refuse, refuseLayer } = env.locks;\n const { requireLayerIndex, assertSubtreeLayer } = env.layers;\n const { record, edit } = env.recorder;\n const { noteArrival, noteDeparture, noteChange } = env.arrivals;\n\n /** Check that `spec` can be placed, and answer with the `kit:add` payload\n * that places it. Throws on an id collision, an unknown or locked layer,\n * a locked parent, and a layer below the\n * parent's. */\n function addPayload(spec: AddNodeSpec<TData, TLayer, TPose>) {\n const id = spec.id ?? env.generateId();\n if (nodes.has(id)) throw new SceneMembershipError(`Scene: id collision on \"${id}\"`);\n requireLayerIndex(spec.layer);\n refuseLayer(spec.layer, 'add to');\n const parent = spec.parent ?? null;\n if (parent !== null) {\n const p = requireNode(parent);\n assertSubtreeLayer(spec.id, spec.layer, parent, p.layer);\n refuse(parent, 'add under');\n }\n return {\n id, kind: spec.kind, layer: spec.layer, pose: spec.pose, data: spec.data,\n parent, index: spec.index ?? siblingsOf(parent).length,\n ...(spec.dependsOn !== undefined ? { dependsOn: spec.dependsOn } : {}),\n ...statedFields(spec),\n ...fnKeysOf(fnFields, { ...spec, id, kind: spec.kind } as unknown as FnBearingNode, 'opKey'),\n };\n }\n\n function removeMany(rootIds: readonly NodeId[]): void {\n if (rootIds.length === 0) return;\n // Every id resolves against the pre-removal tree, so an id another one\n // cascades away is absorbed rather than being a second, failing removal.\n const { ids, removing } = removalClosure(rootIds);\n for (const nid of ids) refuse(nid, 'remove');\n const payload = removeSnapshot(ids, removing, env.store, fnFields);\n const { detached } = payload;\n edit('remove', detached.some((d) => d.parent !== null), () => {\n for (const d of detached) noteDeparture(d.id, d.parent, null);\n record('kit:remove', payload, 'remove');\n });\n }\n\n return {\n construct(specs) {\n env.locks.lifted(() => {\n for (const spec of specs) {\n const payload = addPayload(spec);\n env.runOp('kit:add', payload);\n patchNodeFunctions(spec, payload.id, null);\n }\n });\n },\n\n add(spec) {\n const payload = addPayload(spec);\n const label = `add ${spec.kind}`;\n return edit(label, payload.parent !== null, () => {\n noteArrival(payload.id, payload.parent, null);\n record('kit:add', payload, label);\n patchNodeFunctions(spec, payload.id, payload);\n return payload.id;\n });\n },\n\n remove: (id) => removeMany([id]),\n\n removalClosure: (rootIds) => removalClosure(rootIds).ids,\n\n removeMany,\n\n move(id, parent, index) {\n const node = requireNode(id);\n refuse(id, 'move');\n if (parent !== null) {\n refuse(parent, 'move nodes into');\n const p = requireNode(parent);\n assertSubtreeLayer(id, node.layer, parent, p.layer);\n if (parent === id) throw new Error('Scene: cannot parent a node to itself');\n const desc: NodeId[] = [];\n descendants(id, desc);\n if (desc.includes(parent)) {\n throw new Error('Scene: cannot parent a node to its own descendant');\n }\n }\n const fromParent = node.parent;\n edit('move', parent !== null || fromParent !== null, () => {\n const fromIndex = siblingsOf(fromParent).indexOf(id);\n const toIndex = index ?? siblingsOf(parent).length;\n noteArrival(id, parent, fromParent);\n noteDeparture(id, fromParent, parent);\n noteChange(fromParent);\n record('kit:move', { id, fromParent, fromIndex, toParent: parent, toIndex }, 'move');\n });\n },\n\n reorder(id, index) {\n const node = requireNode(id);\n refuse(id, 'reorder');\n const parent = node.parent;\n edit('reorder', parent !== null, () => {\n noteChange(parent);\n const fromIndex = siblingsOf(parent).indexOf(id);\n record('kit:move', { id, fromParent: parent, fromIndex, toParent: parent, toIndex: index }, 'reorder');\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"treeEdits.js","names":[],"sources":["../../../src/core/scene/treeEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its tree: adding and removing nodes, reparenting, and\n * reordering siblings. Each checks its arguments, then records one kit op.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport type { RemoveSnapshot } from './kitOps';\nimport type { createLayerStack } from './layerStack';\nimport type { LockGuard } from './lockGuard';\nimport { fnKeysOf, type FnBearingNode, type NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { Recorder } from './recorder';\nimport { SceneMembershipError } from './staleReplay';\nimport { statedFields } from './statedFields';\nimport type { AddNodeSpec, Node, NodeId, Scene } from './types';\n\nexport interface TreeEditsEnv<TData, TLayer extends string, TPose> {\n nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>;\n generateId(): NodeId;\n store: Pick<\n ReturnType<typeof createNodeStore<TData, TLayer, TPose>>,\n 'requireNode' | 'siblingsOf' | 'descendants' | 'removalClosure' | 'patchNodeFunctions'\n >;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<ReturnType<typeof createLayerStack<TLayer>>, 'requireLayerIndex' | 'assertSubtreeLayer'>;\n fnFields: readonly NodeFnField[];\n /** Run a registered op without recording it. */\n runOp(kind: string, payload: unknown): void;\n recorder: Pick<Recorder, 'record' | 'edit'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange'>;\n}\n\nexport interface TreeEdits<TData, TLayer extends string, TPose>\n extends Pick<Scene<TData, TLayer, TPose>, 'add' | 'remove' | 'removalClosure' | 'removeMany' | 'move' | 'reorder'> {\n /** Place nodes as the document a scene starts from, or is loaded with:\n * nothing is recorded, and no lock refuses. Specs list parents before\n * children, the order `toJSON()` emits. */\n construct(specs: readonly AddNodeSpec<TData, TLayer, TPose>[]): void;\n}\n\n/** What `kit:remove` carries for the nodes `ids`, read before any of them\n * goes. Throws on an id that is not in the scene. */\nfunction removeSnapshot<TData, TLayer extends string, TPose>(\n ids: readonly NodeId[],\n removing: ReadonlySet<NodeId>,\n store: Pick<TreeEditsEnv<TData, TLayer, TPose>['store'], 'requireNode' | 'siblingsOf'>,\n fnFields: readonly NodeFnField[],\n): RemoveSnapshot<TData, TLayer, TPose> {\n const nodes: Node<TData, TLayer, TPose>[] = ids.map((nid) => {\n const n = store.requireNode(nid);\n return n.children ? { ...n, children: [...n.children] } : { ...n };\n });\n // The rest is reachable through a snapshotted parent's cloned `children`.\n const detached = nodes\n .filter((n) => n.parent === null || !removing.has(n.parent))\n .map((n) => ({ id: n.id, parent: n.parent, index: store.siblingsOf(n.parent).indexOf(n.id) }))\n .sort((l, r) => l.index - r.index);\n const fnKeys = nodes.flatMap((n) => {\n const keys = fnKeysOf(fnFields, n as unknown as FnBearingNode, 'opKey');\n return Object.keys(keys).length === 0 ? [] : [{ id: n.id, ...keys }];\n });\n return { nodes, detached, ...(fnKeys.length > 0 ? { fnKeys } : {}) };\n}\n\nexport function createTreeEdits<TData, TLayer extends string, TPose>(\n env: TreeEditsEnv<TData, TLayer, TPose>,\n): TreeEdits<TData, TLayer, TPose> {\n const { nodes, fnFields } = env;\n const { requireNode, siblingsOf, descendants, removalClosure, patchNodeFunctions } = env.store;\n const { refuse, refuseLayer } = env.locks;\n const { requireLayerIndex, assertSubtreeLayer } = env.layers;\n const { record, edit } = env.recorder;\n const { noteArrival, noteDeparture, noteChange } = env.arrivals;\n\n /** Check that `spec` can be placed, and answer with the `kit:add` payload\n * that places it. Throws on an id collision, an unknown or locked layer,\n * a locked parent, and a layer below the\n * parent's. */\n function addPayload(spec: AddNodeSpec<TData, TLayer, TPose>) {\n const id = spec.id ?? env.generateId();\n if (nodes.has(id)) throw new SceneMembershipError(`Scene: id collision on \"${id}\"`);\n requireLayerIndex(spec.layer);\n refuseLayer(spec.layer, 'add to');\n const parent = spec.parent ?? null;\n if (parent !== null) {\n const p = requireNode(parent);\n assertSubtreeLayer(spec.id, spec.layer, parent, p.layer);\n refuse(parent, 'add under');\n }\n const payload: {\n id: NodeId; kind: 'mark' | 'group'; layer: TLayer; pose: TPose; data: TData;\n parent: NodeId | null; index: number; dependsOn?: AddNodeSpec<TData, TLayer, TPose>['dependsOn'];\n } = {\n id, kind: spec.kind, layer: spec.layer, pose: spec.pose, data: spec.data,\n parent, index: spec.index ?? siblingsOf(parent).length,\n };\n if (spec.dependsOn !== undefined) payload.dependsOn = spec.dependsOn;\n // Assigned rather than spread into the literal: a spread after named fields is V8's slow clone.\n return Object.assign(payload, statedFields(spec), fnKeysOf(fnFields, spec as unknown as FnBearingNode, 'opKey'));\n }\n\n function removeMany(rootIds: readonly NodeId[]): void {\n if (rootIds.length === 0) return;\n // Every id resolves against the pre-removal tree, so an id another one\n // cascades away is absorbed rather than being a second, failing removal.\n const { ids, removing } = removalClosure(rootIds);\n for (const nid of ids) refuse(nid, 'remove');\n const payload = removeSnapshot(ids, removing, env.store, fnFields);\n const { detached } = payload;\n edit('remove', detached.some((d) => d.parent !== null), () => {\n for (const d of detached) noteDeparture(d.id, d.parent, null);\n record('kit:remove', payload, 'remove');\n });\n }\n\n return {\n construct(specs) {\n env.locks.lifted(() => {\n for (const spec of specs) {\n const payload = addPayload(spec);\n env.runOp('kit:add', payload);\n patchNodeFunctions(spec, payload.id, null);\n }\n });\n },\n\n add(spec) {\n const payload = addPayload(spec);\n const label = `add ${spec.kind}`;\n return edit(label, payload.parent !== null, () => {\n noteArrival(payload.id, payload.parent, null);\n record('kit:add', payload, label);\n patchNodeFunctions(spec, payload.id, payload);\n return payload.id;\n });\n },\n\n remove: (id) => removeMany([id]),\n\n removalClosure: (rootIds) => removalClosure(rootIds).ids,\n\n removeMany,\n\n move(id, parent, index) {\n const node = requireNode(id);\n refuse(id, 'move');\n if (parent !== null) {\n refuse(parent, 'move nodes into');\n const p = requireNode(parent);\n assertSubtreeLayer(id, node.layer, parent, p.layer);\n if (parent === id) throw new Error('Scene: cannot parent a node to itself');\n const desc: NodeId[] = [];\n descendants(id, desc);\n if (desc.includes(parent)) {\n throw new Error('Scene: cannot parent a node to its own descendant');\n }\n }\n const fromParent = node.parent;\n edit('move', parent !== null || fromParent !== null, () => {\n const fromIndex = siblingsOf(fromParent).indexOf(id);\n const toIndex = index ?? siblingsOf(parent).length;\n noteArrival(id, parent, fromParent);\n noteDeparture(id, fromParent, parent);\n noteChange(fromParent);\n record('kit:move', { id, fromParent, fromIndex, toParent: parent, toIndex }, 'move');\n });\n },\n\n reorder(id, index) {\n const node = requireNode(id);\n refuse(id, 'reorder');\n const parent = node.parent;\n edit('reorder', parent !== null, () => {\n noteChange(parent);\n const fromIndex = siblingsOf(parent).indexOf(id);\n record('kit:move', { id, fromParent: parent, fromIndex, toParent: parent, toIndex: index }, 'reorder');\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useDeviceProfile } from "../device/useDeviceProfile.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/core/viewport/useCanvasSize.ts
|
|
5
5
|
/** Track a container's content-rect size via `ResizeObserver`, and density via
|
|
@@ -9,40 +9,61 @@ import { useCallback, useEffect, useState } from "react";
|
|
|
9
9
|
* inside the resize callback: that only refreshes when the element resizes,
|
|
10
10
|
* so dragging a window to a different-density display without resizing it
|
|
11
11
|
* leaves the snapshot stale. */
|
|
12
|
-
function useCanvasSize(containerRef) {
|
|
12
|
+
function useCanvasSize(containerRef, options) {
|
|
13
13
|
const { dpr } = useDeviceProfile();
|
|
14
|
-
const { width, height } = useElementSize(containerRef);
|
|
14
|
+
const { width, height } = useElementSize(containerRef, options);
|
|
15
15
|
return {
|
|
16
16
|
width,
|
|
17
17
|
height,
|
|
18
18
|
dpr
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const measure =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
var UNMEASURED = {
|
|
22
|
+
width: 0,
|
|
23
|
+
height: 0
|
|
24
|
+
};
|
|
25
|
+
/** `useSyncExternalStore`'s view of an element's size: measured when subscribed and on each resize. */
|
|
26
|
+
function elementSizeStore(containerRef, beforePaint) {
|
|
27
|
+
let size = UNMEASURED;
|
|
28
|
+
const measure = (el) => {
|
|
29
|
+
const rect = el.getBoundingClientRect();
|
|
30
|
+
if (rect.width === size.width && rect.height === size.height) return false;
|
|
31
|
+
size = {
|
|
32
32
|
width: rect.width,
|
|
33
33
|
height: rect.height
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
};
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
subscribe(changed) {
|
|
39
|
+
const el = containerRef.current;
|
|
40
|
+
if (!el) return () => {};
|
|
41
|
+
measure(el);
|
|
42
|
+
if (typeof ResizeObserver === "undefined") return () => {};
|
|
43
|
+
let pending;
|
|
44
|
+
const observer = new ResizeObserver(() => {
|
|
45
|
+
if (beforePaint) {
|
|
46
|
+
if (measure(el)) changed();
|
|
47
|
+
} else pending ??= setTimeout(() => {
|
|
48
|
+
pending = void 0;
|
|
49
|
+
if (measure(el)) changed();
|
|
50
|
+
}, 0);
|
|
51
|
+
});
|
|
52
|
+
observer.observe(el);
|
|
53
|
+
return () => {
|
|
54
|
+
observer.disconnect();
|
|
55
|
+
clearTimeout(pending);
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
get: () => size
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** An element's CSS-pixel size, `0 × 0` until it is mounted and measured.
|
|
62
|
+
* Re-measured whenever a `ResizeObserver` reports it resized. */
|
|
63
|
+
function useElementSize(containerRef, options) {
|
|
64
|
+
const beforePaint = options?.beforePaint ?? false;
|
|
65
|
+
const store = useMemo(() => elementSizeStore(containerRef, beforePaint), [containerRef, beforePaint]);
|
|
66
|
+
return useSyncExternalStore(store.subscribe, store.get, store.get);
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCanvasSize.js","names":[],"sources":["../../../src/core/viewport/useCanvasSize.ts"],"sourcesContent":["import { type RefObject,
|
|
1
|
+
{"version":3,"file":"useCanvasSize.js","names":[],"sources":["../../../src/core/viewport/useCanvasSize.ts"],"sourcesContent":["import { type RefObject, useMemo, useSyncExternalStore } from 'react';\nimport type { CanvasSize } from './clampView';\nimport { useDeviceProfile } from '../device/useDeviceProfile';\n\n/** Size snapshot returned by `useCanvasSize` — the kit-wide `CanvasSize`\n * (width × height in CSS pixels) plus the current devicePixelRatio. */\nexport interface CanvasSizeSnapshot extends CanvasSize {\n dpr: number;\n}\n\n/** Track a container's content-rect size via `ResizeObserver`, and density via\n * the ambient `DeviceProfile`.\n *\n * Density deliberately does NOT come from a `window.devicePixelRatio` read\n * inside the resize callback: that only refreshes when the element resizes,\n * so dragging a window to a different-density display without resizing it\n * leaves the snapshot stale. */\nexport function useCanvasSize(containerRef: RefObject<HTMLElement | null>, options?: ElementSizeOptions): CanvasSizeSnapshot {\n const { dpr } = useDeviceProfile();\n const { width, height } = useElementSize(containerRef, options);\n return { width, height, dpr };\n}\n\n/** How {@link useElementSize} commits a resize. */\nexport interface ElementSizeOptions {\n /** Render the caller with a new size inside the `ResizeObserver` callback, so the frame it resized in paints\n * it. Only for a caller whose render lays out nothing outside the observed element: the browser delivers a\n * resize it causes outside it only next frame, and reports the loop as an error. By default the size\n * arrives a frame later. */\n beforePaint?: boolean;\n}\n\nconst UNMEASURED: CanvasSize = { width: 0, height: 0 };\n\n/** `useSyncExternalStore`'s view of an element's size: measured when subscribed and on each resize. */\nfunction elementSizeStore(containerRef: RefObject<HTMLElement | null>, beforePaint: boolean) {\n let size = UNMEASURED;\n const measure = (el: HTMLElement): boolean => {\n const rect = el.getBoundingClientRect();\n if (rect.width === size.width && rect.height === size.height) return false;\n size = { width: rect.width, height: rect.height };\n return true;\n };\n return {\n subscribe(changed: () => void): () => void {\n const el = containerRef.current;\n if (!el) return () => {};\n measure(el);\n // Absent under jsdom and in older embedders: one measurement is all there is.\n if (typeof ResizeObserver === 'undefined') return () => {};\n let pending: ReturnType<typeof setTimeout> | undefined;\n const observer = new ResizeObserver(() => {\n // A store update renders on a microtask, still inside the browser's resize loop; a task waits for it.\n if (beforePaint) { if (measure(el)) changed(); }\n else pending ??= setTimeout(() => { pending = undefined; if (measure(el)) changed(); }, 0);\n });\n observer.observe(el);\n return () => { observer.disconnect(); clearTimeout(pending); };\n },\n get: () => size,\n };\n}\n\n/** An element's CSS-pixel size, `0 × 0` until it is mounted and measured.\n * Re-measured whenever a `ResizeObserver` reports it resized. */\nexport function useElementSize(containerRef: RefObject<HTMLElement | null>, options?: ElementSizeOptions): CanvasSize {\n const beforePaint = options?.beforePaint ?? false;\n const store = useMemo(() => elementSizeStore(containerRef, beforePaint), [containerRef, beforePaint]);\n return useSyncExternalStore(store.subscribe, store.get, store.get);\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAgB,cAAc,cAA6C,SAAkD;CAC3H,MAAM,EAAE,QAAQ,iBAAiB;CACjC,MAAM,EAAE,OAAO,WAAW,eAAe,cAAc,OAAO;CAC9D,OAAO;EAAE;EAAO;EAAQ;CAAI;AAC9B;AAWA,IAAM,aAAyB;CAAE,OAAO;CAAG,QAAQ;AAAE;;AAGrD,SAAS,iBAAiB,cAA6C,aAAsB;CAC3F,IAAI,OAAO;CACX,MAAM,WAAW,OAA6B;EAC5C,MAAM,OAAO,GAAG,sBAAsB;EACtC,IAAI,KAAK,UAAU,KAAK,SAAS,KAAK,WAAW,KAAK,QAAQ,OAAO;EACrE,OAAO;GAAE,OAAO,KAAK;GAAO,QAAQ,KAAK;EAAO;EAChD,OAAO;CACT;CACA,OAAO;EACL,UAAU,SAAiC;GACzC,MAAM,KAAK,aAAa;GACxB,IAAI,CAAC,IAAI,aAAa,CAAC;GACvB,QAAQ,EAAE;GAEV,IAAI,OAAO,mBAAmB,aAAa,aAAa,CAAC;GACzD,IAAI;GACJ,MAAM,WAAW,IAAI,qBAAqB;IAExC,IAAI,aAAe;SAAI,QAAQ,EAAE,GAAG,QAAQ;IAAA,OACvC,YAAY,iBAAiB;KAAE,UAAU;KAAW,IAAI,QAAQ,EAAE,GAAG,QAAQ;IAAG,GAAG,CAAC;GAC3F,CAAC;GACD,SAAS,QAAQ,EAAE;GACnB,aAAa;IAAE,SAAS,WAAW;IAAG,aAAa,OAAO;GAAG;EAC/D;EACA,WAAW;CACb;AACF;;;AAIA,SAAgB,eAAe,cAA6C,SAA0C;CACpH,MAAM,cAAc,SAAS,eAAe;CAC5C,MAAM,QAAQ,cAAc,iBAAiB,cAAc,WAAW,GAAG,CAAC,cAAc,WAAW,CAAC;CACpG,OAAO,qBAAqB,MAAM,WAAW,MAAM,KAAK,MAAM,GAAG;AACnE"}
|
|
@@ -5,12 +5,12 @@ import { ComponentType } from 'react';
|
|
|
5
5
|
import { IconRef } from '@weasel-js/icons';
|
|
6
6
|
import { PaintDrawKind, DrawCommand, Effect, CompositeOp, BlendFactors } from '@weasel-js/renderer';
|
|
7
7
|
import * as _weasel_js_routing_react from '@weasel-js/routing/react';
|
|
8
|
+
import { TextOutlinesError, TextNodeSource, TextStyle, StyledRun, TextVerticalAlign } from '@weasel-js/text';
|
|
8
9
|
import * as _weasel_js_geom from '@weasel-js/geom';
|
|
9
10
|
import { FramedBox, View, Path, Affine, Rect, Scale2, PolygonPath, Point, PathFillRule, AnchorSet, PointOrigin } from '@weasel-js/geom';
|
|
10
11
|
import { I as InheritedPaint, S as Scene, R as RectPose } from './types-DW14eu9B.js';
|
|
11
12
|
import { Op, History } from '@weasel-js/history';
|
|
12
13
|
import { NodeId, ModifierState, OverlayRole, WorldPoint, ResizeAnchor, ActionDeps, OngoingHandle, OngoingOverlay, IngestItem, SelectionApi, Point2, DragSample, DebugSink, LongPressState } from '@weasel-js/routing';
|
|
13
|
-
import { TextNodeSource, TextOutlinesError, TextStyle, StyledRun, TextVerticalAlign } from '@weasel-js/text';
|
|
14
14
|
import { CursorSpec } from '@weasel-js/cursor';
|
|
15
15
|
import { ClaimableGesture } from '@weasel-js/gestures';
|
|
16
16
|
import { SnapTier, ProbeRole } from '@weasel-js/geom/nd';
|
|
@@ -201,6 +201,27 @@ declare function paintKindOf(fill: Paint): PaintKindEntry | undefined;
|
|
|
201
201
|
/** @internal Test helper — do not call from product code. */
|
|
202
202
|
declare function _resetPaintKindsForTests(): void;
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* How an action tells the user something went wrong: the `notices` dep a host
|
|
206
|
+
* publishes, shown however it shows such things (draw raises a toast). Without
|
|
207
|
+
* one the message goes to the console.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
/** What a notice says beyond its title. */
|
|
211
|
+
interface NoticeOptions {
|
|
212
|
+
/** One notice per id: a later one with the same id replaces it. */
|
|
213
|
+
id?: string;
|
|
214
|
+
description?: string;
|
|
215
|
+
}
|
|
216
|
+
/** The host's notice surface, read by actions whose result the user must hear about. */
|
|
217
|
+
interface NoticesDep {
|
|
218
|
+
warn(title: string, opts?: NoticeOptions): void;
|
|
219
|
+
}
|
|
220
|
+
/** Warn the user through `notices`, or under `tag` on the console without one. */
|
|
221
|
+
declare function warnUser(notices: NoticesDep | undefined, tag: string, title: string, opts?: NoticeOptions): void;
|
|
222
|
+
/** Warn that text in `error`'s face could not be turned into outlines. */
|
|
223
|
+
declare function warnTextOutlines(notices: NoticesDep | undefined, tag: string, error: TextOutlinesError): void;
|
|
224
|
+
|
|
204
225
|
/** The paint a path node wears, resolved: what an action that turns ink into
|
|
205
226
|
* geometry reads from a node and tells a consumer to put on a result. */
|
|
206
227
|
interface PathPaint {
|
|
@@ -312,12 +333,14 @@ type CreateOutlinesResult =
|
|
|
312
333
|
error: TextOutlinesError;
|
|
313
334
|
};
|
|
314
335
|
/**
|
|
315
|
-
* Convert every text node
|
|
316
|
-
* "Create Outlines". Other
|
|
317
|
-
*
|
|
318
|
-
*
|
|
336
|
+
* Convert every text node among `ids` — the selection, when not given — to
|
|
337
|
+
* outlines, as one batch labeled "Create Outlines". Other nodes are left
|
|
338
|
+
* alone, and the selection keeps them; a selected text node's replacement
|
|
339
|
+
* takes its place there. All or nothing: if any text node's face cannot
|
|
340
|
+
* supply outlines the scene is untouched and the error comes back in the
|
|
341
|
+
* result.
|
|
319
342
|
*/
|
|
320
|
-
declare function applyCreateOutlines(adapter: CreateOutlinesAdapter): CreateOutlinesResult;
|
|
343
|
+
declare function applyCreateOutlines(adapter: CreateOutlinesAdapter, ids?: readonly NodeId[]): CreateOutlinesResult;
|
|
321
344
|
|
|
322
345
|
/**
|
|
323
346
|
* What each selected node gives a Boolean operation: its path, its glyph
|
|
@@ -1249,12 +1272,13 @@ interface SourceCandidate<P = Point> {
|
|
|
1249
1272
|
/** `'handle'` is an angle about an anchor, as `'rotate'` is about a pivot,
|
|
1250
1273
|
* but asks only sources that name it. */
|
|
1251
1274
|
type SnapGesture = 'point' | 'move' | 'resize' | 'rotate' | 'handle';
|
|
1252
|
-
/**
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
interface
|
|
1256
|
-
|
|
1257
|
-
|
|
1275
|
+
/** One side of the subject's box a resize moves: on `axis`, its `side` moves
|
|
1276
|
+
* by `rate · (delta − delta0)`. A turned box's axis-aligned extent moves at
|
|
1277
|
+
* a rate off its axis, and both its sides on one axis may move at once. */
|
|
1278
|
+
interface SnapSide<P = Point> {
|
|
1279
|
+
axis: 'x' | 'y';
|
|
1280
|
+
side: 'min' | 'max';
|
|
1281
|
+
rate: P;
|
|
1258
1282
|
}
|
|
1259
1283
|
/** A point on the subject that should land on a locus. */
|
|
1260
1284
|
interface SnapProbeAt<P = Point> {
|
|
@@ -1274,9 +1298,12 @@ interface SnapCollect<P = Point> {
|
|
|
1274
1298
|
* puts it: a move's union of boxes, or the box a resize drags. Absent for
|
|
1275
1299
|
* other gestures. */
|
|
1276
1300
|
box?: FramedBox;
|
|
1277
|
-
/**
|
|
1278
|
-
* a move
|
|
1279
|
-
|
|
1301
|
+
/** The sides of `box` a resize moves; a side not listed stands still.
|
|
1302
|
+
* Absent for a move, which carries the whole box with it. */
|
|
1303
|
+
sides?: readonly SnapSide<P>[];
|
|
1304
|
+
/** The subject's drawn box where the resolved move `delta` (a function
|
|
1305
|
+
* mark's argument) puts it. Absent where `box` is. */
|
|
1306
|
+
boxAt?(delta: P): FramedBox;
|
|
1280
1307
|
/** `id`'s drawn box, measured as `box` is. Absent in a host with no 2D
|
|
1281
1308
|
* scene. */
|
|
1282
1309
|
drawnBox?(id: string): FramedBox | undefined;
|
|
@@ -1402,10 +1429,15 @@ interface SceneCanvasSnap {
|
|
|
1402
1429
|
/** The reach of a source that sets none, in screen px. Default: the kit
|
|
1403
1430
|
* setting `targets.snapPx`. */
|
|
1404
1431
|
reachPx?: number;
|
|
1405
|
-
/** How a pose's origin is read, for the `origin` probe a grid
|
|
1406
|
-
*
|
|
1407
|
-
* `AUTO_ORIGIN_PROJECTION`. */
|
|
1432
|
+
/** How a pose's origin is read, for the `origin` probe a grid snaps on a
|
|
1433
|
+
* turned container's lattice: a pose neither a rect nor a `Path` needs
|
|
1434
|
+
* its own. Default `AUTO_ORIGIN_PROJECTION`. */
|
|
1408
1435
|
originProjection?: OriginProjection<unknown>;
|
|
1436
|
+
/** Points of node `id` a move snaps, in its own box frame (`sceneBoxFrames`:
|
|
1437
|
+
* from its box corner, through its pose transform, scale included), such as
|
|
1438
|
+
* a cabinet's hanger holes. Read once when a move begins; each becomes a
|
|
1439
|
+
* probe of role `'anchor'`, offered only to a source whose `roles` list it. */
|
|
1440
|
+
anchors?: SnapAnchors;
|
|
1409
1441
|
/** Whether the canvas draws the snap marks. Default `true`. With `false`
|
|
1410
1442
|
* the marks are still written to the `snapReadout` dep's store, for a
|
|
1411
1443
|
* layer of the consumer's own. */
|
|
@@ -1413,6 +1445,8 @@ interface SceneCanvasSnap {
|
|
|
1413
1445
|
/** The readout's default colors and line width, for a mark that sets no `color`. */
|
|
1414
1446
|
readoutStyle?: SnapReadoutStyle;
|
|
1415
1447
|
}
|
|
1448
|
+
/** Node `id`'s snap anchors in its own box frame; none for null or undefined. */
|
|
1449
|
+
type SnapAnchors = (id: string) => readonly Point[] | null | undefined;
|
|
1416
1450
|
/** The snap readout's colors and line width, for a mark that sets no `color`. */
|
|
1417
1451
|
interface SnapReadoutStyle {
|
|
1418
1452
|
/** Points, lines and their labels. Default orange, as the path marker. */
|
|
@@ -2091,12 +2125,6 @@ interface EditAnchorsDep {
|
|
|
2091
2125
|
width: number;
|
|
2092
2126
|
height: number;
|
|
2093
2127
|
} | null): void;
|
|
2094
|
-
/** The path-edit tool in use, or null for plain anchor editing — one of a
|
|
2095
|
-
* set of named tools, such as `'simplifyBrush'`, that each change what a
|
|
2096
|
-
* plain drag in path edit does. Always null with no path open. */
|
|
2097
|
-
tool: string | null;
|
|
2098
|
-
/** Switch path-edit tool, or pass null for plain anchor editing. */
|
|
2099
|
-
setTool(id: string | null): void;
|
|
2100
2128
|
/** The handle locks held for every handle drag, on top of the modifiers —
|
|
2101
2129
|
* what the `handleLock.angle` and `handleLock.length` toggles set. Absent,
|
|
2102
2130
|
* the drag holds what its `handleLock` param names. Unlike the anchor
|
|
@@ -2778,6 +2806,9 @@ declare module '@weasel-js/routing' {
|
|
|
2778
2806
|
* in both its `enabled` predicate and its invoker.
|
|
2779
2807
|
*/
|
|
2780
2808
|
createOutlinesAdapter?: CreateOutlinesAdapter;
|
|
2809
|
+
/** Where an action tells the user something went wrong, such as text it
|
|
2810
|
+
* could not outline. Optional: without it the message goes to the console. */
|
|
2811
|
+
notices?: NoticesDep;
|
|
2781
2812
|
/** Gesture dispatcher control surface, so an action can abort an
|
|
2782
2813
|
* in-flight handle (Escape cancels a drag, etc.). */
|
|
2783
2814
|
dispatcher?: {
|
|
@@ -2847,4 +2878,4 @@ declare module '@weasel-js/routing' {
|
|
|
2847
2878
|
}
|
|
2848
2879
|
}
|
|
2849
2880
|
|
|
2850
|
-
export { type
|
|
2881
|
+
export { type BooleanStrokes as $, type AnchorMarkerShape as A, type CommonAffordanceScratch as B, type ContentHandlerEntry as C, type Dims as D, type EditAnchorsDep as E, type FitViewToBoundsOptions as F, type GestureLifecycleOptions as G, type HandleLocks as H, type InertiaConfig as I, type CreateOutlinesAdapter as J, type PaintKindEntry as K, type LayerHit as L, type PathEditTarget as M, type NoticesDep as N, type FinishedEditFrame as O, type PathEditMark as P, type PathSymmetryDep as Q, type RenderLayer as R, type SnapSource as S, type TextOverflow as T, type BooleanOpOptions as U, type ViewAnimationOptions as V, type BooleansAdapter as W, type BooleanOp as X, type PathPaint as Y, type BooleanOpResult as Z, _resetPaintKindsForTests as _, type PathEditExtension as a, type PathRepresentation as a$, type SnapMods as a0, type SourceCandidate as a1, type SnapProbeAt as a2, type SnapCollect as a3, type SnapMark as a4, type SnapDep as a5, type SnapOutcome as a6, type SnapReadoutStyle as a7, type AngleCandidate as a8, type AngleCollect as a9, type LayerDrawFailure as aA, type LayerDrawSpan as aB, type LayerRetainPolicy as aC, type LayoutDep as aD, type Locus as aE, type MinSize as aF, type NodeAtPointDep as aG, type NoticeOptions as aH, type OutlinePathSpec as aI, PATH_OVERLAY_ROLES as aJ, type PaintKind as aK, type PaintKindEditorProps as aL, type PaintKindFamily as aM, type PaintKindLoader as aN, type PanBounds as aO, type PathEditDrag as aP, type PathEditDragStart as aQ, type PathEditFinishCtx as aR, type PathEditHandleMove as aS, type PathEditMarksCtx as aT, type PathEditOther as aU, type PathEditPart as aV, type PathEditStore as aW, type PathForm as aX, type PathOverlay as aY, type PathOverlayOptions as aZ, type PathOverlayStyle as a_, type AnimateToBoundsOptions as aa, type AreaSelectDep as ab, type BooleanOpPreview as ac, type BooleanPreviewPiece as ad, type CanDropIntoDep as ae, type ClipboardDep as af, type ClipboardIngestCtx as ag, type CreateOutlinesResult as ah, type CustomPaintContext as ai, DEFAULT_LAYER_RETAIN as aj, type DecayLoopConfig as ak, type DropRefusal as al, type DropVerdict as am, type FinishedPathEdit as an, GEOMETRY_BOUND_DATA as ao, type HandleLock as ap, type HitTestView as aq, type IngestCtx as ar, type IngestionDep as as, type InsertDep as at, type InsertEnds as au, type InsertExtras as av, type InsertIntoDep as aw, type InsertLive as ax, type LassoSelectDep as ay, type LayerCommandCache as az, type SnapGesture as b, type PathTransformFinishCtx as b0, type PoseChange as b1, type PreviewPathsDep as b2, type ResizePolicy as b3, STICKY_DEFAULTS as b4, type SnapBegin as b5, type SnapFrame as b6, type SnapMarks as b7, type SnapReadoutDep as b8, type SnapSide as b9, paintKindRegistry as bA, paintedLayers as bB, paintsLayer as bC, paletteInsertPaint as bD, pathPaintOf as bE, patternFamily as bF, previewBooleanOp as bG, refusalOf as bH, registerContentHandler as bI, registerPaintKind as bJ, registerPaintKindLoader as bK, seedTile as bL, stickyData as bM, stickyNodeFactory as bN, subscribeToSources as bO, switchGradientKind as bP, textBoxOf as bQ, useDecayLoop as bR, useEnteredGroup as bS, useViewAnimation as bT, useViewAnimationOn as bU, viewAxes as bV, warnTextOutlines as bW, warnUser as bX, withoutGeometryBoundData as bY, type SvgUnpacker as ba, type TextEditDep as bb, type TextNodeData as bc, type TransformPreviewNode as bd, type TransientPath as be, VIEW_ANIMATION_KEY as bf, type ViewAnimationApi as bg, type ViewApi as bh, type ViewChannel as bi, applyBooleanOp as bj, applyCreateOutlines as bk, asPaint as bl, createEnteredGroup as bm, createPathOverlay as bn, createSnapOutcomeStore as bo, depsUnchanged as bp, drawLayers as bq, drawOneLayer as br, fitViewToBounds as bs, getPaintKind as bt, isLayerPainted as bu, isLayerVisible as bv, listGradientKinds as bw, listPaintKinds as bx, minSizeOf as by, paintKindOf as bz, type SnapConstrain as c, type SnapResizeBox as d, type SnapGeometry as e, type SnapOutcomeStore as f, type SnapAnchors as g, type SnapSession as h, type LayerDims as i, type ViewportDims as j, type RedrawSource as k, type EditAnchorsParams as l, type SliceDep as m, type StickyStyle as n, type EnteredGroupApi as o, type ChromeState as p, type LayerGroup as q, type TransformFollower as r, type GeometryProjection as s, type InsertNodeFactory as t, type InsertPaint as u, type SvgIngestOptions as v, type SceneCanvasSnap as w, type Affordance as x, type AffordanceRegion as y, type AffordanceBinding as z };
|
|
@@ -23,6 +23,7 @@ function resolveVisibility(consumer, ctx) {
|
|
|
23
23
|
allowedCapabilities: DEFAULT_ALLOWED_CAPABILITIES
|
|
24
24
|
};
|
|
25
25
|
return (id) => {
|
|
26
|
+
if (ruleCtx.hiddenChrome?.has(id)) return false;
|
|
26
27
|
const entry = merged[id];
|
|
27
28
|
if (entry === void 0) return true;
|
|
28
29
|
if (typeof entry === "function") return entry(ruleCtx);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.js","names":[],"sources":["../../../src/domains/chrome-caps/resolve.ts"],"sourcesContent":["import type { ChromeCtx, ChromeId, VisibilityRules } from './types';\nimport type { RuleCtx } from '@weasel-js/routing';\nimport { DEFAULT_ALLOWED_CAPABILITIES } from '@weasel-js/routing';\nimport { evaluate } from '@weasel-js/routing';\nimport { defaultVisibilityRules } from './defaults';\nimport { viewZoom } from 'core/viewport/view';\n\n/**\n * Build the per-frame visibility check: consumer rules merged on top of\n * the kit defaults, evaluated against `ctx`.\n *\n * `ChromeCtx` is the legacy shape; surfaces still on it get `mode='normal'`\n * and `DEFAULT_ALLOWED_CAPABILITIES` filled in here, so a caller that never\n * opted into modality behaves exactly like one sitting in normal mode.\n */\nexport function resolveVisibility(\n consumer: VisibilityRules | undefined,\n ctx: ChromeCtx | RuleCtx,\n): (id: ChromeId) => boolean {\n const merged: VisibilityRules = consumer\n ? { ...defaultVisibilityRules, ...consumer }\n : defaultVisibilityRules;\n const ruleCtx: RuleCtx = 'mode' in ctx\n ? ctx\n : {\n ...ctx,\n zoom: viewZoom(ctx.view),\n mode: 'normal',\n allowedCapabilities: DEFAULT_ALLOWED_CAPABILITIES,\n };\n return (id) => {\n const entry = merged[id];\n if (entry === undefined) return true;\n if (typeof entry === 'function') return entry(ruleCtx);\n return evaluate(entry, ruleCtx);\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,kBACd,UACA,KAC2B;CAC3B,MAAM,SAA0B,WAC5B;EAAE,GAAG;EAAwB,GAAG;CAAS,IACzC;CACJ,MAAM,UAAmB,UAAU,MAC/B,MACA;EACA,GAAG;EACH,MAAM,SAAS,IAAI,IAAI;EACvB,MAAM;EACN,qBAAqB;CACvB;CACF,QAAQ,OAAO;EACb,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,QAAW,OAAO;EAChC,IAAI,OAAO,UAAU,YAAY,OAAO,MAAM,OAAO;EACrD,OAAO,SAAS,OAAO,OAAO;CAChC;AACF"}
|
|
1
|
+
{"version":3,"file":"resolve.js","names":[],"sources":["../../../src/domains/chrome-caps/resolve.ts"],"sourcesContent":["import type { ChromeCtx, ChromeId, VisibilityRules } from './types';\nimport type { RuleCtx } from '@weasel-js/routing';\nimport { DEFAULT_ALLOWED_CAPABILITIES } from '@weasel-js/routing';\nimport { evaluate } from '@weasel-js/routing';\nimport { defaultVisibilityRules } from './defaults';\nimport { viewZoom } from 'core/viewport/view';\n\n/**\n * Build the per-frame visibility check: consumer rules merged on top of\n * the kit defaults, evaluated against `ctx`.\n *\n * `ChromeCtx` is the legacy shape; surfaces still on it get `mode='normal'`\n * and `DEFAULT_ALLOWED_CAPABILITIES` filled in here, so a caller that never\n * opted into modality behaves exactly like one sitting in normal mode.\n */\nexport function resolveVisibility(\n consumer: VisibilityRules | undefined,\n ctx: ChromeCtx | RuleCtx,\n): (id: ChromeId) => boolean {\n const merged: VisibilityRules = consumer\n ? { ...defaultVisibilityRules, ...consumer }\n : defaultVisibilityRules;\n const ruleCtx: RuleCtx = 'mode' in ctx\n ? ctx\n : {\n ...ctx,\n zoom: viewZoom(ctx.view),\n mode: 'normal',\n allowedCapabilities: DEFAULT_ALLOWED_CAPABILITIES,\n };\n return (id) => {\n if (ruleCtx.hiddenChrome?.has(id)) return false;\n const entry = merged[id];\n if (entry === undefined) return true;\n if (typeof entry === 'function') return entry(ruleCtx);\n return evaluate(entry, ruleCtx);\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,kBACd,UACA,KAC2B;CAC3B,MAAM,SAA0B,WAC5B;EAAE,GAAG;EAAwB,GAAG;CAAS,IACzC;CACJ,MAAM,UAAmB,UAAU,MAC/B,MACA;EACA,GAAG;EACH,MAAM,SAAS,IAAI,IAAI;EACvB,MAAM;EACN,qBAAqB;CACvB;CACF,QAAQ,OAAO;EACb,IAAI,QAAQ,cAAc,IAAI,EAAE,GAAG,OAAO;EAC1C,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,QAAW,OAAO;EAChC,IAAI,OAAO,UAAU,YAAY,OAAO,MAAM,OAAO;EACrD,OAAO,SAAS,OAAO,OAAO;CAChC;AACF"}
|