"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

// src/gestures/index.ts
var index_exports = {};
__export(index_exports, {
  ACTIVE_INTERACTION_CONTEXT: () => ACTIVE_INTERACTION_CONTEXT,
  DEFAULT_CONTEXT: () => DEFAULT_CONTEXT,
  GestureProvider: () => GestureProvider,
  IDLE: () => IDLE,
  INPUT_MODE_CONTEXTS: () => INPUT_MODE_CONTEXTS,
  InputProvider: () => InputProvider,
  KEYBOARD_MANIPULATE_CONTEXT: () => KEYBOARD_MANIPULATE_CONTEXT,
  KEYBOARD_NAVIGATE_CONTEXT: () => KEYBOARD_NAVIGATE_CONTEXT,
  LONG_PRESS_TIMER: () => LONG_PRESS_TIMER,
  NO_HELD_KEYS: () => NO_HELD_KEYS,
  NO_MODIFIERS: () => NO_MODIFIERS,
  PALM_REJECTION_CONTEXT: () => PALM_REJECTION_CONTEXT,
  PICK_NODES_CONTEXT: () => PICK_NODES_CONTEXT,
  PICK_NODE_CONTEXT: () => PICK_NODE_CONTEXT,
  PICK_POINT_CONTEXT: () => PICK_POINT_CONTEXT,
  PanInertia: () => PanInertia,
  SEARCH_CONTEXT: () => SEARCH_CONTEXT,
  SETTLE_TIMER: () => SETTLE_TIMER,
  TimedStateRunner: () => TimedStateRunner,
  VelocitySampler: () => VelocitySampler,
  ZoomInertia: () => ZoomInertia,
  activateFocusedNode: () => activateFocusedNode,
  buildMappingIndex: () => buildMappingIndex,
  cancelActiveInteraction: () => cancelActiveInteraction,
  clearHandlers: () => clearHandlers,
  createPinchHandlers: () => createPinchHandlers,
  createWheelHandler: () => createWheelHandler,
  cutSelection: () => cutSelection,
  cycleFocus: () => cycleFocus,
  deleteSelection: () => deleteSelection,
  dispatch: () => dispatch,
  escapeInput: () => escapeInput,
  extractModifiers: () => extractModifiers,
  findNearestNode: () => findNearestNode,
  getCurrentSubject: () => getCurrentSubject,
  getHandler: () => getHandler,
  indexContext: () => indexContext,
  isKeyInputEvent: () => isKeyInputEvent,
  isPointerGestureEvent: () => isPointerGestureEvent,
  navigateFocus: () => navigateFocus,
  normalizePointer: () => normalizePointer,
  nudgeSelection: () => nudgeSelection,
  registerAction: () => registerAction,
  resolve: () => resolve,
  snapZoom: () => snapZoom,
  specificity: () => specificity,
  transition: () => transition,
  unregisterAction: () => unregisterAction,
  useCanvasGestures: () => useCanvasGestures,
  useGestureContext: () => useGestureContext,
  useGestureSystem: () => useGestureSystem,
  useGuardContext: () => useGuardContext,
  useInertia: () => useInertia,
  useInputContext: () => useInputContext,
  useInputModeGestureContext: () => useInputModeGestureContext,
  useInputSystem: () => useInputSystem,
  useNodeGestures: () => useNodeGestures,
  useRegisterInputActions: () => useRegisterInputActions
});
module.exports = __toCommonJS(index_exports);

// src/gestures/types.ts
var NO_MODIFIERS = Object.freeze({
  shift: false,
  ctrl: false,
  alt: false,
  meta: false
});
var NO_HELD_KEYS = Object.freeze({
  byKey: Object.freeze({}),
  byCode: Object.freeze({})
});
function isKeyInputEvent(event) {
  return event.kind === "key";
}
function isPointerGestureEvent(event) {
  return event.kind !== "key";
}

// src/core/input-classifier.ts
function classifyPointer(e) {
  const source = pointerTypeToSource(e.pointerType);
  return {
    source,
    pointerId: e.pointerId,
    pressure: e.pressure,
    tiltX: e.tiltX,
    tiltY: e.tiltY,
    isPrimary: e.isPrimary,
    rawPointerType: e.pointerType
  };
}
function pointerTypeToSource(pointerType) {
  switch (pointerType) {
    case "pen":
      return "pencil";
    case "touch":
      return "finger";
    case "mouse":
      return "mouse";
    default:
      return "mouse";
  }
}
function detectInputCapabilities() {
  if (typeof window === "undefined") {
    return {
      hasTouch: false,
      hasStylus: false,
      hasMouse: true,
      hasCoarsePointer: false
    };
  }
  const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
  const supportsMatchMedia = typeof window.matchMedia === "function";
  const hasCoarsePointer = supportsMatchMedia ? window.matchMedia("(pointer: coarse)").matches : false;
  const hasFinePointer = supportsMatchMedia ? window.matchMedia("(pointer: fine)").matches : true;
  const hasMouse = hasFinePointer || !hasTouch;
  return {
    hasTouch,
    hasStylus: false,
    // Set to true on first pen event
    hasMouse,
    hasCoarsePointer
  };
}

// src/gestures/normalize.ts
function extractModifiers(e) {
  if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
    return NO_MODIFIERS;
  }
  return {
    shift: e.shiftKey,
    ctrl: e.ctrlKey,
    alt: e.altKey,
    meta: e.metaKey
  };
}
function clampButton(raw) {
  if (raw === 1) return 1;
  if (raw === 2) return 2;
  return 0;
}
function lifecycleFromEventType(type) {
  switch (type) {
    case "pointerdown":
      return "down";
    case "pointerup":
      return "up";
    case "pointercancel":
      return "cancel";
    default:
      return "move";
  }
}
function normalizePointer(e) {
  const classified = classifyPointer(e);
  return {
    pointerId: e.pointerId,
    lifecycle: lifecycleFromEventType(e.type),
    source: classified.source,
    button: clampButton(e.button),
    modifiers: extractModifiers(e),
    screenX: e.clientX,
    screenY: e.clientY,
    pressure: e.pressure,
    timestamp: e.timeStamp
  };
}

// src/gestures/timed-state.ts
var LONG_PRESS_TIMER = "long-press";
var SETTLE_TIMER = "settle";
var DEFAULT_LONG_PRESS_MS = 600;
var DEFAULT_MULTI_TAP_WINDOW_MS = 300;
var IDLE = {
  tag: "idle",
  tapCount: 0
};
var DEFAULT_CONFIG = {
  longPressMs: DEFAULT_LONG_PRESS_MS,
  multiTapWindowMs: DEFAULT_MULTI_TAP_WINDOW_MS
};
function transition(state, event, config = DEFAULT_CONFIG) {
  switch (event) {
    case "down":
      return onDown(state, config);
    case "up":
      return onUp(state, config);
    case "move-beyond-threshold":
      return onMoveBeyond(state);
    case "cancel":
      return onCancel();
    case "timer:long-press":
      return onLongPressTimer(state);
    case "timer:settle":
      return onSettleTimer();
    default:
      return {
        state
      };
  }
}
function onDown(state, config) {
  switch (state.tag) {
    case "idle":
      return {
        state: {
          tag: "pressed",
          tapCount: 0
        },
        cancelTimer: SETTLE_TIMER,
        scheduleTimer: {
          id: LONG_PRESS_TIMER,
          delayMs: config.longPressMs
        }
      };
    case "released":
      return {
        state: {
          tag: "pressed",
          tapCount: state.tapCount
        },
        cancelTimer: SETTLE_TIMER,
        scheduleTimer: {
          id: LONG_PRESS_TIMER,
          delayMs: config.longPressMs
        }
      };
    default:
      return {
        state
      };
  }
}
function onUp(state, config) {
  if (state.tag !== "pressed") {
    if (state.tag === "long-pressed") {
      return {
        state: IDLE,
        cancelTimer: LONG_PRESS_TIMER
      };
    }
    return {
      state
    };
  }
  const newCount = state.tapCount + 1;
  const emit = tapTypeForCount(newCount);
  return {
    state: {
      tag: "released",
      tapCount: newCount
    },
    emit,
    cancelTimer: LONG_PRESS_TIMER,
    scheduleTimer: {
      id: SETTLE_TIMER,
      delayMs: config.multiTapWindowMs
    }
  };
}
function onMoveBeyond(state) {
  if (state.tag === "pressed") {
    return {
      state: IDLE,
      cancelTimer: LONG_PRESS_TIMER
    };
  }
  return {
    state
  };
}
function onCancel() {
  return {
    state: IDLE,
    cancelTimer: LONG_PRESS_TIMER
  };
}
function onLongPressTimer(state) {
  if (state.tag === "pressed") {
    return {
      state: {
        tag: "long-pressed",
        tapCount: 0
      },
      emit: "long-press"
    };
  }
  return {
    state
  };
}
function onSettleTimer() {
  return {
    state: IDLE
  };
}
function tapTypeForCount(count) {
  switch (count) {
    case 2:
      return "double-tap";
    case 3:
      return "triple-tap";
    default:
      return "tap";
  }
}

// src/gestures/timed-state-runner.ts
var TimedStateRunner = class {
  constructor(config) {
    __publicField(this, "state", IDLE);
    __publicField(this, "timers", /* @__PURE__ */ new Map());
    /** Called when the state machine emits a gesture (tap, double-tap, long-press, etc.) */
    __publicField(this, "onEmit", null);
    this.config = {
      longPressMs: config?.longPressMs ?? DEFAULT_LONG_PRESS_MS,
      multiTapWindowMs: config?.multiTapWindowMs ?? DEFAULT_MULTI_TAP_WINDOW_MS
    };
  }
  /**
   * Feed a lifecycle event into the state machine.
   * Returns the emitted gesture type if any (synchronous emit only).
   * Timer-based emits are delivered asynchronously via `onEmit`.
   */
  feed(event) {
    return this.apply(event);
  }
  /** Current state tag (for debugging/testing). */
  get tag() {
    return this.state.tag;
  }
  /** Destroy: cancel all pending timers. */
  destroy() {
    for (const timer of this.timers.values()) {
      clearTimeout(timer);
    }
    this.timers.clear();
    this.state = IDLE;
    this.onEmit = null;
  }
  apply(event) {
    const result = transition(this.state, event, this.config);
    this.state = result.state;
    if (result.cancelTimer) {
      const timer = this.timers.get(result.cancelTimer);
      if (timer !== void 0) {
        clearTimeout(timer);
        this.timers.delete(result.cancelTimer);
      }
    }
    if (result.scheduleTimer) {
      const {
        id,
        delayMs
      } = result.scheduleTimer;
      const existing = this.timers.get(id);
      if (existing !== void 0) {
        clearTimeout(existing);
      }
      this.timers.set(id, setTimeout(() => {
        this.timers.delete(id);
        const timerEvent = `timer:${id}`;
        const timerResult = transition(this.state, timerEvent, this.config);
        this.state = timerResult.state;
        if (timerResult.emit) {
          this.onEmit?.(timerResult.emit);
        }
      }, delayMs));
    }
    return result.emit;
  }
};

// src/gestures/specificity.ts
var SCORE_TYPE = 128;
var SCORE_KEY = 64;
var SCORE_CODE = 64;
var SCORE_SUBJECT = 32;
var SCORE_MODIFIER_POSITIVE = 16;
var SCORE_MODIFIER_NEGATIVE = 8;
var SCORE_HELD_KEY_POSITIVE = 16;
var SCORE_HELD_KEY_NEGATIVE = 8;
var SCORE_SOURCE = 4;
var SCORE_BUTTON = 2;
function specificity(pattern, event) {
  let score = 0;
  if (pattern.kind !== void 0) {
    const actualKind = isKeyInputEvent(event) ? "key" : "pointer";
    if (pattern.kind !== actualKind) return -1;
  }
  if (pattern.type !== void 0) {
    if (!isPointerGestureEvent(event) || pattern.type !== event.type) return -1;
    score += SCORE_TYPE;
  }
  if (pattern.subjectKind !== void 0) {
    if (event.subject === void 0 || pattern.subjectKind !== event.subject.kind) return -1;
    score += SCORE_SUBJECT;
  }
  if (pattern.phase !== void 0 && pattern.phase !== event.phase) {
    return -1;
  }
  if (pattern.source !== void 0) {
    if (!isPointerGestureEvent(event)) return -1;
    if (pattern.source !== event.source) return -1;
    score += SCORE_SOURCE;
  }
  if (pattern.button !== void 0) {
    if (!isPointerGestureEvent(event)) return -1;
    if (pattern.button !== event.button) return -1;
    score += SCORE_BUTTON;
  }
  if (pattern.key !== void 0) {
    if (!isKeyInputEvent(event) || pattern.key !== event.key) return -1;
    score += SCORE_KEY;
  }
  if (pattern.code !== void 0) {
    if (!isKeyInputEvent(event) || pattern.code !== event.code) return -1;
    score += SCORE_CODE;
  }
  if (pattern.modifiers !== void 0) {
    const ms = modifierScore(pattern.modifiers, event.modifiers);
    if (ms === -1) return -1;
    score += ms;
  }
  if (pattern.heldKeys !== void 0) {
    const hs = heldKeyScore(pattern.heldKeys, event.heldKeys);
    if (hs === -1) return -1;
    score += hs;
  }
  return score;
}
function modifierScore(pattern, actual) {
  let score = 0;
  const keys = ["shift", "ctrl", "alt", "meta"];
  for (const key of keys) {
    const required = pattern[key];
    if (required === void 0) continue;
    if (required !== actual[key]) return -1;
    score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE;
  }
  if (pattern.custom) {
    const actualCustom = actual.custom ?? {};
    for (const [key, required] of Object.entries(pattern.custom)) {
      const actualVal = actualCustom[key] ?? false;
      if (required !== actualVal) return -1;
      score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE;
    }
  }
  return score;
}
function heldKeyScore(pattern, actual) {
  let score = 0;
  for (const [key, required] of Object.entries(pattern.byKey ?? {})) {
    const actualVal = actual.byKey[key] ?? false;
    if (required !== actualVal) return -1;
    score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE;
  }
  for (const [code, required] of Object.entries(pattern.byCode ?? {})) {
    const actualVal = actual.byCode[code] ?? false;
    if (required !== actualVal) return -1;
    score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE;
  }
  return score;
}

// src/gestures/mapper.ts
var WILDCARD = "__wildcard__";
function getPatternBucketKey(binding) {
  const {
    pattern
  } = binding;
  if (pattern.type !== void 0) {
    return `pointer:${pattern.type}`;
  }
  if (pattern.kind === "key" || pattern.key !== void 0 || pattern.code !== void 0) {
    return pattern.phase === "down" || pattern.phase === "up" ? `key:${pattern.phase}` : null;
  }
  return null;
}
function getEventBucketKey(event) {
  return isKeyInputEvent(event) ? `key:${event.phase}` : `pointer:${event.type}`;
}
function indexContext(ctx) {
  const typed = /* @__PURE__ */ new Map();
  const wildcards = [];
  for (const binding of ctx.bindings) {
    const key = getPatternBucketKey(binding);
    if (key === null) {
      wildcards.push(binding);
    } else {
      let bucket = typed.get(key);
      if (!bucket) {
        bucket = [];
        typed.set(key, bucket);
      }
      bucket.push(binding);
    }
  }
  const buckets = /* @__PURE__ */ new Map();
  for (const [key, bucket] of typed) {
    buckets.set(key, bucket);
  }
  if (wildcards.length > 0) {
    buckets.set(WILDCARD, wildcards);
  }
  return {
    contextId: ctx.id,
    priority: ctx.priority,
    enabled: ctx.enabled,
    buckets
  };
}
function buildMappingIndex(contexts) {
  return contexts.map(indexContext).sort((a, b) => a.priority - b.priority);
}
function resolve(event, index, guard) {
  let winner = null;
  for (const ctx of index) {
    if (!ctx.enabled) continue;
    const bucket = ctx.buckets.get(getEventBucketKey(event));
    const wildcardBucket = ctx.buckets.get(WILDCARD);
    if (!bucket && !wildcardBucket) continue;
    let bestScore = -1;
    let bestBinding = null;
    for (const binding of [...bucket ?? [], ...wildcardBucket ?? []]) {
      const score = specificity(binding.pattern, event);
      if (score === -1) continue;
      if (binding.when && !binding.when(guard)) continue;
      if (score > bestScore) {
        bestScore = score;
        bestBinding = binding;
      }
    }
    if (bestBinding !== null) {
      const action = {
        actionId: bestBinding.actionId,
        binding: bestBinding,
        contextId: ctx.contextId,
        score: bestScore,
        consumed: bestBinding.consumeInput === true
      };
      if (action.consumed) {
        return action;
      }
      if (winner === null) {
        winner = action;
      }
    }
  }
  return winner;
}

// src/gestures/keyboard-contexts.ts
var SEARCH_CONTEXT = {
  id: "search-active",
  priority: 25,
  enabled: true,
  bindings: [{
    id: "search-next-enter",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Enter",
      modifiers: {
        shift: false
      }
    },
    actionId: "search-next-result",
    when: (ctx) => ctx.isSearchActive && !ctx.commandLineVisible,
    consumeInput: true
  }, {
    id: "search-prev-enter",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Enter",
      modifiers: {
        shift: true
      }
    },
    actionId: "search-prev-result",
    when: (ctx) => ctx.isSearchActive && !ctx.commandLineVisible,
    consumeInput: true
  }, {
    id: "search-next-ctrl-g",
    pattern: {
      kind: "key",
      phase: "down",
      key: "g",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "search-next-result",
    when: (ctx) => ctx.isSearchActive,
    consumeInput: true
  }, {
    id: "search-prev-ctrl-g",
    pattern: {
      kind: "key",
      phase: "down",
      key: "g",
      modifiers: {
        ctrl: true,
        shift: true
      }
    },
    actionId: "search-prev-result",
    when: (ctx) => ctx.isSearchActive,
    consumeInput: true
  }, {
    id: "search-next-meta-g",
    pattern: {
      kind: "key",
      phase: "down",
      key: "g",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "search-next-result",
    when: (ctx) => ctx.isSearchActive,
    consumeInput: true
  }, {
    id: "search-prev-meta-g",
    pattern: {
      kind: "key",
      phase: "down",
      key: "g",
      modifiers: {
        meta: true,
        shift: true
      }
    },
    actionId: "search-prev-result",
    when: (ctx) => ctx.isSearchActive,
    consumeInput: true
  }]
};
var KEYBOARD_MANIPULATE_CONTEXT = {
  id: "keyboard:manipulate",
  priority: 30,
  enabled: true,
  bindings: [{
    id: "manipulate-arrow-up",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowUp",
      modifiers: {
        shift: false
      }
    },
    actionId: "nudge-selection-up",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-arrow-down",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowDown",
      modifiers: {
        shift: false
      }
    },
    actionId: "nudge-selection-down",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-arrow-left",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowLeft",
      modifiers: {
        shift: false
      }
    },
    actionId: "nudge-selection-left",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-arrow-right",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowRight",
      modifiers: {
        shift: false
      }
    },
    actionId: "nudge-selection-right",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-shift-arrow-up",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowUp",
      modifiers: {
        shift: true
      }
    },
    actionId: "nudge-selection-up-large",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-shift-arrow-down",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowDown",
      modifiers: {
        shift: true
      }
    },
    actionId: "nudge-selection-down-large",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-shift-arrow-left",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowLeft",
      modifiers: {
        shift: true
      }
    },
    actionId: "nudge-selection-left-large",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-shift-arrow-right",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowRight",
      modifiers: {
        shift: true
      }
    },
    actionId: "nudge-selection-right-large",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }, {
    id: "manipulate-escape",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Escape"
    },
    actionId: "exit-keyboard-manipulate-mode",
    when: (ctx) => ctx.keyboardInteractionMode === "manipulate",
    consumeInput: true
  }]
};
var KEYBOARD_NAVIGATE_CONTEXT = {
  id: "keyboard:navigate",
  priority: 40,
  enabled: true,
  bindings: [{
    id: "navigate-arrow-up",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowUp"
    },
    actionId: "navigate-focus-up",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-arrow-down",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowDown"
    },
    actionId: "navigate-focus-down",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-arrow-left",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowLeft"
    },
    actionId: "navigate-focus-left",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-arrow-right",
    pattern: {
      kind: "key",
      phase: "down",
      key: "ArrowRight"
    },
    actionId: "navigate-focus-right",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-enter",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Enter"
    },
    actionId: "enter-keyboard-manipulate-mode",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-space",
    pattern: {
      kind: "key",
      phase: "down",
      code: "Space"
    },
    actionId: "activate-focused-node",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-tab",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Tab",
      modifiers: {
        shift: false
      }
    },
    actionId: "cycle-focus-forward",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }, {
    id: "navigate-shift-tab",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Tab",
      modifiers: {
        shift: true
      }
    },
    actionId: "cycle-focus-backward",
    when: (ctx) => ctx.keyboardInteractionMode === "navigate",
    consumeInput: true
  }]
};

// src/gestures/pointer-bindings.ts
var POINTER_BINDINGS = [
  // --- Pointer taps ---
  {
    id: "tap-node",
    pattern: {
      type: "tap",
      subjectKind: "node"
    },
    actionId: "select-node"
  },
  {
    id: "tap-edge",
    pattern: {
      type: "tap",
      subjectKind: "edge"
    },
    actionId: "select-edge"
  },
  {
    id: "tap-bg",
    pattern: {
      type: "tap",
      subjectKind: "background"
    },
    actionId: "clear-selection"
  },
  {
    id: "shift-tap-node",
    pattern: {
      type: "tap",
      subjectKind: "node",
      modifiers: {
        shift: true
      }
    },
    actionId: "toggle-selection"
  },
  // --- Right-click ---
  {
    id: "rc-node",
    pattern: {
      type: "tap",
      subjectKind: "node",
      button: 2
    },
    actionId: "context-menu",
    consumeInput: true
  },
  {
    id: "rc-bg",
    pattern: {
      type: "tap",
      subjectKind: "background",
      button: 2
    },
    actionId: "context-menu",
    consumeInput: true
  },
  // --- Double/triple tap ---
  {
    id: "dtap-node",
    pattern: {
      type: "double-tap",
      subjectKind: "node"
    },
    actionId: "fit-to-view"
  },
  {
    id: "ttap-node",
    pattern: {
      type: "triple-tap",
      subjectKind: "node"
    },
    actionId: "toggle-lock"
  },
  // --- Drags ---
  {
    id: "drag-node",
    pattern: {
      type: "drag",
      subjectKind: "node"
    },
    actionId: "move-node"
  },
  {
    id: "drag-bg-finger",
    pattern: {
      type: "drag",
      subjectKind: "background",
      source: "finger"
    },
    actionId: "pan"
  },
  {
    id: "drag-bg-mouse",
    pattern: {
      type: "drag",
      subjectKind: "background",
      source: "mouse"
    },
    actionId: "pan"
  },
  {
    id: "drag-bg-pencil",
    pattern: {
      type: "drag",
      subjectKind: "background",
      source: "pencil"
    },
    actionId: "lasso-select"
  },
  {
    id: "space-drag-pan",
    pattern: {
      type: "drag",
      heldKeys: {
        byCode: {
          Space: true
        }
      }
    },
    actionId: "pan"
  },
  {
    id: "shift-drag-bg",
    pattern: {
      type: "drag",
      subjectKind: "background",
      modifiers: {
        shift: true
      }
    },
    actionId: "rect-select"
  },
  // Right/middle drag — no-ops by default
  {
    id: "rdrag-node",
    pattern: {
      type: "drag",
      subjectKind: "node",
      button: 2
    },
    actionId: "none"
  },
  {
    id: "rdrag-bg",
    pattern: {
      type: "drag",
      subjectKind: "background",
      button: 2
    },
    actionId: "none"
  },
  // --- Long-press ---
  {
    id: "lp-node",
    pattern: {
      type: "long-press",
      subjectKind: "node"
    },
    actionId: "context-menu"
  },
  {
    id: "lp-bg-finger",
    pattern: {
      type: "long-press",
      subjectKind: "background",
      source: "finger"
    },
    actionId: "create-node"
  },
  // --- Pinch / scroll ---
  {
    id: "pinch-bg",
    pattern: {
      type: "pinch",
      subjectKind: "background"
    },
    actionId: "zoom"
  },
  {
    id: "scroll-any",
    pattern: {
      type: "scroll"
    },
    actionId: "zoom"
  },
  {
    id: "pinch-node",
    pattern: {
      type: "pinch",
      subjectKind: "node"
    },
    actionId: "split-node"
  }
];

// src/gestures/keyboard-bindings.ts
var isCommandShortcut = (ctx) => !ctx.commandLineVisible;
var KEYBOARD_BINDINGS = [
  {
    id: "slash-open-command-line",
    pattern: {
      kind: "key",
      phase: "down",
      code: "Slash",
      modifiers: {
        ctrl: false,
        meta: false,
        shift: false
      }
    },
    actionId: "open-command-line",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "escape-fallback",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Escape"
    },
    actionId: "escape-input",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "delete-selection",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Delete"
    },
    actionId: "delete-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "backspace-delete-selection",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Backspace",
      modifiers: {
        ctrl: false,
        meta: false
      }
    },
    actionId: "delete-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  // Search
  {
    id: "open-search-ctrl-f",
    pattern: {
      kind: "key",
      phase: "down",
      key: "f",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "open-search",
    consumeInput: true
  },
  {
    id: "open-search-meta-f",
    pattern: {
      kind: "key",
      phase: "down",
      key: "f",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "open-search",
    consumeInput: true
  },
  // Copy / Cut / Paste
  {
    id: "copy-selection-ctrl-c",
    pattern: {
      kind: "key",
      phase: "down",
      key: "c",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "copy-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "copy-selection-meta-c",
    pattern: {
      kind: "key",
      phase: "down",
      key: "c",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "copy-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "cut-selection-ctrl-x",
    pattern: {
      kind: "key",
      phase: "down",
      key: "x",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "cut-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "cut-selection-meta-x",
    pattern: {
      kind: "key",
      phase: "down",
      key: "x",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "cut-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "paste-selection-ctrl-v",
    pattern: {
      kind: "key",
      phase: "down",
      key: "v",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "paste-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "paste-selection-meta-v",
    pattern: {
      kind: "key",
      phase: "down",
      key: "v",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "paste-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  // Duplicate
  {
    id: "duplicate-selection-ctrl-d",
    pattern: {
      kind: "key",
      phase: "down",
      key: "d",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "duplicate-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "duplicate-selection-meta-d",
    pattern: {
      kind: "key",
      phase: "down",
      key: "d",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "duplicate-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  // Select All
  {
    id: "select-all-ctrl-a",
    pattern: {
      kind: "key",
      phase: "down",
      key: "a",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "select-all",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "select-all-meta-a",
    pattern: {
      kind: "key",
      phase: "down",
      key: "a",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "select-all",
    when: isCommandShortcut,
    consumeInput: true
  },
  // Merge
  {
    id: "merge-selection-ctrl-m",
    pattern: {
      kind: "key",
      phase: "down",
      key: "m",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "merge-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "merge-selection-meta-m",
    pattern: {
      kind: "key",
      phase: "down",
      key: "m",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "merge-selection",
    when: isCommandShortcut,
    consumeInput: true
  },
  // Undo / Redo
  {
    id: "undo-ctrl-z",
    pattern: {
      kind: "key",
      phase: "down",
      key: "z",
      modifiers: {
        ctrl: true,
        shift: false
      }
    },
    actionId: "undo",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "undo-meta-z",
    pattern: {
      kind: "key",
      phase: "down",
      key: "z",
      modifiers: {
        meta: true,
        shift: false
      }
    },
    actionId: "undo",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "redo-ctrl-shift-z",
    pattern: {
      kind: "key",
      phase: "down",
      key: "z",
      modifiers: {
        ctrl: true,
        shift: true
      }
    },
    actionId: "redo",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "redo-meta-shift-z",
    pattern: {
      kind: "key",
      phase: "down",
      key: "z",
      modifiers: {
        meta: true,
        shift: true
      }
    },
    actionId: "redo",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "redo-ctrl-y",
    pattern: {
      kind: "key",
      phase: "down",
      key: "y",
      modifiers: {
        ctrl: true
      }
    },
    actionId: "redo",
    when: isCommandShortcut,
    consumeInput: true
  },
  {
    id: "redo-meta-y",
    pattern: {
      kind: "key",
      phase: "down",
      key: "y",
      modifiers: {
        meta: true
      }
    },
    actionId: "redo",
    when: isCommandShortcut,
    consumeInput: true
  }
];

// src/gestures/pointer-contexts.ts
var DEFAULT_CONTEXT = {
  id: "default",
  priority: 100,
  enabled: true,
  bindings: [...POINTER_BINDINGS, ...KEYBOARD_BINDINGS]
};
var PICK_NODE_CONTEXT = {
  id: "input-mode:pickNode",
  priority: 5,
  enabled: true,
  bindings: [{
    id: "pick-tap-node",
    pattern: {
      type: "tap",
      subjectKind: "node"
    },
    actionId: "resolve-pick-node",
    consumeInput: true
  }, {
    id: "pick-cancel-bg",
    pattern: {
      type: "tap",
      subjectKind: "background"
    },
    actionId: "cancel-pick",
    consumeInput: true
  }, {
    id: "pick-cancel-key",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Escape"
    },
    actionId: "cancel-pick",
    consumeInput: true
  }]
};
var PICK_NODES_CONTEXT = {
  id: "input-mode:pickNodes",
  priority: 5,
  enabled: true,
  bindings: [{
    id: "pick-tap-node",
    pattern: {
      type: "tap",
      subjectKind: "node"
    },
    actionId: "resolve-pick-node",
    consumeInput: true
  }, {
    id: "pick-done",
    pattern: {
      type: "double-tap",
      subjectKind: "background"
    },
    actionId: "finish-pick-nodes",
    consumeInput: true
  }, {
    id: "pick-cancel-bg",
    pattern: {
      type: "tap",
      subjectKind: "background",
      button: 2
    },
    actionId: "cancel-pick",
    consumeInput: true
  }, {
    id: "pick-cancel-key",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Escape"
    },
    actionId: "cancel-pick",
    consumeInput: true
  }]
};
var PICK_POINT_CONTEXT = {
  id: "input-mode:pickPoint",
  priority: 5,
  enabled: true,
  bindings: [{
    id: "pick-tap",
    pattern: {
      type: "tap"
    },
    actionId: "resolve-pick-point",
    consumeInput: true
  }, {
    id: "pick-cancel-key",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Escape"
    },
    actionId: "cancel-pick",
    consumeInput: true
  }]
};
var INPUT_MODE_CONTEXTS = {
  normal: null,
  pickNode: PICK_NODE_CONTEXT,
  pickNodes: PICK_NODES_CONTEXT,
  pickPoint: PICK_POINT_CONTEXT,
  text: null,
  select: null
};

// src/gestures/contexts.ts
var PALM_REJECTION_CONTEXT = {
  id: "palm-rejection",
  priority: 0,
  enabled: true,
  bindings: [{
    id: "pr-finger-tap",
    pattern: {
      type: "tap",
      source: "finger"
    },
    actionId: "none",
    when: (ctx) => ctx.isStylusActive,
    consumeInput: true
  }, {
    id: "pr-finger-dtap",
    pattern: {
      type: "double-tap",
      source: "finger"
    },
    actionId: "none",
    when: (ctx) => ctx.isStylusActive,
    consumeInput: true
  }, {
    id: "pr-finger-ttap",
    pattern: {
      type: "triple-tap",
      source: "finger"
    },
    actionId: "none",
    when: (ctx) => ctx.isStylusActive,
    consumeInput: true
  }, {
    id: "pr-finger-lp",
    pattern: {
      type: "long-press",
      source: "finger"
    },
    actionId: "none",
    when: (ctx) => ctx.isStylusActive,
    consumeInput: true
  }, {
    id: "pr-finger-drag-node",
    pattern: {
      type: "drag",
      subjectKind: "node",
      source: "finger"
    },
    actionId: "pan",
    when: (ctx) => ctx.isStylusActive,
    consumeInput: true
  }]
};
var ACTIVE_INTERACTION_CONTEXT = {
  id: "active-interaction",
  priority: 15,
  enabled: true,
  bindings: [{
    id: "escape-cancel-active-interaction",
    pattern: {
      kind: "key",
      phase: "down",
      key: "Escape"
    },
    actionId: "cancel-active-input",
    when: (ctx) => ctx.isDragging || ctx.isSplitting || Boolean(ctx.custom.isSelecting) || Boolean(ctx.custom.isCreatingEdge),
    consumeInput: true
  }]
};

// src/gestures/dispatcher.ts
var handlers = /* @__PURE__ */ new Map();
function registerAction(actionId, handler) {
  handlers.set(actionId, handler);
}
function unregisterAction(actionId) {
  handlers.delete(actionId);
}
function getHandler(actionId) {
  return handlers.get(actionId);
}
function clearHandlers() {
  handlers.clear();
}
function dispatch(event, resolution) {
  if (resolution.actionId === "none") return true;
  const handler = handlers.get(resolution.actionId);
  if (!handler) return false;
  if (typeof handler === "function") {
    if (isKeyInputEvent(event) && event.phase === "down" || !isKeyInputEvent(event) && (event.phase === "start" || event.phase === "instant")) {
      handler(event);
    }
    return true;
  }
  routePhase(handler, event.phase, event);
  return true;
}
function routePhase(handler, phase, event) {
  if (isKeyInputEvent(event)) {
    routeKeyPhase(handler, phase, event);
    return;
  }
  switch (phase) {
    case "start":
      handler.onStart?.(event);
      break;
    case "move":
      handler.onMove?.(event);
      break;
    case "end":
      handler.onEnd?.(event);
      break;
    case "instant":
      handler.onInstant?.(event);
      break;
    case "cancel":
      handler.onCancel?.(event);
      break;
  }
}
function routeKeyPhase(handler, phase, event) {
  switch (phase) {
    case "down":
      handler.onDown?.(event);
      break;
    case "up":
      handler.onUp?.(event);
      break;
  }
}

// src/gestures/inertia.ts
var PAN_FRICTION = 0.92;
var ZOOM_FRICTION = 0.88;
var MIN_VELOCITY = 0.5;
var ZOOM_SNAP_THRESHOLD = 0.03;
var VELOCITY_SAMPLE_COUNT = 5;
var VelocitySampler = class {
  constructor(maxSamples = VELOCITY_SAMPLE_COUNT) {
    __publicField(this, "samples", []);
    this.maxSamples = maxSamples;
  }
  sample(vx, vy, t) {
    if (this.samples.length >= this.maxSamples) {
      this.samples.shift();
    }
    this.samples.push({
      vx,
      vy,
      t
    });
  }
  average() {
    const n = this.samples.length;
    if (n === 0) return {
      x: 0,
      y: 0
    };
    let sx = 0;
    let sy = 0;
    for (const s of this.samples) {
      sx += s.vx;
      sy += s.vy;
    }
    return {
      x: sx / n,
      y: sy / n
    };
  }
  reset() {
    this.samples.length = 0;
  }
};
var PanInertia = class {
  constructor(velocity, friction = PAN_FRICTION, minVelocity = MIN_VELOCITY) {
    this.friction = friction;
    this.minVelocity = minVelocity;
    this.vx = velocity.x;
    this.vy = velocity.y;
  }
  /**
   * Advance one frame. Returns the velocity delta to apply,
   * or null if below threshold (animation complete).
   */
  tick() {
    this.vx *= this.friction;
    this.vy *= this.friction;
    if (Math.abs(this.vx) < this.minVelocity && Math.abs(this.vy) < this.minVelocity) {
      return null;
    }
    return {
      x: this.vx,
      y: this.vy
    };
  }
};
var ZoomInertia = class {
  constructor(velocity, origin, friction = ZOOM_FRICTION, minVelocity = MIN_VELOCITY, snapThreshold = ZOOM_SNAP_THRESHOLD) {
    this.origin = origin;
    this.friction = friction;
    this.minVelocity = minVelocity;
    this.snapThreshold = snapThreshold;
    this.v = velocity;
  }
  /**
   * Advance one frame. Returns zoom delta + origin,
   * or null if below threshold.
   */
  tick(currentZoom) {
    this.v *= this.friction;
    if (Math.abs(this.v) < this.minVelocity) {
      if (Math.abs(currentZoom - 1) < this.snapThreshold) {
        const snapDelta = 1 - currentZoom;
        return Math.abs(snapDelta) > 1e-3 ? {
          delta: snapDelta,
          origin: this.origin
        } : null;
      }
      return null;
    }
    return {
      delta: this.v,
      origin: this.origin
    };
  }
};

// src/core/graph-store.ts
var import_jotai = require("jotai");
var import_graphology = __toESM(require("graphology"));
var graphOptions = {
  type: "directed",
  multi: true,
  allowSelfLoops: true
};
var currentGraphIdAtom = (0, import_jotai.atom)(null);
var graphAtom = (0, import_jotai.atom)(new import_graphology.default(graphOptions));
var graphUpdateVersionAtom = (0, import_jotai.atom)(0);
var edgeCreationAtom = (0, import_jotai.atom)({
  isCreating: false,
  sourceNodeId: null,
  sourceNodePosition: null,
  targetPosition: null,
  hoveredTargetNodeId: null,
  sourceHandle: null,
  targetHandle: null,
  sourcePort: null,
  targetPort: null,
  snappedTargetPosition: null
});
var draggingNodeIdAtom = (0, import_jotai.atom)(null);
var preDragNodeAttributesAtom = (0, import_jotai.atom)(null);

// src/core/graph-position.ts
var import_jotai3 = require("jotai");
var import_jotai_family = require("jotai-family");
var import_graphology2 = __toESM(require("graphology"));

// src/utils/debug.ts
var import_debug = __toESM(require("debug"));
var NAMESPACE = "canvas";
function createDebug(module2) {
  const base = (0, import_debug.default)(`${NAMESPACE}:${module2}`);
  const warn = (0, import_debug.default)(`${NAMESPACE}:${module2}:warn`);
  const error = (0, import_debug.default)(`${NAMESPACE}:${module2}:error`);
  warn.enabled = true;
  error.enabled = true;
  warn.log = console.warn.bind(console);
  error.log = console.error.bind(console);
  const debugFn = Object.assign(base, {
    warn,
    error
  });
  return debugFn;
}
var debug = {
  graph: {
    node: createDebug("graph:node"),
    edge: createDebug("graph:edge"),
    sync: createDebug("graph:sync")
  },
  ui: {
    selection: createDebug("ui:selection"),
    drag: createDebug("ui:drag"),
    resize: createDebug("ui:resize")
  },
  sync: {
    status: createDebug("sync:status"),
    mutations: createDebug("sync:mutations"),
    queue: createDebug("sync:queue")
  },
  viewport: createDebug("viewport")
};

// src/utils/mutation-queue.ts
var pendingNodeMutations = /* @__PURE__ */ new Map();
function clearAllPendingMutations() {
  pendingNodeMutations.clear();
}

// src/core/perf.ts
var import_jotai2 = require("jotai");
var perfEnabledAtom = (0, import_jotai2.atom)(false);
var _enabled = false;
function setPerfEnabled(enabled) {
  _enabled = enabled;
}
if (typeof window !== "undefined") {
  window.__canvasPerf = setPerfEnabled;
}
function canvasMark(name) {
  if (!_enabled) return _noop;
  const markName = `canvas:${name}`;
  try {
    performance.mark(markName);
  } catch {
    return _noop;
  }
  return () => {
    try {
      performance.measure(`canvas:${name}`, markName);
    } catch {
    }
  };
}
function _noop() {
}

// src/core/graph-position.ts
var debug2 = createDebug("graph:position");
var _positionCacheByGraph = /* @__PURE__ */ new WeakMap();
function getPositionCache(graph) {
  let cache = _positionCacheByGraph.get(graph);
  if (!cache) {
    cache = /* @__PURE__ */ new Map();
    _positionCacheByGraph.set(graph, cache);
  }
  return cache;
}
var nodePositionUpdateCounterAtom = (0, import_jotai3.atom)(0);
var nodePositionAtomFamily = (0, import_jotai_family.atomFamily)((nodeId) => (0, import_jotai3.atom)((get) => {
  get(nodePositionUpdateCounterAtom);
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) {
    return {
      x: 0,
      y: 0
    };
  }
  const x = graph.getNodeAttribute(nodeId, "x");
  const y = graph.getNodeAttribute(nodeId, "y");
  const cache = getPositionCache(graph);
  const prev = cache.get(nodeId);
  if (prev && prev.x === x && prev.y === y) {
    return prev;
  }
  const pos = {
    x,
    y
  };
  cache.set(nodeId, pos);
  return pos;
}));
var updateNodePositionAtom = (0, import_jotai3.atom)(null, (get, set, {
  nodeId,
  position
}) => {
  const end = canvasMark("drag-frame");
  const graph = get(graphAtom);
  if (graph.hasNode(nodeId)) {
    debug2("Updating node %s position to %o", nodeId, position);
    graph.setNodeAttribute(nodeId, "x", position.x);
    graph.setNodeAttribute(nodeId, "y", position.y);
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
  }
  end();
});
var cleanupNodePositionAtom = (0, import_jotai3.atom)(null, (get, _set, nodeId) => {
  nodePositionAtomFamily.remove(nodeId);
  const graph = get(graphAtom);
  getPositionCache(graph).delete(nodeId);
  debug2("Removed position atom for node: %s", nodeId);
});
var cleanupAllNodePositionsAtom = (0, import_jotai3.atom)(null, (get, _set) => {
  const graph = get(graphAtom);
  const nodeIds = graph.nodes();
  nodeIds.forEach((nodeId) => {
    nodePositionAtomFamily.remove(nodeId);
  });
  _positionCacheByGraph.delete(graph);
  debug2("Removed %d position atoms", nodeIds.length);
});
var clearGraphOnSwitchAtom = (0, import_jotai3.atom)(null, (get, set) => {
  debug2("Clearing graph for switch");
  set(cleanupAllNodePositionsAtom);
  clearAllPendingMutations();
  const emptyGraph = new import_graphology2.default(graphOptions);
  set(graphAtom, emptyGraph);
  set(graphUpdateVersionAtom, (v) => v + 1);
});

// src/core/graph-derived.ts
var import_jotai8 = require("jotai");
var import_jotai_family2 = require("jotai-family");

// src/core/viewport-store.ts
var import_jotai5 = require("jotai");

// src/core/selection-store.ts
var import_jotai4 = require("jotai");
var debug3 = createDebug("selection");
var selectedNodeIdsAtom = (0, import_jotai4.atom)(/* @__PURE__ */ new Set());
var selectedEdgeIdAtom = (0, import_jotai4.atom)(null);
var handleNodePointerDownSelectionAtom = (0, import_jotai4.atom)(null, (get, set, {
  nodeId,
  isShiftPressed
}) => {
  const currentSelection = get(selectedNodeIdsAtom);
  debug3("handleNodePointerDownSelection: nodeId=%s, shift=%s, current=%o", nodeId, isShiftPressed, Array.from(currentSelection));
  set(selectedEdgeIdAtom, null);
  if (isShiftPressed) {
    const newSelection = new Set(currentSelection);
    if (newSelection.has(nodeId)) {
      newSelection.delete(nodeId);
    } else {
      newSelection.add(nodeId);
    }
    debug3("Shift-click, setting selection to: %o", Array.from(newSelection));
    set(selectedNodeIdsAtom, newSelection);
  } else {
    if (!currentSelection.has(nodeId)) {
      debug3("Node not in selection, selecting: %s", nodeId);
      set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId]));
    } else {
      debug3("Node already selected, preserving multi-select");
    }
  }
});
var selectSingleNodeAtom = (0, import_jotai4.atom)(null, (get, set, nodeId) => {
  debug3("selectSingleNode: %s", nodeId);
  set(selectedEdgeIdAtom, null);
  if (nodeId === null || nodeId === void 0) {
    debug3("Clearing selection");
    set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
  } else {
    const currentSelection = get(selectedNodeIdsAtom);
    if (currentSelection.has(nodeId) && currentSelection.size === 1) {
      return;
    }
    set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId]));
  }
});
var toggleNodeInSelectionAtom = (0, import_jotai4.atom)(null, (get, set, nodeId) => {
  const currentSelection = get(selectedNodeIdsAtom);
  const newSelection = new Set(currentSelection);
  if (newSelection.has(nodeId)) {
    newSelection.delete(nodeId);
  } else {
    newSelection.add(nodeId);
  }
  set(selectedNodeIdsAtom, newSelection);
});
var clearSelectionAtom = (0, import_jotai4.atom)(null, (_get, set) => {
  debug3("clearSelection");
  set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
});
var addNodesToSelectionAtom = (0, import_jotai4.atom)(null, (get, set, nodeIds) => {
  const currentSelection = get(selectedNodeIdsAtom);
  const newSelection = new Set(currentSelection);
  for (const nodeId of nodeIds) {
    newSelection.add(nodeId);
  }
  set(selectedNodeIdsAtom, newSelection);
});
var removeNodesFromSelectionAtom = (0, import_jotai4.atom)(null, (get, set, nodeIds) => {
  const currentSelection = get(selectedNodeIdsAtom);
  const newSelection = new Set(currentSelection);
  for (const nodeId of nodeIds) {
    newSelection.delete(nodeId);
  }
  set(selectedNodeIdsAtom, newSelection);
});
var selectEdgeAtom = (0, import_jotai4.atom)(null, (get, set, edgeId) => {
  set(selectedEdgeIdAtom, edgeId);
  if (edgeId !== null) {
    set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
  }
});
var clearEdgeSelectionAtom = (0, import_jotai4.atom)(null, (_get, set) => {
  set(selectedEdgeIdAtom, null);
});
var focusedNodeIdAtom = (0, import_jotai4.atom)(null);
var setFocusedNodeAtom = (0, import_jotai4.atom)(null, (_get, set, nodeId) => {
  set(focusedNodeIdAtom, nodeId);
});
var hasFocusedNodeAtom = (0, import_jotai4.atom)((get) => get(focusedNodeIdAtom) !== null);
var selectedNodesCountAtom = (0, import_jotai4.atom)((get) => get(selectedNodeIdsAtom).size);
var hasSelectionAtom = (0, import_jotai4.atom)((get) => get(selectedNodeIdsAtom).size > 0);

// src/utils/layout.ts
var FitToBoundsMode = /* @__PURE__ */ (function(FitToBoundsMode2) {
  FitToBoundsMode2["Graph"] = "graph";
  FitToBoundsMode2["Selection"] = "selection";
  return FitToBoundsMode2;
})({});
var calculateBounds = (nodes) => {
  if (nodes.length === 0) {
    return {
      x: 0,
      y: 0,
      width: 0,
      height: 0
    };
  }
  const minX = Math.min(...nodes.map((node) => node.x));
  const minY = Math.min(...nodes.map((node) => node.y));
  const maxX = Math.max(...nodes.map((node) => node.x + node.width));
  const maxY = Math.max(...nodes.map((node) => node.y + node.height));
  return {
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY
  };
};

// src/core/viewport-store.ts
var zoomAtom = (0, import_jotai5.atom)(1);
var panAtom = (0, import_jotai5.atom)({
  x: 0,
  y: 0
});
var viewportRectAtom = (0, import_jotai5.atom)(null);
var screenToWorldAtom = (0, import_jotai5.atom)((get) => {
  return (screenX, screenY) => {
    const pan = get(panAtom);
    const zoom = get(zoomAtom);
    const rect = get(viewportRectAtom);
    if (!rect) {
      return {
        x: screenX,
        y: screenY
      };
    }
    const relativeX = screenX - rect.left;
    const relativeY = screenY - rect.top;
    return {
      x: (relativeX - pan.x) / zoom,
      y: (relativeY - pan.y) / zoom
    };
  };
});
var worldToScreenAtom = (0, import_jotai5.atom)((get) => {
  return (worldX, worldY) => {
    const pan = get(panAtom);
    const zoom = get(zoomAtom);
    const rect = get(viewportRectAtom);
    if (!rect) {
      return {
        x: worldX,
        y: worldY
      };
    }
    return {
      x: worldX * zoom + pan.x + rect.left,
      y: worldY * zoom + pan.y + rect.top
    };
  };
});
var setZoomAtom = (0, import_jotai5.atom)(null, (get, set, {
  zoom,
  centerX,
  centerY
}) => {
  const currentZoom = get(zoomAtom);
  const pan = get(panAtom);
  const rect = get(viewportRectAtom);
  const newZoom = Math.max(0.1, Math.min(5, zoom));
  if (centerX !== void 0 && centerY !== void 0 && rect) {
    const relativeX = centerX - rect.left;
    const relativeY = centerY - rect.top;
    const worldX = (relativeX - pan.x) / currentZoom;
    const worldY = (relativeY - pan.y) / currentZoom;
    const newPanX = relativeX - worldX * newZoom;
    const newPanY = relativeY - worldY * newZoom;
    set(panAtom, {
      x: newPanX,
      y: newPanY
    });
  }
  set(zoomAtom, newZoom);
});
var resetViewportAtom = (0, import_jotai5.atom)(null, (_get, set) => {
  set(zoomAtom, 1);
  set(panAtom, {
    x: 0,
    y: 0
  });
});
var fitToBoundsAtom = (0, import_jotai5.atom)(null, (get, set, {
  mode,
  padding = 20
}) => {
  const normalizedMode = typeof mode === "string" ? mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection : mode;
  const viewportSize = get(viewportRectAtom);
  if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return;
  get(nodePositionUpdateCounterAtom);
  let bounds;
  if (normalizedMode === FitToBoundsMode.Graph) {
    const graph = get(graphAtom);
    const nodes = graph.nodes().map((node) => {
      const attrs = graph.getNodeAttributes(node);
      return {
        x: attrs.x,
        y: attrs.y,
        width: attrs.width || 500,
        height: attrs.height || 500
      };
    });
    bounds = calculateBounds(nodes);
  } else {
    const selectedIds = get(selectedNodeIdsAtom);
    const allNodes = get(uiNodesAtom);
    const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({
      x: n.position.x,
      y: n.position.y,
      width: n.width ?? 500,
      height: n.height ?? 500
    }));
    bounds = calculateBounds(selectedNodes);
  }
  if (bounds.width <= 0 || bounds.height <= 0) return;
  const maxHPad = Math.max(0, viewportSize.width / 2 - 1);
  const maxVPad = Math.max(0, viewportSize.height / 2 - 1);
  const safePadding = Math.max(0, Math.min(padding, maxHPad, maxVPad));
  const effW = Math.max(1, viewportSize.width - 2 * safePadding);
  const effH = Math.max(1, viewportSize.height - 2 * safePadding);
  const scale = Math.min(effW / bounds.width, effH / bounds.height);
  if (scale <= 0 || !isFinite(scale)) return;
  set(zoomAtom, scale);
  const scaledW = bounds.width * scale;
  const scaledH = bounds.height * scale;
  const startX = safePadding + (effW - scaledW) / 2;
  const startY = safePadding + (effH - scaledH) / 2;
  set(panAtom, {
    x: startX - bounds.x * scale,
    y: startY - bounds.y * scale
  });
});
var centerOnNodeAtom = (0, import_jotai5.atom)(null, (get, set, nodeId) => {
  const nodes = get(uiNodesAtom);
  const node = nodes.find((n) => n.id === nodeId);
  if (!node) return;
  const {
    x,
    y,
    width = 200,
    height = 100
  } = node;
  const zoom = get(zoomAtom);
  const centerX = x + width / 2;
  const centerY = y + height / 2;
  const rect = get(viewportRectAtom);
  const halfWidth = rect ? rect.width / 2 : 400;
  const halfHeight = rect ? rect.height / 2 : 300;
  set(panAtom, {
    x: halfWidth - centerX * zoom,
    y: halfHeight - centerY * zoom
  });
});
var zoomFocusNodeIdAtom = (0, import_jotai5.atom)(null);
var zoomTransitionProgressAtom = (0, import_jotai5.atom)(0);
var isZoomTransitioningAtom = (0, import_jotai5.atom)((get) => {
  const progress = get(zoomTransitionProgressAtom);
  return progress > 0 && progress < 1;
});
var zoomAnimationTargetAtom = (0, import_jotai5.atom)(null);
var animateZoomToNodeAtom = (0, import_jotai5.atom)(null, (get, set, {
  nodeId,
  targetZoom,
  duration = 300
}) => {
  const nodes = get(uiNodesAtom);
  const node = nodes.find((n) => n.id === nodeId);
  if (!node) return;
  const {
    x,
    y,
    width = 200,
    height = 100
  } = node;
  const centerX = x + width / 2;
  const centerY = y + height / 2;
  const rect = get(viewportRectAtom);
  const halfWidth = rect ? rect.width / 2 : 400;
  const halfHeight = rect ? rect.height / 2 : 300;
  const finalZoom = targetZoom ?? get(zoomAtom);
  const targetPan = {
    x: halfWidth - centerX * finalZoom,
    y: halfHeight - centerY * finalZoom
  };
  set(zoomFocusNodeIdAtom, nodeId);
  set(zoomAnimationTargetAtom, {
    targetZoom: finalZoom,
    targetPan,
    startZoom: get(zoomAtom),
    startPan: {
      ...get(panAtom)
    },
    duration,
    startTime: performance.now()
  });
});
var animateFitToBoundsAtom = (0, import_jotai5.atom)(null, (get, set, {
  mode,
  padding = 20,
  duration = 300
}) => {
  const viewportSize = get(viewportRectAtom);
  if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return;
  get(nodePositionUpdateCounterAtom);
  let bounds;
  if (mode === "graph") {
    const graph = get(graphAtom);
    const nodes = graph.nodes().map((node) => {
      const attrs = graph.getNodeAttributes(node);
      return {
        x: attrs.x,
        y: attrs.y,
        width: attrs.width || 500,
        height: attrs.height || 500
      };
    });
    bounds = calculateBounds(nodes);
  } else {
    const selectedIds = get(selectedNodeIdsAtom);
    const allNodes = get(uiNodesAtom);
    const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({
      x: n.position.x,
      y: n.position.y,
      width: n.width ?? 500,
      height: n.height ?? 500
    }));
    bounds = calculateBounds(selectedNodes);
  }
  if (bounds.width <= 0 || bounds.height <= 0) return;
  const safePadding = Math.max(0, Math.min(padding, viewportSize.width / 2 - 1, viewportSize.height / 2 - 1));
  const effW = Math.max(1, viewportSize.width - 2 * safePadding);
  const effH = Math.max(1, viewportSize.height - 2 * safePadding);
  const scale = Math.min(effW / bounds.width, effH / bounds.height);
  if (scale <= 0 || !isFinite(scale)) return;
  const scaledW = bounds.width * scale;
  const scaledH = bounds.height * scale;
  const startX = safePadding + (effW - scaledW) / 2;
  const startY = safePadding + (effH - scaledH) / 2;
  const targetPan = {
    x: startX - bounds.x * scale,
    y: startY - bounds.y * scale
  };
  set(zoomAnimationTargetAtom, {
    targetZoom: scale,
    targetPan,
    startZoom: get(zoomAtom),
    startPan: {
      ...get(panAtom)
    },
    duration,
    startTime: performance.now()
  });
});

// src/core/group-store.ts
var import_jotai7 = require("jotai");

// src/core/history-store.ts
var import_jotai6 = require("jotai");

// src/core/history-actions.ts
function applyDelta(graph, delta) {
  switch (delta.type) {
    case "move-node": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.setNodeAttribute(delta.nodeId, "x", delta.to.x);
      graph.setNodeAttribute(delta.nodeId, "y", delta.to.y);
      return false;
    }
    case "resize-node": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.setNodeAttribute(delta.nodeId, "width", delta.to.width);
      graph.setNodeAttribute(delta.nodeId, "height", delta.to.height);
      return false;
    }
    case "add-node": {
      if (graph.hasNode(delta.nodeId)) return false;
      graph.addNode(delta.nodeId, delta.attributes);
      return true;
    }
    case "remove-node": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.dropNode(delta.nodeId);
      return true;
    }
    case "add-edge": {
      if (graph.hasEdge(delta.edgeId)) return false;
      if (!graph.hasNode(delta.source) || !graph.hasNode(delta.target)) return false;
      graph.addEdgeWithKey(delta.edgeId, delta.source, delta.target, delta.attributes);
      return true;
    }
    case "remove-edge": {
      if (!graph.hasEdge(delta.edgeId)) return false;
      graph.dropEdge(delta.edgeId);
      return true;
    }
    case "update-node-attr": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.setNodeAttribute(delta.nodeId, delta.key, delta.to);
      return false;
    }
    case "batch": {
      let structuralChange = false;
      for (const d of delta.deltas) {
        if (applyDelta(graph, d)) structuralChange = true;
      }
      return structuralChange;
    }
    case "full-snapshot": {
      graph.clear();
      for (const node of delta.nodes) {
        graph.addNode(node.id, node.attributes);
      }
      for (const edge of delta.edges) {
        if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
          graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge.attributes);
        }
      }
      return true;
    }
  }
}
function invertDelta(delta) {
  switch (delta.type) {
    case "move-node":
      return {
        ...delta,
        from: delta.to,
        to: delta.from
      };
    case "resize-node":
      return {
        ...delta,
        from: delta.to,
        to: delta.from
      };
    case "add-node":
      return {
        type: "remove-node",
        nodeId: delta.nodeId,
        attributes: delta.attributes,
        connectedEdges: []
      };
    case "remove-node": {
      const batch = [{
        type: "add-node",
        nodeId: delta.nodeId,
        attributes: delta.attributes
      }, ...delta.connectedEdges.map((e) => ({
        type: "add-edge",
        edgeId: e.id,
        source: e.source,
        target: e.target,
        attributes: e.attributes
      }))];
      return batch.length === 1 ? batch[0] : {
        type: "batch",
        deltas: batch
      };
    }
    case "add-edge":
      return {
        type: "remove-edge",
        edgeId: delta.edgeId,
        source: delta.source,
        target: delta.target,
        attributes: delta.attributes
      };
    case "remove-edge":
      return {
        type: "add-edge",
        edgeId: delta.edgeId,
        source: delta.source,
        target: delta.target,
        attributes: delta.attributes
      };
    case "update-node-attr":
      return {
        ...delta,
        from: delta.to,
        to: delta.from
      };
    case "batch":
      return {
        type: "batch",
        deltas: delta.deltas.map(invertDelta).reverse()
      };
    case "full-snapshot":
      return delta;
  }
}
function createSnapshot(graph, label) {
  const nodes = [];
  const edges = [];
  graph.forEachNode((nodeId, attributes) => {
    nodes.push({
      id: nodeId,
      attributes: {
        ...attributes
      }
    });
  });
  graph.forEachEdge((edgeId, attributes, source, target) => {
    edges.push({
      id: edgeId,
      source,
      target,
      attributes: {
        ...attributes
      }
    });
  });
  return {
    timestamp: Date.now(),
    label,
    nodes,
    edges
  };
}

// src/core/history-store.ts
var debug4 = createDebug("history");
var MAX_HISTORY_SIZE = 50;
var historyStateAtom = (0, import_jotai6.atom)({
  past: [],
  future: [],
  isApplying: false
});
var canUndoAtom = (0, import_jotai6.atom)((get) => {
  const history = get(historyStateAtom);
  return history.past.length > 0 && !history.isApplying;
});
var canRedoAtom = (0, import_jotai6.atom)((get) => {
  const history = get(historyStateAtom);
  return history.future.length > 0 && !history.isApplying;
});
var undoCountAtom = (0, import_jotai6.atom)((get) => get(historyStateAtom).past.length);
var redoCountAtom = (0, import_jotai6.atom)((get) => get(historyStateAtom).future.length);
var pushDeltaAtom = (0, import_jotai6.atom)(null, (get, set, delta) => {
  const history = get(historyStateAtom);
  if (history.isApplying) return;
  const {
    label,
    ...cleanDelta
  } = delta;
  const entry = {
    forward: cleanDelta,
    reverse: invertDelta(cleanDelta),
    timestamp: Date.now(),
    label
  };
  const newPast = [...history.past, entry];
  if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();
  set(historyStateAtom, {
    past: newPast,
    future: [],
    // Clear redo stack
    isApplying: false
  });
  debug4("Pushed delta: %s (past: %d)", label || delta.type, newPast.length);
});
var pushHistoryAtom = (0, import_jotai6.atom)(null, (get, set, label) => {
  const history = get(historyStateAtom);
  if (history.isApplying) return;
  const graph = get(graphAtom);
  const snapshot = createSnapshot(graph, label);
  const forward = {
    type: "full-snapshot",
    nodes: snapshot.nodes,
    edges: snapshot.edges
  };
  const entry = {
    forward,
    reverse: forward,
    // For full snapshots, reverse IS the current state
    timestamp: Date.now(),
    label
  };
  const newPast = [...history.past, entry];
  if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();
  set(historyStateAtom, {
    past: newPast,
    future: [],
    isApplying: false
  });
  debug4("Pushed snapshot: %s (past: %d)", label || "unnamed", newPast.length);
});
var undoAtom = (0, import_jotai6.atom)(null, (get, set) => {
  const history = get(historyStateAtom);
  if (history.past.length === 0 || history.isApplying) return false;
  set(historyStateAtom, {
    ...history,
    isApplying: true
  });
  try {
    const graph = get(graphAtom);
    const newPast = [...history.past];
    const entry = newPast.pop();
    let forwardForRedo = entry.forward;
    if (entry.reverse.type === "full-snapshot") {
      const currentSnapshot = createSnapshot(graph, "current");
      forwardForRedo = {
        type: "full-snapshot",
        nodes: currentSnapshot.nodes,
        edges: currentSnapshot.edges
      };
    }
    const structuralChange = applyDelta(graph, entry.reverse);
    if (structuralChange) {
      set(graphAtom, graph);
      set(graphUpdateVersionAtom, (v) => v + 1);
    }
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
    const redoEntry = {
      forward: forwardForRedo,
      reverse: entry.reverse,
      timestamp: entry.timestamp,
      label: entry.label
    };
    set(historyStateAtom, {
      past: newPast,
      future: [redoEntry, ...history.future],
      isApplying: false
    });
    debug4("Undo: %s (past: %d, future: %d)", entry.label, newPast.length, history.future.length + 1);
    return true;
  } catch (error) {
    debug4.error("Undo failed: %O", error);
    set(historyStateAtom, {
      ...history,
      isApplying: false
    });
    return false;
  }
});
var redoAtom = (0, import_jotai6.atom)(null, (get, set) => {
  const history = get(historyStateAtom);
  if (history.future.length === 0 || history.isApplying) return false;
  set(historyStateAtom, {
    ...history,
    isApplying: true
  });
  try {
    const graph = get(graphAtom);
    const newFuture = [...history.future];
    const entry = newFuture.shift();
    let reverseForUndo = entry.reverse;
    if (entry.forward.type === "full-snapshot") {
      const currentSnapshot = createSnapshot(graph, "current");
      reverseForUndo = {
        type: "full-snapshot",
        nodes: currentSnapshot.nodes,
        edges: currentSnapshot.edges
      };
    }
    const structuralChange = applyDelta(graph, entry.forward);
    if (structuralChange) {
      set(graphAtom, graph);
      set(graphUpdateVersionAtom, (v) => v + 1);
    }
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
    const undoEntry = {
      forward: entry.forward,
      reverse: reverseForUndo,
      timestamp: entry.timestamp,
      label: entry.label
    };
    set(historyStateAtom, {
      past: [...history.past, undoEntry],
      future: newFuture,
      isApplying: false
    });
    debug4("Redo: %s (past: %d, future: %d)", entry.label, history.past.length + 1, newFuture.length);
    return true;
  } catch (error) {
    debug4.error("Redo failed: %O", error);
    set(historyStateAtom, {
      ...history,
      isApplying: false
    });
    return false;
  }
});
var clearHistoryAtom = (0, import_jotai6.atom)(null, (_get, set) => {
  set(historyStateAtom, {
    past: [],
    future: [],
    isApplying: false
  });
  debug4("History cleared");
});
var historyLabelsAtom = (0, import_jotai6.atom)((get) => {
  const history = get(historyStateAtom);
  return {
    past: history.past.map((e) => e.label || "Unnamed"),
    future: history.future.map((e) => e.label || "Unnamed")
  };
});

// src/core/group-store.ts
var collapsedGroupsAtom = (0, import_jotai7.atom)(/* @__PURE__ */ new Set());
var toggleGroupCollapseAtom = (0, import_jotai7.atom)(null, (get, set, groupId) => {
  const current = get(collapsedGroupsAtom);
  const next = new Set(current);
  if (next.has(groupId)) {
    next.delete(groupId);
  } else {
    next.add(groupId);
  }
  set(collapsedGroupsAtom, next);
});
var collapseGroupAtom = (0, import_jotai7.atom)(null, (get, set, groupId) => {
  const current = get(collapsedGroupsAtom);
  if (!current.has(groupId)) {
    const next = new Set(current);
    next.add(groupId);
    set(collapsedGroupsAtom, next);
  }
});
var expandGroupAtom = (0, import_jotai7.atom)(null, (get, set, groupId) => {
  const current = get(collapsedGroupsAtom);
  if (current.has(groupId)) {
    const next = new Set(current);
    next.delete(groupId);
    set(collapsedGroupsAtom, next);
  }
});
var nodeChildrenAtom = (0, import_jotai7.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return (parentId) => {
    const children = [];
    graph.forEachNode((nodeId, attrs) => {
      if (attrs.parentId === parentId) {
        children.push(nodeId);
      }
    });
    return children;
  };
});
var nodeParentAtom = (0, import_jotai7.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return (nodeId) => {
    if (!graph.hasNode(nodeId)) return void 0;
    return graph.getNodeAttribute(nodeId, "parentId");
  };
});
var isGroupNodeAtom = (0, import_jotai7.atom)((get) => {
  const getChildren = get(nodeChildrenAtom);
  return (nodeId) => getChildren(nodeId).length > 0;
});
var groupChildCountAtom = (0, import_jotai7.atom)((get) => {
  const getChildren = get(nodeChildrenAtom);
  return (groupId) => getChildren(groupId).length;
});
var setNodeParentAtom = (0, import_jotai7.atom)(null, (get, set, {
  nodeId,
  parentId
}) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) return;
  if (parentId) {
    if (parentId === nodeId) return;
    let current = parentId;
    while (current) {
      if (current === nodeId) return;
      if (!graph.hasNode(current)) break;
      current = graph.getNodeAttribute(current, "parentId");
    }
  }
  graph.setNodeAttribute(nodeId, "parentId", parentId);
  set(graphUpdateVersionAtom, (v) => v + 1);
});
var moveNodesToGroupAtom = (0, import_jotai7.atom)(null, (get, set, {
  nodeIds,
  groupId
}) => {
  for (const nodeId of nodeIds) {
    set(setNodeParentAtom, {
      nodeId,
      parentId: groupId
    });
  }
});
var removeFromGroupAtom = (0, import_jotai7.atom)(null, (get, set, nodeId) => {
  set(setNodeParentAtom, {
    nodeId,
    parentId: void 0
  });
});
var groupSelectedNodesAtom = (0, import_jotai7.atom)(null, (get, set, {
  nodeIds,
  groupNodeId
}) => {
  set(pushHistoryAtom, `Group ${nodeIds.length} nodes`);
  const graph = get(graphAtom);
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (const nodeId of nodeIds) {
    if (!graph.hasNode(nodeId)) continue;
    const attrs = graph.getNodeAttributes(nodeId);
    minX = Math.min(minX, attrs.x);
    minY = Math.min(minY, attrs.y);
    maxX = Math.max(maxX, attrs.x + (attrs.width || 200));
    maxY = Math.max(maxY, attrs.y + (attrs.height || 100));
  }
  const padding = 20;
  if (graph.hasNode(groupNodeId)) {
    graph.setNodeAttribute(groupNodeId, "x", minX - padding);
    graph.setNodeAttribute(groupNodeId, "y", minY - padding - 30);
    graph.setNodeAttribute(groupNodeId, "width", maxX - minX + 2 * padding);
    graph.setNodeAttribute(groupNodeId, "height", maxY - minY + 2 * padding + 30);
  }
  for (const nodeId of nodeIds) {
    if (nodeId !== groupNodeId && graph.hasNode(nodeId)) {
      graph.setNodeAttribute(nodeId, "parentId", groupNodeId);
    }
  }
  set(graphUpdateVersionAtom, (v) => v + 1);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
});
var ungroupNodesAtom = (0, import_jotai7.atom)(null, (get, set, groupId) => {
  set(pushHistoryAtom, "Ungroup nodes");
  const graph = get(graphAtom);
  graph.forEachNode((nodeId, attrs) => {
    if (attrs.parentId === groupId) {
      graph.setNodeAttribute(nodeId, "parentId", void 0);
    }
  });
  set(graphUpdateVersionAtom, (v) => v + 1);
});
var nestNodesOnDropAtom = (0, import_jotai7.atom)(null, (get, set, {
  nodeIds,
  targetId
}) => {
  set(pushHistoryAtom, "Nest nodes");
  for (const nodeId of nodeIds) {
    if (nodeId === targetId) continue;
    set(setNodeParentAtom, {
      nodeId,
      parentId: targetId
    });
  }
  set(autoResizeGroupAtom, targetId);
});
var collapsedEdgeRemapAtom = (0, import_jotai7.atom)((get) => {
  const collapsed = get(collapsedGroupsAtom);
  if (collapsed.size === 0) return /* @__PURE__ */ new Map();
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const remap = /* @__PURE__ */ new Map();
  for (const nodeId of graph.nodes()) {
    let current = nodeId;
    let outermost = null;
    while (true) {
      if (!graph.hasNode(current)) break;
      const parent = graph.getNodeAttribute(current, "parentId");
      if (!parent) break;
      if (collapsed.has(parent)) outermost = parent;
      current = parent;
    }
    if (outermost) remap.set(nodeId, outermost);
  }
  return remap;
});
var autoResizeGroupAtom = (0, import_jotai7.atom)(null, (get, set, groupId) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(groupId)) return;
  const children = [];
  graph.forEachNode((nodeId, attrs) => {
    if (attrs.parentId === groupId) {
      children.push(nodeId);
    }
  });
  if (children.length === 0) return;
  const padding = 20;
  const headerHeight = 30;
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (const childId of children) {
    const attrs = graph.getNodeAttributes(childId);
    minX = Math.min(minX, attrs.x);
    minY = Math.min(minY, attrs.y);
    maxX = Math.max(maxX, attrs.x + (attrs.width || 200));
    maxY = Math.max(maxY, attrs.y + (attrs.height || 100));
  }
  graph.setNodeAttribute(groupId, "x", minX - padding);
  graph.setNodeAttribute(groupId, "y", minY - padding - headerHeight);
  graph.setNodeAttribute(groupId, "width", maxX - minX + 2 * padding);
  graph.setNodeAttribute(groupId, "height", maxY - minY + 2 * padding + headerHeight);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
});

// src/core/graph-derived.ts
var highestZIndexAtom = (0, import_jotai8.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  let maxZ = 0;
  graph.forEachNode((_node, attributes) => {
    if (attributes.zIndex > maxZ) {
      maxZ = attributes.zIndex;
    }
  });
  return maxZ;
});
var _prevUiNodesByGraph = /* @__PURE__ */ new WeakMap();
var uiNodesAtom = (0, import_jotai8.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const currentDraggingId = get(draggingNodeIdAtom);
  const collapsed = get(collapsedGroupsAtom);
  const nodes = [];
  graph.forEachNode((nodeId, attributes) => {
    if (collapsed.size > 0) {
      let current = nodeId;
      let hidden = false;
      while (true) {
        if (!graph.hasNode(current)) break;
        const pid = graph.getNodeAttributes(current).parentId;
        if (!pid) break;
        if (collapsed.has(pid)) {
          hidden = true;
          break;
        }
        current = pid;
      }
      if (hidden) return;
    }
    const position = get(nodePositionAtomFamily(nodeId));
    nodes.push({
      ...attributes,
      id: nodeId,
      position,
      isDragging: nodeId === currentDraggingId
    });
  });
  const prev = _prevUiNodesByGraph.get(graph) ?? [];
  if (nodes.length === prev.length && nodes.every((n, i) => n.id === prev[i].id && n.position === prev[i].position && n.isDragging === prev[i].isDragging)) {
    return prev;
  }
  _prevUiNodesByGraph.set(graph, nodes);
  return nodes;
});
var nodeKeysAtom = (0, import_jotai8.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return graph.nodes();
});
var nodeFamilyAtom = (0, import_jotai_family2.atomFamily)((nodeId) => (0, import_jotai8.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) {
    return null;
  }
  const attributes = graph.getNodeAttributes(nodeId);
  const position = get(nodePositionAtomFamily(nodeId));
  const currentDraggingId = get(draggingNodeIdAtom);
  return {
    ...attributes,
    id: nodeId,
    position,
    isDragging: nodeId === currentDraggingId
  };
}), (a, b) => a === b);
var edgeKeysAtom = (0, import_jotai8.atom)((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return graph.edges();
});
var edgeKeysWithTempEdgeAtom = (0, import_jotai8.atom)((get) => {
  const keys = get(edgeKeysAtom);
  const edgeCreation = get(edgeCreationAtom);
  if (edgeCreation.isCreating) {
    return [...keys, "temp-creating-edge"];
  }
  return keys;
});
var _edgeCacheByGraph = /* @__PURE__ */ new WeakMap();
function getEdgeCache(graph) {
  let cache = _edgeCacheByGraph.get(graph);
  if (!cache) {
    cache = /* @__PURE__ */ new Map();
    _edgeCacheByGraph.set(graph, cache);
  }
  return cache;
}
var edgeFamilyAtom = (0, import_jotai_family2.atomFamily)((key) => (0, import_jotai8.atom)((get) => {
  get(graphUpdateVersionAtom);
  if (key === "temp-creating-edge") {
    const edgeCreationState = get(edgeCreationAtom);
    const graph2 = get(graphAtom);
    if (edgeCreationState.isCreating && edgeCreationState.sourceNodeId && edgeCreationState.targetPosition) {
      const sourceNodeAttrs = graph2.getNodeAttributes(edgeCreationState.sourceNodeId);
      const sourceNodePosition = get(nodePositionAtomFamily(edgeCreationState.sourceNodeId));
      const pan = get(panAtom);
      const zoom = get(zoomAtom);
      const viewportRect = get(viewportRectAtom);
      if (sourceNodeAttrs && viewportRect) {
        const mouseX = edgeCreationState.targetPosition.x - viewportRect.left;
        const mouseY = edgeCreationState.targetPosition.y - viewportRect.top;
        const worldTargetX = (mouseX - pan.x) / zoom;
        const worldTargetY = (mouseY - pan.y) / zoom;
        const tempEdge = {
          key: "temp-creating-edge",
          sourceId: edgeCreationState.sourceNodeId,
          targetId: "temp-cursor",
          sourcePosition: sourceNodePosition,
          targetPosition: {
            x: worldTargetX,
            y: worldTargetY
          },
          sourceNodeSize: sourceNodeAttrs.size,
          sourceNodeWidth: sourceNodeAttrs.width,
          sourceNodeHeight: sourceNodeAttrs.height,
          targetNodeSize: 0,
          targetNodeWidth: 0,
          targetNodeHeight: 0,
          type: "dashed",
          color: "#FF9800",
          weight: 2,
          label: void 0,
          dbData: {
            id: "temp-creating-edge",
            graph_id: get(currentGraphIdAtom) || "",
            source_node_id: edgeCreationState.sourceNodeId,
            target_node_id: "temp-cursor",
            edge_type: "temp",
            filter_condition: null,
            ui_properties: null,
            data: null,
            created_at: (/* @__PURE__ */ new Date()).toISOString(),
            updated_at: (/* @__PURE__ */ new Date()).toISOString()
          }
        };
        return tempEdge;
      }
    }
    return null;
  }
  const graph = get(graphAtom);
  if (!graph.hasEdge(key)) {
    getEdgeCache(graph).delete(key);
    return null;
  }
  const sourceId = graph.source(key);
  const targetId = graph.target(key);
  const attributes = graph.getEdgeAttributes(key);
  const remap = get(collapsedEdgeRemapAtom);
  const effectiveSourceId = remap.get(sourceId) ?? sourceId;
  const effectiveTargetId = remap.get(targetId) ?? targetId;
  if (!graph.hasNode(effectiveSourceId) || !graph.hasNode(effectiveTargetId)) {
    getEdgeCache(graph).delete(key);
    return null;
  }
  const sourceAttributes = graph.getNodeAttributes(effectiveSourceId);
  const targetAttributes = graph.getNodeAttributes(effectiveTargetId);
  const sourcePosition = get(nodePositionAtomFamily(effectiveSourceId));
  const targetPosition = get(nodePositionAtomFamily(effectiveTargetId));
  if (sourceAttributes && targetAttributes) {
    const next = {
      ...attributes,
      key,
      sourceId: effectiveSourceId,
      targetId: effectiveTargetId,
      sourcePosition,
      targetPosition,
      sourceNodeSize: sourceAttributes.size,
      targetNodeSize: targetAttributes.size,
      sourceNodeWidth: sourceAttributes.width ?? sourceAttributes.size,
      sourceNodeHeight: sourceAttributes.height ?? sourceAttributes.size,
      targetNodeWidth: targetAttributes.width ?? targetAttributes.size,
      targetNodeHeight: targetAttributes.height ?? targetAttributes.size
    };
    const edgeCache = getEdgeCache(graph);
    const prev = edgeCache.get(key);
    if (prev && prev.sourcePosition === next.sourcePosition && prev.targetPosition === next.targetPosition && prev.sourceId === next.sourceId && prev.targetId === next.targetId && prev.type === next.type && prev.color === next.color && prev.weight === next.weight && prev.label === next.label && prev.sourceNodeSize === next.sourceNodeSize && prev.targetNodeSize === next.targetNodeSize && prev.sourceNodeWidth === next.sourceNodeWidth && prev.sourceNodeHeight === next.sourceNodeHeight && prev.targetNodeWidth === next.targetNodeWidth && prev.targetNodeHeight === next.targetNodeHeight) {
      return prev;
    }
    edgeCache.set(key, next);
    return next;
  }
  getEdgeCache(graph).delete(key);
  return null;
}), (a, b) => a === b);

// src/core/graph-mutations.ts
var import_jotai12 = require("jotai");
var import_graphology3 = __toESM(require("graphology"));

// src/core/graph-mutations-edges.ts
var import_jotai10 = require("jotai");

// src/core/reduced-motion-store.ts
var import_jotai9 = require("jotai");
var prefersReducedMotionAtom = (0, import_jotai9.atom)(typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false);
var watchReducedMotionAtom = (0, import_jotai9.atom)(null, (_get, set) => {
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
  const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
  const handler = (e) => {
    set(prefersReducedMotionAtom, e.matches);
  };
  set(prefersReducedMotionAtom, mql.matches);
  mql.addEventListener("change", handler);
  return () => mql.removeEventListener("change", handler);
});

// src/core/graph-mutations-edges.ts
var debug5 = createDebug("graph:mutations:edges");
var addEdgeToLocalGraphAtom = (0, import_jotai10.atom)(null, (get, set, newEdge) => {
  const graph = get(graphAtom);
  if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
    const uiProps = newEdge.ui_properties || {};
    const attributes = {
      type: typeof uiProps.style === "string" ? uiProps.style : "solid",
      color: typeof uiProps.color === "string" ? uiProps.color : "#999",
      label: newEdge.edge_type ?? void 0,
      weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
      dbData: newEdge
    };
    if (!graph.hasEdge(newEdge.id)) {
      try {
        debug5("Adding edge %s to local graph", newEdge.id);
        graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
        set(graphAtom, graph.copy());
        set(graphUpdateVersionAtom, (v) => v + 1);
      } catch (e) {
        debug5("Failed to add edge %s: %o", newEdge.id, e);
      }
    }
  }
});
var removeEdgeFromLocalGraphAtom = (0, import_jotai10.atom)(null, (get, set, edgeId) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(edgeId)) {
    graph.dropEdge(edgeId);
    set(graphAtom, graph.copy());
    set(graphUpdateVersionAtom, (v) => v + 1);
  }
});
var swapEdgeAtomicAtom = (0, import_jotai10.atom)(null, (get, set, {
  tempEdgeId,
  newEdge
}) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(tempEdgeId)) {
    graph.dropEdge(tempEdgeId);
  }
  if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
    const uiProps = newEdge.ui_properties || {};
    const attributes = {
      type: typeof uiProps.style === "string" ? uiProps.style : "solid",
      color: typeof uiProps.color === "string" ? uiProps.color : "#999",
      label: newEdge.edge_type ?? void 0,
      weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
      dbData: newEdge
    };
    if (!graph.hasEdge(newEdge.id)) {
      try {
        debug5("Atomically swapping temp edge %s with real edge %s", tempEdgeId, newEdge.id);
        graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
      } catch (e) {
        debug5("Failed to add edge %s: %o", newEdge.id, e);
      }
    }
  }
  set(graphAtom, graph.copy());
  set(graphUpdateVersionAtom, (v) => v + 1);
});
var departingEdgesAtom = (0, import_jotai10.atom)(/* @__PURE__ */ new Map());
var EDGE_ANIMATION_DURATION = 300;
var removeEdgeWithAnimationAtom = (0, import_jotai10.atom)(null, (get, set, edgeKey) => {
  const edgeState = get(edgeFamilyAtom(edgeKey));
  if (edgeState) {
    const departing = new Map(get(departingEdgesAtom));
    departing.set(edgeKey, edgeState);
    set(departingEdgesAtom, departing);
    set(removeEdgeFromLocalGraphAtom, edgeKey);
    const duration = get(prefersReducedMotionAtom) ? 0 : EDGE_ANIMATION_DURATION;
    setTimeout(() => {
      const current = new Map(get(departingEdgesAtom));
      current.delete(edgeKey);
      set(departingEdgesAtom, current);
    }, duration);
  }
});
var editingEdgeLabelAtom = (0, import_jotai10.atom)(null);
var updateEdgeLabelAtom = (0, import_jotai10.atom)(null, (get, set, {
  edgeKey,
  label
}) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(edgeKey)) {
    graph.setEdgeAttribute(edgeKey, "label", label || void 0);
    set(graphUpdateVersionAtom, (v) => v + 1);
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
  }
});

// src/core/graph-mutations-advanced.ts
var import_jotai11 = require("jotai");
var debug6 = createDebug("graph:mutations:advanced");
var dropTargetNodeIdAtom = (0, import_jotai11.atom)(null);
var splitNodeAtom = (0, import_jotai11.atom)(null, (get, set, {
  nodeId,
  position1,
  position2
}) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) return;
  const attrs = graph.getNodeAttributes(nodeId);
  const graphId = get(currentGraphIdAtom) || attrs.dbData.graph_id;
  set(pushHistoryAtom, "Split node");
  graph.setNodeAttribute(nodeId, "x", position1.x);
  graph.setNodeAttribute(nodeId, "y", position1.y);
  const edges = [];
  graph.forEachEdge(nodeId, (_key, eAttrs, source, target) => {
    edges.push({
      source,
      target,
      attrs: eAttrs
    });
  });
  const cloneId = `split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  const cloneDbNode = {
    ...attrs.dbData,
    id: cloneId,
    graph_id: graphId,
    ui_properties: {
      ...attrs.dbData.ui_properties || {},
      x: position2.x,
      y: position2.y
    },
    created_at: (/* @__PURE__ */ new Date()).toISOString(),
    updated_at: (/* @__PURE__ */ new Date()).toISOString()
  };
  set(addNodeToLocalGraphAtom, cloneDbNode);
  for (const edge of edges) {
    const newSource = edge.source === nodeId ? cloneId : edge.source;
    const newTarget = edge.target === nodeId ? cloneId : edge.target;
    set(addEdgeToLocalGraphAtom, {
      ...edge.attrs.dbData,
      id: `split-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
      source_node_id: newSource,
      target_node_id: newTarget
    });
  }
  set(graphUpdateVersionAtom, (v) => v + 1);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
  debug6("Split node %s \u2192 clone %s", nodeId, cloneId);
});
var mergeNodesAtom = (0, import_jotai11.atom)(null, (get, set, {
  nodeIds
}) => {
  if (nodeIds.length < 2) return;
  const graph = get(graphAtom);
  const [survivorId, ...doomed] = nodeIds;
  if (!graph.hasNode(survivorId)) return;
  set(pushHistoryAtom, `Merge ${nodeIds.length} nodes`);
  const doomedSet = new Set(doomed);
  for (const doomedId of doomed) {
    if (!graph.hasNode(doomedId)) continue;
    const edges = [];
    graph.forEachEdge(doomedId, (_key, eAttrs, source, target) => {
      edges.push({
        source,
        target,
        attrs: eAttrs
      });
    });
    for (const edge of edges) {
      const newSource = doomedSet.has(edge.source) ? survivorId : edge.source;
      const newTarget = doomedSet.has(edge.target) ? survivorId : edge.target;
      if (newSource === newTarget) continue;
      set(addEdgeToLocalGraphAtom, {
        ...edge.attrs.dbData,
        id: `merge-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        source_node_id: newSource,
        target_node_id: newTarget
      });
    }
    set(optimisticDeleteNodeAtom, {
      nodeId: doomedId
    });
  }
  set(graphUpdateVersionAtom, (v) => v + 1);
  debug6("Merged nodes %o \u2192 survivor %s", nodeIds, survivorId);
});

// src/core/graph-mutations.ts
var debug7 = createDebug("graph:mutations");
var startNodeDragAtom = (0, import_jotai12.atom)(null, (get, set, {
  nodeId
}) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) return;
  const currentAttributes = graph.getNodeAttributes(nodeId);
  set(preDragNodeAttributesAtom, JSON.parse(JSON.stringify(currentAttributes)));
  const currentHighestZIndex = get(highestZIndexAtom);
  const newZIndex = currentHighestZIndex + 1;
  graph.setNodeAttribute(nodeId, "zIndex", newZIndex);
  set(draggingNodeIdAtom, nodeId);
});
var endNodeDragAtom = (0, import_jotai12.atom)(null, (get, set, _payload) => {
  const currentDraggingId = get(draggingNodeIdAtom);
  if (currentDraggingId) {
    debug7("Node %s drag ended", currentDraggingId);
    const graph = get(graphAtom);
    if (graph.hasNode(currentDraggingId)) {
      const parentId = graph.getNodeAttribute(currentDraggingId, "parentId");
      if (parentId) {
        set(autoResizeGroupAtom, parentId);
      }
    }
  }
  set(draggingNodeIdAtom, null);
  set(preDragNodeAttributesAtom, null);
});
var optimisticDeleteNodeAtom = (0, import_jotai12.atom)(null, (get, set, {
  nodeId
}) => {
  const graph = get(graphAtom);
  if (graph.hasNode(nodeId)) {
    graph.dropNode(nodeId);
    set(cleanupNodePositionAtom, nodeId);
    set(graphAtom, graph.copy());
    debug7("Optimistically deleted node %s", nodeId);
  }
});
var optimisticDeleteEdgeAtom = (0, import_jotai12.atom)(null, (get, set, {
  edgeKey
}) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(edgeKey)) {
    graph.dropEdge(edgeKey);
    set(graphAtom, graph.copy());
    debug7("Optimistically deleted edge %s", edgeKey);
  }
});
var addNodeToLocalGraphAtom = (0, import_jotai12.atom)(null, (get, set, newNode) => {
  const graph = get(graphAtom);
  if (graph.hasNode(newNode.id)) {
    debug7("Node %s already exists, skipping", newNode.id);
    return;
  }
  const uiProps = newNode.ui_properties || {};
  const attributes = {
    x: typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800,
    y: typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600,
    size: typeof uiProps.size === "number" ? uiProps.size : 15,
    width: typeof uiProps.width === "number" ? uiProps.width : 500,
    height: typeof uiProps.height === "number" ? uiProps.height : 500,
    color: typeof uiProps.color === "string" ? uiProps.color : "#ccc",
    label: newNode.label || newNode.node_type || newNode.id,
    zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0,
    dbData: newNode
  };
  debug7("Adding node %s to local graph at (%d, %d)", newNode.id, attributes.x, attributes.y);
  graph.addNode(newNode.id, attributes);
  set(graphAtom, graph.copy());
  set(graphUpdateVersionAtom, (v) => v + 1);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
});
var loadGraphFromDbAtom = (0, import_jotai12.atom)(null, (get, set, fetchedNodes, fetchedEdges) => {
  debug7("========== START SYNC ==========");
  debug7("Fetched nodes: %d, edges: %d", fetchedNodes.length, fetchedEdges.length);
  const currentGraphId = get(currentGraphIdAtom);
  if (fetchedNodes.length > 0 && fetchedNodes[0].graph_id !== currentGraphId) {
    debug7("Skipping sync - data belongs to different graph");
    return;
  }
  const existingGraph = get(graphAtom);
  const isDragging = get(draggingNodeIdAtom) !== null;
  if (isDragging) {
    debug7("Skipping sync - drag in progress");
    return;
  }
  const existingNodeIds = new Set(existingGraph.nodes());
  const fetchedNodeIds = new Set(fetchedNodes.map((n) => n.id));
  const hasAnyCommonNodes = Array.from(existingNodeIds).some((id) => fetchedNodeIds.has(id));
  let graph;
  if (hasAnyCommonNodes && existingNodeIds.size > 0) {
    debug7("Merging DB data into existing graph");
    graph = existingGraph.copy();
  } else {
    debug7("Creating fresh graph (graph switch detected)");
    graph = new import_graphology3.default(graphOptions);
  }
  const fetchedEdgeIds = new Set(fetchedEdges.map((e) => e.id));
  if (hasAnyCommonNodes && existingNodeIds.size > 0) {
    graph.forEachNode((nodeId) => {
      if (!fetchedNodeIds.has(nodeId)) {
        debug7("Removing deleted node: %s", nodeId);
        graph.dropNode(nodeId);
        nodePositionAtomFamily.remove(nodeId);
      }
    });
  }
  fetchedNodes.forEach((node) => {
    const uiProps = node.ui_properties || {};
    const newX = typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800;
    const newY = typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600;
    if (graph.hasNode(node.id)) {
      const currentAttrs = graph.getNodeAttributes(node.id);
      const attributes = {
        x: newX,
        y: newY,
        size: typeof uiProps.size === "number" ? uiProps.size : currentAttrs.size,
        width: typeof uiProps.width === "number" ? uiProps.width : currentAttrs.width ?? 500,
        height: typeof uiProps.height === "number" ? uiProps.height : currentAttrs.height ?? 500,
        color: typeof uiProps.color === "string" ? uiProps.color : currentAttrs.color,
        label: node.label || node.node_type || node.id,
        zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : currentAttrs.zIndex,
        dbData: node
      };
      graph.replaceNodeAttributes(node.id, attributes);
    } else {
      const attributes = {
        x: newX,
        y: newY,
        size: typeof uiProps.size === "number" ? uiProps.size : 15,
        width: typeof uiProps.width === "number" ? uiProps.width : 500,
        height: typeof uiProps.height === "number" ? uiProps.height : 500,
        color: typeof uiProps.color === "string" ? uiProps.color : "#ccc",
        label: node.label || node.node_type || node.id,
        zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0,
        dbData: node
      };
      graph.addNode(node.id, attributes);
    }
  });
  graph.forEachEdge((edgeId) => {
    if (!fetchedEdgeIds.has(edgeId)) {
      debug7("Removing deleted edge: %s", edgeId);
      graph.dropEdge(edgeId);
    }
  });
  fetchedEdges.forEach((edge) => {
    if (graph.hasNode(edge.source_node_id) && graph.hasNode(edge.target_node_id)) {
      const uiProps = edge.ui_properties || {};
      const attributes = {
        type: typeof uiProps.style === "string" ? uiProps.style : "solid",
        color: typeof uiProps.color === "string" ? uiProps.color : "#999",
        label: edge.edge_type ?? void 0,
        weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
        dbData: edge
      };
      if (graph.hasEdge(edge.id)) {
        graph.replaceEdgeAttributes(edge.id, attributes);
      } else {
        try {
          graph.addEdgeWithKey(edge.id, edge.source_node_id, edge.target_node_id, attributes);
        } catch (e) {
          debug7("Failed to add edge %s: %o", edge.id, e);
        }
      }
    }
  });
  set(graphAtom, graph);
  set(graphUpdateVersionAtom, (v) => v + 1);
  debug7("========== SYNC COMPLETE ==========");
  debug7("Final graph: %d nodes, %d edges", graph.order, graph.size);
});

// src/core/sync-store.ts
var import_jotai13 = require("jotai");
var debug8 = createDebug("sync");
var syncStatusAtom = (0, import_jotai13.atom)("synced");
var pendingMutationsCountAtom = (0, import_jotai13.atom)(0);
var isOnlineAtom = (0, import_jotai13.atom)(typeof navigator !== "undefined" ? navigator.onLine : true);
var lastSyncErrorAtom = (0, import_jotai13.atom)(null);
var lastSyncTimeAtom = (0, import_jotai13.atom)(Date.now());
var mutationQueueAtom = (0, import_jotai13.atom)([]);
var syncStateAtom = (0, import_jotai13.atom)((get) => ({
  status: get(syncStatusAtom),
  pendingMutations: get(pendingMutationsCountAtom),
  lastError: get(lastSyncErrorAtom),
  lastSyncTime: get(lastSyncTimeAtom),
  isOnline: get(isOnlineAtom),
  queuedMutations: get(mutationQueueAtom).length
}));
var startMutationAtom = (0, import_jotai13.atom)(null, (get, set) => {
  const currentCount = get(pendingMutationsCountAtom);
  const newCount = currentCount + 1;
  set(pendingMutationsCountAtom, newCount);
  debug8("Mutation started. Pending count: %d -> %d", currentCount, newCount);
  if (newCount > 0 && get(syncStatusAtom) !== "syncing") {
    set(syncStatusAtom, "syncing");
    debug8("Status -> syncing");
  }
});
var completeMutationAtom = (0, import_jotai13.atom)(null, (get, set, success = true) => {
  const currentCount = get(pendingMutationsCountAtom);
  const newCount = Math.max(0, currentCount - 1);
  set(pendingMutationsCountAtom, newCount);
  debug8("Mutation completed (success: %s). Pending count: %d -> %d", success, currentCount, newCount);
  if (success) {
    set(lastSyncTimeAtom, Date.now());
    if (newCount === 0) {
      set(lastSyncErrorAtom, null);
    }
  }
  if (newCount === 0) {
    const isOnline = get(isOnlineAtom);
    const hasError = get(lastSyncErrorAtom) !== null;
    if (hasError) {
      set(syncStatusAtom, "error");
      debug8("Status -> error");
    } else if (!isOnline) {
      set(syncStatusAtom, "offline");
      debug8("Status -> offline");
    } else {
      set(syncStatusAtom, "synced");
      debug8("Status -> synced");
    }
  }
});
var trackMutationErrorAtom = (0, import_jotai13.atom)(null, (_get, set, error) => {
  set(lastSyncErrorAtom, error);
  debug8("Mutation failed: %s", error);
});
var setOnlineStatusAtom = (0, import_jotai13.atom)(null, (get, set, isOnline) => {
  set(isOnlineAtom, isOnline);
  const pendingCount = get(pendingMutationsCountAtom);
  const hasError = get(lastSyncErrorAtom) !== null;
  const queueLength = get(mutationQueueAtom).length;
  if (pendingCount === 0) {
    if (hasError || queueLength > 0) {
      set(syncStatusAtom, "error");
    } else {
      set(syncStatusAtom, isOnline ? "synced" : "offline");
    }
  }
});
var queueMutationAtom = (0, import_jotai13.atom)(null, (get, set, mutation) => {
  const queue = get(mutationQueueAtom);
  const newMutation = {
    ...mutation,
    id: crypto.randomUUID(),
    timestamp: Date.now(),
    retryCount: 0,
    maxRetries: mutation.maxRetries ?? 3
  };
  const newQueue = [...queue, newMutation];
  set(mutationQueueAtom, newQueue);
  debug8("Queued mutation: %s. Queue size: %d", mutation.type, newQueue.length);
  if (get(pendingMutationsCountAtom) === 0) {
    set(syncStatusAtom, "error");
  }
  return newMutation.id;
});
var dequeueMutationAtom = (0, import_jotai13.atom)(null, (get, set, mutationId) => {
  const queue = get(mutationQueueAtom);
  const newQueue = queue.filter((m) => m.id !== mutationId);
  set(mutationQueueAtom, newQueue);
  debug8("Dequeued mutation: %s. Queue size: %d", mutationId, newQueue.length);
  if (newQueue.length === 0 && get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) {
    set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline");
  }
});
var incrementRetryCountAtom = (0, import_jotai13.atom)(null, (get, set, mutationId) => {
  const queue = get(mutationQueueAtom);
  const newQueue = queue.map((m) => m.id === mutationId ? {
    ...m,
    retryCount: m.retryCount + 1
  } : m);
  set(mutationQueueAtom, newQueue);
});
var getNextQueuedMutationAtom = (0, import_jotai13.atom)((get) => {
  const queue = get(mutationQueueAtom);
  return queue.find((m) => m.retryCount < m.maxRetries) ?? null;
});
var clearMutationQueueAtom = (0, import_jotai13.atom)(null, (get, set) => {
  set(mutationQueueAtom, []);
  debug8("Cleared mutation queue");
  if (get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) {
    set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline");
  }
});

// src/core/interaction-store.ts
var import_jotai14 = require("jotai");
var inputModeAtom = (0, import_jotai14.atom)({
  type: "normal"
});
var keyboardInteractionModeAtom = (0, import_jotai14.atom)("navigate");
var interactionFeedbackAtom = (0, import_jotai14.atom)(null);
var pendingInputResolverAtom = (0, import_jotai14.atom)(null);
var resetInputModeAtom = (0, import_jotai14.atom)(null, (_get, set) => {
  set(inputModeAtom, {
    type: "normal"
  });
  set(interactionFeedbackAtom, null);
  set(pendingInputResolverAtom, null);
});
var resetKeyboardInteractionModeAtom = (0, import_jotai14.atom)(null, (_get, set) => {
  set(keyboardInteractionModeAtom, "navigate");
});
var setKeyboardInteractionModeAtom = (0, import_jotai14.atom)(null, (_get, set, mode) => {
  set(keyboardInteractionModeAtom, mode);
});
var startPickNodeAtom = (0, import_jotai14.atom)(null, (_get, set, options) => {
  set(inputModeAtom, {
    type: "pickNode",
    ...options
  });
});
var startPickNodesAtom = (0, import_jotai14.atom)(null, (_get, set, options) => {
  set(inputModeAtom, {
    type: "pickNodes",
    ...options
  });
});
var startPickPointAtom = (0, import_jotai14.atom)(null, (_get, set, options) => {
  set(inputModeAtom, {
    type: "pickPoint",
    ...options
  });
});
var provideInputAtom = (0, import_jotai14.atom)(null, (get, set, value) => {
  set(pendingInputResolverAtom, value);
});
var updateInteractionFeedbackAtom = (0, import_jotai14.atom)(null, (get, set, feedback) => {
  const current = get(interactionFeedbackAtom);
  set(interactionFeedbackAtom, {
    ...current,
    ...feedback
  });
});
var isPickingModeAtom = (0, import_jotai14.atom)((get) => {
  const mode = get(inputModeAtom);
  return mode.type !== "normal";
});
var isPickNodeModeAtom = (0, import_jotai14.atom)((get) => {
  const mode = get(inputModeAtom);
  return mode.type === "pickNode" || mode.type === "pickNodes";
});

// src/core/locked-node-store.ts
var import_jotai15 = require("jotai");
var lockedNodeIdAtom = (0, import_jotai15.atom)(null);
var lockedNodeDataAtom = (0, import_jotai15.atom)((get) => {
  const id = get(lockedNodeIdAtom);
  if (!id) return null;
  const nodes = get(uiNodesAtom);
  return nodes.find((n) => n.id === id) || null;
});
var lockedNodePageIndexAtom = (0, import_jotai15.atom)(0);
var lockedNodePageCountAtom = (0, import_jotai15.atom)(1);
var lockNodeAtom = (0, import_jotai15.atom)(null, (_get, set, payload) => {
  set(lockedNodeIdAtom, payload.nodeId);
  set(lockedNodePageIndexAtom, 0);
});
var unlockNodeAtom = (0, import_jotai15.atom)(null, (_get, set) => {
  set(lockedNodeIdAtom, null);
});
var nextLockedPageAtom = (0, import_jotai15.atom)(null, (get, set) => {
  const current = get(lockedNodePageIndexAtom);
  const pageCount = get(lockedNodePageCountAtom);
  set(lockedNodePageIndexAtom, (current + 1) % pageCount);
});
var prevLockedPageAtom = (0, import_jotai15.atom)(null, (get, set) => {
  const current = get(lockedNodePageIndexAtom);
  const pageCount = get(lockedNodePageCountAtom);
  set(lockedNodePageIndexAtom, (current - 1 + pageCount) % pageCount);
});
var goToLockedPageAtom = (0, import_jotai15.atom)(null, (get, set, index) => {
  const pageCount = get(lockedNodePageCountAtom);
  if (index >= 0 && index < pageCount) {
    set(lockedNodePageIndexAtom, index);
  }
});
var hasLockedNodeAtom = (0, import_jotai15.atom)((get) => get(lockedNodeIdAtom) !== null);

// src/core/node-type-registry.tsx
var import_compiler_runtime = require("react/compiler-runtime");
var import_react = __toESM(require("react"));
var import_jsx_runtime = require("react/jsx-runtime");

// src/core/toast-store.ts
var import_jotai16 = require("jotai");
var canvasToastAtom = (0, import_jotai16.atom)(null);
var showToastAtom = (0, import_jotai16.atom)(null, (_get, set, message) => {
  const id = `toast-${Date.now()}`;
  set(canvasToastAtom, {
    id,
    message,
    timestamp: Date.now()
  });
  setTimeout(() => {
    set(canvasToastAtom, (current) => current?.id === id ? null : current);
  }, 2e3);
});

// src/core/snap-store.ts
var import_jotai17 = require("jotai");
var snapEnabledAtom = (0, import_jotai17.atom)(false);
var snapGridSizeAtom = (0, import_jotai17.atom)(20);
var snapTemporaryDisableAtom = (0, import_jotai17.atom)(false);
var isSnappingActiveAtom = (0, import_jotai17.atom)((get) => {
  return get(snapEnabledAtom) && !get(snapTemporaryDisableAtom);
});
var toggleSnapAtom = (0, import_jotai17.atom)(null, (get, set) => {
  set(snapEnabledAtom, !get(snapEnabledAtom));
});
var setGridSizeAtom = (0, import_jotai17.atom)(null, (_get, set, size) => {
  set(snapGridSizeAtom, Math.max(5, Math.min(200, size)));
});
var snapAlignmentEnabledAtom = (0, import_jotai17.atom)(true);
var toggleAlignmentGuidesAtom = (0, import_jotai17.atom)(null, (get, set) => {
  set(snapAlignmentEnabledAtom, !get(snapAlignmentEnabledAtom));
});
var alignmentGuidesAtom = (0, import_jotai17.atom)({
  verticalGuides: [],
  horizontalGuides: []
});
var clearAlignmentGuidesAtom = (0, import_jotai17.atom)(null, (_get, set) => {
  set(alignmentGuidesAtom, {
    verticalGuides: [],
    horizontalGuides: []
  });
});

// src/core/event-types.ts
var CanvasEventType = /* @__PURE__ */ (function(CanvasEventType2) {
  CanvasEventType2["NodeClick"] = "node:click";
  CanvasEventType2["NodeDoubleClick"] = "node:double-click";
  CanvasEventType2["NodeTripleClick"] = "node:triple-click";
  CanvasEventType2["NodeRightClick"] = "node:right-click";
  CanvasEventType2["NodeLongPress"] = "node:long-press";
  CanvasEventType2["EdgeClick"] = "edge:click";
  CanvasEventType2["EdgeDoubleClick"] = "edge:double-click";
  CanvasEventType2["EdgeRightClick"] = "edge:right-click";
  CanvasEventType2["BackgroundClick"] = "background:click";
  CanvasEventType2["BackgroundDoubleClick"] = "background:double-click";
  CanvasEventType2["BackgroundRightClick"] = "background:right-click";
  CanvasEventType2["BackgroundLongPress"] = "background:long-press";
  return CanvasEventType2;
})({});
var EVENT_TYPE_INFO = {
  [CanvasEventType.NodeClick]: {
    type: CanvasEventType.NodeClick,
    label: "Click Node",
    description: "Triggered when clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeDoubleClick]: {
    type: CanvasEventType.NodeDoubleClick,
    label: "Double-click Node",
    description: "Triggered when double-clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeTripleClick]: {
    type: CanvasEventType.NodeTripleClick,
    label: "Triple-click Node",
    description: "Triggered when triple-clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeRightClick]: {
    type: CanvasEventType.NodeRightClick,
    label: "Right-click Node",
    description: "Triggered when right-clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeLongPress]: {
    type: CanvasEventType.NodeLongPress,
    label: "Long-press Node",
    description: "Triggered when long-pressing on a node (mobile/touch)",
    category: "node"
  },
  [CanvasEventType.EdgeClick]: {
    type: CanvasEventType.EdgeClick,
    label: "Click Edge",
    description: "Triggered when clicking on an edge",
    category: "edge"
  },
  [CanvasEventType.EdgeDoubleClick]: {
    type: CanvasEventType.EdgeDoubleClick,
    label: "Double-click Edge",
    description: "Triggered when double-clicking on an edge",
    category: "edge"
  },
  [CanvasEventType.EdgeRightClick]: {
    type: CanvasEventType.EdgeRightClick,
    label: "Right-click Edge",
    description: "Triggered when right-clicking on an edge",
    category: "edge"
  },
  [CanvasEventType.BackgroundClick]: {
    type: CanvasEventType.BackgroundClick,
    label: "Click Background",
    description: "Triggered when clicking on the canvas background",
    category: "background"
  },
  [CanvasEventType.BackgroundDoubleClick]: {
    type: CanvasEventType.BackgroundDoubleClick,
    label: "Double-click Background",
    description: "Triggered when double-clicking on the canvas background",
    category: "background"
  },
  [CanvasEventType.BackgroundRightClick]: {
    type: CanvasEventType.BackgroundRightClick,
    label: "Right-click Background",
    description: "Triggered when right-clicking on the canvas background",
    category: "background"
  },
  [CanvasEventType.BackgroundLongPress]: {
    type: CanvasEventType.BackgroundLongPress,
    label: "Long-press Background",
    description: "Triggered when long-pressing on the canvas background (mobile/touch)",
    category: "background"
  }
};

// src/core/action-types.ts
var ActionCategory = /* @__PURE__ */ (function(ActionCategory2) {
  ActionCategory2["None"] = "none";
  ActionCategory2["Selection"] = "selection";
  ActionCategory2["Viewport"] = "viewport";
  ActionCategory2["Node"] = "node";
  ActionCategory2["Layout"] = "layout";
  ActionCategory2["History"] = "history";
  ActionCategory2["Custom"] = "custom";
  return ActionCategory2;
})({});
var BuiltInActionId = {
  // None
  None: "none",
  // Selection
  SelectNode: "select-node",
  SelectEdge: "select-edge",
  AddToSelection: "add-to-selection",
  ClearSelection: "clear-selection",
  DeleteSelected: "delete-selected",
  // Viewport
  FitToView: "fit-to-view",
  FitAllToView: "fit-all-to-view",
  CenterOnNode: "center-on-node",
  ResetViewport: "reset-viewport",
  // Node
  LockNode: "lock-node",
  UnlockNode: "unlock-node",
  ToggleLock: "toggle-lock",
  OpenContextMenu: "open-context-menu",
  SplitNode: "split-node",
  GroupNodes: "group-nodes",
  MergeNodes: "merge-nodes",
  // Layout
  ApplyForceLayout: "apply-force-layout",
  // History
  Undo: "undo",
  Redo: "redo",
  // Creation
  CreateNode: "create-node"
};

// src/core/settings-state-types.ts
var DEFAULT_MAPPINGS = {
  [CanvasEventType.NodeClick]: BuiltInActionId.None,
  [CanvasEventType.NodeDoubleClick]: BuiltInActionId.FitToView,
  [CanvasEventType.NodeTripleClick]: BuiltInActionId.ToggleLock,
  [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
  [CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu,
  [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
  [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
  [CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu,
  [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
  [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.FitAllToView,
  [CanvasEventType.BackgroundRightClick]: BuiltInActionId.None,
  [CanvasEventType.BackgroundLongPress]: BuiltInActionId.CreateNode
};

// src/core/actions-node.ts
function registerSelectionActions() {
  registerAction2({
    id: BuiltInActionId.SelectNode,
    label: "Select Node",
    description: "Select this node (replacing current selection)",
    category: ActionCategory.Selection,
    icon: "pointer",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.selectNode(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.SelectEdge,
    label: "Select Edge",
    description: "Select this edge",
    category: ActionCategory.Selection,
    icon: "git-commit",
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.edgeId) {
        helpers.selectEdge(context.edgeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.AddToSelection,
    label: "Add to Selection",
    description: "Add this node to the current selection",
    category: ActionCategory.Selection,
    icon: "plus-square",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.addToSelection(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.ClearSelection,
    label: "Clear Selection",
    description: "Deselect all nodes",
    category: ActionCategory.Selection,
    icon: "x-square",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      helpers.clearSelection();
    }
  });
  registerAction2({
    id: BuiltInActionId.DeleteSelected,
    label: "Delete Selected",
    description: "Delete all selected nodes",
    category: ActionCategory.Selection,
    icon: "trash-2",
    isBuiltIn: true,
    handler: async (_context, helpers) => {
      const selectedIds = helpers.getSelectedNodeIds();
      for (const nodeId of selectedIds) {
        await helpers.deleteNode(nodeId);
      }
    }
  });
}
function registerNodeActions() {
  registerAction2({
    id: BuiltInActionId.LockNode,
    label: "Lock Node",
    description: "Prevent this node from being moved",
    category: ActionCategory.Node,
    icon: "lock",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.lockNode(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.UnlockNode,
    label: "Unlock Node",
    description: "Allow this node to be moved",
    category: ActionCategory.Node,
    icon: "unlock",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.unlockNode(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.ToggleLock,
    label: "Toggle Lock",
    description: "Toggle whether this node can be moved",
    category: ActionCategory.Node,
    icon: "lock",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.toggleLock(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.OpenContextMenu,
    label: "Open Context Menu",
    description: "Show the context menu for this node",
    category: ActionCategory.Node,
    icon: "more-vertical",
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (helpers.openContextMenu) {
        helpers.openContextMenu(context.screenPosition, context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.CreateNode,
    label: "Create Node",
    description: "Create a new node at this position",
    category: ActionCategory.Node,
    icon: "plus",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.createNode) {
        await helpers.createNode(context.worldPosition);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.SplitNode,
    label: "Split Node",
    description: "Split a node into two separate nodes",
    category: ActionCategory.Node,
    icon: "split",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.splitNode && context.nodeId) {
        await helpers.splitNode(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.GroupNodes,
    label: "Group Nodes",
    description: "Group selected nodes into a parent container",
    category: ActionCategory.Node,
    icon: "group",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.groupNodes) {
        await helpers.groupNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds());
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.MergeNodes,
    label: "Merge Nodes",
    description: "Merge selected nodes into one",
    category: ActionCategory.Node,
    icon: "merge",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.mergeNodes) {
        await helpers.mergeNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds());
      }
    }
  });
}

// src/core/actions-viewport.ts
function registerViewportActions() {
  registerAction2({
    id: BuiltInActionId.FitToView,
    label: "Fit to View",
    description: "Zoom and pan to fit this node in view",
    category: ActionCategory.Viewport,
    icon: "maximize-2",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.centerOnNode(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.FitAllToView,
    label: "Fit All to View",
    description: "Zoom and pan to fit all nodes in view",
    category: ActionCategory.Viewport,
    icon: "maximize",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      helpers.fitToBounds("graph");
    }
  });
  registerAction2({
    id: BuiltInActionId.CenterOnNode,
    label: "Center on Node",
    description: "Center the viewport on this node",
    category: ActionCategory.Viewport,
    icon: "crosshair",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.centerOnNode(context.nodeId);
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.ResetViewport,
    label: "Reset Viewport",
    description: "Reset zoom to 100% and center on origin",
    category: ActionCategory.Viewport,
    icon: "home",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      helpers.resetViewport();
    }
  });
}
function registerHistoryActions() {
  registerAction2({
    id: BuiltInActionId.Undo,
    label: "Undo",
    description: "Undo the last action",
    category: ActionCategory.History,
    icon: "undo-2",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      if (helpers.canUndo()) {
        helpers.undo();
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.Redo,
    label: "Redo",
    description: "Redo the last undone action",
    category: ActionCategory.History,
    icon: "redo-2",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      if (helpers.canRedo()) {
        helpers.redo();
      }
    }
  });
  registerAction2({
    id: BuiltInActionId.ApplyForceLayout,
    label: "Apply Force Layout",
    description: "Automatically arrange nodes using force-directed layout",
    category: ActionCategory.Layout,
    icon: "layout-grid",
    isBuiltIn: true,
    handler: async (_context, helpers) => {
      await helpers.applyForceLayout();
    }
  });
}

// src/core/built-in-actions.ts
function registerBuiltInActions() {
  registerAction2({
    id: BuiltInActionId.None,
    label: "None",
    description: "Do nothing",
    category: ActionCategory.None,
    icon: "ban",
    isBuiltIn: true,
    handler: () => {
    }
  });
  registerSelectionActions();
  registerNodeActions();
  registerViewportActions();
  registerHistoryActions();
}

// src/core/action-registry.ts
var actionRegistry = /* @__PURE__ */ new Map();
function registerAction2(action) {
  actionRegistry.set(action.id, action);
}
registerBuiltInActions();

// src/core/action-executor.ts
var debug9 = createDebug("actions");

// src/core/settings-store.ts
var import_jotai18 = require("jotai");
var import_utils = require("jotai/utils");

// src/core/settings-presets.ts
var BUILT_IN_PRESETS = [{
  id: "default",
  name: "Default",
  description: "Standard canvas interactions",
  isBuiltIn: true,
  mappings: DEFAULT_MAPPINGS
}, {
  id: "minimal",
  name: "Minimal",
  description: "Only essential selection and context menu actions",
  isBuiltIn: true,
  mappings: {
    [CanvasEventType.NodeClick]: BuiltInActionId.None,
    [CanvasEventType.NodeDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.NodeTripleClick]: BuiltInActionId.None,
    [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
    [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.EdgeRightClick]: BuiltInActionId.None,
    [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
    [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.BackgroundRightClick]: BuiltInActionId.None,
    [CanvasEventType.BackgroundLongPress]: BuiltInActionId.None
  }
}, {
  id: "power-user",
  name: "Power User",
  description: "Quick actions for experienced users",
  isBuiltIn: true,
  mappings: {
    [CanvasEventType.NodeClick]: BuiltInActionId.None,
    [CanvasEventType.NodeDoubleClick]: BuiltInActionId.ToggleLock,
    [CanvasEventType.NodeTripleClick]: BuiltInActionId.DeleteSelected,
    [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.NodeLongPress]: BuiltInActionId.AddToSelection,
    [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
    [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
    [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.CreateNode,
    [CanvasEventType.BackgroundRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.BackgroundLongPress]: BuiltInActionId.ApplyForceLayout
  }
}];

// src/core/settings-store.ts
var debug10 = createDebug("settings");
var DEFAULT_STATE = {
  mappings: DEFAULT_MAPPINGS,
  activePresetId: "default",
  customPresets: [],
  isPanelOpen: false,
  virtualizationEnabled: true
};
var canvasSettingsAtom = (0, import_utils.atomWithStorage)("@blinksgg/canvas/settings", DEFAULT_STATE);
var eventMappingsAtom = (0, import_jotai18.atom)((get) => get(canvasSettingsAtom).mappings);
var activePresetIdAtom = (0, import_jotai18.atom)((get) => get(canvasSettingsAtom).activePresetId);
var allPresetsAtom = (0, import_jotai18.atom)((get) => {
  const state = get(canvasSettingsAtom);
  return [...BUILT_IN_PRESETS, ...state.customPresets];
});
var activePresetAtom = (0, import_jotai18.atom)((get) => {
  const presetId = get(activePresetIdAtom);
  if (!presetId) return null;
  const allPresets = get(allPresetsAtom);
  return allPresets.find((p) => p.id === presetId) || null;
});
var isPanelOpenAtom = (0, import_jotai18.atom)((get) => get(canvasSettingsAtom).isPanelOpen);
var virtualizationEnabledAtom = (0, import_jotai18.atom)((get) => get(canvasSettingsAtom).virtualizationEnabled ?? true);
var hasUnsavedChangesAtom = (0, import_jotai18.atom)((get) => {
  const state = get(canvasSettingsAtom);
  const activePreset = get(activePresetAtom);
  if (!activePreset) return true;
  const events = Object.values(CanvasEventType);
  return events.some((event) => state.mappings[event] !== activePreset.mappings[event]);
});
var setEventMappingAtom = (0, import_jotai18.atom)(null, (get, set, {
  event,
  actionId
}) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    mappings: {
      ...current.mappings,
      [event]: actionId
    },
    // Clear active preset since mappings have changed
    activePresetId: null
  });
});
var applyPresetAtom = (0, import_jotai18.atom)(null, (get, set, presetId) => {
  const allPresets = get(allPresetsAtom);
  const preset = allPresets.find((p) => p.id === presetId);
  if (!preset) {
    debug10.warn("Preset not found: %s", presetId);
    return;
  }
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    mappings: {
      ...preset.mappings
    },
    activePresetId: presetId
  });
});
var saveAsPresetAtom = (0, import_jotai18.atom)(null, (get, set, {
  name,
  description
}) => {
  const current = get(canvasSettingsAtom);
  const id = `custom-${Date.now()}`;
  const newPreset = {
    id,
    name,
    description,
    mappings: {
      ...current.mappings
    },
    isBuiltIn: false
  };
  set(canvasSettingsAtom, {
    ...current,
    customPresets: [...current.customPresets, newPreset],
    activePresetId: id
  });
  return id;
});
var updatePresetAtom = (0, import_jotai18.atom)(null, (get, set, presetId) => {
  const current = get(canvasSettingsAtom);
  const presetIndex = current.customPresets.findIndex((p) => p.id === presetId);
  if (presetIndex === -1) {
    debug10.warn("Cannot update preset: %s (not found or built-in)", presetId);
    return;
  }
  const updatedPresets = [...current.customPresets];
  updatedPresets[presetIndex] = {
    ...updatedPresets[presetIndex],
    mappings: {
      ...current.mappings
    }
  };
  set(canvasSettingsAtom, {
    ...current,
    customPresets: updatedPresets,
    activePresetId: presetId
  });
});
var deletePresetAtom = (0, import_jotai18.atom)(null, (get, set, presetId) => {
  const current = get(canvasSettingsAtom);
  const newCustomPresets = current.customPresets.filter((p) => p.id !== presetId);
  if (newCustomPresets.length === current.customPresets.length) {
    debug10.warn("Cannot delete preset: %s (not found or built-in)", presetId);
    return;
  }
  const newActiveId = current.activePresetId === presetId ? "default" : current.activePresetId;
  const newMappings = newActiveId === "default" ? DEFAULT_MAPPINGS : current.mappings;
  set(canvasSettingsAtom, {
    ...current,
    customPresets: newCustomPresets,
    activePresetId: newActiveId,
    mappings: newMappings
  });
});
var resetSettingsAtom = (0, import_jotai18.atom)(null, (get, set) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    mappings: DEFAULT_MAPPINGS,
    activePresetId: "default"
  });
});
var togglePanelAtom = (0, import_jotai18.atom)(null, (get, set) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    isPanelOpen: !current.isPanelOpen
  });
});
var setPanelOpenAtom = (0, import_jotai18.atom)(null, (get, set, isOpen) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    isPanelOpen: isOpen
  });
});
var setVirtualizationEnabledAtom = (0, import_jotai18.atom)(null, (get, set, enabled) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    virtualizationEnabled: enabled
  });
});
var toggleVirtualizationAtom = (0, import_jotai18.atom)(null, (get, set) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    virtualizationEnabled: !(current.virtualizationEnabled ?? true)
  });
});

// src/core/canvas-serializer.ts
var import_graphology4 = __toESM(require("graphology"));

// src/core/clipboard-store.ts
var import_jotai19 = require("jotai");
var debug11 = createDebug("clipboard");
var PASTE_OFFSET = {
  x: 50,
  y: 50
};
var clipboardAtom = (0, import_jotai19.atom)(null);
var hasClipboardContentAtom = (0, import_jotai19.atom)((get) => get(clipboardAtom) !== null);
var clipboardNodeCountAtom = (0, import_jotai19.atom)((get) => {
  const clipboard = get(clipboardAtom);
  return clipboard?.nodes.length ?? 0;
});
function calculateBounds2(nodes) {
  if (nodes.length === 0) {
    return {
      minX: 0,
      minY: 0,
      maxX: 0,
      maxY: 0
    };
  }
  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;
  for (const node of nodes) {
    minX = Math.min(minX, node.attrs.x);
    minY = Math.min(minY, node.attrs.y);
    maxX = Math.max(maxX, node.attrs.x + node.attrs.width);
    maxY = Math.max(maxY, node.attrs.y + node.attrs.height);
  }
  return {
    minX,
    minY,
    maxX,
    maxY
  };
}
function generatePasteId(index) {
  return `paste-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 8)}`;
}
var copyToClipboardAtom = (0, import_jotai19.atom)(null, (get, set, nodeIds) => {
  const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
  if (selectedIds.length === 0) {
    debug11("Nothing to copy - no nodes selected");
    return;
  }
  const graph = get(graphAtom);
  const selectedSet = new Set(selectedIds);
  const nodes = [];
  const edges = [];
  for (const nodeId of selectedIds) {
    if (!graph.hasNode(nodeId)) {
      debug11("Node %s not found in graph, skipping", nodeId);
      continue;
    }
    const attrs = graph.getNodeAttributes(nodeId);
    nodes.push({
      attrs: {
        ...attrs
      },
      dbData: {
        ...attrs.dbData
      }
    });
  }
  graph.forEachEdge((edgeKey, attrs, source, target) => {
    if (selectedSet.has(source) && selectedSet.has(target)) {
      edges.push({
        source,
        target,
        attrs: {
          ...attrs
        },
        dbData: {
          ...attrs.dbData
        }
      });
    }
  });
  const bounds = calculateBounds2(nodes);
  const clipboardData = {
    nodes,
    edges,
    bounds,
    timestamp: Date.now()
  };
  set(clipboardAtom, clipboardData);
  debug11("Copied %d nodes and %d edges to clipboard", nodes.length, edges.length);
});
var cutToClipboardAtom = (0, import_jotai19.atom)(null, (get, set, nodeIds) => {
  const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
  if (selectedIds.length === 0) return;
  set(copyToClipboardAtom, selectedIds);
  set(pushHistoryAtom, "Cut nodes");
  for (const nodeId of selectedIds) {
    set(optimisticDeleteNodeAtom, {
      nodeId
    });
  }
  set(clearSelectionAtom);
  debug11("Cut %d nodes \u2014 copied to clipboard and deleted from graph", selectedIds.length);
});
var pasteFromClipboardAtom = (0, import_jotai19.atom)(null, (get, set, offset) => {
  const clipboard = get(clipboardAtom);
  if (!clipboard || clipboard.nodes.length === 0) {
    debug11("Nothing to paste - clipboard empty");
    return [];
  }
  const pasteOffset = offset ?? PASTE_OFFSET;
  const graph = get(graphAtom);
  set(pushHistoryAtom, "Paste nodes");
  const idMap = /* @__PURE__ */ new Map();
  const newNodeIds = [];
  for (let i = 0; i < clipboard.nodes.length; i++) {
    const nodeData = clipboard.nodes[i];
    const newId = generatePasteId(i);
    idMap.set(nodeData.dbData.id, newId);
    newNodeIds.push(newId);
    const newDbNode = {
      ...nodeData.dbData,
      id: newId,
      created_at: (/* @__PURE__ */ new Date()).toISOString(),
      updated_at: (/* @__PURE__ */ new Date()).toISOString(),
      ui_properties: {
        ...nodeData.dbData.ui_properties || {},
        x: nodeData.attrs.x + pasteOffset.x,
        y: nodeData.attrs.y + pasteOffset.y
      }
    };
    debug11("Pasting node %s -> %s at (%d, %d)", nodeData.dbData.id, newId, nodeData.attrs.x + pasteOffset.x, nodeData.attrs.y + pasteOffset.y);
    set(addNodeToLocalGraphAtom, newDbNode);
  }
  for (const edgeData of clipboard.edges) {
    const newSourceId = idMap.get(edgeData.source);
    const newTargetId = idMap.get(edgeData.target);
    if (!newSourceId || !newTargetId) {
      debug11("Edge %s: source or target not found in id map, skipping", edgeData.dbData.id);
      continue;
    }
    const newEdgeId = generatePasteId(clipboard.edges.indexOf(edgeData) + clipboard.nodes.length);
    const newDbEdge = {
      ...edgeData.dbData,
      id: newEdgeId,
      source_node_id: newSourceId,
      target_node_id: newTargetId,
      created_at: (/* @__PURE__ */ new Date()).toISOString(),
      updated_at: (/* @__PURE__ */ new Date()).toISOString()
    };
    debug11("Pasting edge %s -> %s (from %s to %s)", edgeData.dbData.id, newEdgeId, newSourceId, newTargetId);
    set(addEdgeToLocalGraphAtom, newDbEdge);
  }
  set(clearSelectionAtom);
  set(addNodesToSelectionAtom, newNodeIds);
  debug11("Pasted %d nodes and %d edges", newNodeIds.length, clipboard.edges.length);
  return newNodeIds;
});
var duplicateSelectionAtom = (0, import_jotai19.atom)(null, (get, set) => {
  set(copyToClipboardAtom);
  return set(pasteFromClipboardAtom);
});
var clearClipboardAtom = (0, import_jotai19.atom)(null, (_get, set) => {
  set(clipboardAtom, null);
  debug11("Clipboard cleared");
});

// src/core/virtualization-store.ts
var import_jotai20 = require("jotai");

// src/core/spatial-index.ts
var SpatialGrid = class {
  constructor(cellSize = 500) {
    /** cell key → set of node IDs in that cell */
    __publicField(this, "cells", /* @__PURE__ */ new Map());
    /** node ID → entry data (for update/remove) */
    __publicField(this, "entries", /* @__PURE__ */ new Map());
    this.cellSize = cellSize;
  }
  /** Number of tracked entries */
  get size() {
    return this.entries.size;
  }
  cellKey(cx, cy) {
    return `${cx},${cy}`;
  }
  getCellRange(x, y, w, h) {
    const cs = this.cellSize;
    return {
      minCX: Math.floor(x / cs),
      minCY: Math.floor(y / cs),
      maxCX: Math.floor((x + w) / cs),
      maxCY: Math.floor((y + h) / cs)
    };
  }
  /**
   * Insert a node into the index.
   * If the node already exists, it is updated.
   */
  insert(id, x, y, width, height) {
    if (this.entries.has(id)) {
      this.update(id, x, y, width, height);
      return;
    }
    const entry = {
      id,
      x,
      y,
      width,
      height
    };
    this.entries.set(id, entry);
    const {
      minCX,
      minCY,
      maxCX,
      maxCY
    } = this.getCellRange(x, y, width, height);
    for (let cx = minCX; cx <= maxCX; cx++) {
      for (let cy = minCY; cy <= maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        let cell = this.cells.get(key);
        if (!cell) {
          cell = /* @__PURE__ */ new Set();
          this.cells.set(key, cell);
        }
        cell.add(id);
      }
    }
  }
  /**
   * Update a node's position/dimensions.
   */
  update(id, x, y, width, height) {
    const prev = this.entries.get(id);
    if (!prev) {
      this.insert(id, x, y, width, height);
      return;
    }
    const prevRange = this.getCellRange(prev.x, prev.y, prev.width, prev.height);
    const newRange = this.getCellRange(x, y, width, height);
    prev.x = x;
    prev.y = y;
    prev.width = width;
    prev.height = height;
    if (prevRange.minCX === newRange.minCX && prevRange.minCY === newRange.minCY && prevRange.maxCX === newRange.maxCX && prevRange.maxCY === newRange.maxCY) {
      return;
    }
    for (let cx = prevRange.minCX; cx <= prevRange.maxCX; cx++) {
      for (let cy = prevRange.minCY; cy <= prevRange.maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        const cell = this.cells.get(key);
        if (cell) {
          cell.delete(id);
          if (cell.size === 0) this.cells.delete(key);
        }
      }
    }
    for (let cx = newRange.minCX; cx <= newRange.maxCX; cx++) {
      for (let cy = newRange.minCY; cy <= newRange.maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        let cell = this.cells.get(key);
        if (!cell) {
          cell = /* @__PURE__ */ new Set();
          this.cells.set(key, cell);
        }
        cell.add(id);
      }
    }
  }
  /**
   * Remove a node from the index.
   */
  remove(id) {
    const entry = this.entries.get(id);
    if (!entry) return;
    const {
      minCX,
      minCY,
      maxCX,
      maxCY
    } = this.getCellRange(entry.x, entry.y, entry.width, entry.height);
    for (let cx = minCX; cx <= maxCX; cx++) {
      for (let cy = minCY; cy <= maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        const cell = this.cells.get(key);
        if (cell) {
          cell.delete(id);
          if (cell.size === 0) this.cells.delete(key);
        }
      }
    }
    this.entries.delete(id);
  }
  /**
   * Query all node IDs whose bounding box overlaps the given bounds.
   * Returns a Set for O(1) membership checks.
   */
  query(bounds) {
    const result = /* @__PURE__ */ new Set();
    const {
      minCX,
      minCY,
      maxCX,
      maxCY
    } = this.getCellRange(bounds.minX, bounds.minY, bounds.maxX - bounds.minX, bounds.maxY - bounds.minY);
    for (let cx = minCX; cx <= maxCX; cx++) {
      for (let cy = minCY; cy <= maxCY; cy++) {
        const cell = this.cells.get(this.cellKey(cx, cy));
        if (!cell) continue;
        for (const id of cell) {
          if (result.has(id)) continue;
          const entry = this.entries.get(id);
          const entryRight = entry.x + entry.width;
          const entryBottom = entry.y + entry.height;
          if (entry.x <= bounds.maxX && entryRight >= bounds.minX && entry.y <= bounds.maxY && entryBottom >= bounds.minY) {
            result.add(id);
          }
        }
      }
    }
    return result;
  }
  /**
   * Clear all entries.
   */
  clear() {
    this.cells.clear();
    this.entries.clear();
  }
  /**
   * Check if a node is tracked.
   */
  has(id) {
    return this.entries.has(id);
  }
};

// src/core/virtualization-store.ts
var VIRTUALIZATION_BUFFER = 200;
var spatialIndexAtom = (0, import_jotai20.atom)((get) => {
  get(graphUpdateVersionAtom);
  get(nodePositionUpdateCounterAtom);
  const graph = get(graphAtom);
  const grid = new SpatialGrid(500);
  graph.forEachNode((nodeId, attrs) => {
    const a = attrs;
    grid.insert(nodeId, a.x, a.y, a.width || 200, a.height || 100);
  });
  return grid;
});
var visibleBoundsAtom = (0, import_jotai20.atom)((get) => {
  const viewport = get(viewportRectAtom);
  const pan = get(panAtom);
  const zoom = get(zoomAtom);
  if (!viewport || zoom === 0) {
    return null;
  }
  const buffer = VIRTUALIZATION_BUFFER;
  return {
    minX: (-buffer - pan.x) / zoom,
    minY: (-buffer - pan.y) / zoom,
    maxX: (viewport.width + buffer - pan.x) / zoom,
    maxY: (viewport.height + buffer - pan.y) / zoom
  };
});
var visibleNodeKeysAtom = (0, import_jotai20.atom)((get) => {
  const end = canvasMark("virtualization-cull");
  const enabled = get(virtualizationEnabledAtom);
  const allKeys = get(nodeKeysAtom);
  if (!enabled) {
    end();
    return allKeys;
  }
  const bounds = get(visibleBoundsAtom);
  if (!bounds) {
    end();
    return allKeys;
  }
  const grid = get(spatialIndexAtom);
  const visibleSet = grid.query(bounds);
  const result = allKeys.filter((k) => visibleSet.has(k));
  end();
  return result;
});
var visibleEdgeKeysAtom = (0, import_jotai20.atom)((get) => {
  const enabled = get(virtualizationEnabledAtom);
  const allEdgeKeys = get(edgeKeysAtom);
  const edgeCreation = get(edgeCreationAtom);
  const remap = get(collapsedEdgeRemapAtom);
  const tempEdgeKey = edgeCreation.isCreating ? "temp-creating-edge" : null;
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const filteredEdges = allEdgeKeys.filter((edgeKey) => {
    const source = graph.source(edgeKey);
    const target = graph.target(edgeKey);
    const effectiveSource = remap.get(source) ?? source;
    const effectiveTarget = remap.get(target) ?? target;
    if (effectiveSource === effectiveTarget) return false;
    return true;
  });
  if (!enabled) {
    return tempEdgeKey ? [...filteredEdges, tempEdgeKey] : filteredEdges;
  }
  const visibleNodeKeys = get(visibleNodeKeysAtom);
  const visibleNodeSet = new Set(visibleNodeKeys);
  const visibleEdges = filteredEdges.filter((edgeKey) => {
    const source = graph.source(edgeKey);
    const target = graph.target(edgeKey);
    const effectiveSource = remap.get(source) ?? source;
    const effectiveTarget = remap.get(target) ?? target;
    return visibleNodeSet.has(effectiveSource) && visibleNodeSet.has(effectiveTarget);
  });
  return tempEdgeKey ? [...visibleEdges, tempEdgeKey] : visibleEdges;
});
var virtualizationMetricsAtom = (0, import_jotai20.atom)((get) => {
  const enabled = get(virtualizationEnabledAtom);
  const totalNodes = get(nodeKeysAtom).length;
  const totalEdges = get(edgeKeysAtom).length;
  const visibleNodes = get(visibleNodeKeysAtom).length;
  const visibleEdges = get(visibleEdgeKeysAtom).length;
  const bounds = get(visibleBoundsAtom);
  return {
    enabled,
    totalNodes,
    totalEdges,
    visibleNodes,
    visibleEdges,
    culledNodes: totalNodes - visibleNodes,
    culledEdges: totalEdges - visibleEdges,
    bounds
  };
});

// src/core/input-store.ts
var import_jotai21 = require("jotai");
var activePointersAtom = (0, import_jotai21.atom)(/* @__PURE__ */ new Map());
var primaryInputSourceAtom = (0, import_jotai21.atom)("mouse");
var inputCapabilitiesAtom = (0, import_jotai21.atom)(detectInputCapabilities());
var isStylusActiveAtom = (0, import_jotai21.atom)((get) => {
  const pointers = get(activePointersAtom);
  for (const [, pointer] of pointers) {
    if (pointer.source === "pencil") return true;
  }
  return false;
});
var isMultiTouchAtom = (0, import_jotai21.atom)((get) => {
  const pointers = get(activePointersAtom);
  let fingerCount = 0;
  for (const [, pointer] of pointers) {
    if (pointer.source === "finger") fingerCount++;
  }
  return fingerCount > 1;
});
var fingerCountAtom = (0, import_jotai21.atom)((get) => {
  const pointers = get(activePointersAtom);
  let count = 0;
  for (const [, pointer] of pointers) {
    if (pointer.source === "finger") count++;
  }
  return count;
});
var isTouchDeviceAtom = (0, import_jotai21.atom)((get) => {
  const caps = get(inputCapabilitiesAtom);
  return caps.hasTouch;
});
var pointerDownAtom = (0, import_jotai21.atom)(null, (get, set, pointer) => {
  const pointers = new Map(get(activePointersAtom));
  pointers.set(pointer.pointerId, pointer);
  set(activePointersAtom, pointers);
  set(primaryInputSourceAtom, pointer.source);
  if (pointer.source === "pencil") {
    const caps = get(inputCapabilitiesAtom);
    if (!caps.hasStylus) {
      set(inputCapabilitiesAtom, {
        ...caps,
        hasStylus: true
      });
    }
  }
});
var pointerUpAtom = (0, import_jotai21.atom)(null, (get, set, pointerId) => {
  const pointers = new Map(get(activePointersAtom));
  pointers.delete(pointerId);
  set(activePointersAtom, pointers);
});
var clearPointersAtom = (0, import_jotai21.atom)(null, (_get, set) => {
  set(activePointersAtom, /* @__PURE__ */ new Map());
});

// src/core/selection-path-store.ts
var import_jotai22 = require("jotai");
var selectionPathAtom = (0, import_jotai22.atom)(null);
var isSelectingAtom = (0, import_jotai22.atom)((get) => get(selectionPathAtom) !== null);
var startSelectionAtom = (0, import_jotai22.atom)(null, (_get, set, {
  type,
  point
}) => {
  set(selectionPathAtom, {
    type,
    points: [point]
  });
});
var updateSelectionAtom = (0, import_jotai22.atom)(null, (get, set, point) => {
  const current = get(selectionPathAtom);
  if (!current) return;
  if (current.type === "rect") {
    set(selectionPathAtom, {
      ...current,
      points: [current.points[0], point]
    });
  } else {
    set(selectionPathAtom, {
      ...current,
      points: [...current.points, point]
    });
  }
});
var cancelSelectionAtom = (0, import_jotai22.atom)(null, (_get, set) => {
  set(selectionPathAtom, null);
});
var endSelectionAtom = (0, import_jotai22.atom)(null, (get, set) => {
  const path = get(selectionPathAtom);
  if (!path || path.points.length < 2) {
    set(selectionPathAtom, null);
    return;
  }
  const nodes = get(uiNodesAtom);
  const selectedIds = [];
  if (path.type === "rect") {
    const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
    const minX = Math.min(p1.x, p2.x);
    const maxX = Math.max(p1.x, p2.x);
    const minY = Math.min(p1.y, p2.y);
    const maxY = Math.max(p1.y, p2.y);
    for (const node of nodes) {
      const nodeRight = node.position.x + (node.width ?? 200);
      const nodeBottom = node.position.y + (node.height ?? 100);
      if (node.position.x < maxX && nodeRight > minX && node.position.y < maxY && nodeBottom > minY) {
        selectedIds.push(node.id);
      }
    }
  } else {
    const polygon = path.points;
    for (const node of nodes) {
      const cx = node.position.x + (node.width ?? 200) / 2;
      const cy = node.position.y + (node.height ?? 100) / 2;
      if (pointInPolygon(cx, cy, polygon)) {
        selectedIds.push(node.id);
      }
    }
  }
  set(selectedNodeIdsAtom, new Set(selectedIds));
  set(selectionPathAtom, null);
});
var selectionRectAtom = (0, import_jotai22.atom)((get) => {
  const path = get(selectionPathAtom);
  if (!path || path.type !== "rect" || path.points.length < 2) return null;
  const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
  return {
    x: Math.min(p1.x, p2.x),
    y: Math.min(p1.y, p2.y),
    width: Math.abs(p2.x - p1.x),
    height: Math.abs(p2.y - p1.y)
  };
});
function pointInPolygon(px, py, polygon) {
  let inside = false;
  const n = polygon.length;
  for (let i = 0, j = n - 1; i < n; j = i++) {
    const xi = polygon[i].x;
    const yi = polygon[i].y;
    const xj = polygon[j].x;
    const yj = polygon[j].y;
    if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
      inside = !inside;
    }
  }
  return inside;
}

// src/core/search-store.ts
var import_jotai23 = require("jotai");
var searchQueryAtom = (0, import_jotai23.atom)("");
var setSearchQueryAtom = (0, import_jotai23.atom)(null, (_get, set, query) => {
  set(searchQueryAtom, query);
  set(highlightedSearchIndexAtom, 0);
});
var clearSearchAtom = (0, import_jotai23.atom)(null, (_get, set) => {
  set(searchQueryAtom, "");
  set(highlightedSearchIndexAtom, 0);
});
function fuzzyMatch(query, ...haystacks) {
  const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
  if (tokens.length === 0) return false;
  const combined = haystacks.join(" ").toLowerCase();
  return tokens.every((token) => combined.includes(token));
}
var searchResultsAtom = (0, import_jotai23.atom)((get) => {
  const query = get(searchQueryAtom).trim();
  if (!query) return /* @__PURE__ */ new Set();
  const nodes = get(uiNodesAtom);
  const matches = /* @__PURE__ */ new Set();
  for (const node of nodes) {
    if (fuzzyMatch(query, node.label || "", node.dbData.node_type || "", node.id)) {
      matches.add(node.id);
    }
  }
  return matches;
});
var searchResultsArrayAtom = (0, import_jotai23.atom)((get) => {
  return Array.from(get(searchResultsAtom));
});
var searchResultCountAtom = (0, import_jotai23.atom)((get) => {
  return get(searchResultsAtom).size;
});
var searchEdgeResultsAtom = (0, import_jotai23.atom)((get) => {
  const query = get(searchQueryAtom).trim();
  if (!query) return /* @__PURE__ */ new Set();
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const matches = /* @__PURE__ */ new Set();
  graph.forEachEdge((edgeKey, attrs) => {
    const label = attrs.label || "";
    const edgeType = attrs.dbData?.edge_type || "";
    if (fuzzyMatch(query, label, edgeType, edgeKey)) {
      matches.add(edgeKey);
    }
  });
  return matches;
});
var searchEdgeResultCountAtom = (0, import_jotai23.atom)((get) => {
  return get(searchEdgeResultsAtom).size;
});
var isFilterActiveAtom = (0, import_jotai23.atom)((get) => {
  return get(searchQueryAtom).trim().length > 0;
});
var searchTotalResultCountAtom = (0, import_jotai23.atom)((get) => {
  return get(searchResultCountAtom) + get(searchEdgeResultCountAtom);
});
var highlightedSearchIndexAtom = (0, import_jotai23.atom)(0);
var nextSearchResultAtom = (0, import_jotai23.atom)(null, (get, set) => {
  const results = get(searchResultsArrayAtom);
  if (results.length === 0) return;
  const currentIndex = get(highlightedSearchIndexAtom);
  const nextIndex = (currentIndex + 1) % results.length;
  set(highlightedSearchIndexAtom, nextIndex);
  const nodeId = results[nextIndex];
  set(centerOnNodeAtom, nodeId);
  set(selectSingleNodeAtom, nodeId);
});
var prevSearchResultAtom = (0, import_jotai23.atom)(null, (get, set) => {
  const results = get(searchResultsArrayAtom);
  if (results.length === 0) return;
  const currentIndex = get(highlightedSearchIndexAtom);
  const prevIndex = (currentIndex - 1 + results.length) % results.length;
  set(highlightedSearchIndexAtom, prevIndex);
  const nodeId = results[prevIndex];
  set(centerOnNodeAtom, nodeId);
  set(selectSingleNodeAtom, nodeId);
});
var highlightedSearchNodeIdAtom = (0, import_jotai23.atom)((get) => {
  const results = get(searchResultsArrayAtom);
  if (results.length === 0) return null;
  const index = get(highlightedSearchIndexAtom);
  return results[index] ?? null;
});

// src/core/gesture-rules-defaults.ts
function mergeRules(defaults, overrides) {
  const overrideMap = new Map(overrides.map((r) => [r.id, r]));
  const result = [];
  for (const rule of defaults) {
    const override = overrideMap.get(rule.id);
    if (override) {
      result.push(override);
      overrideMap.delete(rule.id);
    } else {
      result.push(rule);
    }
  }
  for (const rule of overrideMap.values()) {
    result.push(rule);
  }
  return result;
}
var DEFAULT_GESTURE_RULES = [
  // ── Tap gestures ──────────────────────────────────────────────
  {
    id: "tap-node",
    pattern: {
      gesture: "tap",
      target: "node"
    },
    actionId: "select-node"
  },
  {
    id: "tap-edge",
    pattern: {
      gesture: "tap",
      target: "edge"
    },
    actionId: "select-edge"
  },
  {
    id: "tap-port",
    pattern: {
      gesture: "tap",
      target: "port"
    },
    actionId: "select-node"
  },
  {
    id: "tap-bg",
    pattern: {
      gesture: "tap",
      target: "background"
    },
    actionId: "clear-selection"
  },
  // ── Double-tap ────────────────────────────────────────────────
  {
    id: "dtap-node",
    pattern: {
      gesture: "double-tap",
      target: "node"
    },
    actionId: "fit-to-view"
  },
  {
    id: "dtap-bg",
    pattern: {
      gesture: "double-tap",
      target: "background"
    },
    actionId: "fit-all-to-view"
  },
  // ── Triple-tap ────────────────────────────────────────────────
  {
    id: "ttap-node",
    pattern: {
      gesture: "triple-tap",
      target: "node"
    },
    actionId: "toggle-lock"
  },
  // ── Left-button drag ──────────────────────────────────────────
  {
    id: "drag-node",
    pattern: {
      gesture: "drag",
      target: "node"
    },
    actionId: "move-node"
  },
  {
    id: "drag-port",
    pattern: {
      gesture: "drag",
      target: "port"
    },
    actionId: "create-edge"
  },
  {
    id: "drag-bg-finger",
    pattern: {
      gesture: "drag",
      target: "background",
      source: "finger"
    },
    actionId: "pan"
  },
  {
    id: "drag-bg-mouse",
    pattern: {
      gesture: "drag",
      target: "background",
      source: "mouse"
    },
    actionId: "pan"
  },
  {
    id: "drag-bg-pencil",
    pattern: {
      gesture: "drag",
      target: "background",
      source: "pencil"
    },
    actionId: "lasso-select"
  },
  // ── Shift+drag overrides ──────────────────────────────────────
  {
    id: "shift-drag-bg",
    pattern: {
      gesture: "drag",
      target: "background",
      modifiers: {
        shift: true
      }
    },
    actionId: "rect-select"
  },
  // ── Right-click tap (context menu) ────────────────────────────
  {
    id: "rc-node",
    pattern: {
      gesture: "tap",
      target: "node",
      button: 2
    },
    actionId: "open-context-menu"
  },
  {
    id: "rc-edge",
    pattern: {
      gesture: "tap",
      target: "edge",
      button: 2
    },
    actionId: "open-context-menu"
  },
  {
    id: "rc-bg",
    pattern: {
      gesture: "tap",
      target: "background",
      button: 2
    },
    actionId: "open-context-menu"
  },
  // ── Long-press ────────────────────────────────────────────────
  {
    id: "lp-node",
    pattern: {
      gesture: "long-press",
      target: "node"
    },
    actionId: "open-context-menu"
  },
  {
    id: "lp-bg-finger",
    pattern: {
      gesture: "long-press",
      target: "background",
      source: "finger"
    },
    actionId: "create-node"
  },
  // ── Right-button drag (defaults to none — consumers override) ─
  {
    id: "rdrag-node",
    pattern: {
      gesture: "drag",
      target: "node",
      button: 2
    },
    actionId: "none"
  },
  {
    id: "rdrag-bg",
    pattern: {
      gesture: "drag",
      target: "background",
      button: 2
    },
    actionId: "none"
  },
  // ── Middle-button drag (defaults to none) ─────────────────────
  {
    id: "mdrag-node",
    pattern: {
      gesture: "drag",
      target: "node",
      button: 1
    },
    actionId: "none"
  },
  {
    id: "mdrag-bg",
    pattern: {
      gesture: "drag",
      target: "background",
      button: 1
    },
    actionId: "none"
  },
  // ── Zoom ──────────────────────────────────────────────────────
  {
    id: "pinch-bg",
    pattern: {
      gesture: "pinch",
      target: "background"
    },
    actionId: "zoom"
  },
  {
    id: "scroll-any",
    pattern: {
      gesture: "scroll"
    },
    actionId: "zoom"
  },
  // ── Split ─────────────────────────────────────────────────────
  {
    id: "pinch-node",
    pattern: {
      gesture: "pinch",
      target: "node"
    },
    actionId: "split-node"
  }
];

// src/core/gesture-rules.ts
function buildRuleIndex(rules) {
  const buckets = /* @__PURE__ */ new Map();
  const wildcardRules = [];
  for (const rule of rules) {
    const key = rule.pattern.gesture;
    if (key === void 0) {
      wildcardRules.push(rule);
    } else {
      const bucket = buckets.get(key);
      if (bucket) {
        bucket.push(rule);
      } else {
        buckets.set(key, [rule]);
      }
    }
  }
  const index = /* @__PURE__ */ new Map();
  if (wildcardRules.length > 0) {
    for (const [key, bucket] of buckets) {
      index.set(key, bucket.concat(wildcardRules));
    }
    index.set("__wildcard__", wildcardRules);
  } else {
    for (const [key, bucket] of buckets) {
      index.set(key, bucket);
    }
  }
  return index;
}

// src/core/gesture-rule-store.ts
var import_jotai24 = require("jotai");
var import_utils2 = require("jotai/utils");
var DEFAULT_RULE_STATE = {
  customRules: [],
  palmRejection: true
};
var gestureRuleSettingsAtom = (0, import_utils2.atomWithStorage)("canvas-gesture-rules", DEFAULT_RULE_STATE);
var consumerGestureRulesAtom = (0, import_jotai24.atom)([]);
var gestureRulesAtom = (0, import_jotai24.atom)((get) => {
  const settings = get(gestureRuleSettingsAtom);
  const consumerRules = get(consumerGestureRulesAtom);
  let rules = mergeRules(DEFAULT_GESTURE_RULES, settings.customRules);
  if (consumerRules.length > 0) {
    rules = mergeRules(rules, consumerRules);
  }
  return rules;
});
var gestureRuleIndexAtom = (0, import_jotai24.atom)((get) => {
  return buildRuleIndex(get(gestureRulesAtom));
});
var palmRejectionEnabledAtom = (0, import_jotai24.atom)((get) => get(gestureRuleSettingsAtom).palmRejection, (get, set, enabled) => {
  const current = get(gestureRuleSettingsAtom);
  set(gestureRuleSettingsAtom, {
    ...current,
    palmRejection: enabled
  });
});
var addGestureRuleAtom = (0, import_jotai24.atom)(null, (get, set, rule) => {
  const current = get(gestureRuleSettingsAtom);
  const existing = current.customRules.findIndex((r) => r.id === rule.id);
  const newRules = [...current.customRules];
  if (existing >= 0) {
    newRules[existing] = rule;
  } else {
    newRules.push(rule);
  }
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: newRules
  });
});
var removeGestureRuleAtom = (0, import_jotai24.atom)(null, (get, set, ruleId) => {
  const current = get(gestureRuleSettingsAtom);
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: current.customRules.filter((r) => r.id !== ruleId)
  });
});
var updateGestureRuleAtom = (0, import_jotai24.atom)(null, (get, set, {
  id,
  updates
}) => {
  const current = get(gestureRuleSettingsAtom);
  const index = current.customRules.findIndex((r) => r.id === id);
  if (index < 0) return;
  const newRules = [...current.customRules];
  newRules[index] = {
    ...newRules[index],
    ...updates
  };
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: newRules
  });
});
var resetGestureRulesAtom = (0, import_jotai24.atom)(null, (get, set) => {
  const current = get(gestureRuleSettingsAtom);
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: []
  });
});

// src/core/external-keyboard-store.ts
var import_jotai25 = require("jotai");
var hasExternalKeyboardAtom = (0, import_jotai25.atom)(false);
var watchExternalKeyboardAtom = (0, import_jotai25.atom)(null, (get, set) => {
  if (typeof window === "undefined") return;
  const handler = (e) => {
    if (e.key && e.key.length === 1 || ["Tab", "Escape", "Enter", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
      set(hasExternalKeyboardAtom, true);
      window.removeEventListener("keydown", handler);
    }
  };
  window.addEventListener("keydown", handler);
  return () => window.removeEventListener("keydown", handler);
});

// src/commands/registry.ts
var CommandRegistry = class {
  constructor() {
    __publicField(this, "commands", /* @__PURE__ */ new Map());
    __publicField(this, "aliases", /* @__PURE__ */ new Map());
  }
  // alias -> command name
  /**
   * Register a command with the registry.
   * @param command The command definition to register
   * @throws Error if command name or alias already exists
   */
  register(command) {
    if (this.commands.has(command.name)) {
      throw new Error(`Command "${command.name}" is already registered`);
    }
    this.commands.set(command.name, command);
    if (command.aliases) {
      for (const alias of command.aliases) {
        if (this.aliases.has(alias)) {
          throw new Error(`Alias "${alias}" is already registered for command "${this.aliases.get(alias)}"`);
        }
        if (this.commands.has(alias)) {
          throw new Error(`Alias "${alias}" conflicts with existing command name`);
        }
        this.aliases.set(alias, command.name);
      }
    }
  }
  /**
   * Unregister a command by name.
   * @param name The command name to remove
   */
  unregister(name) {
    const command = this.commands.get(name);
    if (command) {
      if (command.aliases) {
        for (const alias of command.aliases) {
          this.aliases.delete(alias);
        }
      }
      this.commands.delete(name);
    }
  }
  /**
   * Get a command by name or alias.
   * @param nameOrAlias Command name or alias
   * @returns The command definition or undefined if not found
   */
  get(nameOrAlias) {
    const direct = this.commands.get(nameOrAlias);
    if (direct) return direct;
    const commandName = this.aliases.get(nameOrAlias);
    if (commandName) {
      return this.commands.get(commandName);
    }
    return void 0;
  }
  /**
   * Check if a command exists by name or alias.
   * @param nameOrAlias Command name or alias
   */
  has(nameOrAlias) {
    return this.commands.has(nameOrAlias) || this.aliases.has(nameOrAlias);
  }
  /**
   * Search for commands matching a query.
   * Searches command names, aliases, and descriptions.
   * @param query Search query (case-insensitive)
   * @returns Array of matching commands, sorted by relevance
   */
  search(query) {
    if (!query.trim()) {
      return this.all();
    }
    const lowerQuery = query.toLowerCase().trim();
    const results = [];
    const commands = Array.from(this.commands.values());
    for (const command of commands) {
      let score = 0;
      if (command.name.toLowerCase() === lowerQuery) {
        score = 100;
      } else if (command.name.toLowerCase().startsWith(lowerQuery)) {
        score = 80;
      } else if (command.name.toLowerCase().includes(lowerQuery)) {
        score = 60;
      } else if (command.aliases?.some((a) => a.toLowerCase() === lowerQuery)) {
        score = 90;
      } else if (command.aliases?.some((a) => a.toLowerCase().startsWith(lowerQuery))) {
        score = 70;
      } else if (command.aliases?.some((a) => a.toLowerCase().includes(lowerQuery))) {
        score = 50;
      } else if (command.description.toLowerCase().includes(lowerQuery)) {
        score = 30;
      }
      if (score > 0) {
        results.push({
          command,
          score
        });
      }
    }
    return results.sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name)).map((r) => r.command);
  }
  /**
   * Get all registered commands.
   * @returns Array of all commands, sorted alphabetically by name
   */
  all() {
    return Array.from(this.commands.values()).sort((a, b) => a.name.localeCompare(b.name));
  }
  /**
   * Get commands by category.
   * @param category The category to filter by
   * @returns Array of commands in the category
   */
  byCategory(category) {
    return this.all().filter((cmd) => cmd.category === category);
  }
  /**
   * Get all available categories.
   * @returns Array of unique categories
   */
  categories() {
    const categories = /* @__PURE__ */ new Set();
    const commands = Array.from(this.commands.values());
    for (const command of commands) {
      categories.add(command.category);
    }
    return Array.from(categories).sort();
  }
  /**
   * Get the count of registered commands.
   */
  get size() {
    return this.commands.size;
  }
  /**
   * Clear all registered commands.
   * Useful for testing.
   */
  clear() {
    this.commands.clear();
    this.aliases.clear();
  }
  /**
   * Get a serializable list of commands for API responses.
   */
  toJSON() {
    return this.all().map((cmd) => ({
      name: cmd.name,
      aliases: cmd.aliases || [],
      description: cmd.description,
      category: cmd.category,
      inputs: cmd.inputs.map((input) => ({
        name: input.name,
        type: input.type,
        prompt: input.prompt,
        required: input.required !== false
      }))
    }));
  }
};
var commandRegistry = new CommandRegistry();

// src/core/plugin-registry.ts
var debug12 = createDebug("plugins");

// src/commands/store.ts
var import_jotai27 = require("jotai");

// src/commands/store-atoms.ts
var import_jotai26 = require("jotai");
var import_utils3 = require("jotai/utils");
var inputModeAtom2 = (0, import_jotai26.atom)({
  type: "normal"
});
var commandLineVisibleAtom = (0, import_jotai26.atom)(false);
var commandLineStateAtom = (0, import_jotai26.atom)({
  phase: "idle"
});
var commandFeedbackAtom = (0, import_jotai26.atom)(null);
var commandHistoryAtom = (0, import_utils3.atomWithStorage)("canvas-command-history", []);
var selectedSuggestionIndexAtom = (0, import_jotai26.atom)(0);
var pendingInputResolverAtom2 = (0, import_jotai26.atom)(null);
var isCommandActiveAtom = (0, import_jotai26.atom)((get) => {
  const state = get(commandLineStateAtom);
  return state.phase === "collecting" || state.phase === "executing";
});
var currentInputAtom = (0, import_jotai26.atom)((get) => {
  const state = get(commandLineStateAtom);
  if (state.phase !== "collecting") return null;
  return state.command.inputs[state.inputIndex];
});
var commandProgressAtom = (0, import_jotai26.atom)((get) => {
  const state = get(commandLineStateAtom);
  if (state.phase !== "collecting") return null;
  return {
    current: state.inputIndex + 1,
    total: state.command.inputs.length
  };
});

// src/commands/store.ts
var openCommandLineAtom = (0, import_jotai27.atom)(null, (get, set) => {
  set(commandLineVisibleAtom, true);
  set(commandLineStateAtom, {
    phase: "searching",
    query: "",
    suggestions: commandRegistry.all()
  });
  set(selectedSuggestionIndexAtom, 0);
});
var closeCommandLineAtom = (0, import_jotai27.atom)(null, (get, set) => {
  set(commandLineVisibleAtom, false);
  set(commandLineStateAtom, {
    phase: "idle"
  });
  set(inputModeAtom2, {
    type: "normal"
  });
  set(commandFeedbackAtom, null);
  set(pendingInputResolverAtom2, null);
});
var updateSearchQueryAtom = (0, import_jotai27.atom)(null, (get, set, query) => {
  const suggestions = commandRegistry.search(query);
  set(commandLineStateAtom, {
    phase: "searching",
    query,
    suggestions
  });
  set(selectedSuggestionIndexAtom, 0);
});
var selectCommandAtom = (0, import_jotai27.atom)(null, (get, set, command) => {
  const history = get(commandHistoryAtom);
  const newHistory = [command.name, ...history.filter((h) => h !== command.name)].slice(0, 50);
  set(commandHistoryAtom, newHistory);
  if (command.inputs.length === 0) {
    set(commandLineStateAtom, {
      phase: "executing",
      command
    });
    return;
  }
  set(commandLineStateAtom, {
    phase: "collecting",
    command,
    inputIndex: 0,
    collected: {}
  });
  const firstInput = command.inputs[0];
  set(inputModeAtom2, inputDefToMode(firstInput));
});
var provideInputAtom2 = (0, import_jotai27.atom)(null, (get, set, value) => {
  const state = get(commandLineStateAtom);
  if (state.phase !== "collecting") return;
  const {
    command,
    inputIndex,
    collected
  } = state;
  const currentInput = command.inputs[inputIndex];
  if (currentInput.validate) {
    const result = currentInput.validate(value, collected);
    if (result !== true) {
      set(commandLineStateAtom, {
        phase: "error",
        message: typeof result === "string" ? result : `Invalid value for ${currentInput.name}`
      });
      return;
    }
  }
  const newCollected = {
    ...collected,
    [currentInput.name]: value
  };
  if (inputIndex < command.inputs.length - 1) {
    const nextInputIndex = inputIndex + 1;
    const nextInput = command.inputs[nextInputIndex];
    set(commandLineStateAtom, {
      phase: "collecting",
      command,
      inputIndex: nextInputIndex,
      collected: newCollected
    });
    set(inputModeAtom2, inputDefToMode(nextInput, newCollected));
    if (command.feedback) {
      const feedback = command.feedback(newCollected, nextInput);
      if (feedback) {
        const feedbackState = {
          hoveredNodeId: feedback.highlightNodeId,
          ghostNode: feedback.ghostNode,
          crosshair: feedback.crosshair,
          // Handle previewEdge conversion - toCursor variant needs cursorWorldPos
          previewEdge: feedback.previewEdge && "to" in feedback.previewEdge ? {
            from: feedback.previewEdge.from,
            to: feedback.previewEdge.to
          } : void 0
        };
        set(commandFeedbackAtom, feedbackState);
      } else {
        set(commandFeedbackAtom, null);
      }
    }
  } else {
    set(commandLineStateAtom, {
      phase: "collecting",
      command,
      inputIndex,
      collected: newCollected
    });
    set(inputModeAtom2, {
      type: "normal"
    });
  }
});
var skipInputAtom = (0, import_jotai27.atom)(null, (get, set) => {
  const state = get(commandLineStateAtom);
  if (state.phase !== "collecting") return;
  const {
    command,
    inputIndex
  } = state;
  const currentInput = command.inputs[inputIndex];
  if (currentInput.required !== false) {
    return;
  }
  const value = currentInput.default;
  set(provideInputAtom2, value);
});
var goBackInputAtom = (0, import_jotai27.atom)(null, (get, set) => {
  const state = get(commandLineStateAtom);
  if (state.phase !== "collecting") return;
  const {
    command,
    inputIndex,
    collected
  } = state;
  if (inputIndex === 0) {
    set(commandLineStateAtom, {
      phase: "searching",
      query: command.name,
      suggestions: [command]
    });
    set(inputModeAtom2, {
      type: "normal"
    });
    return;
  }
  const prevInputIndex = inputIndex - 1;
  const prevInput = command.inputs[prevInputIndex];
  const newCollected = {
    ...collected
  };
  delete newCollected[prevInput.name];
  set(commandLineStateAtom, {
    phase: "collecting",
    command,
    inputIndex: prevInputIndex,
    collected: newCollected
  });
  set(inputModeAtom2, inputDefToMode(prevInput, newCollected));
});
var setCommandErrorAtom = (0, import_jotai27.atom)(null, (get, set, message) => {
  set(commandLineStateAtom, {
    phase: "error",
    message
  });
  set(inputModeAtom2, {
    type: "normal"
  });
});
var clearCommandErrorAtom = (0, import_jotai27.atom)(null, (get, set) => {
  set(commandLineStateAtom, {
    phase: "idle"
  });
});
function inputDefToMode(input, collected) {
  switch (input.type) {
    case "point":
      return {
        type: "pickPoint",
        prompt: input.prompt,
        snapToGrid: input.snapToGrid
      };
    case "node":
      return {
        type: "pickNode",
        prompt: input.prompt,
        filter: input.filter ? (node) => input.filter(node, collected || {}) : void 0
      };
    case "nodes":
      return {
        type: "pickNodes",
        prompt: input.prompt,
        filter: input.filter ? (node) => input.filter(node, collected || {}) : void 0
      };
    case "select":
      return {
        type: "select",
        prompt: input.prompt,
        options: input.options || []
      };
    case "text":
    case "number":
    case "color":
    case "boolean":
    default:
      return {
        type: "text",
        prompt: input.prompt
      };
  }
}

// src/gestures/modifier-helpers.ts
function isRepeatBlocked(event) {
  return isKeyInputEvent(event) && event.repeat;
}
function getSelectedNodeIds(store) {
  return Array.from(store.get(selectedNodeIdsAtom));
}
function clearSelectionState(store) {
  store.set(clearSelectionAtom);
  store.set(clearEdgeSelectionAtom);
}
function resolveFocusableNodeId(store) {
  const focusedNodeId = store.get(focusedNodeIdAtom);
  if (focusedNodeId) return focusedNodeId;
  const selected = getSelectedNodeIds(store);
  return selected[0] ?? null;
}
function getCurrentSubject(store) {
  const draggingNodeId = store.get(draggingNodeIdAtom);
  if (draggingNodeId) return {
    kind: "node",
    nodeId: draggingNodeId
  };
  const edgeCreation = store.get(edgeCreationAtom);
  if (edgeCreation.isCreating && edgeCreation.sourceNodeId) {
    return {
      kind: "node",
      nodeId: edgeCreation.sourceNodeId
    };
  }
  const focusedNodeId = resolveFocusableNodeId(store);
  if (focusedNodeId) return {
    kind: "node",
    nodeId: focusedNodeId
  };
  const selectedEdgeId = store.get(selectedEdgeIdAtom);
  if (selectedEdgeId) return {
    kind: "edge",
    edgeId: selectedEdgeId
  };
  return {
    kind: "background"
  };
}
function updateKeySubject(event, store) {
  if (event.kind === "key" && event.subject === void 0) {
    event.subject = getCurrentSubject(store);
  }
}

// src/gestures/gesture-classification.ts
function findNearestNode(currentNodeId, store, direction) {
  const nodes = store.get(uiNodesAtom);
  if (nodes.length === 0) return null;
  if (!currentNodeId) {
    const sorted = [...nodes].sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
    return sorted[0]?.id ?? null;
  }
  const currentNode = nodes.find((node) => node.id === currentNodeId);
  if (!currentNode) return nodes[0]?.id ?? null;
  const cx = currentNode.position.x + (currentNode.width ?? 200) / 2;
  const cy = currentNode.position.y + (currentNode.height ?? 100) / 2;
  let bestId = null;
  let bestScore = Number.POSITIVE_INFINITY;
  for (const candidate of nodes) {
    if (candidate.id === currentNode.id) continue;
    const nx = candidate.position.x + (candidate.width ?? 200) / 2;
    const ny = candidate.position.y + (candidate.height ?? 100) / 2;
    const dx = nx - cx;
    const dy = ny - cy;
    let isValid = false;
    switch (direction) {
      case "right":
        isValid = dx > 0;
        break;
      case "left":
        isValid = dx < 0;
        break;
      case "down":
        isValid = dy > 0;
        break;
      case "up":
        isValid = dy < 0;
        break;
    }
    if (!isValid) continue;
    const dist = Math.sqrt(dx * dx + dy * dy);
    const isHorizontal = direction === "left" || direction === "right";
    const perpendicularDistance = isHorizontal ? Math.abs(dy) : Math.abs(dx);
    const score = dist + perpendicularDistance * 0.5;
    if (score < bestScore) {
      bestScore = score;
      bestId = candidate.id;
    }
  }
  return bestId;
}
function cycleFocus(store, direction) {
  const nodes = store.get(uiNodesAtom);
  if (nodes.length === 0) return;
  const focusedNodeId = store.get(focusedNodeIdAtom);
  const sorted = [...nodes].sort((a, b) => a.zIndex - b.zIndex);
  const currentIdx = focusedNodeId ? sorted.findIndex((node) => node.id === focusedNodeId) : -1;
  const nextIdx = direction === -1 ? currentIdx <= 0 ? sorted.length - 1 : currentIdx - 1 : (currentIdx + 1) % sorted.length;
  store.set(setFocusedNodeAtom, sorted[nextIdx].id);
}
function navigateFocus(store, direction) {
  const nextId = findNearestNode(resolveFocusableNodeId(store), store, direction);
  if (nextId) {
    store.set(setFocusedNodeAtom, nextId);
  }
}
function activateFocusedNode(store, enterManipulate) {
  const focusedNodeId = resolveFocusableNodeId(store);
  if (!focusedNodeId) return;
  store.set(setFocusedNodeAtom, focusedNodeId);
  store.set(selectSingleNodeAtom, focusedNodeId);
  if (enterManipulate) {
    store.set(setKeyboardInteractionModeAtom, "manipulate");
  }
}

// src/gestures/input-action-helpers.ts
var EMPTY_EDGE_CREATION = {
  isCreating: false,
  sourceNodeId: null,
  sourceNodePosition: null,
  targetPosition: null,
  hoveredTargetNodeId: null,
  sourceHandle: null,
  targetHandle: null,
  sourcePort: null,
  targetPort: null,
  snappedTargetPosition: null
};
function nudgeSelection(store, dx, dy, label) {
  const selected = getSelectedNodeIds(store);
  if (selected.length === 0) {
    const focused = store.get(focusedNodeIdAtom);
    if (focused) {
      store.set(selectSingleNodeAtom, focused);
    }
  }
  const nodeIds = getSelectedNodeIds(store);
  if (nodeIds.length === 0) return;
  const graph = store.get(graphAtom).copy();
  store.set(pushHistoryAtom, label);
  for (const nodeId of nodeIds) {
    if (!graph.hasNode(nodeId)) continue;
    const x = graph.getNodeAttribute(nodeId, "x");
    const y = graph.getNodeAttribute(nodeId, "y");
    graph.setNodeAttribute(nodeId, "x", x + dx);
    graph.setNodeAttribute(nodeId, "y", y + dy);
  }
  store.set(graphAtom, graph);
  store.set(nodePositionUpdateCounterAtom, (count) => count + 1);
}
function deleteSelection(store) {
  const selectedEdgeId = store.get(selectedEdgeIdAtom);
  if (selectedEdgeId) {
    store.set(optimisticDeleteEdgeAtom, {
      edgeKey: selectedEdgeId
    });
    store.set(clearEdgeSelectionAtom);
  }
  const selectedNodeIds = getSelectedNodeIds(store);
  if (selectedNodeIds.length === 0) return;
  store.set(pushHistoryAtom, selectedNodeIds.length > 1 ? `Delete ${selectedNodeIds.length} nodes` : "Delete node");
  const focusedNodeId = store.get(focusedNodeIdAtom);
  for (const nodeId of selectedNodeIds) {
    store.set(optimisticDeleteNodeAtom, {
      nodeId
    });
  }
  if (focusedNodeId && selectedNodeIds.includes(focusedNodeId)) {
    store.set(setFocusedNodeAtom, null);
  }
  store.set(clearSelectionAtom);
}
function cutSelection(store) {
  const selectedNodeIds = getSelectedNodeIds(store);
  if (selectedNodeIds.length === 0) return;
  store.set(copyToClipboardAtom, selectedNodeIds);
  deleteSelection(store);
}
function cancelActiveInteraction(store) {
  let didCancel = false;
  if (store.get(draggingNodeIdAtom) !== null) {
    store.set(endNodeDragAtom);
    didCancel = true;
  }
  if (store.get(edgeCreationAtom).isCreating) {
    store.set(edgeCreationAtom, EMPTY_EDGE_CREATION);
    didCancel = true;
  }
  if (store.get(inputModeAtom).type === "pickPoint" || store.get(inputModeAtom).type === "pickNode" || store.get(inputModeAtom).type === "pickNodes") {
    store.set(resetInputModeAtom);
    didCancel = true;
  }
  const selectionPath = store.get(selectionPathAtom);
  if (selectionPath !== null) {
    store.set(cancelSelectionAtom);
    didCancel = true;
  }
  return didCancel;
}
function escapeInput(store) {
  if (cancelActiveInteraction(store)) return;
  if (store.get(keyboardInteractionModeAtom) === "manipulate") {
    store.set(resetKeyboardInteractionModeAtom);
    return;
  }
  if (store.get(isFilterActiveAtom)) {
    store.set(clearSearchAtom);
    return;
  }
  if (store.get(commandLineVisibleAtom)) {
    store.set(closeCommandLineAtom);
    return;
  }
  if (store.get(selectedNodeIdsAtom).size > 0 || store.get(selectedEdgeIdAtom) !== null) {
    clearSelectionState(store);
  }
}
function resolvePickNode(event, store) {
  updateKeySubject(event, store);
  if (event.subject?.kind !== "node") return;
  if (store.get(inputModeAtom).type === "pickNodes") {
    store.set(toggleNodeInSelectionAtom, event.subject.nodeId);
    store.set(setFocusedNodeAtom, event.subject.nodeId);
    return;
  }
  store.set(provideInputAtom, event.subject.nodeId);
  store.set(resetInputModeAtom);
}
function finishPickNodes(store) {
  store.set(provideInputAtom, getSelectedNodeIds(store));
  store.set(resetInputModeAtom);
}
function resolvePickPoint(event, store) {
  if (event.kind !== "pointer") return;
  store.set(provideInputAtom, event.worldPosition);
  store.set(resetInputModeAtom);
}
function selectAll(store) {
  clearSelectionState(store);
  store.set(addNodesToSelectionAtom, store.get(nodeKeysAtom));
}

// src/gestures/useCanvasGestures.ts
var import_react4 = require("react");
var import_jotai29 = require("jotai");
var import_react5 = require("@use-gesture/react");

// src/gestures/useGuardContext.ts
var import_react2 = require("react");
var import_jotai28 = require("jotai");
function useGuardContext(heldKeys = NO_HELD_KEYS) {
  const isStylusActive = (0, import_jotai28.useAtomValue)(isStylusActiveAtom);
  const fingerCount = (0, import_jotai28.useAtomValue)(fingerCountAtom);
  const draggingNodeId = (0, import_jotai28.useAtomValue)(draggingNodeIdAtom);
  const focusedNodeId = (0, import_jotai28.useAtomValue)(focusedNodeIdAtom);
  const selectedNodeIds = (0, import_jotai28.useAtomValue)(selectedNodeIdsAtom);
  const inputMode = (0, import_jotai28.useAtomValue)(inputModeAtom);
  const keyboardInteractionMode = (0, import_jotai28.useAtomValue)(keyboardInteractionModeAtom);
  const selectionPath = (0, import_jotai28.useAtomValue)(selectionPathAtom);
  const edgeCreation = (0, import_jotai28.useAtomValue)(edgeCreationAtom);
  const isSearchActive = (0, import_jotai28.useAtomValue)(isFilterActiveAtom);
  const commandLineVisible = (0, import_jotai28.useAtomValue)(commandLineVisibleAtom);
  const guardRef = (0, import_react2.useRef)({
    isStylusActive: false,
    fingerCount: 0,
    isDragging: false,
    isResizing: false,
    isSplitting: false,
    inputMode: {
      type: "normal"
    },
    keyboardInteractionMode: "navigate",
    selectedNodeIds: /* @__PURE__ */ new Set(),
    focusedNodeId: null,
    isSearchActive: false,
    commandLineVisible: false,
    heldKeys: NO_HELD_KEYS,
    custom: {}
  });
  guardRef.current = {
    isStylusActive,
    fingerCount,
    isDragging: draggingNodeId !== null,
    isResizing: false,
    isSplitting: false,
    inputMode,
    keyboardInteractionMode,
    selectedNodeIds,
    focusedNodeId,
    isSearchActive,
    commandLineVisible,
    heldKeys,
    custom: {
      isSelecting: selectionPath !== null,
      isCreatingEdge: edgeCreation.isCreating
    }
  };
  return guardRef;
}

// src/gestures/useInertia.ts
var import_react3 = require("react");
var ZOOM_SNAP_THRESHOLD2 = 0.03;
var INERTIA_MIN_SPEED = 2;
var VELOCITY_SAMPLE_COUNT2 = 5;
function snapZoom(z) {
  return Math.abs(z - 1) < ZOOM_SNAP_THRESHOLD2 ? 1 : z;
}
function useInertia() {
  const panInertiaRef = (0, import_react3.useRef)(null);
  const zoomInertiaRef = (0, import_react3.useRef)(null);
  const panSamplerRef = (0, import_react3.useRef)(new VelocitySampler(VELOCITY_SAMPLE_COUNT2));
  const zoomSamplerRef = (0, import_react3.useRef)(new VelocitySampler(VELOCITY_SAMPLE_COUNT2));
  const pinchPrevOrigin = (0, import_react3.useRef)(null);
  const lastPinchZoom = (0, import_react3.useRef)(null);
  const cancelPan = () => {
    if (panInertiaRef.current) {
      cancelAnimationFrame(panInertiaRef.current.anim);
      panInertiaRef.current = null;
    }
  };
  const cancelZoom = () => {
    if (zoomInertiaRef.current) {
      cancelAnimationFrame(zoomInertiaRef.current.anim);
      zoomInertiaRef.current = null;
    }
  };
  const cancelAll = () => {
    cancelPan();
    cancelZoom();
  };
  const startPanInertia = (velocity, setPan) => {
    cancelPan();
    const engine = new PanInertia(velocity);
    const animate = () => {
      const tick = engine.tick();
      if (!tick) {
        panInertiaRef.current = null;
        return;
      }
      setPan((prev) => ({
        x: prev.x + tick.x,
        y: prev.y + tick.y
      }));
      panInertiaRef.current.anim = requestAnimationFrame(animate);
    };
    panInertiaRef.current = {
      anim: requestAnimationFrame(animate),
      engine
    };
  };
  const startZoomInertia = (velocity_0, origin, currentZoom, minZoom, maxZoom, setZoom, setPan_0) => {
    cancelZoom();
    const engine_0 = new ZoomInertia(velocity_0, origin);
    const animate_0 = () => {
      const tick_0 = engine_0.tick(currentZoom);
      if (!tick_0) {
        zoomInertiaRef.current = null;
        return;
      }
      setZoom((prevZoom) => {
        const newZoom = Math.max(minZoom, Math.min(maxZoom, prevZoom + tick_0.delta));
        setPan_0((prevPan) => {
          const worldX = (tick_0.origin.x - prevPan.x) / prevZoom;
          const worldY = (tick_0.origin.y - prevPan.y) / prevZoom;
          return {
            x: tick_0.origin.x - worldX * newZoom,
            y: tick_0.origin.y - worldY * newZoom
          };
        });
        return snapZoom(newZoom);
      });
      zoomInertiaRef.current.anim = requestAnimationFrame(animate_0);
    };
    zoomInertiaRef.current = {
      anim: requestAnimationFrame(animate_0),
      engine: engine_0
    };
  };
  (0, import_react3.useEffect)(() => cancelAll, []);
  return {
    startPanInertia,
    startZoomInertia,
    cancelAll,
    cancelPan,
    cancelZoom,
    panSampler: panSamplerRef.current,
    zoomSampler: zoomSamplerRef.current,
    pinchPrevOrigin,
    lastPinchZoom
  };
}

// src/gestures/useWheelZoom.ts
function createWheelHandler(config) {
  return ({
    event,
    pinching,
    delta: [, dy],
    memo
  }) => {
    if (memo === true || pinching || !config.enableZoom || !config.ref.current) return;
    const target = event.target;
    const noDrag = target.closest('[data-no-drag="true"]');
    if (noDrag) {
      const draggableNode = target.closest('[data-draggable-node="true"]');
      if (draggableNode) {
        const nodeId = draggableNode.getAttribute("data-node-id");
        if (nodeId && config.selectedNodeIds.has(nodeId)) {
          let el = target;
          while (el && noDrag.contains(el)) {
            if (el.scrollHeight > el.clientHeight) {
              const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
              const atTop = el.scrollTop <= 1;
              if (!atBottom && dy > 0 || !atTop && dy < 0 || atBottom && dy < 0 || atTop && dy > 0) {
                return;
              }
            }
            el = el.parentElement;
          }
        }
      }
    }
    event.preventDefault();
    const rect = config.ref.current.getBoundingClientRect();
    const mouseX = event.clientX - rect.left;
    const mouseY = event.clientY - rect.top;
    const worldX = (mouseX - config.pan.x) / config.zoom;
    const worldY = (mouseY - config.pan.y) / config.zoom;
    const zoomDelta = -dy * config.zoomSensitivity * config.zoom;
    const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, config.zoom + zoomDelta)));
    config.setZoom(newZoom);
    config.setPan({
      x: mouseX - worldX * newZoom,
      y: mouseY - worldY * newZoom
    });
  };
}

// src/gestures/usePinchZoom.ts
function createPinchHandlers(config) {
  const onPinchStart = ({
    origin: [ox, oy]
  }) => {
    if (!config.enableZoom || !config.ref.current) return true;
    config.inertia.cancelAll();
    config.inertia.pinchPrevOrigin.current = {
      x: ox,
      y: oy
    };
    config.inertia.zoomSampler.reset();
    config.inertia.lastPinchZoom.current = {
      zoom: config.zoom,
      t: performance.now()
    };
    return false;
  };
  const onPinch = ({
    offset: [d],
    origin: [ox, oy],
    event,
    memo
  }) => {
    if (memo === true || !config.enableZoom || !config.ref.current) return;
    event.preventDefault();
    const rect = config.ref.current.getBoundingClientRect();
    const pinchX = ox - rect.left;
    const pinchY = oy - rect.top;
    let pdx = 0;
    let pdy = 0;
    if (config.inertia.pinchPrevOrigin.current) {
      pdx = ox - config.inertia.pinchPrevOrigin.current.x;
      pdy = oy - config.inertia.pinchPrevOrigin.current.y;
    }
    config.inertia.pinchPrevOrigin.current = {
      x: ox,
      y: oy
    };
    const worldX = (pinchX - config.pan.x) / config.zoom;
    const worldY = (pinchY - config.pan.y) / config.zoom;
    const newZoom = snapZoom(Math.max(config.minZoom, Math.min(config.maxZoom, d)));
    const now = performance.now();
    if (config.inertia.lastPinchZoom.current) {
      const dt = (now - config.inertia.lastPinchZoom.current.t) / 1e3;
      if (dt > 0) {
        const v = (newZoom - config.inertia.lastPinchZoom.current.zoom) / dt;
        config.inertia.zoomSampler.sample(v, 0, now);
      }
    }
    config.inertia.lastPinchZoom.current = {
      zoom: newZoom,
      t: now
    };
    config.setZoom(newZoom);
    config.setPan({
      x: pinchX - worldX * newZoom + pdx,
      y: pinchY - worldY * newZoom + pdy
    });
  };
  const onPinchEnd = () => {
    const avg = config.inertia.zoomSampler.average();
    const perFrameV = avg.x / 60;
    if (!config.reducedMotion && Math.abs(perFrameV) > 1e-3 && config.ref.current) {
      const rect = config.ref.current.getBoundingClientRect();
      const origin = config.inertia.pinchPrevOrigin.current ?? {
        x: rect.width / 2,
        y: rect.height / 2
      };
      config.inertia.startZoomInertia(perFrameV, {
        x: origin.x - rect.left,
        y: origin.y - rect.top
      }, config.zoom, config.minZoom, config.maxZoom, config.setZoom, config.setPan);
    }
    config.inertia.pinchPrevOrigin.current = null;
    config.inertia.zoomSampler.reset();
    config.inertia.lastPinchZoom.current = null;
  };
  return {
    onPinchStart,
    onPinch,
    onPinchEnd
  };
}

// src/gestures/useCanvasGestures.ts
function useCanvasGestures({
  ref,
  minZoom = 0.1,
  maxZoom = 5,
  zoomSensitivity = 15e-4,
  enablePan = true,
  enableZoom = true,
  mappingIndex: externalIndex,
  contexts: extraContexts,
  palmRejection = true,
  onAction,
  heldKeys = NO_HELD_KEYS
}) {
  const [panVals, setPan] = (0, import_jotai29.useAtom)(panAtom);
  const [zoomVal, setZoom] = (0, import_jotai29.useAtom)(zoomAtom);
  const setViewportRect = (0, import_jotai29.useSetAtom)(viewportRectAtom);
  const selectedNodeIds = (0, import_jotai29.useAtomValue)(selectedNodeIdsAtom);
  const clearSelection = (0, import_jotai29.useSetAtom)(clearSelectionAtom);
  const screenToWorld = (0, import_jotai29.useAtomValue)(screenToWorldAtom);
  const setPointerDown = (0, import_jotai29.useSetAtom)(pointerDownAtom);
  const setPointerUp = (0, import_jotai29.useSetAtom)(pointerUpAtom);
  const clearPointers = (0, import_jotai29.useSetAtom)(clearPointersAtom);
  const primaryInputSource = (0, import_jotai29.useAtomValue)(primaryInputSourceAtom);
  const reducedMotion = (0, import_jotai29.useAtomValue)(prefersReducedMotionAtom);
  const startSelection = (0, import_jotai29.useSetAtom)(startSelectionAtom);
  const updateSelection = (0, import_jotai29.useSetAtom)(updateSelectionAtom);
  const endSelection = (0, import_jotai29.useSetAtom)(endSelectionAtom);
  const guardRef = useGuardContext(heldKeys);
  const inertia = useInertia();
  const panStartRef = (0, import_react4.useRef)({
    x: 0,
    y: 0
  });
  const dragOriginatedOnBg = (0, import_react4.useRef)(false);
  const dragSourceRef = (0, import_react4.useRef)("mouse");
  const buttonRef = (0, import_react4.useRef)(0);
  const dragIntentRef = (0, import_react4.useRef)("none");
  const timedRunner = (0, import_react4.useRef)(new TimedStateRunner());
  const internalIndex = (0, import_react4.useMemo)(() => {
    const ctxList = [];
    if (palmRejection) ctxList.push(PALM_REJECTION_CONTEXT);
    if (extraContexts) ctxList.push(...extraContexts);
    ctxList.push(DEFAULT_CONTEXT);
    return buildMappingIndex(ctxList);
  }, [palmRejection, extraContexts]);
  const mappingIndex = externalIndex ?? internalIndex;
  const isBackgroundTarget = (target) => {
    if (!ref.current || !target) return false;
    const child = ref.current.firstChild;
    return target === ref.current || target === child;
  };
  const makeGestureEvent = (type, phase, subject, screenX, screenY, extra) => {
    const worldPos = screenToWorld(screenX, screenY);
    return {
      kind: "pointer",
      type,
      phase,
      subject,
      source: dragSourceRef.current,
      button: buttonRef.current,
      modifiers: NO_MODIFIERS,
      heldKeys,
      screenPosition: {
        x: screenX,
        y: screenY
      },
      worldPosition: worldPos,
      ...extra
    };
  };
  const resolveAndDispatch = (event) => {
    const resolution = resolve(event, mappingIndex, guardRef.current);
    if (resolution) {
      dispatch(event, resolution);
      onAction?.(event, resolution);
    }
    return resolution;
  };
  (0, import_react4.useEffect)(() => {
    const runner = timedRunner.current;
    runner.onEmit = (_gestureType) => {
    };
    return () => {
      runner.destroy();
    };
  }, []);
  (0, import_react4.useEffect)(() => {
    const el = ref.current;
    if (!el) {
      setViewportRect(null);
      return;
    }
    setViewportRect(el.getBoundingClientRect());
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        setViewportRect(entry.contentRect);
      }
    });
    observer.observe(el);
    return () => {
      observer.unobserve(el);
      observer.disconnect();
    };
  }, [setViewportRect]);
  (0, import_react4.useEffect)(() => {
    return () => {
      inertia.cancelAll();
      timedRunner.current.destroy();
    };
  }, []);
  (0, import_react4.useEffect)(() => {
    const handleVisChange = () => {
      if (document.hidden) {
        inertia.cancelAll();
        clearPointers();
        timedRunner.current.feed("cancel");
      }
    };
    document.addEventListener("visibilitychange", handleVisChange);
    return () => document.removeEventListener("visibilitychange", handleVisChange);
  }, [clearPointers]);
  (0, import_react5.useGesture)({
    onPointerDown: (state) => {
      if (!ref.current) {
        dragOriginatedOnBg.current = false;
        return;
      }
      const pe = state.event;
      const classified = classifyPointer(pe);
      setPointerDown(classified);
      dragSourceRef.current = classified.source;
      buttonRef.current = pe.button ?? 0;
      inertia.cancelAll();
      if (isBackgroundTarget(state.event.target)) {
        dragOriginatedOnBg.current = true;
        timedRunner.current.feed("down");
        const startX = pe.clientX;
        const startY = pe.clientY;
        timedRunner.current.onEmit = (gestureType) => {
          if (gestureType === "long-press") {
            const worldPos_0 = screenToWorld(startX, startY);
            const event_0 = makeGestureEvent("long-press", "instant", {
              kind: "background"
            }, startX, startY, {
              modifiers: extractModifiers(pe),
              worldPosition: worldPos_0
            });
            resolveAndDispatch(event_0);
          }
        };
      } else {
        dragOriginatedOnBg.current = false;
      }
    },
    onPointerUp: (state_0) => {
      const pe_0 = state_0.event;
      setPointerUp(pe_0.pointerId);
    },
    onDragStart: ({
      event: event_1
    }) => {
      if (!dragOriginatedOnBg.current) return;
      const pe_1 = event_1;
      const modifiers = pe_1 ? extractModifiers(pe_1) : NO_MODIFIERS;
      const gestureEvent = makeGestureEvent("drag", "start", {
        kind: "background"
      }, pe_1?.clientX ?? 0, pe_1?.clientY ?? 0, {
        modifiers
      });
      const resolution_0 = resolve(gestureEvent, mappingIndex, guardRef.current);
      dragIntentRef.current = resolution_0?.actionId ?? "none";
      if (dragIntentRef.current === "pan" && enablePan) {
        panStartRef.current = {
          ...panVals
        };
        inertia.panSampler.reset();
      } else if (dragIntentRef.current === "lasso-select" || dragIntentRef.current === "rect-select") {
        const worldPos_1 = screenToWorld(pe_1?.clientX ?? 0, pe_1?.clientY ?? 0);
        startSelection({
          type: dragIntentRef.current === "lasso-select" ? "lasso" : "rect",
          point: worldPos_1
        });
      }
      if (resolution_0) {
        dispatch(gestureEvent, resolution_0);
        onAction?.(gestureEvent, resolution_0);
      }
    },
    onDrag: ({
      movement: [mx, my],
      tap,
      active,
      pinching,
      event: event_2,
      velocity: [vx, vy],
      direction: [dx, dy]
    }) => {
      if (tap && dragOriginatedOnBg.current) {
        const emitted = timedRunner.current.feed("up");
        const pe_2 = event_2;
        const modifiers_0 = extractModifiers(pe_2);
        const tapType = emitted ?? "tap";
        const gestureEvent_0 = makeGestureEvent(tapType, "instant", {
          kind: "background"
        }, pe_2.clientX, pe_2.clientY, {
          modifiers: modifiers_0
        });
        const resolution_1 = resolve(gestureEvent_0, mappingIndex, guardRef.current);
        if (resolution_1) {
          dispatch(gestureEvent_0, resolution_1);
          onAction?.(gestureEvent_0, resolution_1);
        } else {
          clearSelection();
        }
        return;
      }
      if (!tap && active && !pinching && dragOriginatedOnBg.current) {
        timedRunner.current.feed("move-beyond-threshold");
        const intent = dragIntentRef.current;
        if (intent === "pan" && enablePan) {
          setPan({
            x: panStartRef.current.x + mx,
            y: panStartRef.current.y + my
          });
          const now = performance.now();
          inertia.panSampler.sample(vx * dx, vy * dy, now);
        } else if (intent === "lasso-select" || intent === "rect-select") {
          const pe_3 = event_2;
          const worldPos_2 = screenToWorld(pe_3.clientX, pe_3.clientY);
          updateSelection(worldPos_2);
        }
      }
    },
    onDragEnd: () => {
      timedRunner.current.feed("up");
      const intent_0 = dragIntentRef.current;
      if (intent_0 === "lasso-select" || intent_0 === "rect-select") {
        endSelection();
      }
      if (!reducedMotion && dragOriginatedOnBg.current && dragSourceRef.current === "finger" && intent_0 === "pan") {
        const avg = inertia.panSampler.average();
        const speed = Math.sqrt(avg.x ** 2 + avg.y ** 2);
        if (speed > INERTIA_MIN_SPEED) {
          inertia.startPanInertia(avg, setPan);
        }
      }
      dragOriginatedOnBg.current = false;
      dragIntentRef.current = "none";
      inertia.panSampler.reset();
    },
    onWheel: createWheelHandler({
      ref,
      minZoom,
      maxZoom,
      zoomSensitivity,
      enableZoom,
      zoom: zoomVal,
      pan: panVals,
      selectedNodeIds,
      setZoom,
      setPan
    }),
    ...createPinchHandlers({
      ref,
      minZoom,
      maxZoom,
      enableZoom,
      reducedMotion,
      zoom: zoomVal,
      pan: panVals,
      setZoom,
      setPan,
      inertia
    })
  }, {
    target: ref,
    eventOptions: {
      passive: false,
      capture: true
    },
    drag: {
      filterTaps: true,
      tapsThreshold: primaryInputSource === "finger" ? 10 : 5,
      pointer: {
        touch: true,
        keys: false,
        capture: false,
        buttons: -1
      }
    },
    wheel: {},
    pinch: {
      scaleBounds: () => ({
        min: minZoom,
        max: maxZoom
      }),
      from: () => [zoomVal, 0]
    }
  });
}

// src/gestures/useNodeGestures.ts
var import_react6 = require("react");
var import_jotai30 = require("jotai");
function useNodeGestures({
  nodeId,
  mappingIndex,
  onAction,
  heldKeys = NO_HELD_KEYS
}) {
  const screenToWorld = (0, import_jotai30.useAtomValue)(screenToWorldAtom);
  const isStylusActive = (0, import_jotai30.useAtomValue)(isStylusActiveAtom);
  const fingerCount = (0, import_jotai30.useAtomValue)(fingerCountAtom);
  const inputMode = (0, import_jotai30.useAtomValue)(inputModeAtom);
  const keyboardInteractionMode = (0, import_jotai30.useAtomValue)(keyboardInteractionModeAtom);
  const selectedNodeIds = (0, import_jotai30.useAtomValue)(selectedNodeIdsAtom);
  const focusedNodeId = (0, import_jotai30.useAtomValue)(focusedNodeIdAtom);
  const draggingNodeId = (0, import_jotai30.useAtomValue)(draggingNodeIdAtom);
  const edgeCreation = (0, import_jotai30.useAtomValue)(edgeCreationAtom);
  const selectionPath = (0, import_jotai30.useAtomValue)(selectionPathAtom);
  const isSearchActive = (0, import_jotai30.useAtomValue)(isFilterActiveAtom);
  const commandLineVisible = (0, import_jotai30.useAtomValue)(commandLineVisibleAtom);
  const runner = (0, import_react6.useRef)(new TimedStateRunner());
  const sourceRef = (0, import_react6.useRef)("mouse");
  const buttonRef = (0, import_react6.useRef)(0);
  const posRef = (0, import_react6.useRef)({
    x: 0,
    y: 0
  });
  const modifiersRef = (0, import_react6.useRef)(NO_MODIFIERS);
  const rightDragSeen = (0, import_react6.useRef)(false);
  const guardRef = (0, import_react6.useRef)({
    isStylusActive: false,
    fingerCount: 0,
    isDragging: false,
    isResizing: false,
    isSplitting: false,
    inputMode: {
      type: "normal"
    },
    keyboardInteractionMode: "navigate",
    selectedNodeIds: /* @__PURE__ */ new Set(),
    focusedNodeId: null,
    isSearchActive: false,
    commandLineVisible: false,
    heldKeys: NO_HELD_KEYS,
    custom: {}
  });
  guardRef.current = {
    isStylusActive,
    fingerCount,
    isDragging: draggingNodeId !== null,
    isResizing: false,
    isSplitting: false,
    inputMode,
    keyboardInteractionMode,
    selectedNodeIds,
    focusedNodeId,
    isSearchActive,
    commandLineVisible,
    heldKeys,
    custom: {}
  };
  guardRef.current.custom = {
    isSelecting: selectionPath !== null,
    isCreatingEdge: edgeCreation.isCreating
  };
  (0, import_react6.useEffect)(() => {
    return () => runner.current.destroy();
  }, []);
  const resolveAndDispatch = (event) => {
    const resolution = resolve(event, mappingIndex, guardRef.current);
    if (resolution) {
      dispatch(event, resolution);
      onAction?.(event, resolution);
    }
    return resolution;
  };
  const makeEvent = (type, phase, screenX, screenY) => {
    const worldPos = screenToWorld(screenX, screenY);
    return {
      kind: "pointer",
      type,
      phase,
      subject: {
        kind: "node",
        nodeId
      },
      source: sourceRef.current,
      button: buttonRef.current,
      modifiers: modifiersRef.current,
      heldKeys,
      screenPosition: {
        x: screenX,
        y: screenY
      },
      worldPosition: worldPos
    };
  };
  (0, import_react6.useEffect)(() => {
    runner.current.onEmit = (gestureType) => {
      if (gestureType === "long-press") {
        const event_0 = makeEvent("long-press", "instant", posRef.current.x, posRef.current.y);
        resolveAndDispatch(event_0);
      }
    };
  });
  const onPointerDown = (e) => {
    const classified = classifyPointer(e.nativeEvent);
    sourceRef.current = classified.source;
    buttonRef.current = e.button ?? 0;
    posRef.current = {
      x: e.clientX,
      y: e.clientY
    };
    modifiersRef.current = extractModifiers(e);
    rightDragSeen.current = false;
    runner.current.feed("down");
  };
  const onPointerUp = (e_0) => {
    const emitted = runner.current.feed("up");
    if (emitted) {
      const event_1 = makeEvent(emitted, "instant", e_0.clientX, e_0.clientY);
      resolveAndDispatch(event_1);
    }
  };
  const onPointerCancel = (_e) => {
    runner.current.feed("cancel");
  };
  const onContextMenu = (e_1) => {
    e_1.preventDefault();
    e_1.stopPropagation();
    if (rightDragSeen.current) {
      rightDragSeen.current = false;
      return;
    }
    const event_2 = makeEvent("tap", "instant", e_1.clientX, e_1.clientY);
    event_2.button = 2;
    event_2.modifiers = extractModifiers(e_1);
    resolveAndDispatch(event_2);
  };
  return {
    onPointerDown,
    onPointerUp,
    onPointerCancel,
    onContextMenu
  };
}

// src/gestures/useGestureSystem.ts
var import_compiler_runtime2 = require("react/compiler-runtime");
var import_react7 = require("react");
function useInputSystem(t0) {
  const $ = (0, import_compiler_runtime2.c)(19);
  const config = t0 === void 0 ? {} : t0;
  const {
    contexts: staticContexts,
    palmRejection: t1
  } = config;
  const initialPR = t1 === void 0 ? true : t1;
  let t2;
  if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
    t2 = [];
    $[0] = t2;
  } else {
    t2 = $[0];
  }
  const [dynamicContexts, setDynamicContexts] = (0, import_react7.useState)(t2);
  const [palmRejection, setPalmRejection] = (0, import_react7.useState)(initialPR);
  const [heldKeys, setHeldKeys] = (0, import_react7.useState)(NO_HELD_KEYS);
  const all = [];
  if (palmRejection) {
    all.push(PALM_REJECTION_CONTEXT);
  }
  all.push(ACTIVE_INTERACTION_CONTEXT, SEARCH_CONTEXT, KEYBOARD_MANIPULATE_CONTEXT, KEYBOARD_NAVIGATE_CONTEXT);
  if (staticContexts) {
    all.push(...staticContexts);
  }
  all.push(...dynamicContexts);
  all.push(DEFAULT_CONTEXT);
  const mappingIndex = buildMappingIndex(all);
  let t3;
  if ($[1] !== setDynamicContexts) {
    t3 = (ctx) => {
      setDynamicContexts((prev) => {
        const filtered = prev.filter((c) => c.id !== ctx.id);
        return [...filtered, ctx];
      });
    };
    $[1] = setDynamicContexts;
    $[2] = t3;
  } else {
    t3 = $[2];
  }
  const pushContext = t3;
  let t4;
  if ($[3] !== setDynamicContexts) {
    t4 = (id) => {
      setDynamicContexts((prev_0) => prev_0.filter((c_0) => c_0.id !== id));
    };
    $[3] = setDynamicContexts;
    $[4] = t4;
  } else {
    t4 = $[4];
  }
  const removeContext = t4;
  let t5;
  if ($[5] !== setDynamicContexts) {
    t5 = (id_0, enabled) => {
      setDynamicContexts((prev_1) => prev_1.map((c_1) => c_1.id === id_0 ? {
        ...c_1,
        enabled
      } : c_1));
    };
    $[5] = setDynamicContexts;
    $[6] = t5;
  } else {
    t5 = $[6];
  }
  const setContextEnabled = t5;
  let t6;
  if ($[7] !== setHeldKeys) {
    t6 = () => {
      setHeldKeys(NO_HELD_KEYS);
    };
    $[7] = setHeldKeys;
    $[8] = t6;
  } else {
    t6 = $[8];
  }
  const clearHeldKeys = t6;
  let t7;
  if ($[9] !== clearHeldKeys || $[10] !== heldKeys || $[11] !== mappingIndex || $[12] !== palmRejection || $[13] !== pushContext || $[14] !== removeContext || $[15] !== setContextEnabled || $[16] !== setHeldKeys || $[17] !== setPalmRejection) {
    t7 = {
      mappingIndex,
      pushContext,
      removeContext,
      setContextEnabled,
      palmRejection,
      setPalmRejection,
      heldKeys,
      setHeldKeys,
      clearHeldKeys
    };
    $[9] = clearHeldKeys;
    $[10] = heldKeys;
    $[11] = mappingIndex;
    $[12] = palmRejection;
    $[13] = pushContext;
    $[14] = removeContext;
    $[15] = setContextEnabled;
    $[16] = setHeldKeys;
    $[17] = setPalmRejection;
    $[18] = t7;
  } else {
    t7 = $[18];
  }
  return t7;
}
var useGestureSystem = useInputSystem;

// src/gestures/useInputModeGestureContext.ts
var import_compiler_runtime3 = require("react/compiler-runtime");
var import_react8 = require("react");
var import_jotai31 = require("jotai");
function useInputModeGestureContext(inputSystem) {
  const $ = (0, import_compiler_runtime3.c)(4);
  const inputMode = (0, import_jotai31.useAtomValue)(inputModeAtom);
  let t0;
  let t1;
  if ($[0] !== inputMode.type || $[1] !== inputSystem) {
    t0 = () => {
      const modeType = inputMode.type;
      const ctx = INPUT_MODE_CONTEXTS[modeType] ?? null;
      if (ctx) {
        inputSystem.pushContext(ctx);
      } else {
        for (const key of Object.keys(INPUT_MODE_CONTEXTS)) {
          const existing = INPUT_MODE_CONTEXTS[key];
          if (existing) {
            inputSystem.removeContext(existing.id);
          }
        }
      }
      return () => {
        if (ctx) {
          inputSystem.removeContext(ctx.id);
        }
      };
    };
    t1 = [inputMode.type, inputSystem];
    $[0] = inputMode.type;
    $[1] = inputSystem;
    $[2] = t0;
    $[3] = t1;
  } else {
    t0 = $[2];
    t1 = $[3];
  }
  (0, import_react8.useEffect)(t0, t1);
}

// src/gestures/useRegisterInputActions.ts
var import_compiler_runtime4 = require("react/compiler-runtime");
var import_react9 = require("react");
var import_jotai32 = require("jotai");
function register(actionId, handler) {
  registerAction(actionId, handler);
  return () => unregisterAction(actionId);
}
function useRegisterInputActions() {
  const $ = (0, import_compiler_runtime4.c)(3);
  const store = (0, import_jotai32.useStore)();
  let t0;
  let t1;
  if ($[0] !== store) {
    t0 = () => {
      const unregister = [register("select-node", (event) => {
        updateKeySubject(event, store);
        if (event.subject?.kind !== "node") {
          return;
        }
        store.set(setFocusedNodeAtom, event.subject.nodeId);
        if (event.kind === "key") {
          store.set(selectSingleNodeAtom, event.subject.nodeId);
        }
      }), register("select-edge", (event_0) => {
        updateKeySubject(event_0, store);
        if (event_0.subject?.kind !== "edge") {
          return;
        }
        store.set(selectEdgeAtom, event_0.subject.edgeId);
      }), register("toggle-selection", (event_1) => {
        updateKeySubject(event_1, store);
        if (event_1.subject?.kind !== "node") {
          return;
        }
        store.set(setFocusedNodeAtom, event_1.subject.nodeId);
        if (event_1.kind === "key") {
          store.set(toggleNodeInSelectionAtom, event_1.subject.nodeId);
        }
      }), register("clear-selection", () => {
        clearSelectionState(store);
      }), register("select-all", (event_2) => {
        if (isRepeatBlocked(event_2)) {
          return;
        }
        selectAll(store);
      }), register("fit-to-view", (event_3) => {
        updateKeySubject(event_3, store);
        if (event_3.subject?.kind === "node") {
          store.set(centerOnNodeAtom, event_3.subject.nodeId);
        }
      }), register("fit-all-to-view", () => {
        store.set(fitToBoundsAtom, {
          mode: "graph"
        });
      }), register("toggle-lock", (event_4) => {
        updateKeySubject(event_4, store);
        if (event_4.subject?.kind !== "node") {
          return;
        }
        if (store.get(lockedNodeIdAtom) === event_4.subject.nodeId) {
          store.set(unlockNodeAtom);
        } else {
          store.set(lockNodeAtom, {
            nodeId: event_4.subject.nodeId
          });
        }
      }), register("open-command-line", (event_5) => {
        if (isRepeatBlocked(event_5)) {
          return;
        }
        store.set(openCommandLineAtom);
      }), register("open-search", (event_6) => {
        if (isRepeatBlocked(event_6)) {
          return;
        }
        store.set(openCommandLineAtom);
      }), register("search-next-result", () => {
        store.set(nextSearchResultAtom);
      }), register("search-prev-result", () => {
        store.set(prevSearchResultAtom);
      }), register("copy-selection", (event_7) => {
        if (isRepeatBlocked(event_7)) {
          return;
        }
        const selectedNodeIds = getSelectedNodeIds(store);
        if (selectedNodeIds.length > 0) {
          store.set(copyToClipboardAtom, selectedNodeIds);
        }
      }), register("cut-selection", (event_8) => {
        if (isRepeatBlocked(event_8)) {
          return;
        }
        cutSelection(store);
      }), register("paste-selection", (event_9) => {
        if (isRepeatBlocked(event_9)) {
          return;
        }
        store.set(pasteFromClipboardAtom);
      }), register("duplicate-selection", (event_10) => {
        if (isRepeatBlocked(event_10)) {
          return;
        }
        store.set(duplicateSelectionAtom);
      }), register("merge-selection", (event_11) => {
        if (isRepeatBlocked(event_11)) {
          return;
        }
        const nodeIds = getSelectedNodeIds(store);
        if (nodeIds.length >= 2) {
          store.set(mergeNodesAtom, {
            nodeIds
          });
        }
      }), register("delete-selection", (event_12) => {
        if (isRepeatBlocked(event_12)) {
          return;
        }
        deleteSelection(store);
      }), register("undo", (event_13) => {
        if (isRepeatBlocked(event_13)) {
          return;
        }
        store.set(undoAtom);
      }), register("redo", (event_14) => {
        if (isRepeatBlocked(event_14)) {
          return;
        }
        store.set(redoAtom);
      }), register("cancel-active-input", () => {
        cancelActiveInteraction(store);
      }), register("escape-input", () => {
        escapeInput(store);
      }), register("navigate-focus-up", () => {
        navigateFocus(store, "up");
      }), register("navigate-focus-down", () => {
        navigateFocus(store, "down");
      }), register("navigate-focus-left", () => {
        navigateFocus(store, "left");
      }), register("navigate-focus-right", () => {
        navigateFocus(store, "right");
      }), register("cycle-focus-forward", (event_15) => {
        if (isRepeatBlocked(event_15)) {
          return;
        }
        cycleFocus(store, 1);
      }), register("cycle-focus-backward", (event_16) => {
        if (isRepeatBlocked(event_16)) {
          return;
        }
        cycleFocus(store, -1);
      }), register("activate-focused-node", (event_17) => {
        if (isRepeatBlocked(event_17)) {
          return;
        }
        activateFocusedNode(store, false);
      }), register("enter-keyboard-manipulate-mode", (event_18) => {
        if (isRepeatBlocked(event_18)) {
          return;
        }
        activateFocusedNode(store, true);
      }), register("exit-keyboard-manipulate-mode", () => {
        store.set(resetKeyboardInteractionModeAtom);
      }), register("nudge-selection-up", () => {
        nudgeSelection(store, 0, -10, "Nudge selection");
      }), register("nudge-selection-down", () => {
        nudgeSelection(store, 0, 10, "Nudge selection");
      }), register("nudge-selection-left", () => {
        nudgeSelection(store, -10, 0, "Nudge selection");
      }), register("nudge-selection-right", () => {
        nudgeSelection(store, 10, 0, "Nudge selection");
      }), register("nudge-selection-up-large", () => {
        nudgeSelection(store, 0, -50, "Nudge selection");
      }), register("nudge-selection-down-large", () => {
        nudgeSelection(store, 0, 50, "Nudge selection");
      }), register("nudge-selection-left-large", () => {
        nudgeSelection(store, -50, 0, "Nudge selection");
      }), register("nudge-selection-right-large", () => {
        nudgeSelection(store, 50, 0, "Nudge selection");
      }), register("resolve-pick-node", (event_19) => {
        resolvePickNode(event_19, store);
      }), register("finish-pick-nodes", () => {
        finishPickNodes(store);
      }), register("resolve-pick-point", (event_20) => {
        resolvePickPoint(event_20, store);
      }), register("cancel-pick", () => {
        store.set(resetInputModeAtom);
      })];
      return () => {
        for (const cleanup of unregister) {
          cleanup();
        }
      };
    };
    t1 = [store];
    $[0] = store;
    $[1] = t0;
    $[2] = t1;
  } else {
    t0 = $[1];
    t1 = $[2];
  }
  (0, import_react9.useEffect)(t0, t1);
}

// src/gestures/GestureProvider.tsx
var import_react10 = __toESM(require("react"));
var import_jotai33 = require("jotai");

// src/gestures/gesture-provider-utils.ts
function isEditableTarget(target) {
  if (!(target instanceof HTMLElement)) {
    return false;
  }
  if (target.isContentEditable) {
    return true;
  }
  const editable = target.closest('input, textarea, [contenteditable="true"], [data-no-canvas-keyboard="true"]');
  return editable !== null;
}
function setHeldKeyValue(current, key, isHeld) {
  const previous = current[key] ?? false;
  if (previous === isHeld) {
    return current;
  }
  const next = {
    ...current
  };
  if (isHeld) {
    next[key] = true;
  } else {
    delete next[key];
  }
  return next;
}
function applyHeldKeyDelta(event, current) {
  const isHeld = event.type === "keydown";
  const nextByKey = setHeldKeyValue(current.byKey, event.key, isHeld);
  const nextByCode = setHeldKeyValue(current.byCode, event.code, isHeld);
  if (nextByKey === current.byKey && nextByCode === current.byCode) {
    return current;
  }
  return {
    byKey: nextByKey,
    byCode: nextByCode
  };
}
function getCurrentSubject2(store) {
  const draggingNodeId = store.get(draggingNodeIdAtom);
  if (draggingNodeId) {
    return {
      kind: "node",
      nodeId: draggingNodeId
    };
  }
  const edgeCreation = store.get(edgeCreationAtom);
  if (edgeCreation.isCreating && edgeCreation.sourceNodeId) {
    return {
      kind: "node",
      nodeId: edgeCreation.sourceNodeId
    };
  }
  const focusedNodeId = store.get(focusedNodeIdAtom);
  if (focusedNodeId) {
    return {
      kind: "node",
      nodeId: focusedNodeId
    };
  }
  return {
    kind: "background"
  };
}
function getSubjectPosition(store, root, subject) {
  const worldToScreen = store.get(worldToScreenAtom);
  if (subject.kind === "node") {
    const node = store.get(uiNodesAtom).find((entry) => entry.id === subject.nodeId);
    if (!node) {
      return {};
    }
    const worldPosition = {
      x: node.position.x + (node.width ?? 200) / 2,
      y: node.position.y + (node.height ?? 100) / 2
    };
    return {
      worldPosition,
      screenPosition: worldToScreen(worldPosition.x, worldPosition.y)
    };
  }
  if (subject.kind === "background" && root) {
    const rect = root.getBoundingClientRect();
    const screenPosition = {
      x: rect.left + rect.width / 2,
      y: rect.top + rect.height / 2
    };
    return {
      screenPosition,
      worldPosition: store.get(screenToWorldAtom)(screenPosition.x, screenPosition.y)
    };
  }
  return {};
}
function buildGuardContext(store, system) {
  const heldKeys = "heldKeys" in system ? system.heldKeys : system;
  const edgeCreation = store.get(edgeCreationAtom);
  return {
    isStylusActive: store.get(isStylusActiveAtom),
    fingerCount: store.get(fingerCountAtom),
    isDragging: store.get(draggingNodeIdAtom) !== null,
    isResizing: false,
    isSplitting: false,
    inputMode: store.get(inputModeAtom),
    keyboardInteractionMode: store.get(keyboardInteractionModeAtom),
    selectedNodeIds: store.get(selectedNodeIdsAtom),
    focusedNodeId: store.get(focusedNodeIdAtom),
    isSearchActive: store.get(isFilterActiveAtom),
    commandLineVisible: store.get(commandLineVisibleAtom),
    heldKeys,
    custom: {
      isSelecting: store.get(selectionPathAtom) !== null,
      isCreatingEdge: edgeCreation.isCreating
    }
  };
}

// src/gestures/GestureProvider.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
var nextOwnerId = 1;
var activeOwnerId = null;
var InputContext = /* @__PURE__ */ (0, import_react10.createContext)(null);
function InputProvider({
  children,
  gestureConfig,
  onAction
}) {
  const store = (0, import_jotai33.useStore)();
  const system = useInputSystem({
    contexts: gestureConfig?.contexts,
    palmRejection: gestureConfig?.palmRejection ?? true
  });
  useInputModeGestureContext(system);
  useRegisterInputActions();
  const [ownerId] = (0, import_react10.useState)(() => nextOwnerId++);
  const [canvasRoot, setCanvasRoot] = (0, import_react10.useState)(null);
  const rootRef = (0, import_react10.useRef)(null);
  const systemRef = (0, import_react10.useRef)(system);
  systemRef.current = system;
  const registerCanvasRoot = (node) => {
    rootRef.current = node;
    setCanvasRoot(node);
  };
  (0, import_react10.useEffect)(() => {
    const root = canvasRoot;
    if (!root) {
      return;
    }
    const activate = (event) => {
      activeOwnerId = ownerId;
      if (!isEditableTarget(event.target)) {
        root.focus({
          preventScroll: true
        });
      }
    };
    root.addEventListener("pointerdown", activate, true);
    root.addEventListener("focusin", activate, true);
    return () => {
      root.removeEventListener("pointerdown", activate, true);
      root.removeEventListener("focusin", activate, true);
      if (activeOwnerId === ownerId) {
        activeOwnerId = null;
      }
    };
  }, [canvasRoot, ownerId]);
  (0, import_react10.useEffect)(() => {
    const handleKeyboard = (nativeEvent) => {
      if (activeOwnerId !== ownerId) {
        return;
      }
      const root_0 = rootRef.current;
      if (!root_0 || !root_0.isConnected) {
        return;
      }
      const currentSystem = systemRef.current;
      const editableTarget = isEditableTarget(nativeEvent.target);
      const shouldTrackHeldKey = nativeEvent.type === "keyup" || !nativeEvent.isComposing && !editableTarget;
      const nextHeldKeys = shouldTrackHeldKey ? applyHeldKeyDelta(nativeEvent, currentSystem.heldKeys) : currentSystem.heldKeys;
      if (nextHeldKeys !== currentSystem.heldKeys) {
        currentSystem.setHeldKeys(nextHeldKeys);
      }
      if (nativeEvent.isComposing || editableTarget) {
        return;
      }
      const subject = getCurrentSubject2(store);
      const {
        screenPosition,
        worldPosition
      } = getSubjectPosition(store, root_0, subject);
      const inputEvent = {
        kind: "key",
        phase: nativeEvent.type === "keydown" ? "down" : "up",
        key: nativeEvent.key,
        code: nativeEvent.code,
        repeat: nativeEvent.repeat,
        modifiers: {
          shift: nativeEvent.shiftKey,
          ctrl: nativeEvent.ctrlKey,
          alt: nativeEvent.altKey,
          meta: nativeEvent.metaKey
        },
        heldKeys: nextHeldKeys,
        subject,
        screenPosition,
        worldPosition,
        originalEvent: nativeEvent
      };
      const resolution = resolve(inputEvent, currentSystem.mappingIndex, buildGuardContext(store, {
        heldKeys: nextHeldKeys
      }));
      if (!resolution) {
        return;
      }
      nativeEvent.preventDefault();
      dispatch(inputEvent, resolution);
      onAction?.(inputEvent, resolution);
    };
    const clearHeldKeys = () => {
      systemRef.current.clearHeldKeys();
    };
    window.addEventListener("keydown", handleKeyboard, true);
    window.addEventListener("keyup", handleKeyboard, true);
    window.addEventListener("blur", clearHeldKeys);
    document.addEventListener("visibilitychange", clearHeldKeys);
    return () => {
      window.removeEventListener("keydown", handleKeyboard, true);
      window.removeEventListener("keyup", handleKeyboard, true);
      window.removeEventListener("blur", clearHeldKeys);
      document.removeEventListener("visibilitychange", clearHeldKeys);
    };
  }, [onAction, ownerId, store]);
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InputContext.Provider, {
    value: {
      system,
      onAction,
      registerCanvasRoot
    },
    children
  });
}
function useInputContext() {
  const ctx = (0, import_react10.useContext)(InputContext);
  if (!ctx) {
    throw new Error("useInputContext must be used within an InputProvider");
  }
  return ctx;
}
var GestureProvider = InputProvider;
var useGestureContext = useInputContext;
//# sourceMappingURL=index.js.map