@fieldnotes/core 0.33.0 → 0.34.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.d.cts CHANGED
@@ -20,6 +20,8 @@ interface BaseElement {
20
20
  zIndex: number;
21
21
  locked: boolean;
22
22
  layerId: string;
23
+ /** Optional flat group membership. Elements sharing a groupId select/move/delete as a unit. */
24
+ groupId?: string;
23
25
  }
24
26
  interface StrokeElement extends BaseElement {
25
27
  type: 'stroke';
@@ -523,6 +525,8 @@ declare class Viewport {
523
525
  onSelectionChange(listener: () => void): () => void;
524
526
  getSelectionStyle(): ElementStyle | null;
525
527
  applyStyleToSelection(style: ElementStyle): void;
528
+ groupSelection(): void;
529
+ ungroupSelection(): void;
526
530
  alignSelection(edge: AlignEdge): void;
527
531
  distributeSelection(axis: DistributeAxis): void;
528
532
  private boundedSelection;
@@ -988,6 +992,6 @@ declare class TemplateTool implements Tool {
988
992
  private notifyOptionsChange;
989
993
  }
990
994
 
991
- declare const VERSION = "0.33.0";
995
+ declare const VERSION = "0.34.0";
992
996
 
993
997
  export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ interface BaseElement {
20
20
  zIndex: number;
21
21
  locked: boolean;
22
22
  layerId: string;
23
+ /** Optional flat group membership. Elements sharing a groupId select/move/delete as a unit. */
24
+ groupId?: string;
23
25
  }
24
26
  interface StrokeElement extends BaseElement {
25
27
  type: 'stroke';
@@ -523,6 +525,8 @@ declare class Viewport {
523
525
  onSelectionChange(listener: () => void): () => void;
524
526
  getSelectionStyle(): ElementStyle | null;
525
527
  applyStyleToSelection(style: ElementStyle): void;
528
+ groupSelection(): void;
529
+ ungroupSelection(): void;
526
530
  alignSelection(edge: AlignEdge): void;
527
531
  distributeSelection(axis: DistributeAxis): void;
528
532
  private boundedSelection;
@@ -988,6 +992,6 @@ declare class TemplateTool implements Tool {
988
992
  private notifyOptionsChange;
989
993
  }
990
994
 
991
- declare const VERSION = "0.33.0";
995
+ declare const VERSION = "0.34.0";
992
996
 
993
997
  export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.js CHANGED
@@ -940,6 +940,14 @@ var KeyboardActions = class {
940
940
  if (this.deps.isToolActive()) return;
941
941
  this.deps.fitToContent?.();
942
942
  }
943
+ group() {
944
+ if (this.deps.isToolActive()) return;
945
+ this.deps.group?.();
946
+ }
947
+ ungroup() {
948
+ if (this.deps.isToolActive()) return;
949
+ this.deps.ungroup?.();
950
+ }
943
951
  zOrder(operation) {
944
952
  if (this.deps.isToolActive()) return;
945
953
  this.flushPendingNudge();
@@ -973,6 +981,10 @@ var KeyboardActions = class {
973
981
  for (const el of source) {
974
982
  idMap.set(el.id, createId(el.type));
975
983
  }
984
+ const groupIdMap = /* @__PURE__ */ new Map();
985
+ for (const el of source) {
986
+ if (el.groupId && !groupIdMap.has(el.groupId)) groupIdMap.set(el.groupId, createId("group"));
987
+ }
976
988
  const newIds = [];
977
989
  const recorder = this.deps.getHistoryRecorder();
978
990
  recorder?.begin();
@@ -981,6 +993,7 @@ var KeyboardActions = class {
981
993
  const newId = idMap.get(el.id);
982
994
  if (!newId) continue;
983
995
  clone.id = newId;
996
+ if (clone.groupId) clone.groupId = groupIdMap.get(clone.groupId) ?? clone.groupId;
984
997
  clone.position = { x: clone.position.x + offset.x, y: clone.position.y + offset.y };
985
998
  if (clone.type === "arrow") {
986
999
  const arrow = clone;
@@ -1034,6 +1047,8 @@ var DEFAULT_BINDINGS = [
1034
1047
  ["zoom-in", ["mod+="]],
1035
1048
  ["zoom-out", ["mod+-"]],
1036
1049
  ["zoom-reset", ["mod+0"]],
1050
+ ["group", ["mod+g"]],
1051
+ ["ungroup", ["mod+shift+g"]],
1037
1052
  ["nudge-left", ["arrowleft"]],
1038
1053
  ["nudge-right", ["arrowright"]],
1039
1054
  ["nudge-up", ["arrowup"]],
@@ -1193,6 +1208,8 @@ var InputHandler = class {
1193
1208
  getHistoryStack: () => this.historyStack,
1194
1209
  isToolActive: () => this.isToolActive,
1195
1210
  fitToContent: options.fitToContent,
1211
+ group: options.group,
1212
+ ungroup: options.ungroup,
1196
1213
  getLastPointerWorld: () => this.lastPointerWorld()
1197
1214
  });
1198
1215
  this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
@@ -1432,6 +1449,14 @@ var InputHandler = class {
1432
1449
  e.preventDefault();
1433
1450
  this.actions.zoomToFit();
1434
1451
  return;
1452
+ case "group":
1453
+ e.preventDefault();
1454
+ this.actions.group();
1455
+ return;
1456
+ case "ungroup":
1457
+ e.preventDefault();
1458
+ this.actions.ungroup();
1459
+ return;
1435
1460
  case "zoom-in":
1436
1461
  e.preventDefault();
1437
1462
  this.zoomByFactor(ZOOM_STEP);
@@ -3937,12 +3962,19 @@ var UpdateElementCommand = class {
3937
3962
  this.current = current;
3938
3963
  }
3939
3964
  execute(store) {
3940
- store.update(this.id, { ...this.current });
3965
+ store.update(this.id, diffPatch(this.previous, this.current));
3941
3966
  }
3942
3967
  undo(store) {
3943
- store.update(this.id, { ...this.previous });
3968
+ store.update(this.id, diffPatch(this.current, this.previous));
3944
3969
  }
3945
3970
  };
3971
+ function diffPatch(from, to) {
3972
+ const patch = { ...to };
3973
+ for (const key of Object.keys(from)) {
3974
+ if (!(key in to)) patch[key] = void 0;
3975
+ }
3976
+ return patch;
3977
+ }
3946
3978
  var BatchCommand = class {
3947
3979
  commands;
3948
3980
  constructor(commands) {
@@ -5505,6 +5537,8 @@ var Viewport = class {
5505
5537
  historyRecorder: this.historyRecorder,
5506
5538
  historyStack: this.history,
5507
5539
  fitToContent: () => this.fitToContent(),
5540
+ group: () => this.groupSelection(),
5541
+ ungroup: () => this.ungroupSelection(),
5508
5542
  shortcuts: options.shortcuts
5509
5543
  });
5510
5544
  this.domNodeManager = new DomNodeManager({
@@ -5842,6 +5876,26 @@ var Viewport = class {
5842
5876
  }
5843
5877
  this.historyRecorder.commit();
5844
5878
  }
5879
+ groupSelection() {
5880
+ const ids = this.getSelectedIds();
5881
+ if (ids.length < 2) return;
5882
+ const groupId = createId("group");
5883
+ this.historyRecorder.begin();
5884
+ for (const id of ids) {
5885
+ if (this.store.getById(id)) this.store.update(id, { groupId });
5886
+ }
5887
+ this.historyRecorder.commit();
5888
+ }
5889
+ ungroupSelection() {
5890
+ const ids = this.getSelectedIds();
5891
+ if (ids.length === 0) return;
5892
+ this.historyRecorder.begin();
5893
+ for (const id of ids) {
5894
+ const el = this.store.getById(id);
5895
+ if (el && el.groupId !== void 0) this.store.update(id, { groupId: void 0 });
5896
+ }
5897
+ this.historyRecorder.commit();
5898
+ }
5845
5899
  alignSelection(edge) {
5846
5900
  const bounded = this.boundedSelection();
5847
5901
  if (bounded.length < 2) return;
@@ -6551,6 +6605,26 @@ var EraserTool = class {
6551
6605
  }
6552
6606
  };
6553
6607
 
6608
+ // src/elements/group.ts
6609
+ function expandToGroups(ids, elements) {
6610
+ const byId = new Map(elements.map((e) => [e.id, e]));
6611
+ const groupIds = /* @__PURE__ */ new Set();
6612
+ for (const id of ids) {
6613
+ const g = byId.get(id)?.groupId;
6614
+ if (g) groupIds.add(g);
6615
+ }
6616
+ if (groupIds.size === 0) return ids;
6617
+ const idSet = new Set(ids);
6618
+ const result = [...ids];
6619
+ for (const el of elements) {
6620
+ if (el.groupId && groupIds.has(el.groupId) && !idSet.has(el.id)) {
6621
+ result.push(el.id);
6622
+ idSet.add(el.id);
6623
+ }
6624
+ }
6625
+ return result;
6626
+ }
6627
+
6554
6628
  // src/tools/arrow-handles.ts
6555
6629
  var BIND_THRESHOLD = 20;
6556
6630
  var HANDLE_RADIUS = 5;
@@ -6816,18 +6890,20 @@ var SelectTool = class {
6816
6890
  this.hasDragged = false;
6817
6891
  const hit = this.hitTest(world, ctx);
6818
6892
  if (hit) {
6893
+ const all = ctx.store.getAll();
6819
6894
  const alreadySelected = this._selectedIds.includes(hit.id);
6820
6895
  if (state.shiftKey) {
6821
6896
  if (alreadySelected) {
6822
- this.setSelectedIds(this._selectedIds.filter((id) => id !== hit.id));
6897
+ const grp = new Set(expandToGroups([hit.id], all));
6898
+ this.setSelectedIds(this._selectedIds.filter((id) => !grp.has(id)));
6823
6899
  this.mode = { type: "idle" };
6824
6900
  } else {
6825
- this.setSelectedIds([...this._selectedIds, hit.id]);
6901
+ this.setSelectedIds(expandToGroups([...this._selectedIds, hit.id], all));
6826
6902
  this.mode = hit.locked ? { type: "idle" } : { type: "dragging" };
6827
6903
  }
6828
6904
  } else {
6829
6905
  if (!alreadySelected) {
6830
- this.setSelectedIds([hit.id]);
6906
+ this.setSelectedIds(expandToGroups([hit.id], all));
6831
6907
  } else if (this._selectedIds.length > 1) {
6832
6908
  this.pendingSingleSelectId = hit.id;
6833
6909
  }
@@ -6941,12 +7017,12 @@ var SelectTool = class {
6941
7017
  if (this.mode.type === "marquee") {
6942
7018
  const rect = this.getMarqueeRect();
6943
7019
  if (rect) {
6944
- this.setSelectedIds(this.findElementsInRect(rect, ctx));
7020
+ this.setSelectedIds(expandToGroups(this.findElementsInRect(rect, ctx), ctx.store.getAll()));
6945
7021
  }
6946
7022
  ctx.requestRender();
6947
7023
  }
6948
7024
  if (!this.hasDragged && this.pendingSingleSelectId !== null) {
6949
- this.setSelectedIds([this.pendingSingleSelectId]);
7025
+ this.setSelectedIds(expandToGroups([this.pendingSingleSelectId], ctx.store.getAll()));
6950
7026
  }
6951
7027
  this.pendingSingleSelectId = null;
6952
7028
  this.hasDragged = false;
@@ -8231,7 +8307,7 @@ var TemplateTool = class {
8231
8307
  };
8232
8308
 
8233
8309
  // src/index.ts
8234
- var VERSION = "0.33.0";
8310
+ var VERSION = "0.34.0";
8235
8311
  export {
8236
8312
  ArrowTool,
8237
8313
  AutoSave,