@fieldnotes/core 0.18.0 → 0.19.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
@@ -432,12 +432,25 @@ declare class HistoryRecorder {
432
432
  private flushUpdateSnapshots;
433
433
  }
434
434
 
435
+ type ShortcutBindings = Record<string, string | string[] | null>;
436
+ interface ShortcutOptions {
437
+ scope?: 'focus' | 'window';
438
+ bindings?: ShortcutBindings;
439
+ }
440
+ interface ShortcutsApi {
441
+ rebind(action: string, bindings: string | string[] | null): void;
442
+ disable(action: string): void;
443
+ reset(action?: string): void;
444
+ getBindings(): Record<string, string[]>;
445
+ }
446
+
435
447
  interface InputHandlerOptions {
436
448
  toolManager?: ToolManager;
437
449
  toolContext?: ToolContext;
438
450
  historyRecorder?: HistoryRecorder;
439
451
  historyStack?: HistoryStack;
440
452
  fitToContent?: () => void;
453
+ shortcuts?: ShortcutOptions;
441
454
  }
442
455
  declare class InputHandler {
443
456
  private readonly element;
@@ -458,9 +471,12 @@ declare class InputHandler {
458
471
  private deferredDown;
459
472
  private readonly abortController;
460
473
  private readonly actions;
474
+ private readonly shortcutMap;
475
+ private readonly scope;
461
476
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
462
477
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
463
478
  flushPendingHistory(): void;
479
+ get shortcuts(): ShortcutsApi;
464
480
  destroy(): void;
465
481
  private bind;
466
482
  private onWheel;
@@ -469,6 +485,7 @@ declare class InputHandler {
469
485
  private onPointerUp;
470
486
  private onKeyDown;
471
487
  private onKeyUp;
488
+ private runAction;
472
489
  private startPinch;
473
490
  private handlePinchMove;
474
491
  private getPinchPoints;
@@ -479,6 +496,8 @@ declare class InputHandler {
479
496
  private dispatchToolMove;
480
497
  private dispatchToolHover;
481
498
  private dispatchToolUp;
499
+ private isInScope;
500
+ private focusSelf;
482
501
  private cancelToolIfActive;
483
502
  }
484
503
 
@@ -569,6 +588,7 @@ interface ViewportOptions {
569
588
  background?: BackgroundOptions;
570
589
  fontSizePresets?: FontSizePreset[];
571
590
  toolbar?: boolean;
591
+ shortcuts?: ShortcutOptions;
572
592
  onHtmlElementMount?: (elementId: string, domId: string | undefined, container: HTMLDivElement) => void;
573
593
  onDrop?: (event: DragEvent, worldPosition: {
574
594
  x: number;
@@ -617,6 +637,7 @@ declare class Viewport {
617
637
  loadState(state: CanvasState): void;
618
638
  loadJSON(json: string): void;
619
639
  setTool(name: string): void;
640
+ get shortcuts(): ShortcutsApi;
620
641
  undo(): boolean;
621
642
  redo(): boolean;
622
643
  addImage(src: string, position: {
@@ -1212,6 +1233,6 @@ declare class UpdateLayerCommand implements Command {
1212
1233
  undo(_store: ElementStore): void;
1213
1234
  }
1214
1235
 
1215
- declare const VERSION = "0.18.0";
1236
+ declare const VERSION = "0.19.0";
1216
1237
 
1217
- export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
1238
+ export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
package/dist/index.d.ts CHANGED
@@ -432,12 +432,25 @@ declare class HistoryRecorder {
432
432
  private flushUpdateSnapshots;
433
433
  }
434
434
 
435
+ type ShortcutBindings = Record<string, string | string[] | null>;
436
+ interface ShortcutOptions {
437
+ scope?: 'focus' | 'window';
438
+ bindings?: ShortcutBindings;
439
+ }
440
+ interface ShortcutsApi {
441
+ rebind(action: string, bindings: string | string[] | null): void;
442
+ disable(action: string): void;
443
+ reset(action?: string): void;
444
+ getBindings(): Record<string, string[]>;
445
+ }
446
+
435
447
  interface InputHandlerOptions {
436
448
  toolManager?: ToolManager;
437
449
  toolContext?: ToolContext;
438
450
  historyRecorder?: HistoryRecorder;
439
451
  historyStack?: HistoryStack;
440
452
  fitToContent?: () => void;
453
+ shortcuts?: ShortcutOptions;
441
454
  }
442
455
  declare class InputHandler {
443
456
  private readonly element;
@@ -458,9 +471,12 @@ declare class InputHandler {
458
471
  private deferredDown;
459
472
  private readonly abortController;
460
473
  private readonly actions;
474
+ private readonly shortcutMap;
475
+ private readonly scope;
461
476
  constructor(element: HTMLElement, camera: Camera, options?: InputHandlerOptions);
462
477
  setToolManager(toolManager: ToolManager, toolContext: ToolContext): void;
463
478
  flushPendingHistory(): void;
479
+ get shortcuts(): ShortcutsApi;
464
480
  destroy(): void;
465
481
  private bind;
466
482
  private onWheel;
@@ -469,6 +485,7 @@ declare class InputHandler {
469
485
  private onPointerUp;
470
486
  private onKeyDown;
471
487
  private onKeyUp;
488
+ private runAction;
472
489
  private startPinch;
473
490
  private handlePinchMove;
474
491
  private getPinchPoints;
@@ -479,6 +496,8 @@ declare class InputHandler {
479
496
  private dispatchToolMove;
480
497
  private dispatchToolHover;
481
498
  private dispatchToolUp;
499
+ private isInScope;
500
+ private focusSelf;
482
501
  private cancelToolIfActive;
483
502
  }
484
503
 
@@ -569,6 +588,7 @@ interface ViewportOptions {
569
588
  background?: BackgroundOptions;
570
589
  fontSizePresets?: FontSizePreset[];
571
590
  toolbar?: boolean;
591
+ shortcuts?: ShortcutOptions;
572
592
  onHtmlElementMount?: (elementId: string, domId: string | undefined, container: HTMLDivElement) => void;
573
593
  onDrop?: (event: DragEvent, worldPosition: {
574
594
  x: number;
@@ -617,6 +637,7 @@ declare class Viewport {
617
637
  loadState(state: CanvasState): void;
618
638
  loadJSON(json: string): void;
619
639
  setTool(name: string): void;
640
+ get shortcuts(): ShortcutsApi;
620
641
  undo(): boolean;
621
642
  redo(): boolean;
622
643
  addImage(src: string, position: {
@@ -1212,6 +1233,6 @@ declare class UpdateLayerCommand implements Command {
1212
1233
  undo(_store: ElementStore): void;
1213
1234
  }
1214
1235
 
1215
- declare const VERSION = "0.18.0";
1236
+ declare const VERSION = "0.19.0";
1216
1237
 
1217
- export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
1238
+ export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
package/dist/index.js CHANGED
@@ -1041,14 +1041,158 @@ var KeyboardActions = class {
1041
1041
  }
1042
1042
  };
1043
1043
 
1044
+ // src/canvas/shortcut-map.ts
1045
+ var DEFAULT_BINDINGS = [
1046
+ ["delete", ["delete", "backspace"]],
1047
+ ["deselect", ["escape"]],
1048
+ ["undo", ["mod+z"]],
1049
+ ["redo", ["mod+y", "mod+shift+z"]],
1050
+ ["select-all", ["mod+a"]],
1051
+ ["copy", ["mod+c"]],
1052
+ ["paste", ["mod+v"]],
1053
+ ["duplicate", ["mod+d"]],
1054
+ ["z-forward", ["]"]],
1055
+ ["z-backward", ["["]],
1056
+ ["z-front", ["mod+]"]],
1057
+ ["z-back", ["mod+["]],
1058
+ ["zoom-fit", ["shift+1"]],
1059
+ ["nudge-left", ["arrowleft"]],
1060
+ ["nudge-right", ["arrowright"]],
1061
+ ["nudge-up", ["arrowup"]],
1062
+ ["nudge-down", ["arrowdown"]],
1063
+ ["tool:select", ["v"]],
1064
+ ["tool:hand", ["h"]],
1065
+ ["tool:pencil", ["p"]],
1066
+ ["tool:eraser", ["e"]],
1067
+ ["tool:arrow", ["a"]],
1068
+ ["tool:note", ["n"]],
1069
+ ["tool:text", ["t"]],
1070
+ ["tool:shape", ["s"]],
1071
+ ["tool:measure", ["m"]],
1072
+ ["tool:template", ["g"]]
1073
+ ];
1074
+ var ALLOW_SHIFT = /* @__PURE__ */ new Set(["nudge-left", "nudge-right", "nudge-up", "nudge-down"]);
1075
+ var MODIFIERS = /* @__PURE__ */ new Set(["mod", "ctrl", "meta", "shift", "alt"]);
1076
+ function parseBinding(binding) {
1077
+ const parts = binding.toLowerCase().split("+");
1078
+ const key = parts.pop();
1079
+ if (key === void 0 || key.length === 0 || MODIFIERS.has(key)) {
1080
+ throw new Error(`Invalid shortcut binding "${binding}": missing key`);
1081
+ }
1082
+ const normalizedKey = key === "space" ? " " : key;
1083
+ const parsed = {
1084
+ mod: false,
1085
+ ctrl: false,
1086
+ meta: false,
1087
+ shift: false,
1088
+ alt: false,
1089
+ key: normalizedKey,
1090
+ digit: /^[0-9]$/.test(normalizedKey)
1091
+ };
1092
+ for (const part of parts) {
1093
+ switch (part) {
1094
+ case "mod":
1095
+ parsed.mod = true;
1096
+ break;
1097
+ case "ctrl":
1098
+ parsed.ctrl = true;
1099
+ break;
1100
+ case "meta":
1101
+ parsed.meta = true;
1102
+ break;
1103
+ case "shift":
1104
+ parsed.shift = true;
1105
+ break;
1106
+ case "alt":
1107
+ parsed.alt = true;
1108
+ break;
1109
+ default:
1110
+ throw new Error(`Invalid shortcut binding "${binding}": unknown modifier "${part}"`);
1111
+ }
1112
+ }
1113
+ return parsed;
1114
+ }
1115
+ function bindingMatches(p, e, allowShift) {
1116
+ if (p.mod) {
1117
+ if (!e.ctrlKey && !e.metaKey) return false;
1118
+ } else if (e.ctrlKey !== p.ctrl || e.metaKey !== p.meta) {
1119
+ return false;
1120
+ }
1121
+ if (!allowShift && e.shiftKey !== p.shift) return false;
1122
+ if (e.altKey !== p.alt) return false;
1123
+ return p.digit ? e.code === `Digit${p.key}` : e.key.toLowerCase() === p.key;
1124
+ }
1125
+ function toArray(bindings) {
1126
+ if (bindings === null) return [];
1127
+ return Array.isArray(bindings) ? [...bindings] : [bindings];
1128
+ }
1129
+ var ShortcutMap = class {
1130
+ raw = /* @__PURE__ */ new Map();
1131
+ parsed = /* @__PURE__ */ new Map();
1132
+ constructor(overrides) {
1133
+ this.applyDefaults();
1134
+ if (overrides) {
1135
+ for (const [action, bindings] of Object.entries(overrides)) {
1136
+ this.rebind(action, bindings);
1137
+ }
1138
+ }
1139
+ }
1140
+ /** First matching action in registration order wins when bindings conflict. */
1141
+ match(e) {
1142
+ for (const [action, parsedList] of this.parsed) {
1143
+ const allowShift = ALLOW_SHIFT.has(action);
1144
+ for (const p of parsedList) {
1145
+ if (bindingMatches(p, e, allowShift)) return action;
1146
+ }
1147
+ }
1148
+ return null;
1149
+ }
1150
+ rebind(action, bindings) {
1151
+ const list = toArray(bindings);
1152
+ const parsedList = list.map(parseBinding);
1153
+ this.raw.set(action, list);
1154
+ this.parsed.set(action, parsedList);
1155
+ }
1156
+ disable(action) {
1157
+ this.rebind(action, null);
1158
+ }
1159
+ reset(action) {
1160
+ if (action === void 0) {
1161
+ this.raw.clear();
1162
+ this.parsed.clear();
1163
+ this.applyDefaults();
1164
+ return;
1165
+ }
1166
+ const def = DEFAULT_BINDINGS.find(([name]) => name === action);
1167
+ if (def) {
1168
+ this.rebind(action, [...def[1]]);
1169
+ } else if (this.raw.has(action)) {
1170
+ this.raw.delete(action);
1171
+ this.parsed.delete(action);
1172
+ }
1173
+ }
1174
+ getBindings() {
1175
+ const out = {};
1176
+ for (const [action, list] of this.raw) {
1177
+ out[action] = [...list];
1178
+ }
1179
+ return out;
1180
+ }
1181
+ applyDefaults() {
1182
+ for (const [action, bindings] of DEFAULT_BINDINGS) {
1183
+ this.rebind(action, [...bindings]);
1184
+ }
1185
+ }
1186
+ };
1187
+
1044
1188
  // src/canvas/input-handler.ts
1045
1189
  var ZOOM_SENSITIVITY = 1e-3;
1046
1190
  var MIDDLE_BUTTON = 1;
1047
- var NUDGE_KEYS = {
1048
- ArrowLeft: [-1, 0],
1049
- ArrowRight: [1, 0],
1050
- ArrowUp: [0, -1],
1051
- ArrowDown: [0, 1]
1191
+ var NUDGE_DELTAS = {
1192
+ "nudge-left": [-1, 0],
1193
+ "nudge-right": [1, 0],
1194
+ "nudge-up": [0, -1],
1195
+ "nudge-down": [0, 1]
1052
1196
  };
1053
1197
  var InputHandler = class {
1054
1198
  constructor(element, camera, options = {}) {
@@ -1066,7 +1210,13 @@ var InputHandler = class {
1066
1210
  isToolActive: () => this.isToolActive,
1067
1211
  fitToContent: options.fitToContent
1068
1212
  });
1213
+ this.shortcutMap = new ShortcutMap(options.shortcuts?.bindings);
1214
+ this.scope = options.shortcuts?.scope ?? "focus";
1069
1215
  this.element.style.touchAction = "none";
1216
+ if (this.scope === "focus") {
1217
+ this.element.tabIndex = 0;
1218
+ this.element.style.outline = "none";
1219
+ }
1070
1220
  this.bind();
1071
1221
  }
1072
1222
  isPanning = false;
@@ -1085,6 +1235,8 @@ var InputHandler = class {
1085
1235
  deferredDown = null;
1086
1236
  abortController = new AbortController();
1087
1237
  actions;
1238
+ shortcutMap;
1239
+ scope;
1088
1240
  setToolManager(toolManager, toolContext) {
1089
1241
  this.toolManager = toolManager;
1090
1242
  this.toolContext = toolContext;
@@ -1092,6 +1244,9 @@ var InputHandler = class {
1092
1244
  flushPendingHistory() {
1093
1245
  this.actions.flushPendingNudge();
1094
1246
  }
1247
+ get shortcuts() {
1248
+ return this.shortcutMap;
1249
+ }
1095
1250
  destroy() {
1096
1251
  this.actions.dispose();
1097
1252
  this.abortController.abort();
@@ -1121,6 +1276,7 @@ var InputHandler = class {
1121
1276
  });
1122
1277
  };
1123
1278
  onPointerDown = (e) => {
1279
+ this.focusSelf();
1124
1280
  this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
1125
1281
  this.element.setPointerCapture?.(e.pointerId);
1126
1282
  if (this.activePointers.size === 2) {
@@ -1203,57 +1359,13 @@ var InputHandler = class {
1203
1359
  if (target?.isContentEditable) return;
1204
1360
  const tag = target?.tagName;
1205
1361
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1362
+ if (!this.isInScope()) return;
1206
1363
  if (e.key === " ") {
1207
1364
  this.spaceHeld = true;
1208
1365
  }
1209
- if (e.key === "Delete" || e.key === "Backspace") {
1210
- this.actions.deleteSelected();
1211
- }
1212
- if (e.key === "Escape") {
1213
- this.actions.deselect();
1214
- }
1215
- if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
1216
- e.preventDefault();
1217
- this.actions.undo();
1218
- }
1219
- if ((e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey)) {
1220
- e.preventDefault();
1221
- this.actions.redo();
1222
- }
1223
- if ((e.ctrlKey || e.metaKey) && e.key === "a") {
1224
- e.preventDefault();
1225
- this.actions.selectAll();
1226
- }
1227
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
1228
- e.preventDefault();
1229
- this.actions.copy();
1230
- }
1231
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
1232
- e.preventDefault();
1233
- this.actions.paste();
1234
- }
1235
- if ((e.ctrlKey || e.metaKey) && e.key === "d") {
1236
- e.preventDefault();
1237
- this.actions.duplicate();
1238
- }
1239
- if (e.key === "]") {
1240
- e.preventDefault();
1241
- this.actions.zOrder(e.ctrlKey || e.metaKey ? "front" : "forward");
1242
- }
1243
- if (e.key === "[") {
1244
- e.preventDefault();
1245
- this.actions.zOrder(e.ctrlKey || e.metaKey ? "back" : "backward");
1246
- }
1247
- if (e.shiftKey && e.code === "Digit1" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1248
- e.preventDefault();
1249
- this.actions.zoomToFit();
1250
- }
1251
- const nudgeDelta = NUDGE_KEYS[e.key];
1252
- if (nudgeDelta) {
1253
- const [dx, dy] = nudgeDelta;
1254
- if (this.actions.nudge(dx, dy, e.shiftKey)) {
1255
- e.preventDefault();
1256
- }
1366
+ const action = this.shortcutMap.match(e);
1367
+ if (action !== null) {
1368
+ this.runAction(action, e);
1257
1369
  }
1258
1370
  };
1259
1371
  onKeyUp = (e) => {
@@ -1268,6 +1380,79 @@ var InputHandler = class {
1268
1380
  }
1269
1381
  }
1270
1382
  };
1383
+ runAction(action, e) {
1384
+ switch (action) {
1385
+ case "delete":
1386
+ e.preventDefault();
1387
+ this.actions.deleteSelected();
1388
+ return;
1389
+ case "deselect":
1390
+ this.actions.deselect();
1391
+ return;
1392
+ case "undo":
1393
+ e.preventDefault();
1394
+ this.actions.undo();
1395
+ return;
1396
+ case "redo":
1397
+ e.preventDefault();
1398
+ this.actions.redo();
1399
+ return;
1400
+ case "select-all":
1401
+ e.preventDefault();
1402
+ this.actions.selectAll();
1403
+ return;
1404
+ case "copy":
1405
+ e.preventDefault();
1406
+ this.actions.copy();
1407
+ return;
1408
+ case "paste":
1409
+ e.preventDefault();
1410
+ this.actions.paste();
1411
+ return;
1412
+ case "duplicate":
1413
+ e.preventDefault();
1414
+ this.actions.duplicate();
1415
+ return;
1416
+ case "z-forward":
1417
+ e.preventDefault();
1418
+ this.actions.zOrder("forward");
1419
+ return;
1420
+ case "z-backward":
1421
+ e.preventDefault();
1422
+ this.actions.zOrder("backward");
1423
+ return;
1424
+ case "z-front":
1425
+ e.preventDefault();
1426
+ this.actions.zOrder("front");
1427
+ return;
1428
+ case "z-back":
1429
+ e.preventDefault();
1430
+ this.actions.zOrder("back");
1431
+ return;
1432
+ case "zoom-fit":
1433
+ e.preventDefault();
1434
+ this.actions.zoomToFit();
1435
+ return;
1436
+ case "nudge-left":
1437
+ case "nudge-right":
1438
+ case "nudge-up":
1439
+ case "nudge-down": {
1440
+ const delta = NUDGE_DELTAS[action];
1441
+ if (delta && this.actions.nudge(delta[0], delta[1], e.shiftKey)) {
1442
+ e.preventDefault();
1443
+ }
1444
+ return;
1445
+ }
1446
+ default:
1447
+ if (action.startsWith("tool:")) {
1448
+ if (this.isToolActive) return;
1449
+ e.preventDefault();
1450
+ this.toolContext?.switchTool?.(action.slice("tool:".length));
1451
+ return;
1452
+ }
1453
+ console.warn(`[fieldnotes] unknown shortcut action "${action}"`);
1454
+ }
1455
+ }
1271
1456
  startPinch() {
1272
1457
  this.inputFilter.reset();
1273
1458
  this.deferredDown = null;
@@ -1338,6 +1523,15 @@ var InputHandler = class {
1338
1523
  this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
1339
1524
  this.historyRecorder?.commit();
1340
1525
  }
1526
+ isInScope() {
1527
+ if (this.scope === "window") return true;
1528
+ const active = document.activeElement;
1529
+ return active === this.element || this.element.contains(active);
1530
+ }
1531
+ focusSelf() {
1532
+ if (this.scope !== "focus" || this.isInScope()) return;
1533
+ this.element.focus({ preventScroll: true });
1534
+ }
1341
1535
  cancelToolIfActive(e) {
1342
1536
  if (this.isToolActive) {
1343
1537
  this.dispatchToolUp(e);
@@ -4732,7 +4926,8 @@ var Viewport = class {
4732
4926
  toolContext: this.toolContext,
4733
4927
  historyRecorder: this.historyRecorder,
4734
4928
  historyStack: this.history,
4735
- fitToContent: () => this.fitToContent()
4929
+ fitToContent: () => this.fitToContent(),
4930
+ shortcuts: options.shortcuts
4736
4931
  });
4737
4932
  this.domNodeManager = new DomNodeManager({
4738
4933
  domLayer: this.domLayer,
@@ -4906,6 +5101,9 @@ var Viewport = class {
4906
5101
  setTool(name) {
4907
5102
  this.toolManager.setTool(name, this.toolContext);
4908
5103
  }
5104
+ get shortcuts() {
5105
+ return this.inputHandler.shortcuts;
5106
+ }
4909
5107
  undo() {
4910
5108
  this.inputHandler.flushPendingHistory();
4911
5109
  this.historyRecorder.pause();
@@ -6944,7 +7142,7 @@ var TemplateTool = class {
6944
7142
  };
6945
7143
 
6946
7144
  // src/index.ts
6947
- var VERSION = "0.18.0";
7145
+ var VERSION = "0.19.0";
6948
7146
  export {
6949
7147
  AddElementCommand,
6950
7148
  ArrowTool,