@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.
Files changed (152) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +1 -1
  3. package/dist/affordances/pathAnchors.js +0 -1
  4. package/dist/affordances/pathAnchors.js.map +1 -1
  5. package/dist/barrel/paths.js +1 -1
  6. package/dist/barrel/renderer.js +2 -2
  7. package/dist/barrel/routing.js +6 -4
  8. package/dist/barrel/text.js +5 -3
  9. package/dist/canvas/Canvas.js +5 -1
  10. package/dist/canvas/Canvas.js.map +1 -1
  11. package/dist/canvas/DrawCanvas.js +5 -1
  12. package/dist/canvas/DrawCanvas.js.map +1 -1
  13. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +4 -0
  14. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  15. package/dist/canvas/SceneCanvas/builtinToolSets.js +6 -3
  16. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -1
  17. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +1 -1
  18. package/dist/canvas/SceneCanvas/presets/paths.js +6 -6
  19. package/dist/canvas/SceneCanvas/presets/paths.js.map +1 -1
  20. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +1 -1
  21. package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -1
  22. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -0
  23. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  24. package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
  25. package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
  26. package/dist/canvas/SceneCanvas/useSceneChrome.js +17 -5
  27. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  28. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -0
  29. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  30. package/dist/canvas/SceneCanvas.js +5 -4
  31. package/dist/canvas/SceneCanvas.js.map +1 -1
  32. package/dist/canvas/SceneViewCanvas.js +5 -1
  33. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  34. package/dist/canvas/affordanceAt.js +0 -1
  35. package/dist/canvas/affordanceAt.js.map +1 -1
  36. package/dist/canvas/deps/chromeRules.js +37 -0
  37. package/dist/canvas/deps/chromeRules.js.map +1 -0
  38. package/dist/canvas/deps/editAnchors.js +0 -6
  39. package/dist/canvas/deps/editAnchors.js.map +1 -1
  40. package/dist/canvas/deps/hitTestArea.js +3 -1
  41. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  42. package/dist/canvas/deps/modeDefaultTool.js +23 -0
  43. package/dist/canvas/deps/modeDefaultTool.js.map +1 -0
  44. package/dist/canvas/deps/pathEditCommits.js +1 -1
  45. package/dist/canvas/deps/selectionSnapshot.js +23 -0
  46. package/dist/canvas/deps/selectionSnapshot.js.map +1 -0
  47. package/dist/canvas/deps/snap.js +1 -0
  48. package/dist/canvas/deps/snap.js.map +1 -1
  49. package/dist/canvas/pickReach.js +12 -8
  50. package/dist/canvas/pickReach.js.map +1 -1
  51. package/dist/canvas/pickWalk.js +7 -5
  52. package/dist/canvas/pickWalk.js.map +1 -1
  53. package/dist/canvas/scenePickSource.js +2 -2
  54. package/dist/canvas/scenePickSource.js.map +1 -1
  55. package/dist/canvas/useCanvasSurface.js +21 -3
  56. package/dist/canvas/useCanvasSurface.js.map +1 -1
  57. package/dist/canvas/useFrameLoop.js +6 -7
  58. package/dist/canvas/useFrameLoop.js.map +1 -1
  59. package/dist/canvas/useSurfaceSize.js +4 -3
  60. package/dist/canvas/useSurfaceSize.js.map +1 -1
  61. package/dist/core/geometry/regionHit.js +35 -16
  62. package/dist/core/geometry/regionHit.js.map +1 -1
  63. package/dist/core/paint/meshKindLazy.js +9 -10
  64. package/dist/core/paint/meshKindLazy.js.map +1 -1
  65. package/dist/core/scene/editMode.js +4 -1
  66. package/dist/core/scene/editMode.js.map +1 -1
  67. package/dist/core/scene/historyFacade.js +7 -50
  68. package/dist/core/scene/historyFacade.js.map +1 -1
  69. package/dist/core/scene/lockGuard.js +1 -0
  70. package/dist/core/scene/lockGuard.js.map +1 -1
  71. package/dist/core/scene/nodeGates.js +43 -23
  72. package/dist/core/scene/nodeGates.js.map +1 -1
  73. package/dist/core/scene/opTable.js +8 -8
  74. package/dist/core/scene/opTable.js.map +1 -1
  75. package/dist/core/scene/outerOps.js +2 -2
  76. package/dist/core/scene/outerOps.js.map +1 -1
  77. package/dist/core/scene/poseRemovals.js +1 -0
  78. package/dist/core/scene/poseRemovals.js.map +1 -1
  79. package/dist/core/scene/poseWrites.js +16 -15
  80. package/dist/core/scene/poseWrites.js.map +1 -1
  81. package/dist/core/scene/sceneJournals.js +78 -0
  82. package/dist/core/scene/sceneJournals.js.map +1 -0
  83. package/dist/core/scene/sceneRecording.js +3 -2
  84. package/dist/core/scene/sceneRecording.js.map +1 -1
  85. package/dist/core/scene/statedFields.js +10 -8
  86. package/dist/core/scene/statedFields.js.map +1 -1
  87. package/dist/core/scene/treeEdits.js +4 -9
  88. package/dist/core/scene/treeEdits.js.map +1 -1
  89. package/dist/core/viewport/useCanvasSize.js +47 -26
  90. package/dist/core/viewport/useCanvasSize.js.map +1 -1
  91. package/dist/{depSchema-C3BwxitB.d.ts → depSchema-ChQPzEkW.d.ts} +56 -25
  92. package/dist/domains/chrome-caps/resolve.js +1 -0
  93. package/dist/domains/chrome-caps/resolve.js.map +1 -1
  94. package/dist/domains/chrome-caps/useChromeCaps.js +4 -2
  95. package/dist/domains/chrome-caps/useChromeCaps.js.map +1 -1
  96. package/dist/domains/paths/pathEditState.js +0 -24
  97. package/dist/domains/paths/pathEditState.js.map +1 -1
  98. package/dist/domains/snap/probes2d.js +5 -0
  99. package/dist/domains/snap/probes2d.js.map +1 -1
  100. package/dist/domains/snap/resizeBox.js +43 -22
  101. package/dist/domains/snap/resizeBox.js.map +1 -1
  102. package/dist/domains/snap/sceneReads.js +11 -1
  103. package/dist/domains/snap/sceneReads.js.map +1 -1
  104. package/dist/domains/snap/sceneSession2d.js +4 -2
  105. package/dist/domains/snap/sceneSession2d.js.map +1 -1
  106. package/dist/domains/snap/session2d.js +14 -7
  107. package/dist/domains/snap/session2d.js.map +1 -1
  108. package/dist/domains/text/index.js +2 -2
  109. package/dist/icons.table.js +1 -1
  110. package/dist/icons.table.js.map +1 -1
  111. package/dist/icons.table.json +5 -0
  112. package/dist/index.d.ts +114 -62
  113. package/dist/index.js +12 -8
  114. package/dist/interactions/actions/booleans/action.js +4 -2
  115. package/dist/interactions/actions/booleans/action.js.map +1 -1
  116. package/dist/interactions/actions/defaults/anchorEditing.js +3 -6
  117. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  118. package/dist/interactions/actions/defaults/bendSegment.js +1 -2
  119. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  120. package/dist/interactions/actions/defaults/exitPathEdit.js +17 -1
  121. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  122. package/dist/interactions/actions/edit-anchors/action.js +3 -4
  123. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  124. package/dist/interactions/actions/insert/action.js +21 -2
  125. package/dist/interactions/actions/insert/action.js.map +1 -1
  126. package/dist/interactions/actions/insert/params.js +43 -0
  127. package/dist/interactions/actions/insert/params.js.map +1 -0
  128. package/dist/interactions/actions/notices.js +17 -0
  129. package/dist/interactions/actions/notices.js.map +1 -0
  130. package/dist/interactions/actions/outlines/createOutlines.js +8 -6
  131. package/dist/interactions/actions/outlines/createOutlines.js.map +1 -1
  132. package/dist/interactions/actions/outlines/createOutlinesAction.js +13 -8
  133. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  134. package/dist/interactions/actions/outlines/outlineStrokeAction.js +3 -2
  135. package/dist/interactions/actions/outlines/outlineStrokeAction.js.map +1 -1
  136. package/dist/interactions/actions/pathCommands/pathNodes.js +1 -1
  137. package/dist/interactions/actions/testUtils.js +0 -7
  138. package/dist/interactions/actions/testUtils.js.map +1 -1
  139. package/dist/interactions/actions/useNotices.js +13 -0
  140. package/dist/interactions/actions/useNotices.js.map +1 -0
  141. package/dist/test-seams.d.ts +5 -12
  142. package/dist/tools/builtin/anchors/anchorsTool.js +31 -0
  143. package/dist/tools/builtin/anchors/anchorsTool.js.map +1 -0
  144. package/dist/tools/builtin/polygon/usePolygonTool.js +1 -1
  145. package/dist/tools/builtin/star/useStarTool.js +1 -1
  146. package/dist/tools/index.js +4 -2
  147. package/dist/tools/toolOptions.js +47 -0
  148. package/dist/tools/toolOptions.js.map +1 -0
  149. package/dist/version.js +1 -1
  150. package/package.json +25 -25
  151. package/dist/interactions/actions/defaults/pathEditTools.js +0 -60
  152. 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":"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"}
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
- var statedFields = (n) => ({
7
- ...n.pickable !== void 0 ? { pickable: n.pickable } : {},
8
- ...n.hidden === true ? { hidden: true } : {},
9
- ...n.locked === true ? { locked: true } : {},
10
- ...n.units !== void 0 ? { units: n.units } : {},
11
- ...n.clip !== void 0 ? { clip: n.clip } : {},
12
- ...n.consumesChildren === true ? { consumesChildren: true } : {}
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 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.consumesChildren === true ? { consumesChildren: true } : {}),\n});\n"],"mappings":";;;;;AAiBA,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;CAC/C,GAAI,EAAE,qBAAqB,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAClE"}
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
- return {
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 { useCallback, useEffect, useState } from "react";
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
- /** An element's CSS-pixel size, `0 × 0` until it is mounted and measured.
22
- * Re-measured whenever a `ResizeObserver` reports it resized. */
23
- function useElementSize(containerRef) {
24
- const [size, setSize] = useState({
25
- width: 0,
26
- height: 0
27
- });
28
- const measure = useCallback(() => {
29
- if (!containerRef.current) return;
30
- const rect = containerRef.current.getBoundingClientRect();
31
- setSize((prev) => prev.width === rect.width && prev.height === rect.height ? prev : {
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
- }, [containerRef]);
36
- useEffect(() => {
37
- const el = containerRef.current;
38
- if (!el) return;
39
- measure();
40
- if (typeof ResizeObserver === "undefined") return;
41
- const observer = new ResizeObserver(measure);
42
- observer.observe(el);
43
- return () => observer.disconnect();
44
- }, [measure, containerRef]);
45
- return size;
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, useCallback, useEffect, useState } 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>): CanvasSizeSnapshot {\n const { dpr } = useDeviceProfile();\n const { width, height } = useElementSize(containerRef);\n return { width, height, dpr };\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>): CanvasSize {\n const [size, setSize] = useState<CanvasSize>({ width: 0, height: 0 });\n\n const measure = useCallback(() => {\n if (!containerRef.current) return;\n const rect = containerRef.current.getBoundingClientRect();\n setSize((prev) =>\n prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height },\n );\n }, [containerRef]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n measure();\n // Absent under jsdom and in older embedders: one measurement is all there is.\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [measure, containerRef]);\n\n return size;\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAgB,cAAc,cAAiE;CAC7F,MAAM,EAAE,QAAQ,iBAAiB;CACjC,MAAM,EAAE,OAAO,WAAW,eAAe,YAAY;CACrD,OAAO;EAAE;EAAO;EAAQ;CAAI;AAC9B;;;AAIA,SAAgB,eAAe,cAAyD;CACtF,MAAM,CAAC,MAAM,WAAW,SAAqB;EAAE,OAAO;EAAG,QAAQ;CAAE,CAAC;CAEpE,MAAM,UAAU,kBAAkB;EAChC,IAAI,CAAC,aAAa,SAAS;EAC3B,MAAM,OAAO,aAAa,QAAQ,sBAAsB;EACxD,SAAS,SACP,KAAK,UAAU,KAAK,SAAS,KAAK,WAAW,KAAK,SAC9C,OACA;GAAE,OAAO,KAAK;GAAO,QAAQ,KAAK;EAAO,CAC/C;CACF,GAAG,CAAC,YAAY,CAAC;CAEjB,gBAAgB;EACd,MAAM,KAAK,aAAa;EACxB,IAAI,CAAC,IAAI;EACT,QAAQ;EAER,IAAI,OAAO,mBAAmB,aAAa;EAC3C,MAAM,WAAW,IAAI,eAAe,OAAO;EAC3C,SAAS,QAAQ,EAAE;EACnB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,SAAS,YAAY,CAAC;CAE1B,OAAO;AACT"}
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 in the selection to outlines, as one batch labeled
316
- * "Create Outlines". Other selected nodes are left alone and stay selected.
317
- * All or nothing: if any text node's face cannot supply outlines the scene is
318
- * untouched and the error comes back in the result.
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
- /** The sides of a box a gesture moves on one axis; null for neither. */
1253
- type SnapEdge = 'min' | 'max' | 'both' | null;
1254
- /** Which sides of the subject's box a gesture moves, per axis. */
1255
- interface SnapEdges {
1256
- x: SnapEdge;
1257
- y: SnapEdge;
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
- /** Which sides of `box` the gesture moves on each axis. Default: both, as
1278
- * a move does. */
1279
- edges?: SnapEdges;
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 or a ruler
1406
- * guide snaps: a pose neither a rect nor a `Path` needs its own. Default
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 SourceCandidate as $, type AnchorMarkerShape as A, type CreateOutlinesAdapter 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 PaintKindEntry as J, type PathEditTarget as K, type LayerHit as L, type FinishedEditFrame as M, type PathSymmetryDep as N, type BooleanOpOptions as O, type PathEditMark as P, type BooleansAdapter as Q, type RenderLayer as R, type SnapSource as S, type TextOverflow as T, type BooleanOp as U, type ViewAnimationOptions as V, type PathPaint as W, type BooleanOpResult as X, type BooleanStrokes as Y, type SnapMods as Z, _resetPaintKindsForTests as _, type PathEditExtension as a, type PreviewPathsDep as a$, type SnapProbeAt as a0, type SnapCollect as a1, type SnapMark as a2, type SnapDep as a3, type SnapOutcome as a4, type SnapReadoutStyle as a5, type AngleCandidate as a6, type AngleCollect as a7, type AnimateToBoundsOptions as a8, type AreaSelectDep as a9, type LayerRetainPolicy as aA, type LayoutDep as aB, type Locus as aC, type MinSize as aD, type NodeAtPointDep as aE, type OutlinePathSpec as aF, PATH_OVERLAY_ROLES as aG, type PaintKind as aH, type PaintKindEditorProps as aI, type PaintKindFamily as aJ, type PaintKindLoader as aK, type PanBounds as aL, type PathEditDrag as aM, type PathEditDragStart as aN, type PathEditFinishCtx as aO, type PathEditHandleMove as aP, type PathEditMarksCtx as aQ, type PathEditOther as aR, type PathEditPart as aS, type PathEditStore as aT, type PathForm as aU, type PathOverlay as aV, type PathOverlayOptions as aW, type PathOverlayStyle as aX, type PathRepresentation as aY, type PathTransformFinishCtx as aZ, type PoseChange as a_, type BooleanOpPreview as aa, type BooleanPreviewPiece as ab, type CanDropIntoDep as ac, type ClipboardDep as ad, type ClipboardIngestCtx as ae, type CreateOutlinesResult as af, type CustomPaintContext as ag, DEFAULT_LAYER_RETAIN as ah, type DecayLoopConfig as ai, type DropRefusal as aj, type DropVerdict as ak, type FinishedPathEdit as al, GEOMETRY_BOUND_DATA as am, type HandleLock as an, type HitTestView as ao, type IngestCtx as ap, type IngestionDep as aq, type InsertDep as ar, type InsertEnds as as, type InsertExtras as at, type InsertIntoDep as au, type InsertLive as av, type LassoSelectDep as aw, type LayerCommandCache as ax, type LayerDrawFailure as ay, type LayerDrawSpan as az, type SnapGesture as b, type ResizePolicy as b0, STICKY_DEFAULTS as b1, type SnapBegin as b2, type SnapEdge as b3, type SnapEdges as b4, type SnapFrame as b5, type SnapMarks as b6, type SnapReadoutDep as b7, type SvgUnpacker as b8, type TextEditDep as b9, paintsLayer as bA, paletteInsertPaint as bB, pathPaintOf as bC, patternFamily as bD, previewBooleanOp as bE, refusalOf as bF, registerContentHandler as bG, registerPaintKind as bH, registerPaintKindLoader as bI, seedTile as bJ, stickyData as bK, stickyNodeFactory as bL, subscribeToSources as bM, switchGradientKind as bN, textBoxOf as bO, useDecayLoop as bP, useEnteredGroup as bQ, useViewAnimation as bR, useViewAnimationOn as bS, viewAxes as bT, withoutGeometryBoundData as bU, type TextNodeData as ba, type TransformPreviewNode as bb, type TransientPath as bc, VIEW_ANIMATION_KEY as bd, type ViewAnimationApi as be, type ViewApi as bf, type ViewChannel as bg, applyBooleanOp as bh, applyCreateOutlines as bi, asPaint as bj, createEnteredGroup as bk, createPathOverlay as bl, createSnapOutcomeStore as bm, depsUnchanged as bn, drawLayers as bo, drawOneLayer as bp, fitViewToBounds as bq, getPaintKind as br, isLayerPainted as bs, isLayerVisible as bt, listGradientKinds as bu, listPaintKinds as bv, minSizeOf as bw, paintKindOf as bx, paintKindRegistry as by, paintedLayers as bz, type SnapConstrain as c, type SnapResizeBox as d, type SnapGeometry as e, type SnapOutcomeStore as f, type SnapSession as g, type LayerDims as h, type ViewportDims as i, type RedrawSource as j, type EditAnchorsParams as k, type SliceDep as l, type StickyStyle as m, type EnteredGroupApi as n, type ChromeState as o, type LayerGroup as p, type TransformFollower as q, type GeometryProjection as r, type InsertNodeFactory as s, type InsertPaint as t, type SvgIngestOptions as u, type SceneCanvasSnap as v, type Affordance as w, type AffordanceRegion as x, type AffordanceBinding as y, type CommonAffordanceScratch as z };
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"}