@fieldnotes/core 0.59.0 → 0.60.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/dist/index.cjs +152 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +152 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5272,6 +5272,7 @@ var ToolManager = class {
|
|
|
5272
5272
|
tools = /* @__PURE__ */ new Map();
|
|
5273
5273
|
current = null;
|
|
5274
5274
|
changeListeners = /* @__PURE__ */ new Set();
|
|
5275
|
+
registerListeners = /* @__PURE__ */ new Set();
|
|
5275
5276
|
get activeTool() {
|
|
5276
5277
|
return this.current;
|
|
5277
5278
|
}
|
|
@@ -5280,6 +5281,7 @@ var ToolManager = class {
|
|
|
5280
5281
|
}
|
|
5281
5282
|
register(tool) {
|
|
5282
5283
|
this.tools.set(tool.name, tool);
|
|
5284
|
+
this.registerListeners.forEach((fn) => fn(tool));
|
|
5283
5285
|
}
|
|
5284
5286
|
getTool(name) {
|
|
5285
5287
|
return this.tools.get(name);
|
|
@@ -5305,6 +5307,10 @@ var ToolManager = class {
|
|
|
5305
5307
|
this.changeListeners.add(listener);
|
|
5306
5308
|
return () => this.changeListeners.delete(listener);
|
|
5307
5309
|
}
|
|
5310
|
+
onRegister(listener) {
|
|
5311
|
+
this.registerListeners.add(listener);
|
|
5312
|
+
return () => this.registerListeners.delete(listener);
|
|
5313
|
+
}
|
|
5308
5314
|
};
|
|
5309
5315
|
|
|
5310
5316
|
// src/history/history-stack.ts
|
|
@@ -5496,6 +5502,7 @@ var HistoryRecorder = class {
|
|
|
5496
5502
|
generation = 0;
|
|
5497
5503
|
updateSnapshots = /* @__PURE__ */ new Map();
|
|
5498
5504
|
unsubscribers;
|
|
5505
|
+
transactionEndListeners = /* @__PURE__ */ new Set();
|
|
5499
5506
|
pause() {
|
|
5500
5507
|
this.recording = false;
|
|
5501
5508
|
}
|
|
@@ -5513,19 +5520,35 @@ var HistoryRecorder = class {
|
|
|
5513
5520
|
get currentTransactionId() {
|
|
5514
5521
|
return this.transaction !== null ? this.generation : null;
|
|
5515
5522
|
}
|
|
5523
|
+
onTransactionEnd(listener) {
|
|
5524
|
+
this.transactionEndListeners.add(listener);
|
|
5525
|
+
return () => this.transactionEndListeners.delete(listener);
|
|
5526
|
+
}
|
|
5527
|
+
notifyTransactionEnd() {
|
|
5528
|
+
for (const listener of this.transactionEndListeners) {
|
|
5529
|
+
try {
|
|
5530
|
+
listener();
|
|
5531
|
+
} catch {
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5516
5535
|
commit() {
|
|
5517
5536
|
if (!this.transaction) return;
|
|
5518
5537
|
const finalCommands = this.flushUpdateSnapshots();
|
|
5519
5538
|
const all = [...this.transaction, ...finalCommands];
|
|
5520
5539
|
this.transaction = null;
|
|
5521
5540
|
this.updateSnapshots.clear();
|
|
5522
|
-
if (all.length
|
|
5523
|
-
|
|
5524
|
-
|
|
5541
|
+
if (all.length > 0) {
|
|
5542
|
+
const first = all[0];
|
|
5543
|
+
this.stack.push(all.length === 1 && first ? first : new BatchCommand(all));
|
|
5544
|
+
}
|
|
5545
|
+
this.notifyTransactionEnd();
|
|
5525
5546
|
}
|
|
5526
5547
|
rollback() {
|
|
5548
|
+
const wasOpen = this.transaction !== null;
|
|
5527
5549
|
this.transaction = null;
|
|
5528
5550
|
this.updateSnapshots.clear();
|
|
5551
|
+
if (wasOpen) this.notifyTransactionEnd();
|
|
5529
5552
|
}
|
|
5530
5553
|
destroy() {
|
|
5531
5554
|
this.unsubscribers.forEach((fn) => fn());
|
|
@@ -7817,6 +7840,14 @@ function rotateElementPatch(el, bounds, pivot, delta) {
|
|
|
7817
7840
|
}
|
|
7818
7841
|
|
|
7819
7842
|
// src/canvas/selection-ops.ts
|
|
7843
|
+
var STYLE_FIELDS = [
|
|
7844
|
+
"color",
|
|
7845
|
+
"fillColor",
|
|
7846
|
+
"strokeWidth",
|
|
7847
|
+
"opacity",
|
|
7848
|
+
"fontSize",
|
|
7849
|
+
"strokeStyle"
|
|
7850
|
+
];
|
|
7820
7851
|
function sharedValue(values) {
|
|
7821
7852
|
const present = values.filter((v) => v !== void 0);
|
|
7822
7853
|
if (present.length === 0) return void 0;
|
|
@@ -7851,6 +7882,42 @@ var SelectionOps = class {
|
|
|
7851
7882
|
if (strokeStyle !== void 0) result.strokeStyle = strokeStyle;
|
|
7852
7883
|
return result;
|
|
7853
7884
|
}
|
|
7885
|
+
/**
|
|
7886
|
+
* Unlike `getStyle()` — which returns `{}` for a selection whose elements
|
|
7887
|
+
* have no applicable style fields (e.g. images) — this returns `null` when
|
|
7888
|
+
* no field is applicable, so callers can distinguish "nothing to show" from
|
|
7889
|
+
* "everything shared but empty".
|
|
7890
|
+
*/
|
|
7891
|
+
getStyleDetails() {
|
|
7892
|
+
const ids = this.deps.getSelectedIds();
|
|
7893
|
+
if (ids.length === 0) return null;
|
|
7894
|
+
const styles = [];
|
|
7895
|
+
for (const id of ids) {
|
|
7896
|
+
const el = this.deps.store.getById(id);
|
|
7897
|
+
if (el) styles.push(getElementStyle(el));
|
|
7898
|
+
}
|
|
7899
|
+
if (styles.length === 0) return null;
|
|
7900
|
+
const common = {};
|
|
7901
|
+
const applicable = [];
|
|
7902
|
+
const mixed = [];
|
|
7903
|
+
for (const field of STYLE_FIELDS) {
|
|
7904
|
+
const allValues = styles.map((s) => s[field]);
|
|
7905
|
+
const values = [];
|
|
7906
|
+
for (const v of allValues) {
|
|
7907
|
+
if (v !== void 0) values.push(v);
|
|
7908
|
+
}
|
|
7909
|
+
if (values.length === 0) continue;
|
|
7910
|
+
applicable.push(field);
|
|
7911
|
+
const distinct = new Set(values);
|
|
7912
|
+
if (distinct.size > 1) {
|
|
7913
|
+
mixed.push(field);
|
|
7914
|
+
} else {
|
|
7915
|
+
common[field] = values[0];
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
if (applicable.length === 0) return null;
|
|
7919
|
+
return { common, applicable, mixed };
|
|
7920
|
+
}
|
|
7854
7921
|
applyStyle(style) {
|
|
7855
7922
|
const ids = this.deps.getSelectedIds();
|
|
7856
7923
|
if (ids.length === 0) return;
|
|
@@ -8225,7 +8292,7 @@ var ViewportInteractions = class {
|
|
|
8225
8292
|
var EMPTY_IDS = [];
|
|
8226
8293
|
function noop() {
|
|
8227
8294
|
}
|
|
8228
|
-
var Viewport = class {
|
|
8295
|
+
var Viewport = class _Viewport {
|
|
8229
8296
|
constructor(container, options = {}) {
|
|
8230
8297
|
this.container = container;
|
|
8231
8298
|
this.camera = new Camera(options.camera);
|
|
@@ -8234,6 +8301,13 @@ var Viewport = class {
|
|
|
8234
8301
|
this.store = new ElementStore();
|
|
8235
8302
|
this.layerManager = new LayerManager(this.store);
|
|
8236
8303
|
this.toolManager = new ToolManager();
|
|
8304
|
+
this.unsubToolRegister = this.toolManager.onRegister((tool) => {
|
|
8305
|
+
if (_Viewport.isSelectionSource(tool)) this.attachSelectionSource(tool);
|
|
8306
|
+
});
|
|
8307
|
+
const existingSelect = this.getSelectTool();
|
|
8308
|
+
if (existingSelect && _Viewport.isSelectionSource(existingSelect)) {
|
|
8309
|
+
this.attachSelectionSource(existingSelect);
|
|
8310
|
+
}
|
|
8237
8311
|
this.renderer = new ElementRenderer();
|
|
8238
8312
|
this.renderer.setStore(this.store);
|
|
8239
8313
|
this.renderer.setCamera(this.camera);
|
|
@@ -8268,6 +8342,11 @@ var Viewport = class {
|
|
|
8268
8342
|
this.dropHandler = options.onDrop;
|
|
8269
8343
|
this.history = new HistoryStack();
|
|
8270
8344
|
this.historyRecorder = new HistoryRecorder(this.store, this.history, this.layerManager);
|
|
8345
|
+
this.unsubRecorderEnd = this.historyRecorder.onTransactionEnd(() => {
|
|
8346
|
+
if (!this.pendingSelectionPrune) return;
|
|
8347
|
+
this.pendingSelectionPrune = false;
|
|
8348
|
+
this.pruneSelection();
|
|
8349
|
+
});
|
|
8271
8350
|
this.selectionOps = new SelectionOps({
|
|
8272
8351
|
store: this.store,
|
|
8273
8352
|
recorder: this.historyRecorder,
|
|
@@ -8387,6 +8466,7 @@ var Viewport = class {
|
|
|
8387
8466
|
this.domNodeManager.removeDomNode(el.id);
|
|
8388
8467
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8389
8468
|
this.requestRender();
|
|
8469
|
+
this.handleRemovedElement(el.id);
|
|
8390
8470
|
}),
|
|
8391
8471
|
this.store.on("update", ({ previous, current }) => {
|
|
8392
8472
|
if (current.type === "grid") this.gridController.syncContext();
|
|
@@ -8401,6 +8481,7 @@ var Viewport = class {
|
|
|
8401
8481
|
this.renderLoop.markAllLayersDirty();
|
|
8402
8482
|
this.gridController.syncContext();
|
|
8403
8483
|
this.requestRender();
|
|
8484
|
+
this.pruneSelection();
|
|
8404
8485
|
})
|
|
8405
8486
|
];
|
|
8406
8487
|
this.layerManager.on("change", () => {
|
|
@@ -8470,6 +8551,13 @@ var Viewport = class {
|
|
|
8470
8551
|
minimap = null;
|
|
8471
8552
|
htmlRenderers = /* @__PURE__ */ new Map();
|
|
8472
8553
|
resizeListeners = /* @__PURE__ */ new Set();
|
|
8554
|
+
selectionListeners = /* @__PURE__ */ new Set();
|
|
8555
|
+
detachSelectionSource = null;
|
|
8556
|
+
unsubToolRegister = () => {
|
|
8557
|
+
};
|
|
8558
|
+
pendingSelectionPrune = false;
|
|
8559
|
+
unsubRecorderEnd = () => {
|
|
8560
|
+
};
|
|
8473
8561
|
get ctx() {
|
|
8474
8562
|
return this.canvasEl.getContext("2d");
|
|
8475
8563
|
}
|
|
@@ -8728,6 +8816,42 @@ var Viewport = class {
|
|
|
8728
8816
|
getSelectTool() {
|
|
8729
8817
|
return this.toolManager.getTool("select");
|
|
8730
8818
|
}
|
|
8819
|
+
pruneSelection() {
|
|
8820
|
+
const tool = this.getSelectTool();
|
|
8821
|
+
if (!tool) return;
|
|
8822
|
+
const ids = tool.selectedIds;
|
|
8823
|
+
const filtered = ids.filter((id) => this.store.getById(id) !== void 0);
|
|
8824
|
+
if (filtered.length !== ids.length) tool.setSelection(filtered);
|
|
8825
|
+
}
|
|
8826
|
+
handleRemovedElement(id) {
|
|
8827
|
+
if (!this.getSelectedIds().includes(id)) return;
|
|
8828
|
+
if (this.historyRecorder.currentTransactionId !== null) {
|
|
8829
|
+
this.pendingSelectionPrune = true;
|
|
8830
|
+
return;
|
|
8831
|
+
}
|
|
8832
|
+
this.pruneSelection();
|
|
8833
|
+
}
|
|
8834
|
+
static isSelectionSource(tool) {
|
|
8835
|
+
const candidate = tool;
|
|
8836
|
+
return tool.name === "select" && typeof candidate.onSelectionChange === "function" && typeof candidate.setSelection === "function";
|
|
8837
|
+
}
|
|
8838
|
+
emitSelectionChange() {
|
|
8839
|
+
for (const listener of this.selectionListeners) {
|
|
8840
|
+
try {
|
|
8841
|
+
listener();
|
|
8842
|
+
} catch {
|
|
8843
|
+
}
|
|
8844
|
+
}
|
|
8845
|
+
}
|
|
8846
|
+
attachSelectionSource(tool) {
|
|
8847
|
+
this.detachSelectionSource?.();
|
|
8848
|
+
this.detachSelectionSource = tool.onSelectionChange(() => this.emitSelectionChange());
|
|
8849
|
+
}
|
|
8850
|
+
/**
|
|
8851
|
+
* getSelectedIds() and the onSelectionChange emitter never surface stale ids:
|
|
8852
|
+
* once the enclosing history transaction completes, both reflect
|
|
8853
|
+
* the current selection.
|
|
8854
|
+
*/
|
|
8731
8855
|
getSelectedIds() {
|
|
8732
8856
|
return this.getSelectTool()?.selectedIds ?? EMPTY_IDS;
|
|
8733
8857
|
}
|
|
@@ -8761,13 +8885,29 @@ var Viewport = class {
|
|
|
8761
8885
|
if (items.length === 0) return;
|
|
8762
8886
|
this.contextMenu.open(items, screenPos);
|
|
8763
8887
|
}
|
|
8888
|
+
/**
|
|
8889
|
+
* Persistent, viewport-owned selection-change emitter. Subscribing works
|
|
8890
|
+
* regardless of whether a select tool is registered yet; it forwards
|
|
8891
|
+
* events from whichever select tool is currently attached via
|
|
8892
|
+
* `toolManager.onRegister`. Never delivers stale ids once the enclosing
|
|
8893
|
+
* history transaction completes.
|
|
8894
|
+
*/
|
|
8764
8895
|
onSelectionChange(listener) {
|
|
8765
|
-
|
|
8766
|
-
return
|
|
8896
|
+
this.selectionListeners.add(listener);
|
|
8897
|
+
return () => {
|
|
8898
|
+
this.selectionListeners.delete(listener);
|
|
8899
|
+
};
|
|
8767
8900
|
}
|
|
8768
8901
|
getSelectionStyle() {
|
|
8769
8902
|
return this.selectionOps.getStyle();
|
|
8770
8903
|
}
|
|
8904
|
+
/**
|
|
8905
|
+
* Unlike `getSelectionStyle()` — which returns `{}` for a style-less
|
|
8906
|
+
* selection — this returns `null` when no style field is applicable.
|
|
8907
|
+
*/
|
|
8908
|
+
getSelectionStyleDetails() {
|
|
8909
|
+
return this.selectionOps.getStyleDetails();
|
|
8910
|
+
}
|
|
8771
8911
|
applyStyleToSelection(style) {
|
|
8772
8912
|
this.selectionOps.applyStyle(style);
|
|
8773
8913
|
}
|
|
@@ -8816,6 +8956,11 @@ var Viewport = class {
|
|
|
8816
8956
|
this.inputHandler.destroy();
|
|
8817
8957
|
this.unsubCamera();
|
|
8818
8958
|
this.unsubToolChange();
|
|
8959
|
+
this.unsubToolRegister();
|
|
8960
|
+
this.unsubRecorderEnd();
|
|
8961
|
+
this.detachSelectionSource?.();
|
|
8962
|
+
this.detachSelectionSource = null;
|
|
8963
|
+
this.selectionListeners.clear();
|
|
8819
8964
|
this.unsubStore.forEach((fn) => fn());
|
|
8820
8965
|
this.resizeObserver?.disconnect();
|
|
8821
8966
|
this.resizeObserver = null;
|
|
@@ -12417,7 +12562,7 @@ var PingTool = class {
|
|
|
12417
12562
|
};
|
|
12418
12563
|
|
|
12419
12564
|
// src/index.ts
|
|
12420
|
-
var VERSION = "0.
|
|
12565
|
+
var VERSION = "0.60.0";
|
|
12421
12566
|
// Annotate the CommonJS export names for ESM import in node:
|
|
12422
12567
|
0 && (module.exports = {
|
|
12423
12568
|
ArrowTool,
|