import {
  APP_NAME,
  ARROW_TYPE,
  AbortError,
  AlignBottomIcon,
  AlignLeftIcon,
  AlignRightIcon,
  AlignTopIcon,
  ArrowRightIcon,
  ArrowheadArrowIcon,
  ArrowheadBarIcon,
  ArrowheadCircleIcon,
  ArrowheadCircleOutlineIcon,
  ArrowheadCrowfootIcon,
  ArrowheadCrowfootOneIcon,
  ArrowheadCrowfootOneOrManyIcon,
  ArrowheadDiamondIcon,
  ArrowheadDiamondOutlineIcon,
  ArrowheadNoneIcon,
  ArrowheadTriangleIcon,
  ArrowheadTriangleOutlineIcon,
  BINDING_HIGHLIGHT_OFFSET,
  BINDING_HIGHLIGHT_THICKNESS,
  BOUND_TEXT_PADDING,
  BringForwardIcon,
  BringToFrontIcon,
  CANVAS_SEARCH_TAB,
  CLASSES,
  CODES,
  COLORS_PER_ROW,
  COLOR_CHARCOAL_BLACK,
  COLOR_PALETTE,
  COLOR_VOICE_CALL,
  COLOR_WHITE,
  CURSOR_TYPE,
  CaptureUpdateAction,
  CenterHorizontallyIcon,
  CenterVerticallyIcon,
  CloseIcon,
  DEFAULT_CANVAS_BACKGROUND_PICKS,
  DEFAULT_COLLISION_THRESHOLD,
  DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
  DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
  DEFAULT_ELEMENT_BACKGROUND_PICKS,
  DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
  DEFAULT_ELEMENT_STROKE_COLOR_PALETTE,
  DEFAULT_ELEMENT_STROKE_PICKS,
  DEFAULT_EXPORT_PADDING,
  DEFAULT_FILENAME,
  DEFAULT_FONT_FAMILY,
  DEFAULT_FONT_SIZE,
  DEFAULT_GRID_SIZE,
  DEFAULT_LASER_COLOR,
  DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT,
  DEFAULT_REDUCED_GLOBAL_ALPHA,
  DEFAULT_SIDEBAR,
  DEFAULT_TEXT_ALIGN,
  DEFAULT_TRANSFORM_HANDLE_SPACING,
  DEFAULT_UI_OPTIONS,
  DEFAULT_VERTICAL_ALIGN,
  DRAGGING_THRESHOLD,
  DeviceDesktopIcon,
  DiscordIcon,
  DistributeHorizontallyIcon,
  DistributeVerticallyIcon,
  DotsIcon,
  DuplicateIcon,
  EDITOR_LS_KEYS,
  ELEMENT_SHIFT_TRANSLATE_AMOUNT,
  ELEMENT_TRANSLATE_AMOUNT,
  ENV,
  EXPORT_DATA_TYPES,
  EXPORT_IMAGE_TYPES,
  EXPORT_SCALES,
  EXPORT_SOURCE,
  EdgeRoundIcon,
  EdgeSharpIcon,
  EmbedIcon,
  Emitter,
  ExportIcon,
  ExportImageIcon,
  ExternalLinkIcon,
  FONT_FAMILY,
  FRAME_STYLE,
  FillCrossHatchIcon,
  FillHachureIcon,
  FillSolidIcon,
  FillZigZagIcon,
  FontFamilyCodeIcon,
  FontFamilyNormalIcon,
  FontSizeExtraLargeIcon,
  FontSizeLargeIcon,
  FontSizeMediumIcon,
  FontSizeSmallIcon,
  Fonts,
  FreedrawIcon,
  GithubIcon,
  GroupIcon,
  HEADING_DOWN,
  HEADING_LEFT,
  HEADING_RIGHT,
  HEADING_UP,
  HYPERLINK_TOOLTIP_DELAY,
  HamburgerMenuIcon,
  HelpIcon,
  HelpIconThin,
  IMAGE_MIME_TYPES,
  IMAGE_RENDER_TIMEOUT,
  ImageSceneDataError,
  ImageURLToFile,
  KEYS,
  LIBRARY_DISABLED_TYPES,
  LIBRARY_SIDEBAR_TAB,
  LINE_CONFIRM_THRESHOLD,
  LibraryIcon,
  LinearElementEditor,
  LinkIcon,
  LoadIcon,
  LockedIcon,
  MAX_ALLOWED_FILE_BYTES,
  MAX_CUSTOM_COLORS_USED_IN_CANVAS,
  MAX_ZOOM,
  MIME_TYPES,
  MINIMAL_CROP_SIZE,
  MIN_WIDTH_OR_HEIGHT,
  MIN_ZOOM,
  MQ_MAX_HEIGHT_LANDSCAPE,
  MQ_MAX_WIDTH_LANDSCAPE,
  MQ_MAX_WIDTH_PORTRAIT,
  MQ_RIGHT_SIDEBAR_MIN_WIDTH,
  MagicIcon,
  MoonIcon,
  POINTER_BUTTON,
  POINTER_EVENTS,
  PenModeIcon,
  PinIcon,
  PlusIcon,
  ROUNDNESS,
  RedoIcon,
  SCROLL_TIMEOUT,
  SHAPES,
  STATS_PANELS,
  STROKE_WIDTH,
  SVGStringToFile,
  SVG_NS,
  Scene_default,
  SendBackwardIcon,
  SendToBackIcon,
  ShapeCache,
  SloppinessArchitectIcon,
  SloppinessArtistIcon,
  SloppinessCartoonistIcon,
  SnapCache,
  Store,
  StrokeStyleDashedIcon,
  StrokeStyleDottedIcon,
  StrokeWidthBaseIcon,
  StrokeWidthBoldIcon,
  StrokeWidthExtraBoldIcon,
  SunIcon,
  TAP_TWICE_TIMEOUT,
  TEXT_ALIGN,
  TEXT_TO_CENTER_SNAP_THRESHOLD,
  THEME,
  THEME_FILTER,
  TOOL_TYPE,
  TOUCH_CTX_MENU_TIMEOUT,
  TextAlignBottomIcon,
  TextAlignCenterIcon,
  TextAlignLeftIcon,
  TextAlignMiddleIcon,
  TextAlignRightIcon,
  TextAlignTopIcon,
  TextIcon,
  TrashIcon,
  URL_HASH_KEYS,
  URL_QUERY_KEYS,
  UndoIcon,
  UngroupIcon,
  UnlockedIcon,
  UserIdleState,
  VERSIONS,
  VERTICAL_ALIGN,
  WelcomeScreenHelpArrow,
  WelcomeScreenMenuArrow,
  WelcomeScreenTopToolbarArrow,
  XBrandIcon,
  YOUTUBE_STATES,
  ZOOM_STEP,
  ZoomInIcon,
  ZoomOutIcon,
  ZoomResetIcon,
  aabbForElement,
  abacusIcon,
  actions,
  addElementsToFrame,
  addEventListener,
  addToGroup,
  alertTriangleIcon,
  angleIcon,
  arrayToList,
  arrayToMap,
  arrayToMapWithIndex,
  assertNever,
  bindElementsToFramesAfterDuplication,
  bindLinearElement,
  bindOrUnbindLinearElement,
  bindOrUnbindLinearElements,
  bindPointToSnapToElementOutline,
  bindTextToShapeAfterDuplication,
  boltIcon,
  bootstrapCanvas,
  brainIcon,
  brainIconThin,
  bucketFillIcon,
  bumpVersion,
  calculateFixedPointForElbowArrowBinding,
  calculateScrollCenter,
  canApplyRoundnessTypeToElement,
  canChangeRoundness,
  canCreateLinkFromElements,
  canHaveArrowheads,
  canvasToBlob,
  capitalizeString,
  castArray,
  centerScrollOn,
  checkIcon,
  chunk,
  clamp,
  clockIcon,
  cloneJSON,
  coffeeIcon,
  collapseDownIcon,
  collapseUpIcon,
  compareHeading,
  composeEventHandlers,
  computeBoundTextPosition,
  computeContainerDimensionForBoundText,
  copyBlobToClipboardAsPng,
  copyIcon,
  copyTextToSystemClipboard,
  copyToClipboard,
  createPasteEvent,
  createSrcDoc,
  cropElement,
  cropIcon,
  cutIcon,
  dataURLToFile,
  dataURLToString,
  debounce,
  deepCopyElement,
  defaultGetElementLinkFromSelection,
  degreesToRadians,
  distance,
  done,
  downloadIcon,
  dragNewElement,
  dragSelectedElements,
  duplicateElement,
  duplicateElements,
  easeOut,
  easeToValuesRAF,
  editGroupForSelectedElement,
  elbowArrowIcon,
  elementLinkIcon,
  elementOverlapsWithFrame,
  elementPartiallyOverlapsWithOrContainsBBox,
  elementsAreInFrameBounds,
  elementsAreInSameGroup,
  elementsOverlappingBBox,
  embeddableURLValidator,
  excludeElementsInFramesFromSelection,
  exportToBlob,
  exportToCanvas,
  exportToCanvas2,
  exportToClipboard,
  exportToFileIcon,
  exportToSvg,
  exportToSvg2,
  extraToolsIcon,
  eyeClosedIcon,
  eyeDropperIcon,
  eyeIcon,
  fileOpen,
  fileSave,
  fillCircle,
  filterElementsEligibleAsFrameChildren,
  findIndex,
  findLastIndex,
  findShapeByKey,
  fixBindingsAfterDeletion,
  fixBindingsAfterDuplication,
  flipHorizontal,
  flipVertical,
  focusNearestParent,
  fontSizeIcon,
  frameAndChildrenSelectedTogether,
  frameToolIcon,
  fullscreenIcon,
  generateIdFromFile,
  getApproxMinLineHeight,
  getApproxMinLineWidth,
  getBoundTextElement,
  getBoundTextElementId,
  getBoundTextMaxHeight,
  getBoundTextMaxWidth,
  getBoundTextShape,
  getCommonAttributeOfSelectedElements,
  getCommonBoundingBox,
  getCommonBounds,
  getContainerCenter,
  getContainerElement,
  getContainingFrame,
  getCornerRadius,
  getCursorForResizingElement,
  getDataURL,
  getDataURL_sync,
  getDateTime,
  getDefaultAppState,
  getDefaultRoundnessTypeForElement,
  getDragOffsetXY,
  getElementAbsoluteCoords,
  getElementShape,
  getElementWithTransformHandleType,
  getElementsInGroup,
  getElementsInNewFrame,
  getElementsInResizingFrame,
  getElementsOverlappingFrame,
  getElementsWithinSelection,
  getEmbedLink,
  getExportSize,
  getFileFromEvent,
  getFileHandleType,
  getFlipAdjustedCropPosition,
  getFontFamilyString,
  getFontString,
  getFrame,
  getFrameChildren,
  getFrameLikeElements,
  getFrameLikeTitle,
  getFreeDrawSvgPath,
  getGlobalCSSVariable,
  getGridPoint,
  getHoveredElementForBinding,
  getInitializedImageElements,
  getLineHeight,
  getLineHeightInPx,
  getLinkHandleFromCoords,
  getLinkIdAndTypeFromSelection,
  getLockedLinearCursorAlignSize,
  getMaximumGroups,
  getMinTextElementWidth,
  getNearestScrollableContainer,
  getNonDeletedElements,
  getNormalizedCanvasDimensions,
  getNormalizedDimensions,
  getNormalizedGridStep,
  getNormalizedZoom,
  getOmitSidesForDevice,
  getOriginalContainerHeightFromCache,
  getReferenceSnapPoints,
  getRenderOpacity,
  getResizeArrowDirection,
  getResizeOffsetXY,
  getRootElements,
  getSceneVersion,
  getSelectedElements,
  getSelectedGroupForElement,
  getSelectedGroupIdForElement,
  getSelectedGroupIds,
  getSelectionBoxShape,
  getShortcutKey,
  getSizeFromPoints,
  getSnapLinesAtPointer,
  getSuggestedBindingsForArrows,
  getSvgPathFromStroke,
  getTargetElements,
  getTextElementAngle,
  getTextFromElements,
  getTextWidth,
  getTransformHandleTypeFromCoords,
  getTransformHandles,
  getTransformHandlesFromCoords,
  getUncroppedWidthAndHeight,
  getVisibleGaps,
  getVisibleSceneBounds,
  gridIcon,
  groupByFrameLikes,
  handIcon,
  handleBindTextResize,
  hasBackground,
  hasBoundTextElement,
  hasStrokeColor,
  hasStrokeStyle,
  hasStrokeWidth,
  hashElementsVersion,
  hashString,
  headingForPointFromElement,
  helpIcon,
  hitElementBoundText,
  hitElementBoundingBox,
  hitElementBoundingBoxOnly,
  hitElementItself,
  invariant,
  isActiveToolNonLinearSnappable,
  isArrowElement,
  isArrowKey,
  isBindableElement,
  isBindingElement,
  isBindingElementType,
  isBindingEnabled,
  isBoundToContainer,
  isBrave,
  isCursorInFrame,
  isDarwin,
  isDevEnv,
  isElbowArrow,
  isElementCompletelyInViewport,
  isElementInFrame,
  isElementInGroup,
  isElementInViewport,
  isElementInsideBBox,
  isElementLink,
  isEmbeddableElement,
  isEraserActive,
  isExcalidrawElement,
  isFiniteNumber,
  isFirefox,
  isFlowchartNodeElement,
  isFrameElement,
  isFrameLikeElement,
  isGridModeEnabled,
  isHandToolActive,
  isIOS,
  isIframeElement,
  isIframeLikeElement,
  isImageElement,
  isImageFileHandle,
  isImageFileHandleType,
  isInGroup,
  isInitializedImageElement,
  isInputLike,
  isInteractive,
  isInvisiblySmallElement,
  isLinearElement,
  isLinearElementSimpleAndAlreadyBound,
  isLinearElementType,
  isLocalLink,
  isMagicFrameElement,
  isMeasureTextSupported,
  isMemberOf,
  isNonDeletedElement,
  isPathALoop,
  isPointHittingLink,
  isPointHittingLinkIcon,
  isPointInShape,
  isPromiseLike,
  isSafari,
  isSelectedViaGroup,
  isShallowEqual,
  isSnappingEnabled,
  isSomeElementSelected,
  isSupportedImageFile,
  isTestEnv,
  isTextBindableContainer,
  isTextElement,
  isToolIcon,
  isTransparent,
  isUsingAdaptiveRadius,
  isValidTextContainer,
  isWindows,
  isWritableElement,
  laserPointerToolIcon,
  lineEditorIcon,
  loadFromBlob,
  loadFromJSON,
  loadHTMLImageElement,
  loadLibraryFromBlob,
  loadSceneOrLibraryFromBlob,
  magnetIcon,
  makeNextSelectedElementIds,
  matchKey,
  maxBindingGap,
  maybeBindLinearElement,
  maybeParseEmbedSrc,
  measureText,
  memoize,
  mermaidLogoIcon,
  microphoneIcon,
  microphoneMutedIcon,
  mutateElement,
  muteFSAbortError,
  nativeFileSystemSupported,
  newArrowElement,
  newElement,
  newElementWith,
  newEmbeddableElement,
  newFrameElement,
  newFreeDrawElement,
  newIframeElement,
  newImageElement,
  newLinearElement,
  newMagicFrameElement,
  newTextElement,
  normalizeEOL,
  normalizeFile,
  normalizeLink,
  normalizeSVG,
  normalizeText,
  orderByFractionalIndex,
  originalContainerCache,
  paintIcon,
  palette,
  parseClipboard,
  parseElementLinkFromURL,
  parseLibraryJSON,
  pngIcon,
  pointDistance,
  pointFrom,
  pointRotateRads,
  preventUnload,
  probablySupportsClipboardBlob,
  probablySupportsClipboardWriteText,
  promiseTry,
  publishIcon,
  queryFocusableElements,
  questionCircle,
  radiansToDegrees,
  randomId,
  randomInteger,
  readSystemClipboard,
  redrawTextBoundingBox,
  refreshTextDimensions,
  register,
  removeAllElementsFromFrame,
  removeElementsFromFrame,
  removeFromSelectedGroups,
  renderElement,
  renderSelectionElement,
  renderSpreadsheet,
  renderStaticScene,
  renderStaticSceneThrottled,
  replaceAllElementsInFrame,
  rescalePointsInElement,
  resetCursor,
  resetOriginalContainerCache,
  resizeImageFile,
  resizeMultipleElements,
  resizeSingleElement,
  resolvablePromise,
  restore,
  restoreAppState,
  restoreElements,
  restoreLibraryItems,
  rgbToHex,
  round,
  roundArrowIcon,
  roundToStep,
  save,
  saveAs,
  saveAsJSON,
  saveLibraryAsJSON,
  sceneCoordsToViewportCoords,
  searchIcon,
  selectAllIcon,
  selectGroup,
  selectGroupsForSelectedElements,
  selectGroupsFromGivenElements,
  serializeAsJSON,
  serializeLibraryAsJSON,
  setCursor,
  setCursorForShape,
  setCustomTextMetricsProvider,
  setEraserCursor,
  share,
  sharpArrowIcon,
  shouldAllowVerticalAlign,
  shouldEnableBindingForPointerEvent,
  shouldMaintainAspectRatio,
  shouldResizeFromCenter,
  shouldRotateWithDiscreteAngle,
  shouldShowBoundingBox,
  showSelectedShapeActions,
  snapDraggedElements,
  snapNewElement,
  snapResizingElements,
  supportsResizeObserver,
  suppportsHorizontalAlign,
  svgIcon,
  syncInvalidIndices,
  syncMovedIndices,
  tablerCheckIcon,
  throttleRAF,
  toBrandedType,
  toValidURL,
  toolIsArrow,
  transformElements,
  tupleToCoors,
  upIcon,
  updateActiveTool,
  updateBoundElements,
  updateElbowArrowPoints,
  updateFrameMembershipOfSelectedElements,
  updateImageCache,
  updateObject,
  updateOriginalContainerCache,
  updateStable,
  usersIcon,
  validateFractionalIndices,
  vector,
  vectorDot,
  vectorFromPoint,
  vectorNormalize,
  vectorScale,
  vectorSubtract,
  viewportCoordsToSceneCoords,
  wrapEvent,
  wrapText,
  youtubeIcon,
  zoomAreaIcon
} from "./chunk-3KPV5WBD.js";
import {
  define_import_meta_env_default
} from "./chunk-66VA7UC4.js";
import {
  en_default
} from "./chunk-LMHBUWQS.js";
import {
  percentages_default
} from "./chunk-MFAYKRVR.js";
import {
  __export,
  __glob,
  __publicField
} from "./chunk-XDFCUUT6.js";

// index.tsx
import React44, { useEffect as useEffect45 } from "react";

// components/InitializeApp.tsx
import { useEffect as useEffect2, useState as useState2 } from "react";

// editor-jotai.ts
import { atom, createStore } from "jotai";
import { createIsolation } from "jotai-scope";
var jotai = createIsolation();
var { useAtom, useSetAtom, useAtomValue, useStore } = jotai;
var EditorJotaiProvider = jotai.Provider;
var editorJotaiStore = createStore();

// import("./locales/**/*.json") in i18n.ts
var globImport_locales_json = __glob({
  "./locales/ar-SA.json": () => import("./locales/ar-SA-XORAP2EK.js"),
  "./locales/az-AZ.json": () => import("./locales/az-AZ-NAUU3Z4Y.js"),
  "./locales/bg-BG.json": () => import("./locales/bg-BG-AAABLFCY.js"),
  "./locales/bn-BD.json": () => import("./locales/bn-BD-PM4AC5WG.js"),
  "./locales/ca-ES.json": () => import("./locales/ca-ES-YNNMFRQX.js"),
  "./locales/cs-CZ.json": () => import("./locales/cs-CZ-DGZA5IKG.js"),
  "./locales/da-DK.json": () => import("./locales/da-DK-N76F4QAJ.js"),
  "./locales/de-DE.json": () => import("./locales/de-DE-DMRXZ2SZ.js"),
  "./locales/el-GR.json": () => import("./locales/el-GR-HIKPLEXI.js"),
  "./locales/en.json": () => import("./locales/en-OZCJJ2HN.js"),
  "./locales/es-ES.json": () => import("./locales/es-ES-AQYVXC32.js"),
  "./locales/eu-ES.json": () => import("./locales/eu-ES-3TOEU5DE.js"),
  "./locales/fa-IR.json": () => import("./locales/fa-IR-527GAKUP.js"),
  "./locales/fi-FI.json": () => import("./locales/fi-FI-M3WLVDFP.js"),
  "./locales/fr-FR.json": () => import("./locales/fr-FR-YE4VDJFI.js"),
  "./locales/gl-ES.json": () => import("./locales/gl-ES-KMXUYGUN.js"),
  "./locales/he-IL.json": () => import("./locales/he-IL-4MU5N22B.js"),
  "./locales/hi-IN.json": () => import("./locales/hi-IN-ZHZNZWFC.js"),
  "./locales/hu-HU.json": () => import("./locales/hu-HU-VIYZI3X4.js"),
  "./locales/id-ID.json": () => import("./locales/id-ID-22TWZNLA.js"),
  "./locales/it-IT.json": () => import("./locales/it-IT-MDEQ2SG3.js"),
  "./locales/ja-JP.json": () => import("./locales/ja-JP-K2DI4W6B.js"),
  "./locales/kaa.json": () => import("./locales/kaa-6BPSNM3R.js"),
  "./locales/kab-KAB.json": () => import("./locales/kab-KAB-2S7ZURK7.js"),
  "./locales/kk-KZ.json": () => import("./locales/kk-KZ-UJPYGRQQ.js"),
  "./locales/km-KH.json": () => import("./locales/km-KH-M5T5JKUE.js"),
  "./locales/ko-KR.json": () => import("./locales/ko-KR-RQX37SNF.js"),
  "./locales/ku-TR.json": () => import("./locales/ku-TR-5XJDIERL.js"),
  "./locales/lt-LT.json": () => import("./locales/lt-LT-MGUBX6CA.js"),
  "./locales/lv-LV.json": () => import("./locales/lv-LV-MD7N5VHD.js"),
  "./locales/mr-IN.json": () => import("./locales/mr-IN-4XWMNGQC.js"),
  "./locales/my-MM.json": () => import("./locales/my-MM-O4Z74GN5.js"),
  "./locales/nb-NO.json": () => import("./locales/nb-NO-BMB73KRH.js"),
  "./locales/nl-NL.json": () => import("./locales/nl-NL-F2257BLQ.js"),
  "./locales/nn-NO.json": () => import("./locales/nn-NO-NCORG7TS.js"),
  "./locales/oc-FR.json": () => import("./locales/oc-FR-ATFBDMF6.js"),
  "./locales/pa-IN.json": () => import("./locales/pa-IN-D2I375G4.js"),
  "./locales/percentages.json": () => import("./locales/percentages-YKFLWNK2.js"),
  "./locales/pl-PL.json": () => import("./locales/pl-PL-YJHOWAAW.js"),
  "./locales/pt-BR.json": () => import("./locales/pt-BR-APOPYZJ7.js"),
  "./locales/pt-PT.json": () => import("./locales/pt-PT-W56WCN7P.js"),
  "./locales/ro-RO.json": () => import("./locales/ro-RO-L575VRQA.js"),
  "./locales/ru-RU.json": () => import("./locales/ru-RU-BLG6HZG5.js"),
  "./locales/si-LK.json": () => import("./locales/si-LK-KT7GGO6D.js"),
  "./locales/sk-SK.json": () => import("./locales/sk-SK-DY6IPO5U.js"),
  "./locales/sl-SI.json": () => import("./locales/sl-SI-5DZSRA47.js"),
  "./locales/sv-SE.json": () => import("./locales/sv-SE-V32YHALQ.js"),
  "./locales/ta-IN.json": () => import("./locales/ta-IN-5JRAGQAO.js"),
  "./locales/th-TH.json": () => import("./locales/th-TH-55ACRHDJ.js"),
  "./locales/tr-TR.json": () => import("./locales/tr-TR-7QYBXDBO.js"),
  "./locales/uk-UA.json": () => import("./locales/uk-UA-TJS2TMRH.js"),
  "./locales/vi-VN.json": () => import("./locales/vi-VN-Y5CQ2EKQ.js"),
  "./locales/zh-CN.json": () => import("./locales/zh-CN-4MXUOFTH.js"),
  "./locales/zh-HK.json": () => import("./locales/zh-HK-RBTGIU3U.js"),
  "./locales/zh-TW.json": () => import("./locales/zh-TW-U5VF4CCU.js")
});

// i18n.ts
var COMPLETION_THRESHOLD = 85;
var defaultLang = { code: "en", label: "English" };
var languages = [
  defaultLang,
  ...[
    { code: "ar-SA", label: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", rtl: true },
    { code: "bg-BG", label: "\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438" },
    { code: "ca-ES", label: "Catal\xE0" },
    { code: "cs-CZ", label: "\u010Cesky" },
    { code: "de-DE", label: "Deutsch" },
    { code: "el-GR", label: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC" },
    { code: "es-ES", label: "Espa\xF1ol" },
    { code: "eu-ES", label: "Euskara" },
    { code: "fa-IR", label: "\u0641\u0627\u0631\u0633\u06CC", rtl: true },
    { code: "fi-FI", label: "Suomi" },
    { code: "fr-FR", label: "Fran\xE7ais" },
    { code: "gl-ES", label: "Galego" },
    { code: "he-IL", label: "\u05E2\u05D1\u05E8\u05D9\u05EA", rtl: true },
    { code: "hi-IN", label: "\u0939\u093F\u0928\u094D\u0926\u0940" },
    { code: "hu-HU", label: "Magyar" },
    { code: "id-ID", label: "Bahasa Indonesia" },
    { code: "it-IT", label: "Italiano" },
    { code: "ja-JP", label: "\u65E5\u672C\u8A9E" },
    { code: "kab-KAB", label: "Taqbaylit" },
    { code: "kk-KZ", label: "\u049A\u0430\u0437\u0430\u049B \u0442\u0456\u043B\u0456" },
    { code: "ko-KR", label: "\uD55C\uAD6D\uC5B4" },
    { code: "ku-TR", label: "Kurd\xEE" },
    { code: "lt-LT", label: "Lietuvi\u0173" },
    { code: "lv-LV", label: "Latvie\u0161u" },
    { code: "my-MM", label: "Burmese" },
    { code: "nb-NO", label: "Norsk bokm\xE5l" },
    { code: "nl-NL", label: "Nederlands" },
    { code: "nn-NO", label: "Norsk nynorsk" },
    { code: "oc-FR", label: "Occitan" },
    { code: "pa-IN", label: "\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40" },
    { code: "pl-PL", label: "Polski" },
    { code: "pt-BR", label: "Portugu\xEAs Brasileiro" },
    { code: "pt-PT", label: "Portugu\xEAs" },
    { code: "ro-RO", label: "Rom\xE2n\u0103" },
    { code: "ru-RU", label: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" },
    { code: "sk-SK", label: "Sloven\u010Dina" },
    { code: "sv-SE", label: "Svenska" },
    { code: "sl-SI", label: "Sloven\u0161\u010Dina" },
    { code: "tr-TR", label: "T\xFCrk\xE7e" },
    { code: "uk-UA", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" },
    { code: "zh-CN", label: "\u7B80\u4F53\u4E2D\u6587" },
    { code: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587" },
    { code: "vi-VN", label: "Ti\u1EBFng Vi\u1EC7t" },
    { code: "mr-IN", label: "\u092E\u0930\u093E\u0920\u0940" }
  ].filter(
    (lang) => percentages_default[lang.code] >= COMPLETION_THRESHOLD
  ).sort((left, right) => left.label > right.label ? 1 : -1)
];
var TEST_LANG_CODE = "__test__";
if (define_import_meta_env_default.DEV) {
  languages.unshift(
    { code: TEST_LANG_CODE, label: "test language" },
    {
      code: `${TEST_LANG_CODE}.rtl`,
      label: "\u202Atest language (rtl)\u202C",
      rtl: true
    }
  );
}
var currentLang = defaultLang;
var currentLangData = {};
var setLanguage = async (lang) => {
  currentLang = lang;
  document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
  document.documentElement.lang = currentLang.code;
  if (lang.code.startsWith(TEST_LANG_CODE)) {
    currentLangData = {};
  } else {
    try {
      currentLangData = await globImport_locales_json(`./locales/${currentLang.code}.json`);
    } catch (error) {
      console.error(`Failed to load language ${lang.code}:`, error.message);
      currentLangData = en_default;
    }
  }
  editorJotaiStore.set(editorLangCodeAtom, lang.code);
};
var getLanguage = () => currentLang;
var findPartsForData = (data, parts) => {
  for (let index = 0; index < parts.length; ++index) {
    const part = parts[index];
    if (data[part] === void 0) {
      return void 0;
    }
    data = data[part];
  }
  if (typeof data !== "string") {
    return void 0;
  }
  return data;
};
var t = (path, replacement, fallback) => {
  if (currentLang.code.startsWith(TEST_LANG_CODE)) {
    const name = replacement ? `${path}(${JSON.stringify(replacement).slice(1, -1)})` : path;
    return `\u202A[[${name}]]\u202C`;
  }
  const parts = path.split(".");
  let translation = findPartsForData(currentLangData, parts) || findPartsForData(en_default, parts) || fallback;
  if (translation === void 0) {
    const errorMessage = `Can't find translation for ${path}`;
    if (define_import_meta_env_default.PROD) {
      console.warn(errorMessage);
      return "";
    }
    throw new Error(errorMessage);
  }
  if (replacement) {
    for (const key in replacement) {
      translation = translation.replace(`{{${key}}}`, String(replacement[key]));
    }
  }
  return translation;
};
var editorLangCodeAtom = atom(defaultLang.code);
var useI18n = () => {
  const langCode = useAtomValue(editorLangCodeAtom);
  return { t, langCode };
};

// components/LoadingMessage.tsx
import { useState, useEffect } from "react";

// components/Spinner.tsx
import React from "react";
import { jsx } from "react/jsx-runtime";
var Spinner = ({
  size = "1em",
  circleWidth = 8,
  synchronized = false,
  className = ""
}) => {
  const mountTime = React.useRef(Date.now());
  const mountDelay = -(mountTime.current % 1600);
  return /* @__PURE__ */ jsx("div", { className: `Spinner ${className}`, children: /* @__PURE__ */ jsx(
    "svg",
    {
      viewBox: "0 0 100 100",
      style: {
        width: size,
        height: size,
        // fix for remounting causing spinner flicker
        ["--spinner-delay"]: synchronized ? `${mountDelay}ms` : 0
      },
      children: /* @__PURE__ */ jsx(
        "circle",
        {
          cx: "50",
          cy: "50",
          r: 50 - circleWidth / 2,
          strokeWidth: circleWidth,
          fill: "none",
          strokeMiterlimit: "10"
        }
      )
    }
  ) });
};
var Spinner_default = Spinner;

// components/LoadingMessage.tsx
import clsx from "clsx";
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
var LoadingMessage = ({
  delay,
  theme
}) => {
  const [isWaiting, setIsWaiting] = useState(!!delay);
  useEffect(() => {
    if (!delay) {
      return;
    }
    const timer = setTimeout(() => {
      setIsWaiting(false);
    }, delay);
    return () => clearTimeout(timer);
  }, [delay]);
  if (isWaiting) {
    return null;
  }
  return /* @__PURE__ */ jsxs(
    "div",
    {
      className: clsx("LoadingMessage", {
        "LoadingMessage--dark": theme === THEME.DARK
      }),
      children: [
        /* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsx2(Spinner_default, {}) }),
        /* @__PURE__ */ jsx2("div", { className: "LoadingMessage-text", children: t("labels.loadingScene") })
      ]
    }
  );
};

// components/InitializeApp.tsx
import { jsx as jsx3 } from "react/jsx-runtime";
var InitializeApp = (props) => {
  const [loading, setLoading] = useState2(true);
  useEffect2(() => {
    const updateLang = async () => {
      await setLanguage(currentLang2);
      setLoading(false);
    };
    const currentLang2 = languages.find((lang) => lang.code === props.langCode) || defaultLang;
    updateLang();
  }, [props.langCode]);
  return loading ? /* @__PURE__ */ jsx3(LoadingMessage, { theme: props.theme }) : props.children;
};

// components/App.tsx
import React43, { useContext as useContext3 } from "react";
import { flushSync as flushSync2 } from "react-dom";
import rough from "roughjs/bin/rough";
import clsx55 from "clsx";
import { nanoid } from "nanoid";

// components/ToolButton.tsx
import React3, { useEffect as useEffect3, useRef, useState as useState3 } from "react";
import clsx2 from "clsx";
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
var ToolButton = React3.forwardRef(
  ({
    size = "medium",
    visible = true,
    className = "",
    ...props
  }, ref) => {
    const { id: excalId } = useExcalidrawContainer();
    const innerRef = React3.useRef(null);
    React3.useImperativeHandle(ref, () => innerRef.current);
    const sizeCn = `ToolIcon_size_${size}`;
    const [isLoading, setIsLoading] = useState3(false);
    const isMountedRef = useRef(true);
    const onClick = async (event) => {
      const ret = "onClick" in props && props.onClick?.(event);
      if (isPromiseLike(ret)) {
        try {
          setIsLoading(true);
          await ret;
        } catch (error) {
          if (!(error instanceof AbortError)) {
            throw error;
          } else {
            console.warn(error);
          }
        } finally {
          if (isMountedRef.current) {
            setIsLoading(false);
          }
        }
      }
    };
    useEffect3(() => {
      isMountedRef.current = true;
      return () => {
        isMountedRef.current = false;
      };
    }, []);
    const lastPointerTypeRef = useRef(null);
    if (props.type === "button" || props.type === "icon" || props.type === "submit") {
      const type = props.type === "icon" ? "button" : props.type;
      return /* @__PURE__ */ jsxs2(
        "button",
        {
          className: clsx2(
            "ToolIcon_type_button",
            sizeCn,
            className,
            visible && !props.hidden ? "ToolIcon_type_button--show" : "ToolIcon_type_button--hide",
            {
              ToolIcon: !props.hidden,
              "ToolIcon--selected": props.selected,
              "ToolIcon--plain": props.type === "icon"
            }
          ),
          style: props.style,
          "data-testid": props["data-testid"],
          hidden: props.hidden,
          title: props.title,
          "aria-label": props["aria-label"],
          type,
          onClick,
          ref: innerRef,
          disabled: isLoading || props.isLoading || !!props.disabled,
          children: [
            (props.icon || props.label) && /* @__PURE__ */ jsxs2(
              "div",
              {
                className: "ToolIcon__icon",
                "aria-hidden": "true",
                "aria-disabled": !!props.disabled,
                children: [
                  props.icon || props.label,
                  props.keyBindingLabel && /* @__PURE__ */ jsx4("span", { className: "ToolIcon__keybinding", children: props.keyBindingLabel }),
                  props.isLoading && /* @__PURE__ */ jsx4(Spinner_default, {})
                ]
              }
            ),
            props.showAriaLabel && /* @__PURE__ */ jsxs2("div", { className: "ToolIcon__label", children: [
              props["aria-label"],
              " ",
              isLoading && /* @__PURE__ */ jsx4(Spinner_default, {})
            ] }),
            props.children
          ]
        }
      );
    }
    return /* @__PURE__ */ jsxs2(
      "label",
      {
        className: clsx2("ToolIcon", className),
        title: props.title,
        onPointerDown: (event) => {
          lastPointerTypeRef.current = event.pointerType || null;
          props.onPointerDown?.({ pointerType: event.pointerType || null });
        },
        onPointerUp: () => {
          requestAnimationFrame(() => {
            lastPointerTypeRef.current = null;
          });
        },
        children: [
          /* @__PURE__ */ jsx4(
            "input",
            {
              className: `ToolIcon_type_radio ${sizeCn}`,
              type: "radio",
              name: props.name,
              "aria-label": props["aria-label"],
              "aria-keyshortcuts": props["aria-keyshortcuts"],
              "data-testid": props["data-testid"],
              id: `${excalId}-${props.id}`,
              onChange: () => {
                props.onChange?.({ pointerType: lastPointerTypeRef.current });
              },
              checked: props.checked,
              ref: innerRef
            }
          ),
          /* @__PURE__ */ jsxs2("div", { className: "ToolIcon__icon", children: [
            props.icon,
            props.keyBindingLabel && /* @__PURE__ */ jsx4("span", { className: "ToolIcon__keybinding", children: props.keyBindingLabel })
          ] })
        ]
      }
    );
  }
);
ToolButton.displayName = "ToolButton";

// actions/actionDeleteSelected.tsx
import { jsx as jsx5 } from "react/jsx-runtime";
var deleteSelectedElements = (elements, appState, app) => {
  const framesToBeDeleted = new Set(
    getSelectedElements(
      elements.filter((el) => isFrameLikeElement(el)),
      appState
    ).map((el) => el.id)
  );
  const selectedElementIds = {};
  const elementsMap = app.scene.getNonDeletedElementsMap();
  const processedElements = /* @__PURE__ */ new Set();
  for (const frameId of framesToBeDeleted) {
    const frameChildren = getFrameChildren(elements, frameId);
    for (const el of frameChildren) {
      if (processedElements.has(el.id)) {
        continue;
      }
      if (isBoundToContainer(el)) {
        const containerElement = getContainerElement(el, elementsMap);
        if (containerElement) {
          selectedElementIds[containerElement.id] = true;
        }
      } else {
        selectedElementIds[el.id] = true;
      }
      processedElements.add(el.id);
    }
  }
  let shouldSelectEditingGroup = true;
  const nextElements = elements.map((el) => {
    if (appState.selectedElementIds[el.id]) {
      const boundElement = isBoundToContainer(el) ? getContainerElement(el, elementsMap) : null;
      if (el.frameId && framesToBeDeleted.has(el.frameId)) {
        shouldSelectEditingGroup = false;
        selectedElementIds[el.id] = true;
        return el;
      }
      if (boundElement?.frameId && framesToBeDeleted.has(boundElement?.frameId)) {
        return el;
      }
      if (el.boundElements) {
        el.boundElements.forEach((candidate) => {
          const bound = app.scene.getNonDeletedElementsMap().get(candidate.id);
          if (bound && isElbowArrow(bound)) {
            mutateElement(bound, {
              startBinding: el.id === bound.startBinding?.elementId ? null : bound.startBinding,
              endBinding: el.id === bound.endBinding?.elementId ? null : bound.endBinding
            });
            mutateElement(bound, { points: bound.points });
          }
        });
      }
      return newElementWith(el, { isDeleted: true });
    }
    if (el.frameId && framesToBeDeleted.has(el.frameId)) {
      shouldSelectEditingGroup = false;
      if (!isBoundToContainer(el)) {
        selectedElementIds[el.id] = true;
      }
      return newElementWith(el, { frameId: null });
    }
    if (isBoundToContainer(el) && appState.selectedElementIds[el.containerId]) {
      return newElementWith(el, { isDeleted: true });
    }
    return el;
  });
  let nextEditingGroupId = appState.editingGroupId;
  if (shouldSelectEditingGroup && appState.editingGroupId) {
    const elems = getElementsInGroup(
      nextElements,
      appState.editingGroupId
    ).filter((el) => !el.isDeleted);
    if (elems.length > 1) {
      if (elems[0]) {
        selectedElementIds[elems[0].id] = true;
      }
    } else {
      nextEditingGroupId = null;
      if (elems[0]) {
        selectedElementIds[elems[0].id] = true;
      }
      const lastElementInGroup = elems[0];
      if (lastElementInGroup) {
        const editingGroupIdx = lastElementInGroup.groupIds.findIndex(
          (groupId) => {
            return groupId === appState.editingGroupId;
          }
        );
        const superGroupId = lastElementInGroup.groupIds[editingGroupIdx + 1];
        if (superGroupId) {
          const elems2 = getElementsInGroup(nextElements, superGroupId).filter(
            (el) => !el.isDeleted
          );
          if (elems2.length > 1) {
            nextEditingGroupId = superGroupId;
            elems2.forEach((el) => {
              selectedElementIds[el.id] = true;
            });
          }
        }
      }
    }
  }
  return {
    elements: nextElements,
    appState: {
      ...appState,
      ...selectGroupsForSelectedElements(
        {
          selectedElementIds,
          editingGroupId: nextEditingGroupId
        },
        nextElements,
        appState,
        null
      )
    }
  };
};
var handleGroupEditingState = (appState, elements) => {
  if (appState.editingGroupId) {
    const siblingElements = getElementsInGroup(
      getNonDeletedElements(elements),
      appState.editingGroupId
    );
    if (siblingElements.length) {
      return {
        ...appState,
        selectedElementIds: { [siblingElements[0].id]: true }
      };
    }
  }
  return appState;
};
var actionDeleteSelected = register({
  name: "deleteSelectedElements",
  label: "labels.delete",
  icon: TrashIcon,
  trackEvent: { category: "element", action: "delete" },
  perform: (elements, appState, formData, app) => {
    if (appState.editingLinearElement) {
      const {
        elementId,
        selectedPointsIndices,
        startBindingElement,
        endBindingElement
      } = appState.editingLinearElement;
      const elementsMap = app.scene.getNonDeletedElementsMap();
      const element = LinearElementEditor.getElement(elementId, elementsMap);
      if (!element) {
        return false;
      }
      if (selectedPointsIndices == null) {
        return false;
      }
      if (element.points.length < 2) {
        const nextElements2 = elements.map((el) => {
          if (el.id === element.id) {
            return newElementWith(el, { isDeleted: true });
          }
          return el;
        });
        const nextAppState2 = handleGroupEditingState(appState, nextElements2);
        return {
          elements: nextElements2,
          appState: {
            ...nextAppState2,
            editingLinearElement: null
          },
          captureUpdate: CaptureUpdateAction.IMMEDIATELY
        };
      }
      const binding = {
        startBindingElement: selectedPointsIndices?.includes(0) ? null : startBindingElement,
        endBindingElement: selectedPointsIndices?.includes(
          element.points.length - 1
        ) ? null : endBindingElement
      };
      LinearElementEditor.deletePoints(element, selectedPointsIndices);
      return {
        elements,
        appState: {
          ...appState,
          editingLinearElement: {
            ...appState.editingLinearElement,
            ...binding,
            selectedPointsIndices: selectedPointsIndices?.[0] > 0 ? [selectedPointsIndices[0] - 1] : [0]
          }
        },
        captureUpdate: CaptureUpdateAction.IMMEDIATELY
      };
    }
    let { elements: nextElements, appState: nextAppState } = deleteSelectedElements(elements, appState, app);
    fixBindingsAfterDeletion(
      nextElements,
      nextElements.filter((el) => el.isDeleted)
    );
    nextAppState = handleGroupEditingState(nextAppState, nextElements);
    return {
      elements: nextElements,
      appState: {
        ...nextAppState,
        activeTool: updateActiveTool(appState, { type: "selection" }),
        multiElement: null,
        activeEmbeddable: null
      },
      captureUpdate: isSomeElementSelected(
        getNonDeletedElements(elements),
        appState
      ) ? CaptureUpdateAction.IMMEDIATELY : CaptureUpdateAction.EVENTUALLY
    };
  },
  keyTest: (event, appState, elements) => (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) && !event[KEYS.CTRL_OR_CMD],
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsx5(
    ToolButton,
    {
      type: "button",
      icon: TrashIcon,
      title: t("labels.delete"),
      "aria-label": t("labels.delete"),
      onClick: () => updateData(null),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});

// zindex.ts
var isOfTargetFrame = (element, frameId) => {
  return element.frameId === frameId || element.id === frameId;
};
var getIndicesToMove = (elements, appState, elementsToBeMoved) => {
  let selectedIndices = [];
  let deletedIndices = [];
  let includeDeletedIndex = null;
  let index = -1;
  const selectedElementIds = arrayToMap(
    elementsToBeMoved ? elementsToBeMoved : getSelectedElements(elements, appState, {
      includeBoundTextElement: true,
      includeElementsInFrames: true
    })
  );
  while (++index < elements.length) {
    const element = elements[index];
    if (selectedElementIds.get(element.id)) {
      if (deletedIndices.length) {
        selectedIndices = selectedIndices.concat(deletedIndices);
        deletedIndices = [];
      }
      selectedIndices.push(index);
      includeDeletedIndex = index + 1;
    } else if (element.isDeleted && includeDeletedIndex === index) {
      includeDeletedIndex = index + 1;
      deletedIndices.push(index);
    } else {
      deletedIndices = [];
    }
  }
  return selectedIndices;
};
var toContiguousGroups = (array) => {
  let cursor = 0;
  return array.reduce((acc, value, index) => {
    if (index > 0 && array[index - 1] !== value - 1) {
      cursor = ++cursor;
    }
    (acc[cursor] || (acc[cursor] = [])).push(value);
    return acc;
  }, []);
};
var getTargetIndexAccountingForBinding = (nextElement, elements, direction) => {
  if ("containerId" in nextElement && nextElement.containerId) {
    const containerElement = Scene_default.getScene(nextElement).getElement(
      nextElement.containerId
    );
    if (containerElement) {
      return direction === "left" ? Math.min(
        elements.indexOf(containerElement),
        elements.indexOf(nextElement)
      ) : Math.max(
        elements.indexOf(containerElement),
        elements.indexOf(nextElement)
      );
    }
  } else {
    const boundElementId = nextElement.boundElements?.find(
      (binding) => binding.type !== "arrow"
    )?.id;
    if (boundElementId) {
      const boundTextElement = Scene_default.getScene(nextElement).getElement(boundElementId);
      if (boundTextElement) {
        return direction === "left" ? Math.min(
          elements.indexOf(boundTextElement),
          elements.indexOf(nextElement)
        ) : Math.max(
          elements.indexOf(boundTextElement),
          elements.indexOf(nextElement)
        );
      }
    }
  }
};
var getContiguousFrameRangeElements = (allElements, frameId) => {
  let rangeStart = -1;
  let rangeEnd = -1;
  allElements.forEach((element, index) => {
    if (isOfTargetFrame(element, frameId)) {
      if (rangeStart === -1) {
        rangeStart = index;
      }
      rangeEnd = index;
    }
  });
  if (rangeStart === -1) {
    return [];
  }
  return allElements.slice(rangeStart, rangeEnd + 1);
};
var getTargetIndex = (appState, elements, boundaryIndex, direction, containingFrame) => {
  const sourceElement = elements[boundaryIndex];
  const indexFilter = (element) => {
    if (element.isDeleted) {
      return false;
    }
    if (containingFrame) {
      return element.frameId === containingFrame;
    }
    if (appState.editingGroupId) {
      return element.groupIds.includes(appState.editingGroupId);
    }
    return true;
  };
  const candidateIndex = direction === "left" ? findLastIndex(
    elements,
    (el) => indexFilter(el),
    Math.max(0, boundaryIndex - 1)
  ) : findIndex(elements, (el) => indexFilter(el), boundaryIndex + 1);
  const nextElement = elements[candidateIndex];
  if (!nextElement) {
    return -1;
  }
  if (appState.editingGroupId) {
    if (
      // candidate element is a sibling in current editing group → return
      sourceElement?.groupIds.join("") === nextElement?.groupIds.join("")
    ) {
      return getTargetIndexAccountingForBinding(nextElement, elements, direction) ?? candidateIndex;
    } else if (!nextElement?.groupIds.includes(appState.editingGroupId)) {
      return -1;
    }
  }
  if (!containingFrame && (nextElement.frameId || isFrameLikeElement(nextElement))) {
    const frameElements = getContiguousFrameRangeElements(
      elements,
      nextElement.frameId || nextElement.id
    );
    return direction === "left" ? elements.indexOf(frameElements[0]) : elements.indexOf(frameElements[frameElements.length - 1]);
  }
  if (!nextElement.groupIds.length) {
    return getTargetIndexAccountingForBinding(nextElement, elements, direction) ?? candidateIndex;
  }
  const siblingGroupId = appState.editingGroupId ? nextElement.groupIds[nextElement.groupIds.indexOf(appState.editingGroupId) - 1] : nextElement.groupIds[nextElement.groupIds.length - 1];
  const elementsInSiblingGroup = getElementsInGroup(elements, siblingGroupId);
  if (elementsInSiblingGroup.length) {
    return direction === "left" ? elements.indexOf(elementsInSiblingGroup[0]) : elements.indexOf(
      elementsInSiblingGroup[elementsInSiblingGroup.length - 1]
    );
  }
  return candidateIndex;
};
var getTargetElementsMap = (elements, indices) => {
  return indices.reduce((acc, index) => {
    const element = elements[index];
    acc.set(element.id, element);
    return acc;
  }, /* @__PURE__ */ new Map());
};
var shiftElementsByOne = (elements, appState, direction) => {
  const indicesToMove = getIndicesToMove(elements, appState);
  const targetElementsMap = getTargetElementsMap(elements, indicesToMove);
  let groupedIndices = toContiguousGroups(indicesToMove);
  if (direction === "right") {
    groupedIndices = groupedIndices.reverse();
  }
  const selectedFrames = new Set(
    indicesToMove.filter((idx) => isFrameLikeElement(elements[idx])).map((idx) => elements[idx].id)
  );
  groupedIndices.forEach((indices, i) => {
    const leadingIndex = indices[0];
    const trailingIndex = indices[indices.length - 1];
    const boundaryIndex = direction === "left" ? leadingIndex : trailingIndex;
    const containingFrame = indices.some((idx) => {
      const el = elements[idx];
      return el.frameId && selectedFrames.has(el.frameId);
    }) ? null : elements[boundaryIndex]?.frameId;
    const targetIndex = getTargetIndex(
      appState,
      elements,
      boundaryIndex,
      direction,
      containingFrame
    );
    if (targetIndex === -1 || boundaryIndex === targetIndex) {
      return;
    }
    const leadingElements = direction === "left" ? elements.slice(0, targetIndex) : elements.slice(0, leadingIndex);
    const targetElements = elements.slice(leadingIndex, trailingIndex + 1);
    const displacedElements = direction === "left" ? elements.slice(targetIndex, leadingIndex) : elements.slice(trailingIndex + 1, targetIndex + 1);
    const trailingElements = direction === "left" ? elements.slice(trailingIndex + 1) : elements.slice(targetIndex + 1);
    elements = direction === "left" ? [
      ...leadingElements,
      ...targetElements,
      ...displacedElements,
      ...trailingElements
    ] : [
      ...leadingElements,
      ...displacedElements,
      ...targetElements,
      ...trailingElements
    ];
  });
  syncMovedIndices(elements, targetElementsMap);
  return elements;
};
var shiftElementsToEnd = (elements, appState, direction, containingFrame, elementsToBeMoved) => {
  const indicesToMove = getIndicesToMove(elements, appState, elementsToBeMoved);
  const targetElementsMap = getTargetElementsMap(elements, indicesToMove);
  const displacedElements = [];
  let leadingIndex;
  let trailingIndex;
  if (direction === "left") {
    if (containingFrame) {
      leadingIndex = findIndex(
        elements,
        (el) => isOfTargetFrame(el, containingFrame)
      );
    } else if (appState.editingGroupId) {
      const groupElements = getElementsInGroup(
        elements,
        appState.editingGroupId
      );
      if (!groupElements.length) {
        return elements;
      }
      leadingIndex = elements.indexOf(groupElements[0]);
    } else {
      leadingIndex = 0;
    }
    trailingIndex = indicesToMove[indicesToMove.length - 1];
  } else {
    if (containingFrame) {
      trailingIndex = findLastIndex(
        elements,
        (el) => isOfTargetFrame(el, containingFrame)
      );
    } else if (appState.editingGroupId) {
      const groupElements = getElementsInGroup(
        elements,
        appState.editingGroupId
      );
      if (!groupElements.length) {
        return elements;
      }
      trailingIndex = elements.indexOf(groupElements[groupElements.length - 1]);
    } else {
      trailingIndex = elements.length - 1;
    }
    leadingIndex = indicesToMove[0];
  }
  if (leadingIndex === -1) {
    leadingIndex = 0;
  }
  for (let index = leadingIndex; index < trailingIndex + 1; index++) {
    if (!indicesToMove.includes(index)) {
      displacedElements.push(elements[index]);
    }
  }
  const targetElements = Array.from(targetElementsMap.values());
  const leadingElements = elements.slice(0, leadingIndex);
  const trailingElements = elements.slice(trailingIndex + 1);
  const nextElements = direction === "left" ? [
    ...leadingElements,
    ...targetElements,
    ...displacedElements,
    ...trailingElements
  ] : [
    ...leadingElements,
    ...displacedElements,
    ...targetElements,
    ...trailingElements
  ];
  syncMovedIndices(nextElements, targetElementsMap);
  return nextElements;
};
function shiftElementsAccountingForFrames(allElements, appState, direction, shiftFunction) {
  const elementsToMove = arrayToMap(
    getSelectedElements(allElements, appState, {
      includeBoundTextElement: true,
      includeElementsInFrames: true
    })
  );
  const frameAwareContiguousElementsToMove = { regularElements: [], frameChildren: /* @__PURE__ */ new Map() };
  const fullySelectedFrames = /* @__PURE__ */ new Set();
  for (const element of allElements) {
    if (elementsToMove.has(element.id) && isFrameLikeElement(element)) {
      fullySelectedFrames.add(element.id);
    }
  }
  for (const element of allElements) {
    if (elementsToMove.has(element.id)) {
      if (isFrameLikeElement(element) || element.frameId && fullySelectedFrames.has(element.frameId)) {
        frameAwareContiguousElementsToMove.regularElements.push(element);
      } else if (!element.frameId) {
        frameAwareContiguousElementsToMove.regularElements.push(element);
      } else {
        const frameChildren = frameAwareContiguousElementsToMove.frameChildren.get(
          element.frameId
        ) || [];
        frameChildren.push(element);
        frameAwareContiguousElementsToMove.frameChildren.set(
          element.frameId,
          frameChildren
        );
      }
    }
  }
  let nextElements = allElements;
  const frameChildrenSets = Array.from(
    frameAwareContiguousElementsToMove.frameChildren.entries()
  );
  for (const [frameId, children] of frameChildrenSets) {
    nextElements = shiftFunction(
      allElements,
      appState,
      direction,
      frameId,
      children
    );
  }
  return shiftFunction(
    nextElements,
    appState,
    direction,
    null,
    frameAwareContiguousElementsToMove.regularElements
  );
}
var moveOneLeft = (allElements, appState) => {
  return shiftElementsByOne(allElements, appState, "left");
};
var moveOneRight = (allElements, appState) => {
  return shiftElementsByOne(allElements, appState, "right");
};
var moveAllLeft = (allElements, appState) => {
  return shiftElementsAccountingForFrames(
    allElements,
    appState,
    "left",
    shiftElementsToEnd
  );
};
var moveAllRight = (allElements, appState) => {
  return shiftElementsAccountingForFrames(
    allElements,
    appState,
    "right",
    shiftElementsToEnd
  );
};

// actions/actionZindex.tsx
import { jsx as jsx6 } from "react/jsx-runtime";
var actionSendBackward = register({
  name: "sendBackward",
  label: "labels.sendBackward",
  keywords: ["move down", "zindex", "layer"],
  icon: SendBackwardIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState) => {
    return {
      elements: moveOneLeft(elements, appState),
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyPriority: 40,
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && !event.shiftKey && event.code === CODES.BRACKET_LEFT,
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx6(
    "button",
    {
      type: "button",
      className: "zIndexButton",
      onClick: () => updateData(null),
      title: `${t("labels.sendBackward")} \u2014 ${getShortcutKey("CtrlOrCmd+[")}`,
      children: SendBackwardIcon
    }
  )
});
var actionBringForward = register({
  name: "bringForward",
  label: "labels.bringForward",
  keywords: ["move up", "zindex", "layer"],
  icon: BringForwardIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState) => {
    return {
      elements: moveOneRight(elements, appState),
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyPriority: 40,
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && !event.shiftKey && event.code === CODES.BRACKET_RIGHT,
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx6(
    "button",
    {
      type: "button",
      className: "zIndexButton",
      onClick: () => updateData(null),
      title: `${t("labels.bringForward")} \u2014 ${getShortcutKey("CtrlOrCmd+]")}`,
      children: BringForwardIcon
    }
  )
});
var actionSendToBack = register({
  name: "sendToBack",
  label: "labels.sendToBack",
  keywords: ["move down", "zindex", "layer"],
  icon: SendToBackIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState) => {
    return {
      elements: moveAllLeft(elements, appState),
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => isDarwin ? event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.BRACKET_LEFT : event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.code === CODES.BRACKET_LEFT,
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx6(
    "button",
    {
      type: "button",
      className: "zIndexButton",
      onClick: () => updateData(null),
      title: `${t("labels.sendToBack")} \u2014 ${isDarwin ? getShortcutKey("CtrlOrCmd+Alt+[") : getShortcutKey("CtrlOrCmd+Shift+[")}`,
      children: SendToBackIcon
    }
  )
});
var actionBringToFront = register({
  name: "bringToFront",
  label: "labels.bringToFront",
  keywords: ["move up", "zindex", "layer"],
  icon: BringToFrontIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState) => {
    return {
      elements: moveAllRight(elements, appState),
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => isDarwin ? event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.BRACKET_RIGHT : event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.code === CODES.BRACKET_RIGHT,
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx6(
    "button",
    {
      type: "button",
      className: "zIndexButton",
      onClick: (event) => updateData(null),
      title: `${t("labels.bringToFront")} \u2014 ${isDarwin ? getShortcutKey("CtrlOrCmd+Alt+]") : getShortcutKey("CtrlOrCmd+Shift+]")}`,
      children: BringToFrontIcon
    }
  )
});

// actions/actionSelectAll.ts
var actionSelectAll = register({
  name: "selectAll",
  label: "labels.selectAll",
  icon: selectAllIcon,
  trackEvent: { category: "canvas" },
  viewMode: false,
  perform: (elements, appState, value, app) => {
    if (appState.editingLinearElement) {
      return false;
    }
    const selectedElementIds = elements.filter(
      (element) => !element.isDeleted && !(isTextElement(element) && element.containerId) && !element.locked
    ).reduce((map, element) => {
      map[element.id] = true;
      return map;
    }, {});
    return {
      appState: {
        ...appState,
        ...selectGroupsForSelectedElements(
          {
            editingGroupId: null,
            selectedElementIds
          },
          getNonDeletedElements(elements),
          appState,
          app
        ),
        selectedLinearElement: (
          // single linear element selected
          Object.keys(selectedElementIds).length === 1 && isLinearElement(elements[0]) ? new LinearElementEditor(elements[0]) : null
        )
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.A
});

// element/sortElements.ts
var normalizeGroupElementOrder = (elements) => {
  const origElements = elements.slice();
  const sortedElements = /* @__PURE__ */ new Set();
  const orderInnerGroups = (elements2) => {
    const firstGroupSig = elements2[0]?.groupIds?.join("");
    const aGroup = [elements2[0]];
    const bGroup = [];
    for (const element of elements2.slice(1)) {
      if (element.groupIds?.join("") === firstGroupSig) {
        aGroup.push(element);
      } else {
        bGroup.push(element);
      }
    }
    return bGroup.length ? [...aGroup, ...orderInnerGroups(bGroup)] : aGroup;
  };
  const groupHandledElements = /* @__PURE__ */ new Map();
  origElements.forEach((element, idx) => {
    if (groupHandledElements.has(element.id)) {
      return;
    }
    if (element.groupIds?.length) {
      const topGroup = element.groupIds[element.groupIds.length - 1];
      const groupElements = origElements.slice(idx).filter((element2) => {
        const ret = element2?.groupIds?.some((id) => id === topGroup);
        if (ret) {
          groupHandledElements.set(element2.id, true);
        }
        return ret;
      });
      for (const elem of orderInnerGroups(groupElements)) {
        sortedElements.add(elem);
      }
    } else {
      sortedElements.add(element);
    }
  });
  if (sortedElements.size !== elements.length) {
    console.error("normalizeGroupElementOrder: lost some elements... bailing!");
    return elements;
  }
  return [...sortedElements];
};
var normalizeBoundElementsOrder = (elements) => {
  const elementsMap = arrayToMapWithIndex(elements);
  const origElements = elements.slice();
  const sortedElements = /* @__PURE__ */ new Set();
  origElements.forEach((element, idx) => {
    if (!element) {
      return;
    }
    if (element.boundElements?.length) {
      sortedElements.add(element);
      origElements[idx] = null;
      element.boundElements.forEach((boundElement) => {
        const child = elementsMap.get(boundElement.id);
        if (child && boundElement.type === "text") {
          sortedElements.add(child[0]);
          origElements[child[1]] = null;
        }
      });
    } else if (element.type === "text" && element.containerId) {
      const parent = elementsMap.get(element.containerId);
      if (!parent?.[0].boundElements?.find((x) => x.id === element.id)) {
        sortedElements.add(element);
        origElements[idx] = null;
      }
    } else {
      sortedElements.add(element);
      origElements[idx] = null;
    }
  });
  if (sortedElements.size !== elements.length) {
    console.error(
      "normalizeBoundElementsOrder: lost some elements... bailing!"
    );
    return elements;
  }
  return [...sortedElements];
};
var normalizeElementOrder = (elements) => {
  return normalizeBoundElementsOrder(normalizeGroupElementOrder(elements));
};

// actions/actionDuplicateSelection.tsx
import { jsx as jsx7 } from "react/jsx-runtime";
var actionDuplicateSelection = register({
  name: "duplicateSelection",
  label: "labels.duplicateSelection",
  icon: DuplicateIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState, formData, app) => {
    if (appState.editingLinearElement) {
      try {
        const newAppState = LinearElementEditor.duplicateSelectedPoints(
          appState,
          app.scene.getNonDeletedElementsMap()
        );
        return {
          elements,
          appState: newAppState,
          captureUpdate: CaptureUpdateAction.IMMEDIATELY
        };
      } catch {
        return false;
      }
    }
    const nextState = duplicateElements2(elements, appState);
    if (app.props.onDuplicate && nextState.elements) {
      const mappedElements = app.props.onDuplicate(
        nextState.elements,
        elements
      );
      if (mappedElements) {
        nextState.elements = mappedElements;
      }
    }
    return {
      ...nextState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.D,
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsx7(
    ToolButton,
    {
      type: "button",
      icon: DuplicateIcon,
      title: `${t("labels.duplicateSelection")} \u2014 ${getShortcutKey(
        "CtrlOrCmd+D"
      )}`,
      "aria-label": t("labels.duplicateSelection"),
      onClick: () => updateData(null),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var duplicateElements2 = (elements, appState) => {
  const groupIdMap = /* @__PURE__ */ new Map();
  const newElements = [];
  const oldElements = [];
  const oldIdToDuplicatedId = /* @__PURE__ */ new Map();
  const duplicatedElementsMap = /* @__PURE__ */ new Map();
  const elementsMap = arrayToMap(elements);
  const duplicateAndOffsetElement = (element) => {
    const elements2 = castArray(element);
    const _newElements = elements2.reduce(
      (acc, element2) => {
        if (processedIds.has(element2.id)) {
          return acc;
        }
        processedIds.set(element2.id, true);
        const newElement2 = duplicateElement(
          appState.editingGroupId,
          groupIdMap,
          element2,
          {
            x: element2.x + DEFAULT_GRID_SIZE / 2,
            y: element2.y + DEFAULT_GRID_SIZE / 2
          }
        );
        processedIds.set(newElement2.id, true);
        duplicatedElementsMap.set(newElement2.id, newElement2);
        oldIdToDuplicatedId.set(element2.id, newElement2.id);
        oldElements.push(element2);
        newElements.push(newElement2);
        acc.push(newElement2);
        return acc;
      },
      []
    );
    return Array.isArray(element) ? _newElements : _newElements[0] || null;
  };
  elements = normalizeElementOrder(elements);
  const idsOfElementsToDuplicate = arrayToMap(
    getSelectedElements(elements, appState, {
      includeBoundTextElement: true,
      includeElementsInFrames: true
    })
  );
  const processedIds = /* @__PURE__ */ new Map();
  const elementsWithClones = elements.slice();
  const insertAfterIndex = (index, elements2) => {
    invariant(index !== -1, "targetIndex === -1 ");
    if (!Array.isArray(elements2) && !elements2) {
      return;
    }
    elementsWithClones.splice(index + 1, 0, ...castArray(elements2));
  };
  const frameIdsToDuplicate = new Set(
    elements.filter(
      (el) => idsOfElementsToDuplicate.has(el.id) && isFrameLikeElement(el)
    ).map((el) => el.id)
  );
  for (const element of elements) {
    if (processedIds.has(element.id)) {
      continue;
    }
    if (!idsOfElementsToDuplicate.has(element.id)) {
      continue;
    }
    const groupId = getSelectedGroupForElement(appState, element);
    if (groupId) {
      const groupElements = getElementsInGroup(elements, groupId).flatMap(
        (element2) => isFrameLikeElement(element2) ? [...getFrameChildren(elements, element2.id), element2] : [element2]
      );
      const targetIndex = findLastIndex(elementsWithClones, (el) => {
        return el.groupIds?.includes(groupId);
      });
      insertAfterIndex(targetIndex, duplicateAndOffsetElement(groupElements));
      continue;
    }
    if (element.frameId && frameIdsToDuplicate.has(element.frameId)) {
      continue;
    }
    if (isFrameLikeElement(element)) {
      const frameId = element.id;
      const frameChildren = getFrameChildren(elements, frameId);
      const targetIndex = findLastIndex(elementsWithClones, (el) => {
        return el.frameId === frameId || el.id === frameId;
      });
      insertAfterIndex(
        targetIndex,
        duplicateAndOffsetElement([...frameChildren, element])
      );
      continue;
    }
    if (hasBoundTextElement(element)) {
      const boundTextElement = getBoundTextElement(element, elementsMap);
      const targetIndex = findLastIndex(elementsWithClones, (el) => {
        return el.id === element.id || "containerId" in el && el.containerId === element.id;
      });
      if (boundTextElement) {
        insertAfterIndex(
          targetIndex,
          duplicateAndOffsetElement([element, boundTextElement])
        );
      } else {
        insertAfterIndex(targetIndex, duplicateAndOffsetElement(element));
      }
      continue;
    }
    if (isBoundToContainer(element)) {
      const container = getContainerElement(element, elementsMap);
      const targetIndex = findLastIndex(elementsWithClones, (el) => {
        return el.id === element.id || el.id === container?.id;
      });
      if (container) {
        insertAfterIndex(
          targetIndex,
          duplicateAndOffsetElement([container, element])
        );
      } else {
        insertAfterIndex(targetIndex, duplicateAndOffsetElement(element));
      }
      continue;
    }
    insertAfterIndex(
      findLastIndex(elementsWithClones, (el) => el.id === element.id),
      duplicateAndOffsetElement(element)
    );
  }
  bindTextToShapeAfterDuplication(
    elementsWithClones,
    oldElements,
    oldIdToDuplicatedId
  );
  fixBindingsAfterDuplication(
    elementsWithClones,
    oldElements,
    oldIdToDuplicatedId
  );
  bindElementsToFramesAfterDuplication(
    elementsWithClones,
    oldElements,
    oldIdToDuplicatedId
  );
  const nextElementsToSelect = excludeElementsInFramesFromSelection(newElements);
  return {
    elements: elementsWithClones,
    appState: {
      ...appState,
      ...selectGroupsForSelectedElements(
        {
          editingGroupId: appState.editingGroupId,
          selectedElementIds: nextElementsToSelect.reduce(
            (acc, element) => {
              if (!isBoundToContainer(element)) {
                acc[element.id] = true;
              }
              return acc;
            },
            {}
          )
        },
        getNonDeletedElements(elementsWithClones),
        appState,
        null
      )
    }
  };
};

// actions/actionProperties.tsx
import { useEffect as useEffect15, useMemo as useMemo4, useRef as useRef11, useState as useState8 } from "react";

// analytics.ts
var ALLOWED_CATEGORIES_TO_TRACK = /* @__PURE__ */ new Set(["command_palette", "export"]);
var trackEvent = (category, action, label, value) => {
  try {
    if (typeof window === "undefined" || define_import_meta_env_default.VITE_WORKER_ID || define_import_meta_env_default.VITE_APP_ENABLE_TRACKING !== "true") {
      return;
    }
    if (!ALLOWED_CATEGORIES_TO_TRACK.has(category)) {
      return;
    }
    if (define_import_meta_env_default.DEV) {
      return;
    }
    if (!define_import_meta_env_default.PROD) {
      console.info("trackEvent", { category, action, label, value });
    }
    if (window.sa_event) {
      window.sa_event(action, {
        category,
        label,
        value
      });
    }
  } catch (error) {
    console.error("error during analytics", error);
  }
};

// components/ButtonIconSelect.tsx
import clsx4 from "clsx";

// components/ButtonIcon.tsx
import { forwardRef } from "react";
import clsx3 from "clsx";
import { jsx as jsx8 } from "react/jsx-runtime";
var ButtonIcon = forwardRef(
  (props, ref) => {
    const { title, className, testId, active, standalone, icon, onClick } = props;
    return /* @__PURE__ */ jsx8(
      "button",
      {
        type: "button",
        ref,
        title,
        "data-testid": testId,
        className: clsx3(className, { standalone, active }),
        onClick,
        children: icon
      },
      title
    );
  }
);

// components/ButtonIconSelect.tsx
import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
var ButtonIconSelect = (props) => /* @__PURE__ */ jsx9("div", { className: "buttonList", children: props.options.map(
  (option) => props.type === "button" ? /* @__PURE__ */ jsx9(
    ButtonIcon,
    {
      icon: option.icon,
      title: option.text,
      testId: option.testId,
      active: option.active ?? props.value === option.value,
      onClick: (event) => props.onClick(option.value, event)
    },
    option.text
  ) : /* @__PURE__ */ jsxs3(
    "label",
    {
      className: clsx4({ active: props.value === option.value }),
      title: option.text,
      children: [
        /* @__PURE__ */ jsx9(
          "input",
          {
            type: "radio",
            name: props.group,
            onChange: () => props.onChange(option.value),
            checked: props.value === option.value,
            "data-testid": option.testId
          }
        ),
        option.icon
      ]
    },
    option.text
  )
) });

// components/ColorPicker/TopPicks.tsx
import clsx5 from "clsx";
import { jsx as jsx10 } from "react/jsx-runtime";
var TopPicks = ({
  onChange,
  type,
  activeColor,
  topPicks
}) => {
  let colors;
  if (type === "elementStroke") {
    colors = DEFAULT_ELEMENT_STROKE_PICKS;
  }
  if (type === "elementBackground") {
    colors = DEFAULT_ELEMENT_BACKGROUND_PICKS;
  }
  if (type === "canvasBackground") {
    colors = DEFAULT_CANVAS_BACKGROUND_PICKS;
  }
  if (topPicks) {
    colors = topPicks;
  }
  if (!colors) {
    console.error("Invalid type for TopPicks");
    return null;
  }
  return /* @__PURE__ */ jsx10("div", { className: "color-picker__top-picks", children: colors.map((color) => /* @__PURE__ */ jsx10(
    "button",
    {
      className: clsx5("color-picker__button", {
        active: color === activeColor,
        "is-transparent": color === "transparent" || !color
      }),
      style: { "--swatch-color": color },
      type: "button",
      title: color,
      onClick: () => onChange(color),
      "data-testid": `color-top-pick-${color}`,
      children: /* @__PURE__ */ jsx10("div", { className: "color-picker__button-outline" })
    },
    color
  )) });
};

// components/ButtonSeparator.tsx
import { jsx as jsx11 } from "react/jsx-runtime";
var ButtonSeparator = () => /* @__PURE__ */ jsx11(
  "div",
  {
    style: {
      width: 1,
      height: "1rem",
      backgroundColor: "var(--default-border-color)",
      margin: "0 auto"
    }
  }
);

// components/ColorPicker/Picker.tsx
import React4, { useEffect as useEffect7, useState as useState4 } from "react";

// components/ColorPicker/ShadeList.tsx
import clsx6 from "clsx";
import { useEffect as useEffect4, useRef as useRef2 } from "react";

// components/ColorPicker/colorPickerUtils.ts
var getColorNameAndShadeFromColor = ({
  palette: palette2,
  color
}) => {
  for (const [colorName, colorVal] of Object.entries(palette2)) {
    if (Array.isArray(colorVal)) {
      const shade = colorVal.indexOf(color);
      if (shade > -1) {
        return { colorName, shade };
      }
    } else if (colorVal === color) {
      return { colorName, shade: null };
    }
  }
  return null;
};
var colorPickerHotkeyBindings = [
  ["q", "w", "e", "r", "t"],
  ["a", "s", "d", "f", "g"],
  ["z", "x", "c", "v", "b"]
].flat();
var isCustomColor = ({
  color,
  palette: palette2
}) => {
  const paletteValues = Object.values(palette2).flat();
  return !paletteValues.includes(color);
};
var getMostUsedCustomColors = (elements, type, palette2) => {
  const elementColorTypeMap = {
    elementBackground: "backgroundColor",
    elementStroke: "strokeColor"
  };
  const colors = elements.filter((element) => {
    if (element.isDeleted) {
      return false;
    }
    const color = element[elementColorTypeMap[type]];
    return isCustomColor({ color, palette: palette2 });
  });
  const colorCountMap = /* @__PURE__ */ new Map();
  colors.forEach((element) => {
    const color = element[elementColorTypeMap[type]];
    if (colorCountMap.has(color)) {
      colorCountMap.set(color, colorCountMap.get(color) + 1);
    } else {
      colorCountMap.set(color, 1);
    }
  });
  return [...colorCountMap.entries()].sort((a, b) => b[1] - a[1]).map((c) => c[0]).slice(0, MAX_CUSTOM_COLORS_USED_IN_CANVAS);
};
var activeColorPickerSectionAtom = atom(null);
var calculateContrast = (r, g, b) => {
  const yiq = (r * 299 + g * 587 + b * 114) / 1e3;
  return yiq >= 160 ? "black" : "white";
};
var getContrastYIQ = (bgHex, isCustomColor2) => {
  if (isCustomColor2) {
    const style = new Option().style;
    style.color = bgHex;
    if (style.color) {
      const rgb = style.color.replace(/^(rgb|rgba)\(/, "").replace(/\)$/, "").replace(/\s/g, "").split(",");
      const r2 = parseInt(rgb[0]);
      const g2 = parseInt(rgb[1]);
      const b2 = parseInt(rgb[2]);
      return calculateContrast(r2, g2, b2);
    }
  }
  if (bgHex === "transparent") {
    return "black";
  }
  const r = parseInt(bgHex.substring(1, 3), 16);
  const g = parseInt(bgHex.substring(3, 5), 16);
  const b = parseInt(bgHex.substring(5, 7), 16);
  return calculateContrast(r, g, b);
};

// components/ColorPicker/HotkeyLabel.tsx
import { jsxs as jsxs4 } from "react/jsx-runtime";
var HotkeyLabel = ({
  color,
  keyLabel,
  isCustomColor: isCustomColor2 = false,
  isShade = false
}) => {
  return /* @__PURE__ */ jsxs4(
    "div",
    {
      className: "color-picker__button__hotkey-label",
      style: {
        color: getContrastYIQ(color, isCustomColor2)
      },
      children: [
        isShade && "\u21E7",
        keyLabel
      ]
    }
  );
};
var HotkeyLabel_default = HotkeyLabel;

// components/ColorPicker/ShadeList.tsx
import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
var ShadeList = ({ hex, onChange, palette: palette2 }) => {
  const colorObj = getColorNameAndShadeFromColor({
    color: hex || "transparent",
    palette: palette2
  });
  const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
    activeColorPickerSectionAtom
  );
  const btnRef = useRef2(null);
  useEffect4(() => {
    if (btnRef.current && activeColorPickerSection === "shades") {
      btnRef.current.focus();
    }
  }, [colorObj, activeColorPickerSection]);
  if (colorObj) {
    const { colorName, shade } = colorObj;
    const shades = palette2[colorName];
    if (Array.isArray(shades)) {
      return /* @__PURE__ */ jsx12("div", { className: "color-picker-content--default shades", children: shades.map((color, i) => /* @__PURE__ */ jsxs5(
        "button",
        {
          ref: i === shade && activeColorPickerSection === "shades" ? btnRef : void 0,
          tabIndex: -1,
          type: "button",
          className: clsx6(
            "color-picker__button color-picker__button--large",
            { active: i === shade }
          ),
          "aria-label": "Shade",
          title: `${colorName} - ${i + 1}`,
          style: color ? { "--swatch-color": color } : void 0,
          onClick: () => {
            onChange(color);
            setActiveColorPickerSection("shades");
          },
          children: [
            /* @__PURE__ */ jsx12("div", { className: "color-picker__button-outline" }),
            /* @__PURE__ */ jsx12(HotkeyLabel_default, { color, keyLabel: i + 1, isShade: true })
          ]
        },
        i
      )) });
    }
  }
  return /* @__PURE__ */ jsxs5(
    "div",
    {
      className: "color-picker-content--default",
      style: { position: "relative" },
      tabIndex: -1,
      children: [
        /* @__PURE__ */ jsx12(
          "button",
          {
            type: "button",
            tabIndex: -1,
            className: "color-picker__button color-picker__button--large color-picker__button--no-focus-visible"
          }
        ),
        /* @__PURE__ */ jsx12(
          "div",
          {
            tabIndex: -1,
            style: {
              position: "absolute",
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              textAlign: "center",
              fontSize: "0.75rem"
            },
            children: t("colorPicker.noShades")
          }
        )
      ]
    }
  );
};

// components/ColorPicker/PickerColorList.tsx
import clsx7 from "clsx";
import { useEffect as useEffect5, useRef as useRef3 } from "react";
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
var PickerColorList = ({
  palette: palette2,
  color,
  onChange,
  label,
  activeShade
}) => {
  const colorObj = getColorNameAndShadeFromColor({
    color: color || "transparent",
    palette: palette2
  });
  const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
    activeColorPickerSectionAtom
  );
  const btnRef = useRef3(null);
  useEffect5(() => {
    if (btnRef.current && activeColorPickerSection === "baseColors") {
      btnRef.current.focus();
    }
  }, [colorObj?.colorName, activeColorPickerSection]);
  return /* @__PURE__ */ jsx13("div", { className: "color-picker-content--default", children: Object.entries(palette2).map(([key, value], index) => {
    const color2 = (Array.isArray(value) ? value[activeShade] : value) || "transparent";
    const keybinding = colorPickerHotkeyBindings[index];
    const label2 = t(
      `colors.${key.replace(/\d+/, "")}`,
      null,
      ""
    );
    return /* @__PURE__ */ jsxs6(
      "button",
      {
        ref: colorObj?.colorName === key ? btnRef : void 0,
        tabIndex: -1,
        type: "button",
        className: clsx7(
          "color-picker__button color-picker__button--large",
          {
            active: colorObj?.colorName === key,
            "is-transparent": color2 === "transparent" || !color2
          }
        ),
        onClick: () => {
          onChange(color2);
          setActiveColorPickerSection("baseColors");
        },
        title: `${label2}${color2.startsWith("#") ? ` ${color2}` : ""} \u2014 ${keybinding}`,
        "aria-label": `${label2} \u2014 ${keybinding}`,
        style: color2 ? { "--swatch-color": color2 } : void 0,
        "data-testid": `color-${key}`,
        children: [
          /* @__PURE__ */ jsx13("div", { className: "color-picker__button-outline" }),
          /* @__PURE__ */ jsx13(HotkeyLabel_default, { color: color2, keyLabel: keybinding })
        ]
      },
      key
    );
  }) });
};
var PickerColorList_default = PickerColorList;

// components/ColorPicker/CustomColorList.tsx
import clsx8 from "clsx";
import { useEffect as useEffect6, useRef as useRef4 } from "react";
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
var CustomColorList = ({
  colors,
  color,
  onChange,
  label
}) => {
  const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
    activeColorPickerSectionAtom
  );
  const btnRef = useRef4(null);
  useEffect6(() => {
    if (btnRef.current) {
      btnRef.current.focus();
    }
  }, [color, activeColorPickerSection]);
  return /* @__PURE__ */ jsx14("div", { className: "color-picker-content--default", children: colors.map((c, i) => {
    return /* @__PURE__ */ jsxs7(
      "button",
      {
        ref: color === c ? btnRef : void 0,
        tabIndex: -1,
        type: "button",
        className: clsx8(
          "color-picker__button color-picker__button--large",
          {
            active: color === c,
            "is-transparent": c === "transparent" || !c
          }
        ),
        onClick: () => {
          onChange(c);
          setActiveColorPickerSection("custom");
        },
        title: c,
        "aria-label": label,
        style: { "--swatch-color": c },
        children: [
          /* @__PURE__ */ jsx14("div", { className: "color-picker__button-outline" }),
          /* @__PURE__ */ jsx14(HotkeyLabel_default, { color: c, keyLabel: i + 1, isCustomColor: true })
        ]
      },
      i
    );
  }) });
};

// components/ColorPicker/keyboardNavHandlers.ts
var arrowHandler = (eventKey, currentIndex, length) => {
  const rows = Math.ceil(length / COLORS_PER_ROW);
  currentIndex = currentIndex ?? -1;
  switch (eventKey) {
    case "ArrowLeft": {
      const prevIndex = currentIndex - 1;
      return prevIndex < 0 ? length - 1 : prevIndex;
    }
    case "ArrowRight": {
      return (currentIndex + 1) % length;
    }
    case "ArrowDown": {
      const nextIndex = currentIndex + COLORS_PER_ROW;
      return nextIndex >= length ? currentIndex % COLORS_PER_ROW : nextIndex;
    }
    case "ArrowUp": {
      const prevIndex = currentIndex - COLORS_PER_ROW;
      const newIndex = prevIndex < 0 ? COLORS_PER_ROW * rows + prevIndex : prevIndex;
      return newIndex >= length ? void 0 : newIndex;
    }
  }
};
var hotkeyHandler = ({
  e,
  colorObj,
  onChange,
  palette: palette2,
  customColors,
  setActiveColorPickerSection,
  activeShade
}) => {
  if (colorObj?.shade != null) {
    if (["Digit1", "Digit2", "Digit3", "Digit4", "Digit5"].includes(e.code) && e.shiftKey) {
      const newShade = Number(e.code.slice(-1)) - 1;
      onChange(palette2[colorObj.colorName][newShade]);
      setActiveColorPickerSection("shades");
      return true;
    }
  }
  if (["1", "2", "3", "4", "5"].includes(e.key)) {
    const c = customColors[Number(e.key) - 1];
    if (c) {
      onChange(customColors[Number(e.key) - 1]);
      setActiveColorPickerSection("custom");
      return true;
    }
  }
  if (colorPickerHotkeyBindings.includes(e.key)) {
    const index = colorPickerHotkeyBindings.indexOf(e.key);
    const paletteKey = Object.keys(palette2)[index];
    const paletteValue = palette2[paletteKey];
    const r = Array.isArray(paletteValue) ? paletteValue[activeShade] : paletteValue;
    onChange(r);
    setActiveColorPickerSection("baseColors");
    return true;
  }
  return false;
};
var colorPickerKeyNavHandler = ({
  event,
  activeColorPickerSection,
  palette: palette2,
  color,
  onChange,
  customColors,
  setActiveColorPickerSection,
  updateData,
  activeShade,
  onEyeDropperToggle,
  onEscape
}) => {
  if (event[KEYS.CTRL_OR_CMD]) {
    return false;
  }
  if (event.key === KEYS.ESCAPE) {
    onEscape(event);
    return true;
  }
  if (event.key === KEYS.ALT) {
    onEyeDropperToggle(true);
    return true;
  }
  if (event.key === KEYS.I) {
    onEyeDropperToggle();
    return true;
  }
  const colorObj = getColorNameAndShadeFromColor({ color, palette: palette2 });
  if (event.key === KEYS.TAB) {
    const sectionsMap = {
      custom: !!customColors.length,
      baseColors: true,
      shades: colorObj?.shade != null,
      hex: true
    };
    const sections = Object.entries(sectionsMap).reduce((acc, [key, value]) => {
      if (value) {
        acc.push(key);
      }
      return acc;
    }, []);
    const activeSectionIndex = sections.indexOf(activeColorPickerSection);
    const indexOffset = event.shiftKey ? -1 : 1;
    const nextSectionIndex = activeSectionIndex + indexOffset > sections.length - 1 ? 0 : activeSectionIndex + indexOffset < 0 ? sections.length - 1 : activeSectionIndex + indexOffset;
    const nextSection = sections[nextSectionIndex];
    if (nextSection) {
      setActiveColorPickerSection(nextSection);
    }
    if (nextSection === "custom") {
      onChange(customColors[0]);
    } else if (nextSection === "baseColors") {
      const baseColorName = Object.entries(palette2).find(([name, shades]) => {
        if (Array.isArray(shades)) {
          return shades.includes(color);
        } else if (shades === color) {
          return name;
        }
        return null;
      });
      if (!baseColorName) {
        onChange(COLOR_PALETTE.black);
      }
    }
    event.preventDefault();
    event.stopPropagation();
    return true;
  }
  if (hotkeyHandler({
    e: event,
    colorObj,
    onChange,
    palette: palette2,
    customColors,
    setActiveColorPickerSection,
    activeShade
  })) {
    return true;
  }
  if (activeColorPickerSection === "shades") {
    if (colorObj) {
      const { shade } = colorObj;
      const newShade = arrowHandler(event.key, shade, COLORS_PER_ROW);
      if (newShade !== void 0) {
        onChange(palette2[colorObj.colorName][newShade]);
        return true;
      }
    }
  }
  if (activeColorPickerSection === "baseColors") {
    if (colorObj) {
      const { colorName } = colorObj;
      const colorNames = Object.keys(palette2);
      const indexOfColorName = colorNames.indexOf(colorName);
      const newColorIndex = arrowHandler(
        event.key,
        indexOfColorName,
        colorNames.length
      );
      if (newColorIndex !== void 0) {
        const newColorName = colorNames[newColorIndex];
        const newColorNameValue = palette2[newColorName];
        onChange(
          Array.isArray(newColorNameValue) ? newColorNameValue[activeShade] : newColorNameValue
        );
        return true;
      }
    }
  }
  if (activeColorPickerSection === "custom") {
    const indexOfColor = customColors.indexOf(color);
    const newColorIndex = arrowHandler(
      event.key,
      indexOfColor,
      customColors.length
    );
    if (newColorIndex !== void 0) {
      const newColor = customColors[newColorIndex];
      onChange(newColor);
      return true;
    }
  }
  return false;
};

// components/ColorPicker/PickerHeading.tsx
import { jsx as jsx15 } from "react/jsx-runtime";
var PickerHeading = ({ children }) => /* @__PURE__ */ jsx15("div", { className: "color-picker__heading", children });
var PickerHeading_default = PickerHeading;

// components/ColorPicker/Picker.tsx
import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
var Picker = ({
  color,
  onChange,
  label,
  type,
  elements,
  palette: palette2,
  updateData,
  children,
  onEyeDropperToggle,
  onEscape
}) => {
  const [customColors] = React4.useState(() => {
    if (type === "canvasBackground") {
      return [];
    }
    return getMostUsedCustomColors(elements, type, palette2);
  });
  const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
    activeColorPickerSectionAtom
  );
  const colorObj = getColorNameAndShadeFromColor({
    color,
    palette: palette2
  });
  useEffect7(() => {
    if (!activeColorPickerSection) {
      const isCustom = isCustomColor({ color, palette: palette2 });
      const isCustomButNotInList = isCustom && !customColors.includes(color);
      setActiveColorPickerSection(
        isCustomButNotInList ? "hex" : isCustom ? "custom" : colorObj?.shade != null ? "shades" : "baseColors"
      );
    }
  }, [
    activeColorPickerSection,
    color,
    palette2,
    setActiveColorPickerSection,
    colorObj,
    customColors
  ]);
  const [activeShade, setActiveShade] = useState4(
    colorObj?.shade ?? (type === "elementBackground" ? DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX : DEFAULT_ELEMENT_STROKE_COLOR_INDEX)
  );
  useEffect7(() => {
    if (colorObj?.shade != null) {
      setActiveShade(colorObj.shade);
    }
    const keyup = (event) => {
      if (event.key === KEYS.ALT) {
        onEyeDropperToggle(false);
      }
    };
    document.addEventListener("keyup" /* KEYUP */, keyup, { capture: true });
    return () => {
      document.removeEventListener("keyup" /* KEYUP */, keyup, { capture: true });
    };
  }, [colorObj, onEyeDropperToggle]);
  const pickerRef = React4.useRef(null);
  return /* @__PURE__ */ jsx16("div", { role: "dialog", "aria-modal": "true", "aria-label": t("labels.colorPicker"), children: /* @__PURE__ */ jsxs8(
    "div",
    {
      ref: pickerRef,
      onKeyDown: (event) => {
        const handled = colorPickerKeyNavHandler({
          event,
          activeColorPickerSection,
          palette: palette2,
          color,
          onChange,
          onEyeDropperToggle,
          customColors,
          setActiveColorPickerSection,
          updateData,
          activeShade,
          onEscape
        });
        if (handled) {
          event.preventDefault();
          event.stopPropagation();
        }
      },
      className: "color-picker-content properties-content",
      tabIndex: -1,
      children: [
        !!customColors.length && /* @__PURE__ */ jsxs8("div", { children: [
          /* @__PURE__ */ jsx16(PickerHeading_default, { children: t("colorPicker.mostUsedCustomColors") }),
          /* @__PURE__ */ jsx16(
            CustomColorList,
            {
              colors: customColors,
              color,
              label: t("colorPicker.mostUsedCustomColors"),
              onChange
            }
          )
        ] }),
        /* @__PURE__ */ jsxs8("div", { children: [
          /* @__PURE__ */ jsx16(PickerHeading_default, { children: t("colorPicker.colors") }),
          /* @__PURE__ */ jsx16(
            PickerColorList_default,
            {
              color,
              label,
              palette: palette2,
              onChange,
              activeShade
            }
          )
        ] }),
        /* @__PURE__ */ jsxs8("div", { children: [
          /* @__PURE__ */ jsx16(PickerHeading_default, { children: t("colorPicker.shades") }),
          /* @__PURE__ */ jsx16(ShadeList, { hex: color, onChange, palette: palette2 })
        ] }),
        children
      ]
    }
  ) });
};

// components/ColorPicker/ColorPicker.tsx
import * as Popover2 from "@radix-ui/react-popover";
import clsx12 from "clsx";
import { useRef as useRef8 } from "react";

// components/ColorPicker/ColorInput.tsx
import { useCallback, useEffect as useEffect10, useRef as useRef7, useState as useState6 } from "react";

// components/EyeDropper.tsx
import { useEffect as useEffect9, useRef as useRef6 } from "react";
import { createPortal } from "react-dom";

// context/ui-appState.ts
import React5 from "react";
var UIAppStateContext = React5.createContext(null);
var useUIAppState = () => React5.useContext(UIAppStateContext);

// hooks/useCreatePortalContainer.ts
import { useState as useState5, useLayoutEffect } from "react";
var useCreatePortalContainer = (opts) => {
  const [div, setDiv] = useState5(null);
  const device = useDevice();
  const { theme } = useUIAppState();
  const { container: excalidrawContainer } = useExcalidrawContainer();
  useLayoutEffect(() => {
    if (div) {
      div.className = "";
      div.classList.add("excalidraw", ...opts?.className?.split(/\s+/) || []);
      div.classList.toggle("excalidraw--mobile", device.editor.isMobile);
      div.classList.toggle("theme--dark", theme === THEME.DARK);
    }
  }, [div, theme, device.editor.isMobile, opts?.className]);
  useLayoutEffect(() => {
    const container = opts?.parentSelector ? excalidrawContainer?.querySelector(opts.parentSelector) : document.body;
    if (!container) {
      return;
    }
    const div2 = document.createElement("div");
    container.appendChild(div2);
    setDiv(div2);
    return () => {
      container.removeChild(div2);
    };
  }, [excalidrawContainer, opts?.parentSelector]);
  return div;
};

// hooks/useOutsideClick.ts
import { useEffect as useEffect8 } from "react";
function useOutsideClick(ref, callback, isInside) {
  useEffect8(() => {
    function onOutsideClick(event) {
      const _event = event;
      if (!ref.current) {
        return;
      }
      const isInsideOverride = isInside?.(_event, ref.current);
      if (isInsideOverride === true) {
        return;
      } else if (isInsideOverride === false) {
        return callback(_event);
      }
      if (ref.current.contains(_event.target) || // target is detached from DOM (happens when the element is removed
      // on a pointerup event fired *before* this handler's pointerup is
      // dispatched)
      !document.documentElement.contains(_event.target)) {
        return;
      }
      const isClickOnRadixPortal = _event.target.closest("[data-radix-portal]") || // when radix popup is in "modal" mode, it disables pointer events on
      // the `body` element, so the target element is going to be the `html`
      // (note: this won't work if we selectively re-enable pointer events on
      // specific elements as we do with navbar or excalidraw UI elements)
      _event.target === document.documentElement && document.body.style.pointerEvents === "none";
      if (isClickOnRadixPortal) {
        return;
      }
      if (_event.target.closest("[data-prevent-outside-click]")) {
        return;
      }
      callback(_event);
    }
    document.addEventListener("pointerdown" /* POINTER_DOWN */, onOutsideClick);
    document.addEventListener("touchstart" /* TOUCH_START */, onOutsideClick);
    return () => {
      document.removeEventListener("pointerdown" /* POINTER_DOWN */, onOutsideClick);
      document.removeEventListener("touchstart" /* TOUCH_START */, onOutsideClick);
    };
  }, [ref, callback, isInside]);
}

// hooks/useStable.ts
import { useRef as useRef5 } from "react";
var useStable = (value) => {
  const ref = useRef5(value);
  Object.assign(ref.current, value);
  return ref.current;
};

// components/EyeDropper.tsx
import { jsx as jsx17 } from "react/jsx-runtime";
var activeEyeDropperAtom = atom(null);
var EyeDropper = ({ onCancel, onChange, onSelect, colorPickerType }) => {
  const eyeDropperContainer = useCreatePortalContainer({
    className: "excalidraw-eye-dropper-backdrop",
    parentSelector: ".excalidraw-eye-dropper-container"
  });
  const appState = useUIAppState();
  const elements = useExcalidrawElements();
  const app = useApp();
  const selectedElements = getSelectedElements(elements, appState);
  const stableProps = useStable({
    app,
    onCancel,
    onChange,
    onSelect,
    selectedElements
  });
  const { container: excalidrawContainer } = useExcalidrawContainer();
  useEffect9(() => {
    const colorPreviewDiv = ref.current;
    if (!colorPreviewDiv || !app.canvas || !eyeDropperContainer) {
      return;
    }
    let isHoldingPointerDown = false;
    const ctx = app.canvas.getContext("2d");
    const getCurrentColor = ({
      clientX,
      clientY
    }) => {
      const pixel = ctx.getImageData(
        (clientX - appState.offsetLeft) * window.devicePixelRatio,
        (clientY - appState.offsetTop) * window.devicePixelRatio,
        1,
        1
      ).data;
      return rgbToHex(pixel[0], pixel[1], pixel[2]);
    };
    const mouseMoveListener = ({
      clientX,
      clientY,
      altKey
    }) => {
      colorPreviewDiv.style.top = `${clientY + 20}px`;
      colorPreviewDiv.style.left = `${clientX + 20}px`;
      const currentColor = getCurrentColor({ clientX, clientY });
      if (isHoldingPointerDown) {
        stableProps.onChange(
          colorPickerType,
          currentColor,
          stableProps.selectedElements,
          { altKey }
        );
      }
      colorPreviewDiv.style.background = currentColor;
    };
    const onCancel2 = () => {
      stableProps.onCancel();
    };
    const onSelect2 = (color, event) => {
      stableProps.onSelect(color, event);
    };
    const pointerDownListener = (event) => {
      isHoldingPointerDown = true;
      event.stopImmediatePropagation();
    };
    const pointerUpListener = (event) => {
      isHoldingPointerDown = false;
      excalidrawContainer?.focus();
      event.stopImmediatePropagation();
      event.preventDefault();
      onSelect2(getCurrentColor(event), event);
    };
    const keyDownListener = (event) => {
      if (event.key === KEYS.ESCAPE) {
        event.preventDefault();
        event.stopImmediatePropagation();
        onCancel2();
      }
    };
    eyeDropperContainer.tabIndex = -1;
    eyeDropperContainer.focus();
    mouseMoveListener({
      clientX: stableProps.app.lastViewportPosition.x,
      clientY: stableProps.app.lastViewportPosition.y,
      altKey: false
    });
    eyeDropperContainer.addEventListener("keydown" /* KEYDOWN */, keyDownListener);
    eyeDropperContainer.addEventListener(
      "pointerdown" /* POINTER_DOWN */,
      pointerDownListener
    );
    eyeDropperContainer.addEventListener("pointerup" /* POINTER_UP */, pointerUpListener);
    window.addEventListener("pointermove", mouseMoveListener, {
      passive: true
    });
    window.addEventListener("blur" /* BLUR */, onCancel2);
    return () => {
      isHoldingPointerDown = false;
      eyeDropperContainer.removeEventListener("keydown" /* KEYDOWN */, keyDownListener);
      eyeDropperContainer.removeEventListener(
        "pointerdown" /* POINTER_DOWN */,
        pointerDownListener
      );
      eyeDropperContainer.removeEventListener(
        "pointerup" /* POINTER_UP */,
        pointerUpListener
      );
      window.removeEventListener("pointermove", mouseMoveListener);
      window.removeEventListener("blur" /* BLUR */, onCancel2);
    };
  }, [
    stableProps,
    app.canvas,
    eyeDropperContainer,
    colorPickerType,
    excalidrawContainer,
    appState.offsetLeft,
    appState.offsetTop
  ]);
  const ref = useRef6(null);
  useOutsideClick(
    ref,
    () => {
      onCancel();
    },
    (event) => {
      if (event.target.closest(
        ".excalidraw-eye-dropper-trigger, .excalidraw-eye-dropper-backdrop"
      )) {
        return true;
      }
      return false;
    }
  );
  if (!eyeDropperContainer) {
    return null;
  }
  return createPortal(
    /* @__PURE__ */ jsx17("div", { ref, className: "excalidraw-eye-dropper-preview" }),
    eyeDropperContainer
  );
};

// components/ColorPicker/ColorInput.tsx
import clsx9 from "clsx";
import { Fragment, jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
var ColorInput = ({
  color,
  onChange,
  label,
  colorPickerType
}) => {
  const device = useDevice();
  const [innerValue, setInnerValue] = useState6(color);
  const [activeSection, setActiveColorPickerSection] = useAtom(
    activeColorPickerSectionAtom
  );
  useEffect10(() => {
    setInnerValue(color);
  }, [color]);
  const changeColor = useCallback(
    (inputValue) => {
      const value = inputValue.toLowerCase();
      const color2 = getColor(value);
      if (color2) {
        onChange(color2);
      }
      setInnerValue(value);
    },
    [onChange]
  );
  const inputRef = useRef7(null);
  const eyeDropperTriggerRef = useRef7(null);
  useEffect10(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, [activeSection]);
  const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
  useEffect10(() => {
    return () => {
      setEyeDropperState(null);
    };
  }, [setEyeDropperState]);
  return /* @__PURE__ */ jsxs9("div", { className: "color-picker__input-label", children: [
    /* @__PURE__ */ jsx18("div", { className: "color-picker__input-hash", children: "#" }),
    /* @__PURE__ */ jsx18(
      "input",
      {
        ref: activeSection === "hex" ? inputRef : void 0,
        style: { border: 0, padding: 0 },
        spellCheck: false,
        className: "color-picker-input",
        "aria-label": label,
        onChange: (event) => {
          changeColor(event.target.value);
        },
        value: (innerValue || "").replace(/^#/, ""),
        onBlur: () => {
          setInnerValue(color);
        },
        tabIndex: -1,
        onFocus: () => setActiveColorPickerSection("hex"),
        onKeyDown: (event) => {
          if (event.key === KEYS.TAB) {
            return;
          } else if (event.key === KEYS.ESCAPE) {
            eyeDropperTriggerRef.current?.focus();
          }
          event.stopPropagation();
        }
      }
    ),
    !device.editor.isMobile && /* @__PURE__ */ jsxs9(Fragment, { children: [
      /* @__PURE__ */ jsx18(
        "div",
        {
          style: {
            width: "1px",
            height: "1.25rem",
            backgroundColor: "var(--default-border-color)"
          }
        }
      ),
      /* @__PURE__ */ jsx18(
        "div",
        {
          ref: eyeDropperTriggerRef,
          className: clsx9("excalidraw-eye-dropper-trigger", {
            selected: eyeDropperState
          }),
          onClick: () => setEyeDropperState(
            (s) => s ? null : {
              keepOpenOnAlt: false,
              onSelect: (color2) => onChange(color2),
              colorPickerType
            }
          ),
          title: `${t(
            "labels.eyeDropper"
          )} \u2014 ${KEYS.I.toLocaleUpperCase()} or ${getShortcutKey("Alt")} `,
          children: eyeDropperIcon
        }
      )
    ] })
  ] });
};

// components/PropertiesPopover.tsx
import React7 from "react";
import clsx11 from "clsx";
import * as Popover from "@radix-ui/react-popover";

// components/Island.tsx
import React6 from "react";
import clsx10 from "clsx";
import { jsx as jsx19 } from "react/jsx-runtime";
var Island = React6.forwardRef(
  ({ children, padding, className, style }, ref) => /* @__PURE__ */ jsx19(
    "div",
    {
      className: clsx10("Island", className),
      style: { "--padding": padding, ...style },
      ref,
      children
    }
  )
);

// components/PropertiesPopover.tsx
import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
var PropertiesPopover = React7.forwardRef(
  ({
    className,
    container,
    children,
    style,
    onClose,
    onKeyDown,
    onFocusOutside,
    onPointerLeave,
    onPointerDownOutside
  }, ref) => {
    const device = useDevice();
    return /* @__PURE__ */ jsx20(Popover.Portal, { container, children: /* @__PURE__ */ jsxs10(
      Popover.Content,
      {
        ref,
        className: clsx11("focus-visible-none", className),
        "data-prevent-outside-click": true,
        side: device.editor.isMobile && !device.viewport.isLandscape ? "bottom" : "right",
        align: device.editor.isMobile && !device.viewport.isLandscape ? "center" : "start",
        alignOffset: -16,
        sideOffset: 20,
        style: {
          zIndex: "var(--zIndex-popup)"
        },
        onPointerLeave,
        onKeyDown,
        onFocusOutside,
        onPointerDownOutside,
        onCloseAutoFocus: (e) => {
          e.stopPropagation();
          e.preventDefault();
          if (container && !isInteractive(document.activeElement)) {
            container.focus();
          }
          onClose();
        },
        children: [
          /* @__PURE__ */ jsx20(Island, { padding: 3, style, children }),
          /* @__PURE__ */ jsx20(
            Popover.Arrow,
            {
              width: 20,
              height: 10,
              style: {
                fill: "var(--popup-bg-color)",
                filter: "drop-shadow(rgba(0, 0, 0, 0.05) 0px 3px 2px)"
              }
            }
          )
        ]
      }
    ) });
  }
);

// components/ColorPicker/ColorPicker.tsx
import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
var isValidColor = (color) => {
  const style = new Option().style;
  style.color = color;
  return !!style.color;
};
var getColor = (color) => {
  if (isTransparent(color)) {
    return color;
  }
  return isValidColor(`#${color}`) ? `#${color}` : isValidColor(color) ? color : null;
};
var ColorPickerPopupContent = ({
  type,
  color,
  onChange,
  label,
  elements,
  palette: palette2 = COLOR_PALETTE,
  updateData
}) => {
  const { container } = useExcalidrawContainer();
  const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom);
  const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
  const colorInputJSX = /* @__PURE__ */ jsxs11("div", { children: [
    /* @__PURE__ */ jsx21(PickerHeading_default, { children: t("colorPicker.hexCode") }),
    /* @__PURE__ */ jsx21(
      ColorInput,
      {
        color,
        label,
        onChange: (color2) => {
          onChange(color2);
        },
        colorPickerType: type
      }
    )
  ] });
  const popoverRef = useRef8(null);
  const focusPickerContent = () => {
    popoverRef.current?.querySelector(".color-picker-content")?.focus();
  };
  return /* @__PURE__ */ jsx21(
    PropertiesPopover,
    {
      container,
      style: { maxWidth: "13rem" },
      onFocusOutside: (event) => {
        focusPickerContent();
        event.preventDefault();
      },
      onPointerDownOutside: (event) => {
        if (eyeDropperState) {
          event.preventDefault();
        }
      },
      onClose: () => {
        updateData({ openPopup: null });
        setActiveColorPickerSection(null);
      },
      children: palette2 ? /* @__PURE__ */ jsx21(
        Picker,
        {
          palette: palette2,
          color,
          onChange: (changedColor) => {
            onChange(changedColor);
          },
          onEyeDropperToggle: (force) => {
            setEyeDropperState((state) => {
              if (force) {
                state = state || {
                  keepOpenOnAlt: true,
                  onSelect: onChange,
                  colorPickerType: type
                };
                state.keepOpenOnAlt = true;
                return state;
              }
              return force === false || state ? null : {
                keepOpenOnAlt: false,
                onSelect: onChange,
                colorPickerType: type
              };
            });
          },
          onEscape: (event) => {
            if (eyeDropperState) {
              setEyeDropperState(null);
            } else {
              updateData({ openPopup: null });
            }
          },
          label,
          type,
          elements,
          updateData,
          children: colorInputJSX
        }
      ) : colorInputJSX
    }
  );
};
var ColorPickerTrigger = ({
  label,
  color,
  type
}) => {
  return /* @__PURE__ */ jsx21(
    Popover2.Trigger,
    {
      type: "button",
      className: clsx12("color-picker__button active-color properties-trigger", {
        "is-transparent": color === "transparent" || !color
      }),
      "aria-label": label,
      style: color ? { "--swatch-color": color } : void 0,
      title: type === "elementStroke" ? t("labels.showStroke") : t("labels.showBackground"),
      children: /* @__PURE__ */ jsx21("div", { className: "color-picker__button-outline" })
    }
  );
};
var ColorPicker = ({
  type,
  color,
  onChange,
  label,
  elements,
  palette: palette2 = COLOR_PALETTE,
  topPicks,
  updateData,
  appState
}) => {
  return /* @__PURE__ */ jsx21("div", { children: /* @__PURE__ */ jsxs11("div", { role: "dialog", "aria-modal": "true", className: "color-picker-container", children: [
    /* @__PURE__ */ jsx21(
      TopPicks,
      {
        activeColor: color,
        onChange,
        type,
        topPicks
      }
    ),
    /* @__PURE__ */ jsx21(ButtonSeparator, {}),
    /* @__PURE__ */ jsxs11(
      Popover2.Root,
      {
        open: appState.openPopup === type,
        onOpenChange: (open) => {
          updateData({ openPopup: open ? type : null });
        },
        children: [
          /* @__PURE__ */ jsx21(ColorPickerTrigger, { color, label, type }),
          appState.openPopup === type && /* @__PURE__ */ jsx21(
            ColorPickerPopupContent,
            {
              type,
              color,
              onChange,
              label,
              elements,
              palette: palette2,
              updateData
            }
          )
        ]
      }
    )
  ] }) });
};

// components/IconPicker.tsx
import React8, { useEffect as useEffect11 } from "react";
import * as Popover3 from "@radix-ui/react-popover";
import clsx13 from "clsx";

// components/InlineIcon.tsx
import { jsx as jsx22 } from "react/jsx-runtime";
var InlineIcon = ({ icon }) => {
  return /* @__PURE__ */ jsx22(
    "span",
    {
      style: {
        width: "1em",
        margin: "0 0.5ex 0 0.5ex",
        display: "inline-block",
        lineHeight: 0,
        verticalAlign: "middle"
      },
      children: icon
    }
  );
};

// components/Stats/Collapsible.tsx
import { Fragment as Fragment2, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
var Collapsible = ({
  label,
  open,
  openTrigger,
  children,
  className
}) => {
  return /* @__PURE__ */ jsxs12(Fragment2, { children: [
    /* @__PURE__ */ jsxs12(
      "div",
      {
        style: {
          cursor: "pointer",
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center"
        },
        className,
        onClick: openTrigger,
        children: [
          label,
          /* @__PURE__ */ jsx23(InlineIcon, { icon: open ? collapseUpIcon : collapseDownIcon })
        ]
      }
    ),
    open && /* @__PURE__ */ jsx23("div", { style: { display: "flex", flexDirection: "column" }, children })
  ] });
};
var Collapsible_default = Collapsible;

// components/IconPicker.tsx
import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
var moreOptionsAtom = atom(false);
function Picker2({
  options,
  value,
  label,
  onChange,
  onClose,
  numberOfOptionsToAlwaysShow = options.length
}) {
  const device = useDevice();
  const handleKeyDown = (event) => {
    const pressedOption = options.find(
      (option) => option.keyBinding === event.key.toLowerCase()
    );
    if (!(event.metaKey || event.altKey || event.ctrlKey) && pressedOption) {
      onChange(pressedOption.value);
      event.preventDefault();
    } else if (event.key === KEYS.TAB) {
      const index = options.findIndex((option) => option.value === value);
      const nextIndex = event.shiftKey ? (options.length + index - 1) % options.length : (index + 1) % options.length;
      onChange(options[nextIndex].value);
    } else if (isArrowKey(event.key)) {
      const isRTL = getLanguage().rtl;
      const index = options.findIndex((option) => option.value === value);
      if (index !== -1) {
        const length = options.length;
        let nextIndex = index;
        switch (event.key) {
          case (isRTL ? KEYS.ARROW_LEFT : KEYS.ARROW_RIGHT):
            nextIndex = (index + 1) % length;
            break;
          case (isRTL ? KEYS.ARROW_RIGHT : KEYS.ARROW_LEFT):
            nextIndex = (length + index - 1) % length;
            break;
          case KEYS.ARROW_DOWN: {
            nextIndex = (index + (numberOfOptionsToAlwaysShow ?? 1)) % length;
            break;
          }
          case KEYS.ARROW_UP: {
            nextIndex = (length + index - (numberOfOptionsToAlwaysShow ?? 1)) % length;
            break;
          }
        }
        onChange(options[nextIndex].value);
      }
      event.preventDefault();
    } else if (event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) {
      event.preventDefault();
      onClose();
    }
    event.nativeEvent.stopImmediatePropagation();
    event.stopPropagation();
  };
  const [showMoreOptions, setShowMoreOptions] = useAtom(moreOptionsAtom);
  const alwaysVisibleOptions = React8.useMemo(
    () => options.slice(0, numberOfOptionsToAlwaysShow),
    [options, numberOfOptionsToAlwaysShow]
  );
  const moreOptions = React8.useMemo(
    () => options.slice(numberOfOptionsToAlwaysShow),
    [options, numberOfOptionsToAlwaysShow]
  );
  useEffect11(() => {
    if (!alwaysVisibleOptions.some((option) => option.value === value)) {
      setShowMoreOptions(true);
    }
  }, [value, alwaysVisibleOptions, setShowMoreOptions]);
  const renderOptions = (options2) => {
    return /* @__PURE__ */ jsx24("div", { className: "picker-content", children: options2.map((option, i) => /* @__PURE__ */ jsxs13(
      "button",
      {
        type: "button",
        className: clsx13("picker-option", {
          active: value === option.value
        }),
        onClick: (event) => {
          onChange(option.value);
        },
        title: `${option.text} ${option.keyBinding && `\u2014 ${option.keyBinding.toUpperCase()}`}`,
        "aria-label": option.text || "none",
        "aria-keyshortcuts": option.keyBinding || void 0,
        ref: (ref) => {
          if (value === option.value) {
            setTimeout(() => {
              ref?.focus();
            }, 0);
          }
        },
        children: [
          option.icon,
          option.keyBinding && /* @__PURE__ */ jsx24("span", { className: "picker-keybinding", children: option.keyBinding })
        ]
      },
      option.text
    )) });
  };
  return /* @__PURE__ */ jsx24(
    Popover3.Content,
    {
      side: device.editor.isMobile && !device.viewport.isLandscape ? "top" : "bottom",
      align: "start",
      sideOffset: 12,
      style: { zIndex: "var(--zIndex-popup)" },
      onKeyDown: handleKeyDown,
      children: /* @__PURE__ */ jsxs13(
        "div",
        {
          className: `picker`,
          role: "dialog",
          "aria-modal": "true",
          "aria-label": label,
          children: [
            renderOptions(alwaysVisibleOptions),
            moreOptions.length > 0 && /* @__PURE__ */ jsx24(
              Collapsible_default,
              {
                label: t("labels.more_options"),
                open: showMoreOptions,
                openTrigger: () => {
                  setShowMoreOptions((value2) => !value2);
                },
                className: "picker-collapsible",
                children: renderOptions(moreOptions)
              }
            )
          ]
        }
      )
    }
  );
}
function IconPicker({
  value,
  label,
  options,
  onChange,
  group = "",
  numberOfOptionsToAlwaysShow
}) {
  const [isActive, setActive] = React8.useState(false);
  const rPickerButton = React8.useRef(null);
  return /* @__PURE__ */ jsx24("div", { children: /* @__PURE__ */ jsxs13(Popover3.Root, { open: isActive, onOpenChange: (open) => setActive(open), children: [
    /* @__PURE__ */ jsx24(
      Popover3.Trigger,
      {
        name: group,
        type: "button",
        "aria-label": label,
        onClick: () => setActive(!isActive),
        ref: rPickerButton,
        className: isActive ? "active" : "",
        children: options.find((option) => option.value === value)?.icon
      }
    ),
    isActive && /* @__PURE__ */ jsx24(
      Picker2,
      {
        options,
        value,
        label,
        onChange,
        onClose: () => {
          setActive(false);
        },
        numberOfOptionsToAlwaysShow
      }
    )
  ] }) });
}

// components/FontPicker/FontPicker.tsx
import React13, { useCallback as useCallback3, useMemo as useMemo3 } from "react";
import * as Popover5 from "@radix-ui/react-popover";

// components/FontPicker/FontPickerList.tsx
import React12, {
  useMemo,
  useState as useState7,
  useRef as useRef10,
  useEffect as useEffect13,
  useCallback as useCallback2
} from "react";

// components/QuickSearch.tsx
import clsx14 from "clsx";
import React9 from "react";
import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
var QuickSearch = React9.forwardRef(
  ({ className, placeholder, onChange }, ref) => {
    return /* @__PURE__ */ jsxs14("div", { className: clsx14("QuickSearch__wrapper", className), children: [
      searchIcon,
      /* @__PURE__ */ jsx25(
        "input",
        {
          ref,
          className: "QuickSearch__input",
          type: "text",
          placeholder,
          onChange: (e) => onChange(e.target.value.trim().toLowerCase())
        }
      )
    ] });
  }
);

// components/ScrollableList.tsx
import clsx15 from "clsx";
import { Children } from "react";
import { jsx as jsx26 } from "react/jsx-runtime";
var ScrollableList = ({
  className,
  placeholder,
  children
}) => {
  const isEmpty = !Children.count(children);
  return /* @__PURE__ */ jsx26("div", { className: clsx15("ScrollableList__wrapper", className), role: "menu", children: isEmpty ? /* @__PURE__ */ jsx26("div", { className: "empty", children: placeholder }) : children });
};

// components/dropdownMenu/DropdownMenuGroup.tsx
import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
var MenuGroup = ({
  children,
  className = "",
  style,
  title
}) => {
  return /* @__PURE__ */ jsxs15("div", { className: `dropdown-menu-group ${className}`, style, children: [
    title && /* @__PURE__ */ jsx27("p", { className: "dropdown-menu-group-title", children: title }),
    children
  ] });
};
var DropdownMenuGroup_default = MenuGroup;
MenuGroup.displayName = "DropdownMenuGroup";

// components/dropdownMenu/DropdownMenuItem.tsx
import { useEffect as useEffect12, useRef as useRef9 } from "react";

// components/dropdownMenu/common.ts
import React10, { useContext } from "react";
var DropdownMenuContentPropsContext = React10.createContext({});
var getDropdownMenuItemClassName = (className = "", selected = false, hovered = false) => {
  return `dropdown-menu-item dropdown-menu-item-base ${className}
  ${selected ? "dropdown-menu-item--selected" : ""} ${hovered ? "dropdown-menu-item--hovered" : ""}`.trim();
};
var useHandleDropdownMenuItemClick = (origOnClick, onSelect) => {
  const DropdownMenuContentProps = useContext(DropdownMenuContentPropsContext);
  return composeEventHandlers(origOnClick, (event) => {
    const itemSelectEvent = new CustomEvent("menu.itemSelect" /* MENU_ITEM_SELECT */, {
      bubbles: true,
      cancelable: true
    });
    onSelect?.(itemSelectEvent);
    if (!itemSelectEvent.defaultPrevented) {
      DropdownMenuContentProps.onSelect?.(itemSelectEvent);
    }
  });
};

// components/dropdownMenu/DropdownMenuItemContent.tsx
import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
var MenuItemContent = ({
  textStyle,
  icon,
  shortcut,
  children
}) => {
  const device = useDevice();
  return /* @__PURE__ */ jsxs16(Fragment3, { children: [
    icon && /* @__PURE__ */ jsx28("div", { className: "dropdown-menu-item__icon", children: icon }),
    /* @__PURE__ */ jsx28("div", { style: textStyle, className: "dropdown-menu-item__text", children }),
    shortcut && !device.editor.isMobile && /* @__PURE__ */ jsx28("div", { className: "dropdown-menu-item__shortcut", children: shortcut })
  ] });
};
var DropdownMenuItemContent_default = MenuItemContent;

// components/dropdownMenu/DropdownMenuItem.tsx
import { jsx as jsx29 } from "react/jsx-runtime";
var DropdownMenuItem = ({
  icon,
  value,
  order,
  children,
  shortcut,
  className,
  hovered,
  selected,
  textStyle,
  onSelect,
  onClick,
  ...rest
}) => {
  const handleClick = useHandleDropdownMenuItemClick(onClick, onSelect);
  const ref = useRef9(null);
  useEffect12(() => {
    if (hovered) {
      if (order === 0) {
        ref.current?.scrollIntoView({ block: "end" });
      } else {
        ref.current?.scrollIntoView({ block: "nearest" });
      }
    }
  }, [hovered, order]);
  return /* @__PURE__ */ jsx29(
    "button",
    {
      ...rest,
      ref,
      value,
      onClick: handleClick,
      className: getDropdownMenuItemClassName(className, selected, hovered),
      title: rest.title ?? rest["aria-label"],
      children: /* @__PURE__ */ jsx29(DropdownMenuItemContent_default, { textStyle, icon, shortcut, children })
    }
  );
};
DropdownMenuItem.displayName = "DropdownMenuItem";
var DropDownMenuItemBadgeType = {
  GREEN: "green",
  RED: "red",
  BLUE: "blue"
};
var DropDownMenuItemBadge = ({
  type = DropDownMenuItemBadgeType.BLUE,
  children
}) => {
  const { theme } = useExcalidrawAppState();
  const style = {
    display: "inline-flex",
    marginLeft: "auto",
    padding: "2px 4px",
    borderRadius: 6,
    fontSize: 9,
    fontFamily: "Cascadia, monospace",
    border: theme === THEME.LIGHT ? "1.5px solid white" : "none"
  };
  switch (type) {
    case DropDownMenuItemBadgeType.GREEN:
      Object.assign(style, {
        backgroundColor: "var(--background-color-badge)",
        color: "var(--color-badge)"
      });
      break;
    case DropDownMenuItemBadgeType.RED:
      Object.assign(style, {
        backgroundColor: "pink",
        color: "darkred"
      });
      break;
    case DropDownMenuItemBadgeType.BLUE:
    default:
      Object.assign(style, {
        background: "var(--color-promo)",
        color: "var(--color-surface-lowest)"
      });
  }
  return /* @__PURE__ */ jsx29("div", { className: "DropDownMenuItemBadge", style, children });
};
DropDownMenuItemBadge.displayName = "DropdownMenuItemBadge";
DropdownMenuItem.Badge = DropDownMenuItemBadge;
var DropdownMenuItem_default = DropdownMenuItem;

// components/FontPicker/keyboardNavHandlers.ts
var fontPickerKeyHandler = ({
  event,
  inputRef,
  hoveredFont,
  filteredFonts,
  onClose,
  onSelect,
  onHover
}) => {
  if (!event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key.toLowerCase() === KEYS.F) {
    inputRef.current?.focus();
    return true;
  }
  if (event.key === KEYS.ESCAPE) {
    onClose();
    return true;
  }
  if (event.key === KEYS.ENTER) {
    if (hoveredFont?.value) {
      onSelect(hoveredFont.value);
    }
    return true;
  }
  if (event.key === KEYS.ARROW_DOWN) {
    if (hoveredFont?.next) {
      onHover(hoveredFont.next.value);
    } else if (filteredFonts[0]?.value) {
      onHover(filteredFonts[0].value);
    }
    return true;
  }
  if (event.key === KEYS.ARROW_UP) {
    if (hoveredFont?.prev) {
      onHover(hoveredFont.prev.value);
    } else if (filteredFonts[filteredFonts.length - 1]?.value) {
      onHover(filteredFonts[filteredFonts.length - 1].value);
    }
    return true;
  }
};

// components/FontPicker/FontPickerList.tsx
import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
var FontPickerList = React12.memo(
  ({
    selectedFontFamily,
    hoveredFontFamily,
    onSelect,
    onHover,
    onLeave,
    onOpen,
    onClose
  }) => {
    const { container } = useExcalidrawContainer();
    const { fonts } = useApp();
    const { showDeprecatedFonts } = useAppProps();
    const [searchTerm, setSearchTerm] = useState7("");
    const inputRef = useRef10(null);
    const allFonts = useMemo(
      () => Array.from(Fonts.registered.entries()).filter(
        ([_, { metadata }]) => !metadata.serverSide && !metadata.fallback
      ).map(([familyId, { metadata, fontFaces }]) => {
        const fontDescriptor = {
          value: familyId,
          icon: metadata.icon ?? FontFamilyNormalIcon,
          text: fontFaces[0]?.fontFace?.family ?? "Unknown"
        };
        if (metadata.deprecated) {
          Object.assign(fontDescriptor, {
            deprecated: metadata.deprecated,
            badge: {
              type: DropDownMenuItemBadgeType.RED,
              placeholder: t("fontList.badge.old")
            }
          });
        }
        return fontDescriptor;
      }).sort(
        (a, b) => a.text.toLowerCase() > b.text.toLowerCase() ? 1 : -1
      ),
      []
    );
    const sceneFamilies = useMemo(
      () => new Set(fonts.getSceneFamilies()),
      // cache per selected font family, so hover re-render won't mess it up
      // eslint-disable-next-line react-hooks/exhaustive-deps
      [selectedFontFamily]
    );
    const sceneFonts = useMemo(
      () => allFonts.filter((font) => sceneFamilies.has(font.value)),
      // always show all the fonts in the scene, even those that were deprecated
      [allFonts, sceneFamilies]
    );
    const availableFonts = useMemo(
      () => allFonts.filter(
        (font) => !sceneFamilies.has(font.value) && (showDeprecatedFonts || !font.deprecated)
        // skip deprecated fonts
      ),
      [allFonts, sceneFamilies, showDeprecatedFonts]
    );
    const filteredFonts = useMemo(
      () => arrayToList(
        [...sceneFonts, ...availableFonts].filter(
          (font) => font.text?.toLowerCase().includes(searchTerm)
        )
      ),
      [sceneFonts, availableFonts, searchTerm]
    );
    const hoveredFont = useMemo(() => {
      let font;
      if (hoveredFontFamily) {
        font = filteredFonts.find((font2) => font2.value === hoveredFontFamily);
      } else if (selectedFontFamily) {
        font = filteredFonts.find((font2) => font2.value === selectedFontFamily);
      }
      if (!font && searchTerm) {
        if (filteredFonts[0]?.value) {
          onHover(filteredFonts[0].value);
        } else {
          onLeave();
        }
      }
      return font;
    }, [
      hoveredFontFamily,
      selectedFontFamily,
      searchTerm,
      filteredFonts,
      onHover,
      onLeave
    ]);
    const onKeyDown = useCallback2(
      (event) => {
        const handled = fontPickerKeyHandler({
          event,
          inputRef,
          hoveredFont,
          filteredFonts,
          onSelect,
          onHover,
          onClose
        });
        if (handled) {
          event.preventDefault();
          event.stopPropagation();
        }
      },
      [hoveredFont, filteredFonts, onSelect, onHover, onClose]
    );
    useEffect13(() => {
      onOpen();
      return () => {
        onClose();
      };
    }, []);
    const sceneFilteredFonts = useMemo(
      () => filteredFonts.filter((font) => sceneFamilies.has(font.value)),
      [filteredFonts, sceneFamilies]
    );
    const availableFilteredFonts = useMemo(
      () => filteredFonts.filter((font) => !sceneFamilies.has(font.value)),
      [filteredFonts, sceneFamilies]
    );
    const renderFont = (font, index) => /* @__PURE__ */ jsxs17(
      DropdownMenuItem_default,
      {
        icon: font.icon,
        value: font.value,
        order: index,
        textStyle: {
          fontFamily: getFontFamilyString({ fontFamily: font.value })
        },
        hovered: font.value === hoveredFont?.value,
        selected: font.value === selectedFontFamily,
        tabIndex: font.value === selectedFontFamily ? 0 : -1,
        onClick: (e) => {
          onSelect(Number(e.currentTarget.value));
        },
        onMouseMove: () => {
          if (hoveredFont?.value !== font.value) {
            onHover(font.value);
          }
        },
        children: [
          font.text,
          font.badge && /* @__PURE__ */ jsx30(DropDownMenuItemBadge, { type: font.badge.type, children: font.badge.placeholder })
        ]
      },
      font.value
    );
    const groups = [];
    if (sceneFilteredFonts.length) {
      groups.push(
        /* @__PURE__ */ jsx30(DropdownMenuGroup_default, { title: t("fontList.sceneFonts"), children: sceneFilteredFonts.map(renderFont) }, "group_1")
      );
    }
    if (availableFilteredFonts.length) {
      groups.push(
        /* @__PURE__ */ jsx30(DropdownMenuGroup_default, { title: t("fontList.availableFonts"), children: availableFilteredFonts.map(
          (font, index) => renderFont(font, index + sceneFilteredFonts.length)
        ) }, "group_2")
      );
    }
    return /* @__PURE__ */ jsxs17(
      PropertiesPopover,
      {
        className: "properties-content",
        container,
        style: { width: "15rem" },
        onClose,
        onPointerLeave: onLeave,
        onKeyDown,
        children: [
          /* @__PURE__ */ jsx30(
            QuickSearch,
            {
              ref: inputRef,
              placeholder: t("quickSearch.placeholder"),
              onChange: debounce(setSearchTerm, 20)
            }
          ),
          /* @__PURE__ */ jsx30(
            ScrollableList,
            {
              className: "dropdown-menu fonts manual-hover",
              placeholder: t("fontList.empty"),
              children: groups.length ? groups : null
            }
          )
        ]
      }
    );
  },
  (prev, next) => prev.selectedFontFamily === next.selectedFontFamily && prev.hoveredFontFamily === next.hoveredFontFamily
);

// components/FontPicker/FontPickerTrigger.tsx
import * as Popover4 from "@radix-ui/react-popover";
import { useMemo as useMemo2 } from "react";
import { jsx as jsx31 } from "react/jsx-runtime";
var FontPickerTrigger = ({
  selectedFontFamily
}) => {
  const isTriggerActive = useMemo2(
    () => Boolean(selectedFontFamily && !isDefaultFont(selectedFontFamily)),
    [selectedFontFamily]
  );
  return /* @__PURE__ */ jsx31(Popover4.Trigger, { asChild: true, children: /* @__PURE__ */ jsx31("div", { children: /* @__PURE__ */ jsx31(
    ButtonIcon,
    {
      standalone: true,
      icon: TextIcon,
      title: t("labels.showFonts"),
      className: "properties-trigger",
      testId: "font-family-show-fonts",
      active: isTriggerActive,
      onClick: () => {
      }
    }
  ) }) });
};

// components/FontPicker/FontPicker.tsx
import { jsx as jsx32, jsxs as jsxs18 } from "react/jsx-runtime";
var DEFAULT_FONTS = [
  {
    value: FONT_FAMILY.Excalifont,
    icon: FreedrawIcon,
    text: t("labels.handDrawn"),
    testId: "font-family-hand-drawn"
  },
  {
    value: FONT_FAMILY.Nunito,
    icon: FontFamilyNormalIcon,
    text: t("labels.normal"),
    testId: "font-family-normal"
  },
  {
    value: FONT_FAMILY["Comic Shanns"],
    icon: FontFamilyCodeIcon,
    text: t("labels.code"),
    testId: "font-family-code"
  }
];
var defaultFontFamilies = new Set(DEFAULT_FONTS.map((x) => x.value));
var isDefaultFont = (fontFamily) => {
  if (!fontFamily) {
    return false;
  }
  return defaultFontFamilies.has(fontFamily);
};
var FontPicker = React13.memo(
  ({
    isOpened,
    selectedFontFamily,
    hoveredFontFamily,
    onSelect,
    onHover,
    onLeave,
    onPopupChange
  }) => {
    const defaultFonts = useMemo3(() => DEFAULT_FONTS, []);
    const onSelectCallback = useCallback3(
      (value) => {
        if (value) {
          onSelect(value);
        }
      },
      [onSelect]
    );
    return /* @__PURE__ */ jsxs18("div", { role: "dialog", "aria-modal": "true", className: "FontPicker__container", children: [
      /* @__PURE__ */ jsx32(
        ButtonIconSelect,
        {
          type: "button",
          options: defaultFonts,
          value: selectedFontFamily,
          onClick: onSelectCallback
        }
      ),
      /* @__PURE__ */ jsx32(ButtonSeparator, {}),
      /* @__PURE__ */ jsxs18(Popover5.Root, { open: isOpened, onOpenChange: onPopupChange, children: [
        /* @__PURE__ */ jsx32(FontPickerTrigger, { selectedFontFamily }),
        isOpened && /* @__PURE__ */ jsx32(
          FontPickerList,
          {
            selectedFontFamily,
            hoveredFontFamily,
            onSelect: onSelectCallback,
            onHover,
            onLeave,
            onOpen: () => onPopupChange(true),
            onClose: () => onPopupChange(false)
          }
        )
      ] })
    ] });
  },
  (prev, next) => prev.isOpened === next.isOpened && prev.selectedFontFamily === next.selectedFontFamily && prev.hoveredFontFamily === next.hoveredFontFamily
);

// components/Range.tsx
import React14, { useEffect as useEffect14 } from "react";
import { jsx as jsx33, jsxs as jsxs19 } from "react/jsx-runtime";
var Range = ({
  updateData,
  appState,
  elements,
  testId
}) => {
  const rangeRef = React14.useRef(null);
  const valueRef = React14.useRef(null);
  const value = getFormValue(
    elements,
    appState,
    (element) => element.opacity,
    true,
    appState.currentItemOpacity
  );
  useEffect14(() => {
    if (rangeRef.current && valueRef.current) {
      const rangeElement = rangeRef.current;
      const valueElement = valueRef.current;
      const inputWidth = rangeElement.offsetWidth;
      const thumbWidth = 15;
      const position = value / 100 * (inputWidth - thumbWidth) + thumbWidth / 2;
      valueElement.style.left = `${position}px`;
      rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${value}%, var(--button-bg) ${value}%, var(--button-bg) 100%)`;
    }
  }, [value]);
  return /* @__PURE__ */ jsxs19("label", { className: "control-label", children: [
    t("labels.opacity"),
    /* @__PURE__ */ jsxs19("div", { className: "range-wrapper", children: [
      /* @__PURE__ */ jsx33(
        "input",
        {
          ref: rangeRef,
          type: "range",
          min: "0",
          max: "100",
          step: "10",
          onChange: (event) => {
            updateData(+event.target.value);
          },
          value,
          className: "range-input",
          "data-testid": testId
        }
      ),
      /* @__PURE__ */ jsx33("div", { className: "value-bubble", ref: valueRef, children: value !== 0 ? value : null }),
      /* @__PURE__ */ jsx33("div", { className: "zero-label", children: "0" })
    ] })
  ] });
};

// actions/actionProperties.tsx
import { Fragment as Fragment4, jsx as jsx34, jsxs as jsxs20 } from "react/jsx-runtime";
var FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
var changeProperty = (elements, appState, callback, includeBoundText = false) => {
  const selectedElementIds = arrayToMap(
    getSelectedElements(elements, appState, {
      includeBoundTextElement: includeBoundText
    })
  );
  return elements.map((element) => {
    if (selectedElementIds.get(element.id) || element.id === appState.editingTextElement?.id) {
      return callback(element);
    }
    return element;
  });
};
var getFormValue = function(elements, appState, getAttribute, isRelevantElement, defaultValue) {
  const editingTextElement = appState.editingTextElement;
  const nonDeletedElements = getNonDeletedElements(elements);
  let ret = null;
  if (editingTextElement) {
    ret = getAttribute(editingTextElement);
  }
  if (!ret) {
    const hasSelection = isSomeElementSelected(nonDeletedElements, appState);
    if (hasSelection) {
      ret = getCommonAttributeOfSelectedElements(
        isRelevantElement === true ? nonDeletedElements : nonDeletedElements.filter((el) => isRelevantElement(el)),
        appState,
        getAttribute
      ) ?? (typeof defaultValue === "function" ? defaultValue(true) : defaultValue);
    } else {
      ret = typeof defaultValue === "function" ? defaultValue(false) : defaultValue;
    }
  }
  return ret;
};
var offsetElementAfterFontResize = (prevElement, nextElement) => {
  if (isBoundToContainer(nextElement) || !nextElement.autoResize) {
    return nextElement;
  }
  return mutateElement(
    nextElement,
    {
      x: prevElement.textAlign === "left" ? prevElement.x : prevElement.x + (prevElement.width - nextElement.width) / (prevElement.textAlign === "center" ? 2 : 1),
      // centering vertically is non-standard, but for Excalidraw I think
      // it makes sense
      y: prevElement.y + (prevElement.height - nextElement.height) / 2
    },
    false
  );
};
var changeFontSize = (elements, appState, app, getNewFontSize, fallbackValue) => {
  const newFontSizes = /* @__PURE__ */ new Set();
  const updatedElements = changeProperty(
    elements,
    appState,
    (oldElement) => {
      if (isTextElement(oldElement)) {
        const newFontSize = getNewFontSize(oldElement);
        newFontSizes.add(newFontSize);
        let newElement2 = newElementWith(oldElement, {
          fontSize: newFontSize
        });
        redrawTextBoundingBox(
          newElement2,
          app.scene.getContainerElement(oldElement),
          app.scene.getNonDeletedElementsMap()
        );
        newElement2 = offsetElementAfterFontResize(oldElement, newElement2);
        return newElement2;
      }
      return oldElement;
    },
    true
  );
  const updatedElementsMap = arrayToMap(updatedElements);
  getSelectedElements(elements, appState, {
    includeBoundTextElement: true
  }).forEach((element) => {
    if (isTextElement(element)) {
      updateBoundElements(
        element,
        updatedElementsMap
      );
    }
  });
  return {
    elements: updatedElements,
    appState: {
      ...appState,
      // update state only if we've set all select text elements to
      // the same font size
      currentItemFontSize: newFontSizes.size === 1 ? [...newFontSizes][0] : fallbackValue ?? appState.currentItemFontSize
    },
    captureUpdate: CaptureUpdateAction.IMMEDIATELY
  };
};
var actionChangeStrokeColor = register({
  name: "changeStrokeColor",
  label: "labels.stroke",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      ...value.currentItemStrokeColor && {
        elements: changeProperty(
          elements,
          appState,
          (el) => {
            return hasStrokeColor(el.type) ? newElementWith(el, {
              strokeColor: value.currentItemStrokeColor
            }) : el;
          },
          true
        )
      },
      appState: {
        ...appState,
        ...value
      },
      captureUpdate: !!value.currentItemStrokeColor ? CaptureUpdateAction.IMMEDIATELY : CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ elements, appState, updateData, appProps }) => /* @__PURE__ */ jsxs20(Fragment4, { children: [
    /* @__PURE__ */ jsx34("h3", { "aria-hidden": "true", children: t("labels.stroke") }),
    /* @__PURE__ */ jsx34(
      ColorPicker,
      {
        topPicks: DEFAULT_ELEMENT_STROKE_PICKS,
        palette: DEFAULT_ELEMENT_STROKE_COLOR_PALETTE,
        type: "elementStroke",
        label: t("labels.stroke"),
        color: getFormValue(
          elements,
          appState,
          (element) => element.strokeColor,
          true,
          appState.currentItemStrokeColor
        ),
        onChange: (color) => updateData({ currentItemStrokeColor: color }),
        elements,
        appState,
        updateData
      }
    )
  ] })
});
var actionChangeBackgroundColor = register({
  name: "changeBackgroundColor",
  label: "labels.changeBackground",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      ...value.currentItemBackgroundColor && {
        elements: changeProperty(
          elements,
          appState,
          (el) => newElementWith(el, {
            backgroundColor: value.currentItemBackgroundColor
          })
        )
      },
      appState: {
        ...appState,
        ...value
      },
      captureUpdate: !!value.currentItemBackgroundColor ? CaptureUpdateAction.IMMEDIATELY : CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ elements, appState, updateData, appProps }) => /* @__PURE__ */ jsxs20(Fragment4, { children: [
    /* @__PURE__ */ jsx34("h3", { "aria-hidden": "true", children: t("labels.background") }),
    /* @__PURE__ */ jsx34(
      ColorPicker,
      {
        topPicks: DEFAULT_ELEMENT_BACKGROUND_PICKS,
        palette: DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
        type: "elementBackground",
        label: t("labels.background"),
        color: getFormValue(
          elements,
          appState,
          (element) => element.backgroundColor,
          true,
          appState.currentItemBackgroundColor
        ),
        onChange: (color) => updateData({ currentItemBackgroundColor: color }),
        elements,
        appState,
        updateData
      }
    )
  ] })
});
var actionChangeFillStyle = register({
  name: "changeFillStyle",
  label: "labels.fill",
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    trackEvent(
      "element",
      "changeFillStyle",
      `${value} (${app.device.editor.isMobile ? "mobile" : "desktop"})`
    );
    return {
      elements: changeProperty(
        elements,
        appState,
        (el) => newElementWith(el, {
          fillStyle: value
        })
      ),
      appState: { ...appState, currentItemFillStyle: value },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => {
    const selectedElements = getSelectedElements(elements, appState);
    const allElementsZigZag = selectedElements.length > 0 && selectedElements.every((el) => el.fillStyle === "zigzag");
    return /* @__PURE__ */ jsxs20("fieldset", { children: [
      /* @__PURE__ */ jsx34("legend", { children: t("labels.fill") }),
      /* @__PURE__ */ jsx34(
        ButtonIconSelect,
        {
          type: "button",
          options: [
            {
              value: "hachure",
              text: `${allElementsZigZag ? t("labels.zigzag") : t("labels.hachure")} (${getShortcutKey("Alt-Click")})`,
              icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
              active: allElementsZigZag ? true : void 0,
              testId: `fill-hachure`
            },
            {
              value: "cross-hatch",
              text: t("labels.crossHatch"),
              icon: FillCrossHatchIcon,
              testId: `fill-cross-hatch`
            },
            {
              value: "solid",
              text: t("labels.solid"),
              icon: FillSolidIcon,
              testId: `fill-solid`
            }
          ],
          value: getFormValue(
            elements,
            appState,
            (element) => element.fillStyle,
            (element) => element.hasOwnProperty("fillStyle"),
            (hasSelection) => hasSelection ? null : appState.currentItemFillStyle
          ),
          onClick: (value, event) => {
            const nextValue = event.altKey && value === "hachure" && selectedElements.every((el) => el.fillStyle === "hachure") ? "zigzag" : value;
            updateData(nextValue);
          }
        }
      )
    ] });
  }
});
var actionChangeStrokeWidth = register({
  name: "changeStrokeWidth",
  label: "labels.strokeWidth",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      elements: changeProperty(
        elements,
        appState,
        (el) => newElementWith(el, {
          strokeWidth: value
        })
      ),
      appState: { ...appState, currentItemStrokeWidth: value },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsxs20("fieldset", { children: [
    /* @__PURE__ */ jsx34("legend", { children: t("labels.strokeWidth") }),
    /* @__PURE__ */ jsx34(
      ButtonIconSelect,
      {
        group: "stroke-width",
        options: [
          {
            value: STROKE_WIDTH.thin,
            text: t("labels.thin"),
            icon: StrokeWidthBaseIcon,
            testId: "strokeWidth-thin"
          },
          {
            value: STROKE_WIDTH.bold,
            text: t("labels.bold"),
            icon: StrokeWidthBoldIcon,
            testId: "strokeWidth-bold"
          },
          {
            value: STROKE_WIDTH.extraBold,
            text: t("labels.extraBold"),
            icon: StrokeWidthExtraBoldIcon,
            testId: "strokeWidth-extraBold"
          }
        ],
        value: getFormValue(
          elements,
          appState,
          (element) => element.strokeWidth,
          (element) => element.hasOwnProperty("strokeWidth"),
          (hasSelection) => hasSelection ? null : appState.currentItemStrokeWidth
        ),
        onChange: (value) => updateData(value)
      }
    )
  ] })
});
var actionChangeSloppiness = register({
  name: "changeSloppiness",
  label: "labels.sloppiness",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      elements: changeProperty(
        elements,
        appState,
        (el) => newElementWith(el, {
          seed: randomInteger(),
          roughness: value
        })
      ),
      appState: { ...appState, currentItemRoughness: value },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsxs20("fieldset", { children: [
    /* @__PURE__ */ jsx34("legend", { children: t("labels.sloppiness") }),
    /* @__PURE__ */ jsx34(
      ButtonIconSelect,
      {
        group: "sloppiness",
        options: [
          {
            value: 0,
            text: t("labels.architect"),
            icon: SloppinessArchitectIcon
          },
          {
            value: 1,
            text: t("labels.artist"),
            icon: SloppinessArtistIcon
          },
          {
            value: 2,
            text: t("labels.cartoonist"),
            icon: SloppinessCartoonistIcon
          }
        ],
        value: getFormValue(
          elements,
          appState,
          (element) => element.roughness,
          (element) => element.hasOwnProperty("roughness"),
          (hasSelection) => hasSelection ? null : appState.currentItemRoughness
        ),
        onChange: (value) => updateData(value)
      }
    )
  ] })
});
var actionChangeStrokeStyle = register({
  name: "changeStrokeStyle",
  label: "labels.strokeStyle",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      elements: changeProperty(
        elements,
        appState,
        (el) => newElementWith(el, {
          strokeStyle: value
        })
      ),
      appState: { ...appState, currentItemStrokeStyle: value },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsxs20("fieldset", { children: [
    /* @__PURE__ */ jsx34("legend", { children: t("labels.strokeStyle") }),
    /* @__PURE__ */ jsx34(
      ButtonIconSelect,
      {
        group: "strokeStyle",
        options: [
          {
            value: "solid",
            text: t("labels.strokeStyle_solid"),
            icon: StrokeWidthBaseIcon
          },
          {
            value: "dashed",
            text: t("labels.strokeStyle_dashed"),
            icon: StrokeStyleDashedIcon
          },
          {
            value: "dotted",
            text: t("labels.strokeStyle_dotted"),
            icon: StrokeStyleDottedIcon
          }
        ],
        value: getFormValue(
          elements,
          appState,
          (element) => element.strokeStyle,
          (element) => element.hasOwnProperty("strokeStyle"),
          (hasSelection) => hasSelection ? null : appState.currentItemStrokeStyle
        ),
        onChange: (value) => updateData(value)
      }
    )
  ] })
});
var actionChangeOpacity = register({
  name: "changeOpacity",
  label: "labels.opacity",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      elements: changeProperty(
        elements,
        appState,
        (el) => newElementWith(el, {
          opacity: value
        }),
        true
      ),
      appState: { ...appState, currentItemOpacity: value },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsx34(
    Range,
    {
      updateData,
      elements,
      appState,
      testId: "opacity"
    }
  )
});
var actionChangeFontSize = register({
  name: "changeFontSize",
  label: "labels.fontSize",
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    return changeFontSize(elements, appState, app, () => value, value);
  },
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsxs20("fieldset", { children: [
    /* @__PURE__ */ jsx34("legend", { children: t("labels.fontSize") }),
    /* @__PURE__ */ jsx34(
      ButtonIconSelect,
      {
        group: "font-size",
        options: [
          {
            value: 16,
            text: t("labels.small"),
            icon: FontSizeSmallIcon,
            testId: "fontSize-small"
          },
          {
            value: 20,
            text: t("labels.medium"),
            icon: FontSizeMediumIcon,
            testId: "fontSize-medium"
          },
          {
            value: 28,
            text: t("labels.large"),
            icon: FontSizeLargeIcon,
            testId: "fontSize-large"
          },
          {
            value: 36,
            text: t("labels.veryLarge"),
            icon: FontSizeExtraLargeIcon,
            testId: "fontSize-veryLarge"
          }
        ],
        value: getFormValue(
          elements,
          appState,
          (element) => {
            if (isTextElement(element)) {
              return element.fontSize;
            }
            const boundTextElement = getBoundTextElement(
              element,
              app.scene.getNonDeletedElementsMap()
            );
            if (boundTextElement) {
              return boundTextElement.fontSize;
            }
            return null;
          },
          (element) => isTextElement(element) || getBoundTextElement(
            element,
            app.scene.getNonDeletedElementsMap()
          ) !== null,
          (hasSelection) => hasSelection ? null : appState.currentItemFontSize || DEFAULT_FONT_SIZE
        ),
        onChange: (value) => updateData(value)
      }
    )
  ] })
});
var actionDecreaseFontSize = register({
  name: "decreaseFontSize",
  label: "labels.decreaseFontSize",
  icon: fontSizeIcon,
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    return changeFontSize(
      elements,
      appState,
      app,
      (element) => Math.round(
        // get previous value before relative increase (doesn't work fully
        // due to rounding and float precision issues)
        1 / (1 + FONT_SIZE_RELATIVE_INCREASE_STEP) * element.fontSize
      )
    );
  },
  keyTest: (event) => {
    return event[KEYS.CTRL_OR_CMD] && event.shiftKey && // KEYS.COMMA needed for MacOS
    (event.key === KEYS.CHEVRON_LEFT || event.key === KEYS.COMMA);
  }
});
var actionIncreaseFontSize = register({
  name: "increaseFontSize",
  label: "labels.increaseFontSize",
  icon: fontSizeIcon,
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    return changeFontSize(
      elements,
      appState,
      app,
      (element) => Math.round(element.fontSize * (1 + FONT_SIZE_RELATIVE_INCREASE_STEP))
    );
  },
  keyTest: (event) => {
    return event[KEYS.CTRL_OR_CMD] && event.shiftKey && // KEYS.PERIOD needed for MacOS
    (event.key === KEYS.CHEVRON_RIGHT || event.key === KEYS.PERIOD);
  }
});
var actionChangeFontFamily = register({
  name: "changeFontFamily",
  label: "labels.fontFamily",
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    const { cachedElements, resetAll, resetContainers, ...nextAppState } = value;
    if (resetAll) {
      const nextElements = changeProperty(
        elements,
        appState,
        (element) => {
          const cachedElement = cachedElements?.get(element.id);
          if (cachedElement) {
            const newElement2 = newElementWith(element, {
              ...cachedElement
            });
            return newElement2;
          }
          return element;
        },
        true
      );
      return {
        elements: nextElements,
        appState: {
          ...appState,
          ...nextAppState
        },
        captureUpdate: CaptureUpdateAction.NEVER
      };
    }
    const { currentItemFontFamily, currentHoveredFontFamily } = value;
    let nextCaptureUpdateAction = CaptureUpdateAction.EVENTUALLY;
    let nextFontFamily;
    let skipOnHoverRender = false;
    if (currentItemFontFamily) {
      nextFontFamily = currentItemFontFamily;
      nextCaptureUpdateAction = CaptureUpdateAction.IMMEDIATELY;
    } else if (currentHoveredFontFamily) {
      nextFontFamily = currentHoveredFontFamily;
      nextCaptureUpdateAction = CaptureUpdateAction.EVENTUALLY;
      const selectedTextElements = getSelectedElements(elements, appState, {
        includeBoundTextElement: true
      }).filter((element) => isTextElement(element));
      if (selectedTextElements.length > 200) {
        skipOnHoverRender = true;
      } else {
        let i = 0;
        let textLengthAccumulator = 0;
        while (i < selectedTextElements.length && textLengthAccumulator < 5e3) {
          const textElement = selectedTextElements[i];
          textLengthAccumulator += textElement?.originalText.length || 0;
          i++;
        }
        if (textLengthAccumulator > 5e3) {
          skipOnHoverRender = true;
        }
      }
    }
    const result = {
      appState: {
        ...appState,
        ...nextAppState
      },
      captureUpdate: nextCaptureUpdateAction
    };
    if (nextFontFamily && !skipOnHoverRender) {
      const elementContainerMapping = /* @__PURE__ */ new Map();
      let uniqueChars = /* @__PURE__ */ new Set();
      let skipFontFaceCheck = false;
      const fontsCache = Array.from(Fonts.loadedFontsCache.values());
      const fontFamily = Object.entries(FONT_FAMILY).find(
        ([_, value2]) => value2 === nextFontFamily
      )?.[0];
      if (currentHoveredFontFamily && fontFamily && fontsCache.some((sig) => sig.startsWith(fontFamily))) {
        skipFontFaceCheck = true;
      }
      Object.assign(result, {
        elements: changeProperty(
          elements,
          appState,
          (oldElement) => {
            if (isTextElement(oldElement) && (oldElement.fontFamily !== nextFontFamily || currentItemFontFamily)) {
              const newElement2 = newElementWith(
                oldElement,
                {
                  fontFamily: nextFontFamily,
                  lineHeight: getLineHeight(nextFontFamily)
                }
              );
              const cachedContainer = cachedElements?.get(oldElement.containerId || "") || {};
              const container = app.scene.getContainerElement(oldElement);
              if (resetContainers && container && cachedContainer) {
                mutateElement(container, { ...cachedContainer }, false);
              }
              if (!skipFontFaceCheck) {
                uniqueChars = /* @__PURE__ */ new Set([
                  ...uniqueChars,
                  ...Array.from(newElement2.originalText)
                ]);
              }
              elementContainerMapping.set(newElement2, container);
              return newElement2;
            }
            return oldElement;
          },
          true
        )
      });
      const fontString = `10px ${getFontFamilyString({
        fontFamily: nextFontFamily
      })}`;
      const chars = Array.from(uniqueChars.values()).join();
      if (skipFontFaceCheck || window.document.fonts.check(fontString, chars)) {
        for (const [element, container] of elementContainerMapping) {
          redrawTextBoundingBox(
            element,
            container,
            app.scene.getNonDeletedElementsMap(),
            false
          );
        }
      } else {
        window.document.fonts.load(fontString, chars).then((fontFaces) => {
          for (const [element, container] of elementContainerMapping) {
            const latestElement = app.scene.getElement(element.id);
            const latestContainer = container ? app.scene.getElement(container.id) : null;
            if (latestElement) {
              redrawTextBoundingBox(
                latestElement,
                latestContainer,
                app.scene.getNonDeletedElementsMap(),
                false
              );
            }
          }
          app.fonts.onLoaded(fontFaces);
        });
      }
    }
    return result;
  },
  PanelComponent: ({ elements, appState, app, updateData }) => {
    const cachedElementsRef = useRef11(/* @__PURE__ */ new Map());
    const prevSelectedFontFamilyRef = useRef11(null);
    const [batchedData, setBatchedData] = useState8({});
    const isUnmounted = useRef11(true);
    const selectedFontFamily = useMemo4(() => {
      const getFontFamily = (elementsArray, elementsMap) => getFormValue(
        elementsArray,
        appState,
        (element) => {
          if (isTextElement(element)) {
            return element.fontFamily;
          }
          const boundTextElement = getBoundTextElement(element, elementsMap);
          if (boundTextElement) {
            return boundTextElement.fontFamily;
          }
          return null;
        },
        (element) => isTextElement(element) || getBoundTextElement(element, elementsMap) !== null,
        (hasSelection) => hasSelection ? null : appState.currentItemFontFamily || DEFAULT_FONT_FAMILY
      );
      if (batchedData.openPopup === "fontFamily" && appState.openPopup === "fontFamily") {
        return getFontFamily(
          Array.from(cachedElementsRef.current?.values() ?? []),
          cachedElementsRef.current
        );
      }
      if (!batchedData.openPopup && appState.openPopup !== "fontFamily") {
        return getFontFamily(elements, app.scene.getNonDeletedElementsMap());
      }
      return prevSelectedFontFamilyRef.current;
    }, [batchedData.openPopup, appState, elements, app.scene]);
    useEffect15(() => {
      prevSelectedFontFamilyRef.current = selectedFontFamily;
    }, [selectedFontFamily]);
    useEffect15(() => {
      if (Object.keys(batchedData).length) {
        updateData(batchedData);
        setBatchedData({});
      }
    }, [batchedData]);
    useEffect15(() => {
      isUnmounted.current = false;
      return () => {
        isUnmounted.current = true;
      };
    }, []);
    return /* @__PURE__ */ jsxs20("fieldset", { children: [
      /* @__PURE__ */ jsx34("legend", { children: t("labels.fontFamily") }),
      /* @__PURE__ */ jsx34(
        FontPicker,
        {
          isOpened: appState.openPopup === "fontFamily",
          selectedFontFamily,
          hoveredFontFamily: appState.currentHoveredFontFamily,
          onSelect: (fontFamily) => {
            setBatchedData({
              openPopup: null,
              currentHoveredFontFamily: null,
              currentItemFontFamily: fontFamily
            });
            cachedElementsRef.current.clear();
          },
          onHover: (fontFamily) => {
            setBatchedData({
              currentHoveredFontFamily: fontFamily,
              cachedElements: new Map(cachedElementsRef.current),
              resetContainers: true
            });
          },
          onLeave: () => {
            setBatchedData({
              currentHoveredFontFamily: null,
              cachedElements: new Map(cachedElementsRef.current),
              resetAll: true
            });
          },
          onPopupChange: (open) => {
            if (open) {
              cachedElementsRef.current.clear();
              const { editingTextElement } = appState;
              if (editingTextElement?.type === "text") {
                const latesteditingTextElement = app.scene.getElement(
                  editingTextElement.id
                );
                cachedElementsRef.current.set(
                  editingTextElement.id,
                  newElementWith(
                    latesteditingTextElement || editingTextElement,
                    {},
                    true
                  )
                );
              } else {
                const selectedElements = getSelectedElements(
                  elements,
                  appState,
                  {
                    includeBoundTextElement: true
                  }
                );
                for (const element of selectedElements) {
                  cachedElementsRef.current.set(
                    element.id,
                    newElementWith(element, {}, true)
                  );
                }
              }
              setBatchedData({
                openPopup: "fontFamily"
              });
            } else {
              const data = {
                openPopup: null,
                currentHoveredFontFamily: null,
                cachedElements: new Map(cachedElementsRef.current),
                resetAll: true
              };
              if (isUnmounted.current) {
                updateData({ ...batchedData, ...data });
              } else {
                setBatchedData(data);
              }
              cachedElementsRef.current.clear();
            }
          }
        }
      )
    ] });
  }
});
var actionChangeTextAlign = register({
  name: "changeTextAlign",
  label: "Change text alignment",
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    return {
      elements: changeProperty(
        elements,
        appState,
        (oldElement) => {
          if (isTextElement(oldElement)) {
            const newElement2 = newElementWith(
              oldElement,
              { textAlign: value }
            );
            redrawTextBoundingBox(
              newElement2,
              app.scene.getContainerElement(oldElement),
              app.scene.getNonDeletedElementsMap()
            );
            return newElement2;
          }
          return oldElement;
        },
        true
      ),
      appState: {
        ...appState,
        currentItemTextAlign: value
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData, app }) => {
    const elementsMap = app.scene.getNonDeletedElementsMap();
    return /* @__PURE__ */ jsxs20("fieldset", { children: [
      /* @__PURE__ */ jsx34("legend", { children: t("labels.textAlign") }),
      /* @__PURE__ */ jsx34(
        ButtonIconSelect,
        {
          group: "text-align",
          options: [
            {
              value: "left",
              text: t("labels.left"),
              icon: TextAlignLeftIcon,
              testId: "align-left"
            },
            {
              value: "center",
              text: t("labels.center"),
              icon: TextAlignCenterIcon,
              testId: "align-horizontal-center"
            },
            {
              value: "right",
              text: t("labels.right"),
              icon: TextAlignRightIcon,
              testId: "align-right"
            }
          ],
          value: getFormValue(
            elements,
            appState,
            (element) => {
              if (isTextElement(element)) {
                return element.textAlign;
              }
              const boundTextElement = getBoundTextElement(
                element,
                elementsMap
              );
              if (boundTextElement) {
                return boundTextElement.textAlign;
              }
              return null;
            },
            (element) => isTextElement(element) || getBoundTextElement(element, elementsMap) !== null,
            (hasSelection) => hasSelection ? null : appState.currentItemTextAlign
          ),
          onChange: (value) => updateData(value)
        }
      )
    ] });
  }
});
var actionChangeVerticalAlign = register({
  name: "changeVerticalAlign",
  label: "Change vertical alignment",
  trackEvent: { category: "element" },
  perform: (elements, appState, value, app) => {
    return {
      elements: changeProperty(
        elements,
        appState,
        (oldElement) => {
          if (isTextElement(oldElement)) {
            const newElement2 = newElementWith(
              oldElement,
              { verticalAlign: value }
            );
            redrawTextBoundingBox(
              newElement2,
              app.scene.getContainerElement(oldElement),
              app.scene.getNonDeletedElementsMap()
            );
            return newElement2;
          }
          return oldElement;
        },
        true
      ),
      appState: {
        ...appState
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData, app }) => {
    return /* @__PURE__ */ jsx34("fieldset", { children: /* @__PURE__ */ jsx34(
      ButtonIconSelect,
      {
        group: "text-align",
        options: [
          {
            value: VERTICAL_ALIGN.TOP,
            text: t("labels.alignTop"),
            icon: /* @__PURE__ */ jsx34(TextAlignTopIcon, { theme: appState.theme }),
            testId: "align-top"
          },
          {
            value: VERTICAL_ALIGN.MIDDLE,
            text: t("labels.centerVertically"),
            icon: /* @__PURE__ */ jsx34(TextAlignMiddleIcon, { theme: appState.theme }),
            testId: "align-middle"
          },
          {
            value: VERTICAL_ALIGN.BOTTOM,
            text: t("labels.alignBottom"),
            icon: /* @__PURE__ */ jsx34(TextAlignBottomIcon, { theme: appState.theme }),
            testId: "align-bottom"
          }
        ],
        value: getFormValue(
          elements,
          appState,
          (element) => {
            if (isTextElement(element) && element.containerId) {
              return element.verticalAlign;
            }
            const boundTextElement = getBoundTextElement(
              element,
              app.scene.getNonDeletedElementsMap()
            );
            if (boundTextElement) {
              return boundTextElement.verticalAlign;
            }
            return null;
          },
          (element) => isTextElement(element) || getBoundTextElement(
            element,
            app.scene.getNonDeletedElementsMap()
          ) !== null,
          (hasSelection) => hasSelection ? null : VERTICAL_ALIGN.MIDDLE
        ),
        onChange: (value) => updateData(value)
      }
    ) });
  }
});
var actionChangeRoundness = register({
  name: "changeRoundness",
  label: "Change edge roundness",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      elements: changeProperty(elements, appState, (el) => {
        if (isElbowArrow(el)) {
          return el;
        }
        return newElementWith(el, {
          roundness: value === "round" ? {
            type: isUsingAdaptiveRadius(el.type) ? ROUNDNESS.ADAPTIVE_RADIUS : ROUNDNESS.PROPORTIONAL_RADIUS
          } : null
        });
      }),
      appState: {
        ...appState,
        currentItemRoundness: value
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => {
    const targetElements = getTargetElements(
      getNonDeletedElements(elements),
      appState
    );
    const hasLegacyRoundness = targetElements.some(
      (el) => el.roundness?.type === ROUNDNESS.LEGACY
    );
    return /* @__PURE__ */ jsxs20("fieldset", { children: [
      /* @__PURE__ */ jsx34("legend", { children: t("labels.edges") }),
      /* @__PURE__ */ jsx34(
        ButtonIconSelect,
        {
          group: "edges",
          options: [
            {
              value: "sharp",
              text: t("labels.sharp"),
              icon: EdgeSharpIcon
            },
            {
              value: "round",
              text: t("labels.round"),
              icon: EdgeRoundIcon
            }
          ],
          value: getFormValue(
            elements,
            appState,
            (element) => hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
            (element) => !isArrowElement(element) && element.hasOwnProperty("roundness"),
            (hasSelection) => hasSelection ? null : appState.currentItemRoundness
          ),
          onChange: (value) => updateData(value)
        }
      )
    ] });
  }
});
var getArrowheadOptions = (flip) => {
  return [
    {
      value: null,
      text: t("labels.arrowhead_none"),
      keyBinding: "q",
      icon: ArrowheadNoneIcon
    },
    {
      value: "arrow",
      text: t("labels.arrowhead_arrow"),
      keyBinding: "w",
      icon: /* @__PURE__ */ jsx34(ArrowheadArrowIcon, { flip })
    },
    {
      value: "triangle",
      text: t("labels.arrowhead_triangle"),
      icon: /* @__PURE__ */ jsx34(ArrowheadTriangleIcon, { flip }),
      keyBinding: "e"
    },
    {
      value: "triangle_outline",
      text: t("labels.arrowhead_triangle_outline"),
      icon: /* @__PURE__ */ jsx34(ArrowheadTriangleOutlineIcon, { flip }),
      keyBinding: "r"
    },
    {
      value: "circle",
      text: t("labels.arrowhead_circle"),
      keyBinding: "a",
      icon: /* @__PURE__ */ jsx34(ArrowheadCircleIcon, { flip })
    },
    {
      value: "circle_outline",
      text: t("labels.arrowhead_circle_outline"),
      keyBinding: "s",
      icon: /* @__PURE__ */ jsx34(ArrowheadCircleOutlineIcon, { flip })
    },
    {
      value: "diamond",
      text: t("labels.arrowhead_diamond"),
      icon: /* @__PURE__ */ jsx34(ArrowheadDiamondIcon, { flip }),
      keyBinding: "d"
    },
    {
      value: "diamond_outline",
      text: t("labels.arrowhead_diamond_outline"),
      icon: /* @__PURE__ */ jsx34(ArrowheadDiamondOutlineIcon, { flip }),
      keyBinding: "f"
    },
    {
      value: "bar",
      text: t("labels.arrowhead_bar"),
      keyBinding: "z",
      icon: /* @__PURE__ */ jsx34(ArrowheadBarIcon, { flip })
    },
    {
      value: "crowfoot_one",
      text: t("labels.arrowhead_crowfoot_one"),
      icon: /* @__PURE__ */ jsx34(ArrowheadCrowfootOneIcon, { flip }),
      keyBinding: "c"
    },
    {
      value: "crowfoot_many",
      text: t("labels.arrowhead_crowfoot_many"),
      icon: /* @__PURE__ */ jsx34(ArrowheadCrowfootIcon, { flip }),
      keyBinding: "x"
    },
    {
      value: "crowfoot_one_or_many",
      text: t("labels.arrowhead_crowfoot_one_or_many"),
      icon: /* @__PURE__ */ jsx34(ArrowheadCrowfootOneOrManyIcon, { flip }),
      keyBinding: "v"
    }
  ];
};
var actionChangeArrowhead = register({
  name: "changeArrowhead",
  label: "Change arrowheads",
  trackEvent: false,
  perform: (elements, appState, value) => {
    return {
      elements: changeProperty(elements, appState, (el) => {
        if (isLinearElement(el)) {
          const { position, type } = value;
          if (position === "start") {
            const element = newElementWith(el, {
              startArrowhead: type
            });
            return element;
          } else if (position === "end") {
            const element = newElementWith(el, {
              endArrowhead: type
            });
            return element;
          }
        }
        return el;
      }),
      appState: {
        ...appState,
        [value.position === "start" ? "currentItemStartArrowhead" : "currentItemEndArrowhead"]: value.type
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => {
    const isRTL = getLanguage().rtl;
    return /* @__PURE__ */ jsxs20("fieldset", { children: [
      /* @__PURE__ */ jsx34("legend", { children: t("labels.arrowheads") }),
      /* @__PURE__ */ jsxs20("div", { className: "iconSelectList buttonList", children: [
        /* @__PURE__ */ jsx34(
          IconPicker,
          {
            label: "arrowhead_start",
            options: getArrowheadOptions(!isRTL),
            value: getFormValue(
              elements,
              appState,
              (element) => isLinearElement(element) && canHaveArrowheads(element.type) ? element.startArrowhead : appState.currentItemStartArrowhead,
              true,
              appState.currentItemStartArrowhead
            ),
            onChange: (value) => updateData({ position: "start", type: value }),
            numberOfOptionsToAlwaysShow: 4
          }
        ),
        /* @__PURE__ */ jsx34(
          IconPicker,
          {
            label: "arrowhead_end",
            group: "arrowheads",
            options: getArrowheadOptions(!!isRTL),
            value: getFormValue(
              elements,
              appState,
              (element) => isLinearElement(element) && canHaveArrowheads(element.type) ? element.endArrowhead : appState.currentItemEndArrowhead,
              true,
              appState.currentItemEndArrowhead
            ),
            onChange: (value) => updateData({ position: "end", type: value }),
            numberOfOptionsToAlwaysShow: 4
          }
        )
      ] })
    ] });
  }
});
var actionChangeArrowType = register({
  name: "changeArrowType",
  label: "Change arrow types",
  trackEvent: false,
  perform: (elements, appState, value, app) => {
    const newElements = changeProperty(elements, appState, (el) => {
      if (!isArrowElement(el)) {
        return el;
      }
      const newElement2 = newElementWith(el, {
        roundness: value === ARROW_TYPE.round ? {
          type: ROUNDNESS.PROPORTIONAL_RADIUS
        } : null,
        elbowed: value === ARROW_TYPE.elbow,
        points: value === ARROW_TYPE.elbow || el.elbowed ? [el.points[0], el.points[el.points.length - 1]] : el.points
      });
      if (isElbowArrow(newElement2)) {
        const elementsMap = app.scene.getNonDeletedElementsMap();
        app.dismissLinearEditor();
        const startGlobalPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
          newElement2,
          0,
          elementsMap
        );
        const endGlobalPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
          newElement2,
          -1,
          elementsMap
        );
        const startHoveredElement = !newElement2.startBinding && getHoveredElementForBinding(
          tupleToCoors(startGlobalPoint),
          elements,
          elementsMap,
          appState.zoom,
          false,
          true
        );
        const endHoveredElement = !newElement2.endBinding && getHoveredElementForBinding(
          tupleToCoors(endGlobalPoint),
          elements,
          elementsMap,
          appState.zoom,
          false,
          true
        );
        const startElement = startHoveredElement ? startHoveredElement : newElement2.startBinding && elementsMap.get(
          newElement2.startBinding.elementId
        );
        const endElement = endHoveredElement ? endHoveredElement : newElement2.endBinding && elementsMap.get(
          newElement2.endBinding.elementId
        );
        const finalStartPoint = startHoveredElement ? bindPointToSnapToElementOutline(
          newElement2,
          startHoveredElement,
          "start"
        ) : startGlobalPoint;
        const finalEndPoint = endHoveredElement ? bindPointToSnapToElementOutline(
          newElement2,
          endHoveredElement,
          "end"
        ) : endGlobalPoint;
        startHoveredElement && bindLinearElement(
          newElement2,
          startHoveredElement,
          "start",
          elementsMap
        );
        endHoveredElement && bindLinearElement(newElement2, endHoveredElement, "end", elementsMap);
        mutateElement(newElement2, {
          points: [finalStartPoint, finalEndPoint].map(
            (p) => pointFrom(p[0] - newElement2.x, p[1] - newElement2.y)
          ),
          ...startElement && newElement2.startBinding ? {
            startBinding: {
              // @ts-ignore TS cannot discern check above
              ...newElement2.startBinding,
              ...calculateFixedPointForElbowArrowBinding(
                newElement2,
                startElement,
                "start",
                elementsMap
              )
            }
          } : {},
          ...endElement && newElement2.endBinding ? {
            endBinding: {
              // @ts-ignore TS cannot discern check above
              ...newElement2.endBinding,
              ...calculateFixedPointForElbowArrowBinding(
                newElement2,
                endElement,
                "end",
                elementsMap
              )
            }
          } : {}
        });
        LinearElementEditor.updateEditorMidPointsCache(
          newElement2,
          elementsMap,
          app.state
        );
      }
      return newElement2;
    });
    const newState = {
      ...appState,
      currentItemArrowType: value
    };
    const selectedId = appState.selectedLinearElement?.elementId;
    if (selectedId) {
      const selected = newElements.find((el) => el.id === selectedId);
      if (selected) {
        newState.selectedLinearElement = new LinearElementEditor(
          selected
        );
      }
    }
    return {
      elements: newElements,
      appState: newState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData }) => {
    return /* @__PURE__ */ jsxs20("fieldset", { children: [
      /* @__PURE__ */ jsx34("legend", { children: t("labels.arrowtypes") }),
      /* @__PURE__ */ jsx34(
        ButtonIconSelect,
        {
          group: "arrowtypes",
          options: [
            {
              value: ARROW_TYPE.sharp,
              text: t("labels.arrowtype_sharp"),
              icon: sharpArrowIcon,
              testId: "sharp-arrow"
            },
            {
              value: ARROW_TYPE.round,
              text: t("labels.arrowtype_round"),
              icon: roundArrowIcon,
              testId: "round-arrow"
            },
            {
              value: ARROW_TYPE.elbow,
              text: t("labels.arrowtype_elbowed"),
              icon: elbowArrowIcon,
              testId: "elbow-arrow"
            }
          ],
          value: getFormValue(
            elements,
            appState,
            (element) => {
              if (isArrowElement(element)) {
                return element.elbowed ? ARROW_TYPE.elbow : element.roundness ? ARROW_TYPE.round : ARROW_TYPE.sharp;
              }
              return null;
            },
            (element) => isArrowElement(element),
            (hasSelection) => hasSelection ? null : appState.currentItemArrowType
          ),
          onChange: (value) => updateData(value)
        }
      )
    ] });
  }
});

// scene/zoom.ts
var getStateForZoom = ({
  viewportX,
  viewportY,
  nextZoom
}, appState) => {
  const appLayerX = viewportX - appState.offsetLeft;
  const appLayerY = viewportY - appState.offsetTop;
  const currentZoom = appState.zoom.value;
  const baseScrollX = appState.scrollX + (appLayerX - appLayerX / currentZoom);
  const baseScrollY = appState.scrollY + (appLayerY - appLayerY / currentZoom);
  const zoomOffsetScrollX = -(appLayerX - appLayerX / nextZoom);
  const zoomOffsetScrollY = -(appLayerY - appLayerY / nextZoom);
  return {
    scrollX: baseScrollX + zoomOffsetScrollX,
    scrollY: baseScrollY + zoomOffsetScrollY,
    zoom: {
      value: nextZoom
    }
  };
};

// components/Tooltip.tsx
import { useEffect as useEffect16 } from "react";
import { jsx as jsx35 } from "react/jsx-runtime";
var getTooltipDiv = () => {
  const existingDiv = document.querySelector(
    ".excalidraw-tooltip"
  );
  if (existingDiv) {
    return existingDiv;
  }
  const div = document.createElement("div");
  document.body.appendChild(div);
  div.classList.add("excalidraw-tooltip");
  return div;
};
var updateTooltipPosition = (tooltip, item, position = "bottom") => {
  const tooltipRect = tooltip.getBoundingClientRect();
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  const margin = 5;
  let left = item.left + item.width / 2 - tooltipRect.width / 2;
  if (left < 0) {
    left = margin;
  } else if (left + tooltipRect.width >= viewportWidth) {
    left = viewportWidth - tooltipRect.width - margin;
  }
  let top;
  if (position === "bottom") {
    top = item.top + item.height + margin;
    if (top + tooltipRect.height >= viewportHeight) {
      top = item.top - tooltipRect.height - margin;
    }
  } else {
    top = item.top - tooltipRect.height - margin;
    if (top < 0) {
      top = item.top + item.height + margin;
    }
  }
  Object.assign(tooltip.style, {
    top: `${top}px`,
    left: `${left}px`
  });
};
var updateTooltip = (item, tooltip, label, long) => {
  tooltip.classList.add("excalidraw-tooltip--visible");
  tooltip.style.minWidth = long ? "50ch" : "10ch";
  tooltip.style.maxWidth = long ? "50ch" : "15ch";
  tooltip.textContent = label;
  const itemRect = item.getBoundingClientRect();
  updateTooltipPosition(tooltip, itemRect);
};
var Tooltip = ({
  children,
  label,
  long = false,
  style,
  disabled
}) => {
  useEffect16(() => {
    return () => getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
  }, []);
  if (disabled) {
    return null;
  }
  return /* @__PURE__ */ jsx35(
    "div",
    {
      className: "excalidraw-tooltip-wrapper",
      onPointerEnter: (event) => updateTooltip(
        event.currentTarget,
        getTooltipDiv(),
        label,
        long
      ),
      onPointerLeave: () => getTooltipDiv().classList.remove("excalidraw-tooltip--visible"),
      style,
      children
    }
  );
};

// actions/actionCanvas.tsx
import { jsx as jsx36, jsxs as jsxs21 } from "react/jsx-runtime";
var actionChangeViewBackgroundColor = register({
  name: "changeViewBackgroundColor",
  label: "labels.canvasBackground",
  paletteName: "Change canvas background color",
  trackEvent: false,
  predicate: (elements, appState, props, app) => {
    return !!app.props.UIOptions.canvasActions.changeViewBackgroundColor && !appState.viewModeEnabled;
  },
  perform: (_, appState, value) => {
    return {
      appState: { ...appState, ...value },
      captureUpdate: !!value.viewBackgroundColor ? CaptureUpdateAction.IMMEDIATELY : CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ elements, appState, updateData, appProps }) => {
    return /* @__PURE__ */ jsx36(
      ColorPicker,
      {
        palette: null,
        topPicks: DEFAULT_CANVAS_BACKGROUND_PICKS,
        label: t("labels.canvasBackground"),
        type: "canvasBackground",
        color: appState.viewBackgroundColor,
        onChange: (color) => updateData({ viewBackgroundColor: color }),
        "data-testid": "canvas-background-picker",
        elements,
        appState,
        updateData
      }
    );
  }
});
var actionClearCanvas = register({
  name: "clearCanvas",
  label: "labels.clearCanvas",
  paletteName: "Clear canvas",
  icon: TrashIcon,
  trackEvent: { category: "canvas" },
  predicate: (elements, appState, props, app) => {
    return !!app.props.UIOptions.canvasActions.clearCanvas && !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector";
  },
  perform: (elements, appState, _, app) => {
    app.imageCache.clear();
    return {
      elements: elements.map(
        (element) => newElementWith(element, { isDeleted: true })
      ),
      appState: {
        ...getDefaultAppState(),
        files: {},
        theme: appState.theme,
        penMode: appState.penMode,
        penDetected: appState.penDetected,
        exportBackground: appState.exportBackground,
        exportEmbedScene: appState.exportEmbedScene,
        gridSize: appState.gridSize,
        gridStep: appState.gridStep,
        gridModeEnabled: appState.gridModeEnabled,
        stats: appState.stats,
        pasteDialog: appState.pasteDialog,
        activeTool: appState.activeTool.type === "image" ? { ...appState.activeTool, type: "selection" } : appState.activeTool
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  }
});
var actionZoomIn = register({
  name: "zoomIn",
  label: "buttons.zoomIn",
  viewMode: true,
  icon: ZoomInIcon,
  trackEvent: { category: "canvas" },
  perform: (_elements, appState, _, app) => {
    return {
      appState: {
        ...appState,
        ...getStateForZoom(
          {
            viewportX: appState.width / 2 + appState.offsetLeft,
            viewportY: appState.height / 2 + appState.offsetTop,
            nextZoom: getNormalizedZoom(appState.zoom.value + ZOOM_STEP)
          },
          appState
        ),
        userToFollow: null
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx36(
    ToolButton,
    {
      type: "button",
      className: "zoom-in-button zoom-button",
      icon: ZoomInIcon,
      title: `${t("buttons.zoomIn")} \u2014 ${getShortcutKey("CtrlOrCmd++")}`,
      "aria-label": t("buttons.zoomIn"),
      disabled: appState.zoom.value >= MAX_ZOOM,
      onClick: () => {
        updateData(null);
      }
    }
  ),
  keyTest: (event) => (event.code === CODES.EQUAL || event.code === CODES.NUM_ADD) && (event[KEYS.CTRL_OR_CMD] || event.shiftKey)
});
var actionZoomOut = register({
  name: "zoomOut",
  label: "buttons.zoomOut",
  icon: ZoomOutIcon,
  viewMode: true,
  trackEvent: { category: "canvas" },
  perform: (_elements, appState, _, app) => {
    return {
      appState: {
        ...appState,
        ...getStateForZoom(
          {
            viewportX: appState.width / 2 + appState.offsetLeft,
            viewportY: appState.height / 2 + appState.offsetTop,
            nextZoom: getNormalizedZoom(appState.zoom.value - ZOOM_STEP)
          },
          appState
        ),
        userToFollow: null
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx36(
    ToolButton,
    {
      type: "button",
      className: "zoom-out-button zoom-button",
      icon: ZoomOutIcon,
      title: `${t("buttons.zoomOut")} \u2014 ${getShortcutKey("CtrlOrCmd+-")}`,
      "aria-label": t("buttons.zoomOut"),
      disabled: appState.zoom.value <= MIN_ZOOM,
      onClick: () => {
        updateData(null);
      }
    }
  ),
  keyTest: (event) => (event.code === CODES.MINUS || event.code === CODES.NUM_SUBTRACT) && (event[KEYS.CTRL_OR_CMD] || event.shiftKey)
});
var actionResetZoom = register({
  name: "resetZoom",
  label: "buttons.resetZoom",
  icon: ZoomResetIcon,
  viewMode: true,
  trackEvent: { category: "canvas" },
  perform: (_elements, appState, _, app) => {
    return {
      appState: {
        ...appState,
        ...getStateForZoom(
          {
            viewportX: appState.width / 2 + appState.offsetLeft,
            viewportY: appState.height / 2 + appState.offsetTop,
            nextZoom: getNormalizedZoom(1)
          },
          appState
        ),
        userToFollow: null
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ updateData, appState }) => /* @__PURE__ */ jsx36(Tooltip, { label: t("buttons.resetZoom"), style: { height: "100%" }, children: /* @__PURE__ */ jsxs21(
    ToolButton,
    {
      type: "button",
      className: "reset-zoom-button zoom-button",
      title: t("buttons.resetZoom"),
      "aria-label": t("buttons.resetZoom"),
      onClick: () => {
        updateData(null);
      },
      children: [
        (appState.zoom.value * 100).toFixed(0),
        "%"
      ]
    }
  ) }),
  keyTest: (event) => (event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) && (event[KEYS.CTRL_OR_CMD] || event.shiftKey)
});
var zoomValueToFitBoundsOnViewport = (bounds, viewportDimensions, viewportZoomFactor = 1) => {
  const [x1, y1, x2, y2] = bounds;
  const commonBoundsWidth = x2 - x1;
  const zoomValueForWidth = viewportDimensions.width / commonBoundsWidth;
  const commonBoundsHeight = y2 - y1;
  const zoomValueForHeight = viewportDimensions.height / commonBoundsHeight;
  const smallestZoomValue = Math.min(zoomValueForWidth, zoomValueForHeight);
  const adjustedZoomValue = smallestZoomValue * clamp(viewportZoomFactor, 0.1, 1);
  return Math.min(adjustedZoomValue, 1);
};
var zoomToFitBounds = ({
  bounds,
  appState,
  canvasOffsets,
  fitToViewport = false,
  viewportZoomFactor = 1,
  minZoom = -Infinity,
  maxZoom = Infinity
}) => {
  viewportZoomFactor = clamp(viewportZoomFactor, MIN_ZOOM, MAX_ZOOM);
  const [x1, y1, x2, y2] = bounds;
  const centerX = (x1 + x2) / 2;
  const centerY = (y1 + y2) / 2;
  const canvasOffsetLeft = canvasOffsets?.left ?? 0;
  const canvasOffsetTop = canvasOffsets?.top ?? 0;
  const canvasOffsetRight = canvasOffsets?.right ?? 0;
  const canvasOffsetBottom = canvasOffsets?.bottom ?? 0;
  const effectiveCanvasWidth = appState.width - canvasOffsetLeft - canvasOffsetRight;
  const effectiveCanvasHeight = appState.height - canvasOffsetTop - canvasOffsetBottom;
  let adjustedZoomValue;
  if (fitToViewport) {
    const commonBoundsWidth = x2 - x1;
    const commonBoundsHeight = y2 - y1;
    adjustedZoomValue = Math.min(
      effectiveCanvasWidth / commonBoundsWidth,
      effectiveCanvasHeight / commonBoundsHeight
    ) * viewportZoomFactor;
  } else {
    adjustedZoomValue = zoomValueToFitBoundsOnViewport(
      bounds,
      {
        width: effectiveCanvasWidth,
        height: effectiveCanvasHeight
      },
      viewportZoomFactor
    );
  }
  const newZoomValue = getNormalizedZoom(
    clamp(roundToStep(adjustedZoomValue, ZOOM_STEP, "floor"), minZoom, maxZoom)
  );
  const centerScroll = centerScrollOn({
    scenePoint: { x: centerX, y: centerY },
    viewportDimensions: {
      width: appState.width,
      height: appState.height
    },
    offsets: canvasOffsets,
    zoom: { value: newZoomValue }
  });
  return {
    appState: {
      ...appState,
      scrollX: centerScroll.scrollX,
      scrollY: centerScroll.scrollY,
      zoom: { value: newZoomValue }
    },
    captureUpdate: CaptureUpdateAction.EVENTUALLY
  };
};
var zoomToFit = ({
  canvasOffsets,
  targetElements,
  appState,
  fitToViewport,
  viewportZoomFactor,
  minZoom,
  maxZoom
}) => {
  const commonBounds = getCommonBounds(getNonDeletedElements(targetElements));
  return zoomToFitBounds({
    canvasOffsets,
    bounds: commonBounds,
    appState,
    fitToViewport,
    viewportZoomFactor,
    minZoom,
    maxZoom
  });
};
var actionZoomToFitSelectionInViewport = register({
  name: "zoomToFitSelectionInViewport",
  label: "labels.zoomToFitViewport",
  icon: zoomAreaIcon,
  trackEvent: { category: "canvas" },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    return zoomToFit({
      targetElements: selectedElements.length ? selectedElements : elements,
      appState: {
        ...appState,
        userToFollow: null
      },
      fitToViewport: false,
      canvasOffsets: app.getEditorUIOffsets()
    });
  },
  // NOTE shift-2 should have been assigned actionZoomToFitSelection.
  // TBD on how proceed
  keyTest: (event) => event.code === CODES.TWO && event.shiftKey && !event.altKey && !event[KEYS.CTRL_OR_CMD]
});
var actionZoomToFitSelection = register({
  name: "zoomToFitSelection",
  label: "helpDialog.zoomToSelection",
  icon: zoomAreaIcon,
  trackEvent: { category: "canvas" },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    return zoomToFit({
      targetElements: selectedElements.length ? selectedElements : elements,
      appState: {
        ...appState,
        userToFollow: null
      },
      fitToViewport: true,
      canvasOffsets: app.getEditorUIOffsets()
    });
  },
  // NOTE this action should use shift-2 per figma, alas
  keyTest: (event) => event.code === CODES.THREE && event.shiftKey && !event.altKey && !event[KEYS.CTRL_OR_CMD]
});
var actionZoomToFit = register({
  name: "zoomToFit",
  label: "helpDialog.zoomToFit",
  icon: zoomAreaIcon,
  viewMode: true,
  trackEvent: { category: "canvas" },
  perform: (elements, appState, _, app) => zoomToFit({
    targetElements: elements,
    appState: {
      ...appState,
      userToFollow: null
    },
    fitToViewport: false,
    canvasOffsets: app.getEditorUIOffsets()
  }),
  keyTest: (event) => event.code === CODES.ONE && event.shiftKey && !event.altKey && !event[KEYS.CTRL_OR_CMD]
});
var actionToggleTheme = register({
  name: "toggleTheme",
  label: (_, appState) => {
    return appState.theme === THEME.DARK ? "buttons.lightMode" : "buttons.darkMode";
  },
  keywords: ["toggle", "dark", "light", "mode", "theme"],
  icon: (appState) => appState.theme === THEME.LIGHT ? MoonIcon : SunIcon,
  viewMode: true,
  trackEvent: { category: "canvas" },
  perform: (_, appState, value) => {
    return {
      appState: {
        ...appState,
        theme: value || (appState.theme === THEME.LIGHT ? THEME.DARK : THEME.LIGHT)
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  keyTest: (event) => event.altKey && event.shiftKey && event.code === CODES.D,
  predicate: (elements, appState, props, app) => {
    return !!app.props.UIOptions.canvasActions.toggleTheme;
  }
});
var actionToggleEraserTool = register({
  name: "toggleEraserTool",
  label: "toolBar.eraser",
  trackEvent: { category: "toolbar" },
  perform: (elements, appState) => {
    let activeTool;
    if (isEraserActive(appState)) {
      activeTool = updateActiveTool(appState, {
        ...appState.activeTool.lastActiveTool || {
          type: "selection"
        },
        lastActiveToolBeforeEraser: null
      });
    } else {
      activeTool = updateActiveTool(appState, {
        type: "eraser",
        lastActiveToolBeforeEraser: appState.activeTool
      });
    }
    return {
      appState: {
        ...appState,
        selectedElementIds: {},
        selectedGroupIds: {},
        activeEmbeddable: null,
        activeTool
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event.key === KEYS.E
});
var actionToggleHandTool = register({
  name: "toggleHandTool",
  label: "toolBar.hand",
  paletteName: "Toggle hand tool",
  trackEvent: { category: "toolbar" },
  icon: handIcon,
  viewMode: false,
  perform: (elements, appState, _, app) => {
    let activeTool;
    if (isHandToolActive(appState)) {
      activeTool = updateActiveTool(appState, {
        ...appState.activeTool.lastActiveTool || {
          type: "selection"
        },
        lastActiveToolBeforeEraser: null
      });
    } else {
      activeTool = updateActiveTool(appState, {
        type: "hand",
        lastActiveToolBeforeEraser: appState.activeTool
      });
      setCursor(app.interactiveCanvas, CURSOR_TYPE.GRAB);
    }
    return {
      appState: {
        ...appState,
        selectedElementIds: {},
        selectedGroupIds: {},
        activeEmbeddable: null,
        activeTool
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => !event.altKey && !event[KEYS.CTRL_OR_CMD] && event.key === KEYS.H
});

// actions/actionFinalize.tsx
import { jsx as jsx37 } from "react/jsx-runtime";
var actionFinalize = register({
  name: "finalize",
  label: "",
  trackEvent: false,
  perform: (elements, appState, _, app) => {
    const { interactiveCanvas, focusContainer, scene } = app;
    const elementsMap = scene.getNonDeletedElementsMap();
    if (appState.editingLinearElement) {
      const { elementId, startBindingElement, endBindingElement } = appState.editingLinearElement;
      const element = LinearElementEditor.getElement(elementId, elementsMap);
      if (element) {
        if (isBindingElement(element)) {
          bindOrUnbindLinearElement(
            element,
            startBindingElement,
            endBindingElement,
            elementsMap,
            scene
          );
        }
        return {
          elements: element.points.length < 2 || isInvisiblySmallElement(element) ? elements.filter((el) => el.id !== element.id) : void 0,
          appState: {
            ...appState,
            cursorButton: "up",
            editingLinearElement: null
          },
          captureUpdate: CaptureUpdateAction.IMMEDIATELY
        };
      }
    }
    let newElements = elements;
    const pendingImageElement = appState.pendingImageElementId && scene.getElement(appState.pendingImageElementId);
    if (pendingImageElement) {
      mutateElement(pendingImageElement, { isDeleted: true }, false);
    }
    if (window.document.activeElement instanceof HTMLElement) {
      focusContainer();
    }
    const multiPointElement = appState.multiElement ? appState.multiElement : appState.newElement?.type === "freedraw" ? appState.newElement : null;
    if (multiPointElement) {
      if (multiPointElement.type !== "freedraw" && appState.lastPointerDownWith !== "touch") {
        const { points, lastCommittedPoint } = multiPointElement;
        if (!lastCommittedPoint || points[points.length - 1] !== lastCommittedPoint) {
          mutateElement(multiPointElement, {
            points: multiPointElement.points.slice(0, -1)
          });
        }
      }
      if (isInvisiblySmallElement(multiPointElement)) {
        newElements = newElements.filter(
          (el) => el.id !== multiPointElement.id
        );
      }
      const isLoop = isPathALoop(multiPointElement.points, appState.zoom.value);
      if (multiPointElement.type === "line" || multiPointElement.type === "freedraw") {
        if (isLoop) {
          const linePoints = multiPointElement.points;
          const firstPoint = linePoints[0];
          mutateElement(multiPointElement, {
            points: linePoints.map(
              (p, index) => index === linePoints.length - 1 ? pointFrom(firstPoint[0], firstPoint[1]) : p
            )
          });
        }
      }
      if (isBindingElement(multiPointElement) && !isLoop && multiPointElement.points.length > 1) {
        const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
          multiPointElement,
          -1,
          arrayToMap(elements)
        );
        maybeBindLinearElement(
          multiPointElement,
          appState,
          { x, y },
          elementsMap,
          elements
        );
      }
    }
    if (!appState.activeTool.locked && appState.activeTool.type !== "freedraw" || !multiPointElement) {
      resetCursor(interactiveCanvas);
    }
    let activeTool;
    if (appState.activeTool.type === "eraser") {
      activeTool = updateActiveTool(appState, {
        ...appState.activeTool.lastActiveTool || {
          type: "selection"
        },
        lastActiveToolBeforeEraser: null
      });
    } else {
      activeTool = updateActiveTool(appState, {
        type: "selection"
      });
    }
    return {
      elements: newElements,
      appState: {
        ...appState,
        cursorButton: "up",
        activeTool: (appState.activeTool.locked || appState.activeTool.type === "freedraw") && multiPointElement ? appState.activeTool : activeTool,
        activeEmbeddable: null,
        newElement: null,
        selectionElement: null,
        multiElement: null,
        editingTextElement: null,
        startBoundElement: null,
        suggestedBindings: [],
        selectedElementIds: multiPointElement && !appState.activeTool.locked && appState.activeTool.type !== "freedraw" ? {
          ...appState.selectedElementIds,
          [multiPointElement.id]: true
        } : appState.selectedElementIds,
        // To select the linear element when user has finished mutipoint editing
        selectedLinearElement: multiPointElement && isLinearElement(multiPointElement) ? new LinearElementEditor(multiPointElement) : appState.selectedLinearElement,
        pendingImageElementId: null
      },
      // TODO: #7348 we should not capture everything, but if we don't, it leads to incosistencies -> revisit
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event, appState) => event.key === KEYS.ESCAPE && (appState.editingLinearElement !== null || !appState.newElement && appState.multiElement === null) || (event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) && appState.multiElement !== null,
  PanelComponent: ({ appState, updateData, data }) => /* @__PURE__ */ jsx37(
    ToolButton,
    {
      type: "button",
      icon: done,
      title: t("buttons.done"),
      "aria-label": t("buttons.done"),
      onClick: updateData,
      visible: appState.multiElement != null,
      size: data?.size || "medium",
      style: { pointerEvents: "all" }
    }
  )
});

// components/ProjectName.tsx
import { useState as useState9 } from "react";
import { jsx as jsx38, jsxs as jsxs22 } from "react/jsx-runtime";
var ProjectName = (props) => {
  const { id } = useExcalidrawContainer();
  const [fileName, setFileName] = useState9(props.value);
  const handleBlur = (event) => {
    if (!props.ignoreFocus) {
      focusNearestParent(event.target);
    }
    const value = event.target.value;
    if (value !== props.value) {
      props.onChange(value);
    }
  };
  const handleKeyDown = (event) => {
    if (event.key === KEYS.ENTER) {
      event.preventDefault();
      if (event.nativeEvent.isComposing || event.keyCode === 229) {
        return;
      }
      event.currentTarget.blur();
    }
  };
  return /* @__PURE__ */ jsxs22("div", { className: "ProjectName", children: [
    /* @__PURE__ */ jsx38("label", { className: "ProjectName-label", htmlFor: "filename", children: `${props.label}:` }),
    /* @__PURE__ */ jsx38(
      "input",
      {
        type: "text",
        className: "TextInput",
        onBlur: handleBlur,
        onKeyDown: handleKeyDown,
        id: `${id}-filename`,
        value: fileName,
        onChange: (event) => setFileName(event.target.value)
      }
    )
  ] });
};

// components/DarkModeToggle.tsx
import { jsx as jsx39 } from "react/jsx-runtime";
var DarkModeToggle = (props) => {
  const title = props.title || (props.value === THEME.DARK ? t("buttons.lightMode") : t("buttons.darkMode"));
  return /* @__PURE__ */ jsx39(
    ToolButton,
    {
      type: "icon",
      icon: props.value === THEME.LIGHT ? ICONS.MOON : ICONS.SUN,
      title,
      "aria-label": title,
      onClick: () => props.onChange(props.value === THEME.DARK ? THEME.LIGHT : THEME.DARK),
      "data-testid": "toggle-dark-mode"
    }
  );
};
var ICONS = {
  SUN: /* @__PURE__ */ jsx39("svg", { width: "512", height: "512", className: "rtl-mirror", viewBox: "0 0 512 512", children: /* @__PURE__ */ jsx39(
    "path",
    {
      fill: "currentColor",
      d: "M256 160c-52.9 0-96 43.1-96 96s43.1 96 96 96 96-43.1 96-96-43.1-96-96-96zm246.4 80.5l-94.7-47.3 33.5-100.4c4.5-13.6-8.4-26.5-21.9-21.9l-100.4 33.5-47.4-94.8c-6.4-12.8-24.6-12.8-31 0l-47.3 94.7L92.7 70.8c-13.6-4.5-26.5 8.4-21.9 21.9l33.5 100.4-94.7 47.4c-12.8 6.4-12.8 24.6 0 31l94.7 47.3-33.5 100.5c-4.5 13.6 8.4 26.5 21.9 21.9l100.4-33.5 47.3 94.7c6.4 12.8 24.6 12.8 31 0l47.3-94.7 100.4 33.5c13.6 4.5 26.5-8.4 21.9-21.9l-33.5-100.4 94.7-47.3c13-6.5 13-24.7.2-31.1zm-155.9 106c-49.9 49.9-131.1 49.9-181 0-49.9-49.9-49.9-131.1 0-181 49.9-49.9 131.1-49.9 181 0 49.9 49.9 49.9 131.1 0 181z"
    }
  ) }),
  MOON: /* @__PURE__ */ jsx39("svg", { width: "512", height: "512", className: "rtl-mirror", viewBox: "0 0 512 512", children: /* @__PURE__ */ jsx39(
    "path",
    {
      fill: "currentColor",
      d: "M283.211 512c78.962 0 151.079-35.925 198.857-94.792 7.068-8.708-.639-21.43-11.562-19.35-124.203 23.654-238.262-71.576-238.262-196.954 0-72.222 38.662-138.635 101.498-174.394 9.686-5.512 7.25-20.197-3.756-22.23A258.156 258.156 0 0 0 283.211 0c-141.309 0-256 114.511-256 256 0 141.309 114.511 256 256 256z"
    }
  ) })
};

// data/index.ts
var prepareElementsForExport = (elements, { selectedElementIds }, exportSelectionOnly) => {
  elements = getNonDeletedElements(elements);
  const isExportingSelection = exportSelectionOnly && isSomeElementSelected(elements, { selectedElementIds });
  let exportingFrame = null;
  let exportedElements = isExportingSelection ? getSelectedElements(
    elements,
    { selectedElementIds },
    {
      includeBoundTextElement: true
    }
  ) : elements;
  if (isExportingSelection) {
    if (exportedElements.length === 1 && isFrameLikeElement(exportedElements[0])) {
      exportingFrame = exportedElements[0];
      exportedElements = getElementsOverlappingFrame(elements, exportingFrame);
    } else if (exportedElements.length > 1) {
      exportedElements = getSelectedElements(
        elements,
        { selectedElementIds },
        {
          includeBoundTextElement: true,
          includeElementsInFrames: true
        }
      );
    }
  }
  return {
    exportingFrame,
    exportedElements: cloneJSON(exportedElements)
  };
};
var exportCanvas = async (type, elements, appState, files, {
  exportBackground,
  exportPadding = DEFAULT_EXPORT_PADDING,
  viewBackgroundColor,
  name = appState.name || DEFAULT_FILENAME,
  fileHandle = null,
  exportingFrame = null
}) => {
  if (elements.length === 0) {
    throw new Error(t("alerts.cannotExportEmptyCanvas"));
  }
  if (type === "svg" || type === "clipboard-svg") {
    const svgPromise = exportToSvg(
      elements,
      {
        exportBackground,
        exportWithDarkMode: appState.exportWithDarkMode,
        viewBackgroundColor,
        exportPadding,
        exportScale: appState.exportScale,
        exportEmbedScene: appState.exportEmbedScene && type === "svg"
      },
      files,
      { exportingFrame }
    );
    if (type === "svg") {
      return fileSave(
        svgPromise.then((svg) => {
          return new Blob([svg.outerHTML], { type: MIME_TYPES.svg });
        }),
        {
          description: "Export to SVG",
          name,
          extension: appState.exportEmbedScene ? "excalidraw.svg" : "svg",
          mimeTypes: [IMAGE_MIME_TYPES.svg],
          fileHandle
        }
      );
    } else if (type === "clipboard-svg") {
      const svg = await svgPromise.then((svg2) => svg2.outerHTML);
      try {
        await copyTextToSystemClipboard(svg);
      } catch (e) {
        throw new Error(t("errors.copyToSystemClipboardFailed"));
      }
      return;
    }
  }
  const tempCanvas = exportToCanvas(elements, appState, files, {
    exportBackground,
    viewBackgroundColor,
    exportPadding,
    exportingFrame
  });
  if (type === "png") {
    let blob = canvasToBlob(tempCanvas);
    if (appState.exportEmbedScene) {
      blob = blob.then(
        (blob2) => import("./data/image-WB3WPX2D.js").then(
          ({ encodePngMetadata }) => encodePngMetadata({
            blob: blob2,
            metadata: serializeAsJSON(elements, appState, files, "local")
          })
        )
      );
    }
    return fileSave(blob, {
      description: "Export to PNG",
      name,
      extension: appState.exportEmbedScene ? "excalidraw.png" : "png",
      mimeTypes: [IMAGE_MIME_TYPES.png],
      fileHandle
    });
  } else if (type === "clipboard") {
    try {
      const blob = canvasToBlob(tempCanvas);
      await copyBlobToClipboardAsPng(blob);
    } catch (error) {
      console.warn(error);
      if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
        throw new Error(t("canvasError.canvasTooBig"));
      }
      if (isFirefox && error.name === "TypeError") {
        throw new Error(
          `${t("alerts.couldNotCopyToClipboard")}

${t(
            "hints.firefox_clipboard_write"
          )}`
        );
      } else {
        throw new Error(t("alerts.couldNotCopyToClipboard"));
      }
    }
  } else {
    throw new Error("Unsupported export type");
  }
};

// data/resave.ts
var resaveAsImageWithScene = async (elements, appState, files, name) => {
  const { exportBackground, viewBackgroundColor, fileHandle } = appState;
  const fileHandleType = getFileHandleType(fileHandle);
  if (!fileHandle || !isImageFileHandleType(fileHandleType)) {
    throw new Error(
      "fileHandle should exist and should be of type svg or png when resaving"
    );
  }
  appState = {
    ...appState,
    exportEmbedScene: true
  };
  const { exportedElements, exportingFrame } = prepareElementsForExport(
    elements,
    appState,
    false
  );
  await exportCanvas(fileHandleType, exportedElements, appState, files, {
    exportBackground,
    viewBackgroundColor,
    name,
    fileHandle,
    exportingFrame
  });
  return { fileHandle };
};

// components/CheckboxItem.tsx
import clsx16 from "clsx";
import { jsx as jsx40, jsxs as jsxs23 } from "react/jsx-runtime";
var CheckboxItem = ({ children, checked, onChange, className }) => {
  return /* @__PURE__ */ jsxs23(
    "div",
    {
      className: clsx16("Checkbox", className, { "is-checked": checked }),
      onClick: (event) => {
        onChange(!checked, event);
        event.currentTarget.querySelector(
          ".Checkbox-box"
        ).focus();
      },
      children: [
        /* @__PURE__ */ jsx40(
          "button",
          {
            type: "button",
            className: "Checkbox-box",
            role: "checkbox",
            "aria-checked": checked,
            children: checkIcon
          }
        ),
        /* @__PURE__ */ jsx40("div", { className: "Checkbox-label", children })
      ]
    }
  );
};

// actions/actionExport.tsx
import { Fragment as Fragment5, jsx as jsx41, jsxs as jsxs24 } from "react/jsx-runtime";
var actionChangeProjectName = register({
  name: "changeProjectName",
  label: "labels.fileTitle",
  trackEvent: false,
  perform: (_elements, appState, value) => {
    return {
      appState: { ...appState, name: value },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ appState, updateData, appProps, data, app }) => /* @__PURE__ */ jsx41(
    ProjectName,
    {
      label: t("labels.fileTitle"),
      value: app.getName(),
      onChange: (name) => updateData(name),
      ignoreFocus: data?.ignoreFocus ?? false
    }
  )
});
var actionChangeExportScale = register({
  name: "changeExportScale",
  label: "imageExportDialog.scale",
  trackEvent: { category: "export", action: "scale" },
  perform: (_elements, appState, value) => {
    return {
      appState: { ...appState, exportScale: value },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ elements: allElements, appState, updateData }) => {
    const elements = getNonDeletedElements(allElements);
    const exportSelected = isSomeElementSelected(elements, appState);
    const exportedElements = exportSelected ? getSelectedElements(elements, appState) : elements;
    return /* @__PURE__ */ jsx41(Fragment5, { children: EXPORT_SCALES.map((s) => {
      const [width, height] = getExportSize(
        exportedElements,
        DEFAULT_EXPORT_PADDING,
        s
      );
      const scaleButtonTitle = `${t(
        "imageExportDialog.label.scale"
      )} ${s}x (${width}x${height})`;
      return /* @__PURE__ */ jsx41(
        ToolButton,
        {
          size: "small",
          type: "radio",
          icon: `${s}x`,
          name: "export-canvas-scale",
          title: scaleButtonTitle,
          "aria-label": scaleButtonTitle,
          id: "export-canvas-scale",
          checked: s === appState.exportScale,
          onChange: () => updateData(s)
        },
        s
      );
    }) });
  }
});
var actionChangeExportBackground = register({
  name: "changeExportBackground",
  label: "imageExportDialog.label.withBackground",
  trackEvent: { category: "export", action: "toggleBackground" },
  perform: (_elements, appState, value) => {
    return {
      appState: { ...appState, exportBackground: value },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ appState, updateData }) => /* @__PURE__ */ jsx41(
    CheckboxItem,
    {
      checked: appState.exportBackground,
      onChange: (checked) => updateData(checked),
      children: t("imageExportDialog.label.withBackground")
    }
  )
});
var actionChangeExportEmbedScene = register({
  name: "changeExportEmbedScene",
  label: "imageExportDialog.tooltip.embedScene",
  trackEvent: { category: "export", action: "embedScene" },
  perform: (_elements, appState, value) => {
    return {
      appState: { ...appState, exportEmbedScene: value },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ appState, updateData }) => /* @__PURE__ */ jsxs24(
    CheckboxItem,
    {
      checked: appState.exportEmbedScene,
      onChange: (checked) => updateData(checked),
      children: [
        t("imageExportDialog.label.embedScene"),
        /* @__PURE__ */ jsx41(Tooltip, { label: t("imageExportDialog.tooltip.embedScene"), long: true, children: /* @__PURE__ */ jsx41("div", { className: "excalidraw-tooltip-icon", children: questionCircle }) })
      ]
    }
  )
});
var actionSaveToActiveFile = register({
  name: "saveToActiveFile",
  label: "buttons.save",
  icon: ExportIcon,
  trackEvent: { category: "export" },
  predicate: (elements, appState, props, app) => {
    return !!app.props.UIOptions.canvasActions.saveToActiveFile && !!appState.fileHandle && !appState.viewModeEnabled;
  },
  perform: async (elements, appState, value, app) => {
    const fileHandleExists = !!appState.fileHandle;
    try {
      const { fileHandle } = isImageFileHandle(appState.fileHandle) ? await resaveAsImageWithScene(
        elements,
        appState,
        app.files,
        app.getName()
      ) : await saveAsJSON(elements, appState, app.files, app.getName());
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          fileHandle,
          toast: fileHandleExists ? {
            message: fileHandle?.name ? t("toast.fileSavedToFilename").replace(
              "{filename}",
              `"${fileHandle.name}"`
            ) : t("toast.fileSaved")
          } : null
        }
      };
    } catch (error) {
      if (error?.name !== "AbortError") {
        console.error(error);
      } else {
        console.warn(error);
      }
      return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
    }
  },
  keyTest: (event) => event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey
});
var actionSaveFileToDisk = register({
  name: "saveFileToDisk",
  label: "exportDialog.disk_title",
  icon: ExportIcon,
  viewMode: true,
  trackEvent: { category: "export" },
  perform: async (elements, appState, value, app) => {
    try {
      const { fileHandle } = await saveAsJSON(
        elements,
        {
          ...appState,
          fileHandle: null
        },
        app.files,
        app.getName()
      );
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          openDialog: null,
          fileHandle,
          toast: { message: t("toast.fileSaved") }
        }
      };
    } catch (error) {
      if (error?.name !== "AbortError") {
        console.error(error);
      } else {
        console.warn(error);
      }
      return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
    }
  },
  keyTest: (event) => event.key === KEYS.S && event.shiftKey && event[KEYS.CTRL_OR_CMD],
  PanelComponent: ({ updateData }) => /* @__PURE__ */ jsx41(
    ToolButton,
    {
      type: "button",
      icon: saveAs,
      title: t("buttons.saveAs"),
      "aria-label": t("buttons.saveAs"),
      showAriaLabel: useDevice().editor.isMobile,
      hidden: !nativeFileSystemSupported,
      onClick: () => updateData(null),
      "data-testid": "save-as-button"
    }
  )
});
var actionLoadScene = register({
  name: "loadScene",
  label: "buttons.load",
  trackEvent: { category: "export" },
  predicate: (elements, appState, props, app) => {
    return !!app.props.UIOptions.canvasActions.loadScene && !appState.viewModeEnabled;
  },
  perform: async (elements, appState, _, app) => {
    try {
      const {
        elements: loadedElements,
        appState: loadedAppState,
        files
      } = await loadFromJSON(appState, elements);
      return {
        elements: loadedElements,
        appState: loadedAppState,
        files,
        captureUpdate: CaptureUpdateAction.IMMEDIATELY
      };
    } catch (error) {
      if (error?.name === "AbortError") {
        console.warn(error);
        return false;
      }
      return {
        elements,
        appState: { ...appState, errorMessage: error.message },
        files: app.files,
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O
});
var actionExportWithDarkMode = register({
  name: "exportWithDarkMode",
  label: "imageExportDialog.label.darkMode",
  trackEvent: { category: "export", action: "toggleTheme" },
  perform: (_elements, appState, value) => {
    return {
      appState: { ...appState, exportWithDarkMode: value },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ appState, updateData }) => /* @__PURE__ */ jsx41(
    "div",
    {
      style: {
        display: "flex",
        justifyContent: "flex-end",
        marginTop: "-45px",
        marginBottom: "10px"
      },
      children: /* @__PURE__ */ jsx41(
        DarkModeToggle,
        {
          value: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
          onChange: (theme) => {
            updateData(theme === THEME.DARK);
          },
          title: t("imageExportDialog.label.darkMode")
        }
      )
    }
  )
});

// actions/actionStyles.ts
var copiedStyles = "{}";
var actionCopyStyles = register({
  name: "copyStyles",
  label: "labels.copyStyles",
  icon: paintIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState, formData, app) => {
    const elementsCopied = [];
    const element = elements.find((el) => appState.selectedElementIds[el.id]);
    elementsCopied.push(element);
    if (element && hasBoundTextElement(element)) {
      const boundTextElement = getBoundTextElement(
        element,
        app.scene.getNonDeletedElementsMap()
      );
      elementsCopied.push(boundTextElement);
    }
    if (element) {
      copiedStyles = JSON.stringify(elementsCopied);
    }
    return {
      appState: {
        ...appState,
        toast: { message: t("toast.copyStyles") }
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.C
});
var actionPasteStyles = register({
  name: "pasteStyles",
  label: "labels.pasteStyles",
  icon: paintIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState, formData, app) => {
    const elementsCopied = JSON.parse(copiedStyles);
    const pastedElement = elementsCopied[0];
    const boundTextElement = elementsCopied[1];
    if (!isExcalidrawElement(pastedElement)) {
      return { elements, captureUpdate: CaptureUpdateAction.EVENTUALLY };
    }
    const selectedElements = getSelectedElements(elements, appState, {
      includeBoundTextElement: true
    });
    const selectedElementIds = selectedElements.map((element) => element.id);
    return {
      elements: elements.map((element) => {
        if (selectedElementIds.includes(element.id)) {
          let elementStylesToCopyFrom = pastedElement;
          if (isTextElement(element) && element.containerId) {
            elementStylesToCopyFrom = boundTextElement;
          }
          if (!elementStylesToCopyFrom) {
            return element;
          }
          let newElement2 = newElementWith(element, {
            backgroundColor: elementStylesToCopyFrom?.backgroundColor,
            strokeWidth: elementStylesToCopyFrom?.strokeWidth,
            strokeColor: elementStylesToCopyFrom?.strokeColor,
            strokeStyle: elementStylesToCopyFrom?.strokeStyle,
            fillStyle: elementStylesToCopyFrom?.fillStyle,
            opacity: elementStylesToCopyFrom?.opacity,
            roughness: elementStylesToCopyFrom?.roughness,
            roundness: elementStylesToCopyFrom.roundness ? canApplyRoundnessTypeToElement(
              elementStylesToCopyFrom.roundness.type,
              element
            ) ? elementStylesToCopyFrom.roundness : getDefaultRoundnessTypeForElement(element) : null
          });
          if (isTextElement(newElement2)) {
            const fontSize = elementStylesToCopyFrom.fontSize || DEFAULT_FONT_SIZE;
            const fontFamily = elementStylesToCopyFrom.fontFamily || DEFAULT_FONT_FAMILY;
            newElement2 = newElementWith(newElement2, {
              fontSize,
              fontFamily,
              textAlign: elementStylesToCopyFrom.textAlign || DEFAULT_TEXT_ALIGN,
              lineHeight: elementStylesToCopyFrom.lineHeight || getLineHeight(fontFamily)
            });
            let container = null;
            if (newElement2.containerId) {
              container = selectedElements.find(
                (element2) => isTextElement(newElement2) && element2.id === newElement2.containerId
              ) || null;
            }
            redrawTextBoundingBox(
              newElement2,
              container,
              app.scene.getNonDeletedElementsMap()
            );
          }
          if (newElement2.type === "arrow" && isArrowElement(elementStylesToCopyFrom)) {
            newElement2 = newElementWith(newElement2, {
              startArrowhead: elementStylesToCopyFrom.startArrowhead,
              endArrowhead: elementStylesToCopyFrom.endArrowhead
            });
          }
          if (isFrameLikeElement(element)) {
            newElement2 = newElementWith(newElement2, {
              roundness: null,
              backgroundColor: "transparent"
            });
          }
          return newElement2;
        }
        return element;
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V
});

// actions/actionMenu.tsx
import { jsx as jsx42 } from "react/jsx-runtime";
var actionToggleCanvasMenu = register({
  name: "toggleCanvasMenu",
  label: "buttons.menu",
  trackEvent: { category: "menu" },
  perform: (_, appState) => ({
    appState: {
      ...appState,
      openMenu: appState.openMenu === "canvas" ? null : "canvas"
    },
    captureUpdate: CaptureUpdateAction.EVENTUALLY
  }),
  PanelComponent: ({ appState, updateData }) => /* @__PURE__ */ jsx42(
    ToolButton,
    {
      type: "button",
      icon: HamburgerMenuIcon,
      "aria-label": t("buttons.menu"),
      onClick: updateData,
      selected: appState.openMenu === "canvas"
    }
  )
});
var actionToggleEditMenu = register({
  name: "toggleEditMenu",
  label: "buttons.edit",
  trackEvent: { category: "menu" },
  perform: (_elements, appState) => ({
    appState: {
      ...appState,
      openMenu: appState.openMenu === "shape" ? null : "shape"
    },
    captureUpdate: CaptureUpdateAction.EVENTUALLY
  }),
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsx42(
    ToolButton,
    {
      visible: showSelectedShapeActions(
        appState,
        getNonDeletedElements(elements)
      ),
      type: "button",
      icon: palette,
      "aria-label": t("buttons.edit"),
      onClick: updateData,
      selected: appState.openMenu === "shape"
    }
  )
});
var actionShortcuts = register({
  name: "toggleShortcuts",
  label: "welcomeScreen.defaults.helpHint",
  icon: HelpIconThin,
  viewMode: true,
  trackEvent: { category: "menu", action: "toggleHelpDialog" },
  perform: (_elements, appState, _, { focusContainer }) => {
    if (appState.openDialog?.name === "help") {
      focusContainer();
    }
    return {
      appState: {
        ...appState,
        openDialog: appState.openDialog?.name === "help" ? null : {
          name: "help"
        }
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  keyTest: (event) => event.key === KEYS.QUESTION_MARK
});

// actions/actionGroup.tsx
import { jsx as jsx43 } from "react/jsx-runtime";
var allElementsInSameGroup = (elements) => {
  if (elements.length >= 2) {
    const groupIds = elements[0].groupIds;
    for (const groupId of groupIds) {
      if (elements.reduce(
        (acc, element) => acc && isElementInGroup(element, groupId),
        true
      )) {
        return true;
      }
    }
  }
  return false;
};
var enableActionGroup = (elements, appState, app) => {
  const selectedElements = app.scene.getSelectedElements({
    selectedElementIds: appState.selectedElementIds,
    includeBoundTextElement: true
  });
  return selectedElements.length >= 2 && !allElementsInSameGroup(selectedElements) && !frameAndChildrenSelectedTogether(selectedElements);
};
var actionGroup = register({
  name: "group",
  label: "labels.group",
  icon: (appState) => /* @__PURE__ */ jsx43(GroupIcon, { theme: appState.theme }),
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    const selectedElements = getRootElements(
      app.scene.getSelectedElements({
        selectedElementIds: appState.selectedElementIds,
        includeBoundTextElement: true
      })
    );
    if (selectedElements.length < 2) {
      return {
        appState,
        elements,
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
    const selectedGroupIds = getSelectedGroupIds(appState);
    if (selectedGroupIds.length === 1) {
      const selectedGroupId = selectedGroupIds[0];
      const elementIdsInGroup = new Set(
        getElementsInGroup(elements, selectedGroupId).map(
          (element) => element.id
        )
      );
      const selectedElementIds = new Set(
        selectedElements.map((element) => element.id)
      );
      const combinedSet = /* @__PURE__ */ new Set([
        ...Array.from(elementIdsInGroup),
        ...Array.from(selectedElementIds)
      ]);
      if (combinedSet.size === elementIdsInGroup.size) {
        return {
          appState,
          elements,
          captureUpdate: CaptureUpdateAction.EVENTUALLY
        };
      }
    }
    let nextElements = [...elements];
    const groupingElementsFromDifferentFrames = new Set(selectedElements.map((element) => element.frameId)).size > 1;
    if (groupingElementsFromDifferentFrames) {
      const frameElementsMap = groupByFrameLikes(selectedElements);
      frameElementsMap.forEach((elementsInFrame, frameId) => {
        removeElementsFromFrame(
          elementsInFrame,
          app.scene.getNonDeletedElementsMap()
        );
      });
    }
    const newGroupId = randomId();
    const selectElementIds = arrayToMap(selectedElements);
    nextElements = nextElements.map((element) => {
      if (!selectElementIds.get(element.id)) {
        return element;
      }
      return newElementWith(element, {
        groupIds: addToGroup(
          element.groupIds,
          newGroupId,
          appState.editingGroupId
        )
      });
    });
    const elementsInGroup = getElementsInGroup(nextElements, newGroupId);
    const lastElementInGroup = elementsInGroup[elementsInGroup.length - 1];
    const lastGroupElementIndex = nextElements.lastIndexOf(
      lastElementInGroup
    );
    const elementsAfterGroup = nextElements.slice(lastGroupElementIndex + 1);
    const elementsBeforeGroup = nextElements.slice(0, lastGroupElementIndex).filter(
      (updatedElement) => !isElementInGroup(updatedElement, newGroupId)
    );
    const reorderedElements = syncMovedIndices(
      [...elementsBeforeGroup, ...elementsInGroup, ...elementsAfterGroup],
      arrayToMap(elementsInGroup)
    );
    return {
      appState: {
        ...appState,
        ...selectGroup(
          newGroupId,
          { ...appState, selectedGroupIds: {} },
          getNonDeletedElements(nextElements)
        )
      },
      elements: reorderedElements,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  predicate: (elements, appState, _, app) => enableActionGroup(elements, appState, app),
  keyTest: (event) => !event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.key === KEYS.G,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx43(
    ToolButton,
    {
      hidden: !enableActionGroup(elements, appState, app),
      type: "button",
      icon: /* @__PURE__ */ jsx43(GroupIcon, { theme: appState.theme }),
      onClick: () => updateData(null),
      title: `${t("labels.group")} \u2014 ${getShortcutKey("CtrlOrCmd+G")}`,
      "aria-label": t("labels.group"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var actionUngroup = register({
  name: "ungroup",
  label: "labels.ungroup",
  icon: (appState) => /* @__PURE__ */ jsx43(UngroupIcon, { theme: appState.theme }),
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    const groupIds = getSelectedGroupIds(appState);
    const elementsMap = arrayToMap(elements);
    if (groupIds.length === 0) {
      return {
        appState,
        elements,
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
    let nextElements = [...elements];
    const boundTextElementIds = [];
    nextElements = nextElements.map((element) => {
      if (isBoundToContainer(element)) {
        boundTextElementIds.push(element.id);
      }
      const nextGroupIds = removeFromSelectedGroups(
        element.groupIds,
        appState.selectedGroupIds
      );
      if (nextGroupIds.length === element.groupIds.length) {
        return element;
      }
      return newElementWith(element, {
        groupIds: nextGroupIds
      });
    });
    const updateAppState = selectGroupsForSelectedElements(
      appState,
      getNonDeletedElements(nextElements),
      appState,
      null
    );
    const selectedElements = app.scene.getSelectedElements(appState);
    const selectedElementFrameIds = new Set(
      selectedElements.filter((element) => element.frameId).map((element) => element.frameId)
    );
    const targetFrames = getFrameLikeElements(elements).filter(
      (frame) => selectedElementFrameIds.has(frame.id)
    );
    targetFrames.forEach((frame) => {
      if (frame) {
        nextElements = replaceAllElementsInFrame(
          nextElements,
          getElementsInResizingFrame(
            nextElements,
            frame,
            appState,
            elementsMap
          ),
          frame,
          app
        );
      }
    });
    updateAppState.selectedElementIds = Object.entries(
      updateAppState.selectedElementIds
    ).reduce(
      (acc, [id, selected]) => {
        if (selected && !boundTextElementIds.includes(id)) {
          acc[id] = true;
        }
        return acc;
      },
      {}
    );
    return {
      appState: { ...appState, ...updateAppState },
      elements: nextElements,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.key === KEYS.G.toUpperCase(),
  predicate: (elements, appState) => getSelectedGroupIds(appState).length > 0,
  PanelComponent: ({ elements, appState, updateData }) => /* @__PURE__ */ jsx43(
    ToolButton,
    {
      type: "button",
      hidden: getSelectedGroupIds(appState).length === 0,
      icon: /* @__PURE__ */ jsx43(UngroupIcon, { theme: appState.theme }),
      onClick: () => updateData(null),
      title: `${t("labels.ungroup")} \u2014 ${getShortcutKey("CtrlOrCmd+Shift+G")}`,
      "aria-label": t("labels.ungroup"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});

// renderer/roundRect.ts
var roundRect = (context, x, y, width, height, radius, strokeColor) => {
  context.beginPath();
  context.moveTo(x + radius, y);
  context.lineTo(x + width - radius, y);
  context.quadraticCurveTo(x + width, y, x + width, y + radius);
  context.lineTo(x + width, y + height - radius);
  context.quadraticCurveTo(
    x + width,
    y + height,
    x + width - radius,
    y + height
  );
  context.lineTo(x + radius, y + height);
  context.quadraticCurveTo(x, y + height, x, y + height - radius);
  context.lineTo(x, y + radius);
  context.quadraticCurveTo(x, y, x + radius, y);
  context.closePath();
  context.fill();
  if (strokeColor) {
    context.strokeStyle = strokeColor;
  }
  context.stroke();
};

// clients.ts
function hashToInteger(id) {
  let hash = 0;
  if (id.length === 0) {
    return hash;
  }
  for (let i = 0; i < id.length; i++) {
    const char = id.charCodeAt(i);
    hash = (hash << 5) - hash + char;
  }
  return hash;
}
var getClientColor = (socketId, collaborator) => {
  const hash = Math.abs(hashToInteger(collaborator?.id || socketId));
  const hue = hash % 37 * 10;
  const saturation = 100;
  const lightness = 83;
  return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
};
var getNameInitial = (name) => {
  const firstCodePoint = name?.trim()?.codePointAt(0);
  return (firstCodePoint ? String.fromCodePoint(firstCodePoint) : "?").toUpperCase();
};
var renderRemoteCursors = ({
  context,
  renderConfig,
  appState,
  normalizedWidth,
  normalizedHeight
}) => {
  for (const [socketId, pointer] of renderConfig.remotePointerViewportCoords) {
    let { x, y } = pointer;
    const collaborator = appState.collaborators.get(socketId);
    x -= appState.offsetLeft;
    y -= appState.offsetTop;
    const width = 11;
    const height = 14;
    const isOutOfBounds = x < 0 || x > normalizedWidth - width || y < 0 || y > normalizedHeight - height;
    x = Math.max(x, 0);
    x = Math.min(x, normalizedWidth - width);
    y = Math.max(y, 0);
    y = Math.min(y, normalizedHeight - height);
    const background = getClientColor(socketId, collaborator);
    context.save();
    context.strokeStyle = background;
    context.fillStyle = background;
    const userState = renderConfig.remotePointerUserStates.get(socketId);
    const isInactive = isOutOfBounds || userState === "idle" /* IDLE */ || userState === "away" /* AWAY */;
    if (isInactive) {
      context.globalAlpha = 0.3;
    }
    if (renderConfig.remotePointerButton.get(socketId) === "down") {
      context.beginPath();
      context.arc(x, y, 15, 0, 2 * Math.PI, false);
      context.lineWidth = 3;
      context.strokeStyle = "#ffffff88";
      context.stroke();
      context.closePath();
      context.beginPath();
      context.arc(x, y, 15, 0, 2 * Math.PI, false);
      context.lineWidth = 1;
      context.strokeStyle = background;
      context.stroke();
      context.closePath();
    }
    const IS_SPEAKING_COLOR = appState.theme === THEME.DARK ? "#2f6330" : COLOR_VOICE_CALL;
    const isSpeaking = collaborator?.isSpeaking;
    if (isSpeaking) {
      context.fillStyle = IS_SPEAKING_COLOR;
      context.strokeStyle = IS_SPEAKING_COLOR;
      context.lineWidth = 10;
      context.lineJoin = "round";
      context.beginPath();
      context.moveTo(x, y);
      context.lineTo(x + 0, y + 14);
      context.lineTo(x + 4, y + 9);
      context.lineTo(x + 11, y + 8);
      context.closePath();
      context.stroke();
      context.fill();
    }
    context.fillStyle = COLOR_WHITE;
    context.strokeStyle = COLOR_WHITE;
    context.lineWidth = 6;
    context.lineJoin = "round";
    context.beginPath();
    context.moveTo(x, y);
    context.lineTo(x + 0, y + 14);
    context.lineTo(x + 4, y + 9);
    context.lineTo(x + 11, y + 8);
    context.closePath();
    context.stroke();
    context.fill();
    context.fillStyle = background;
    context.strokeStyle = background;
    context.lineWidth = 2;
    context.lineJoin = "round";
    context.beginPath();
    if (isInactive) {
      context.moveTo(x - 1, y - 1);
      context.lineTo(x - 1, y + 15);
      context.lineTo(x + 5, y + 10);
      context.lineTo(x + 12, y + 9);
      context.closePath();
      context.fill();
    } else {
      context.moveTo(x, y);
      context.lineTo(x + 0, y + 14);
      context.lineTo(x + 4, y + 9);
      context.lineTo(x + 11, y + 8);
      context.closePath();
      context.fill();
      context.stroke();
    }
    const username = renderConfig.remotePointerUsernames.get(socketId) || "";
    if (!isOutOfBounds && username) {
      context.font = "600 12px sans-serif";
      const offsetX = (isSpeaking ? x + 0 : x) + width / 2;
      const offsetY = (isSpeaking ? y + 0 : y) + height + 2;
      const paddingHorizontal = 5;
      const paddingVertical = 3;
      const measure = context.measureText(username);
      const measureHeight = measure.actualBoundingBoxDescent + measure.actualBoundingBoxAscent;
      const finalHeight = Math.max(measureHeight, 12);
      const boxX = offsetX - 1;
      const boxY = offsetY - 1;
      const boxWidth = measure.width + 2 + paddingHorizontal * 2 + 2;
      const boxHeight = finalHeight + 2 + paddingVertical * 2 + 2;
      if (context.roundRect) {
        context.beginPath();
        context.roundRect(boxX, boxY, boxWidth, boxHeight, 8);
        context.fillStyle = background;
        context.fill();
        context.strokeStyle = COLOR_WHITE;
        context.stroke();
        if (isSpeaking) {
          context.beginPath();
          context.roundRect(boxX - 2, boxY - 2, boxWidth + 4, boxHeight + 4, 8);
          context.strokeStyle = IS_SPEAKING_COLOR;
          context.stroke();
        }
      } else {
        roundRect(context, boxX, boxY, boxWidth, boxHeight, 8, COLOR_WHITE);
      }
      context.fillStyle = COLOR_CHARCOAL_BLACK;
      context.fillText(
        username,
        offsetX + paddingHorizontal + 1,
        offsetY + paddingVertical + measure.actualBoundingBoxAscent + Math.floor((finalHeight - measureHeight) / 2) + 2
      );
      if (isSpeaking) {
        context.fillStyle = IS_SPEAKING_COLOR;
        const barheight = 8;
        const margin = 8;
        const gap = 5;
        context.fillRect(
          boxX + boxWidth + margin,
          boxY + (boxHeight / 2 - barheight / 2),
          2,
          barheight
        );
        context.fillRect(
          boxX + boxWidth + margin + gap,
          boxY + (boxHeight / 2 - barheight * 2 / 2),
          2,
          barheight * 2
        );
        context.fillRect(
          boxX + boxWidth + margin + gap * 2,
          boxY + (boxHeight / 2 - barheight / 2),
          2,
          barheight
        );
      }
    }
    context.restore();
    context.closePath();
  }
};

// components/Avatar.tsx
import { useState as useState10 } from "react";
import clsx17 from "clsx";
import { jsx as jsx44 } from "react/jsx-runtime";
var Avatar = ({
  color,
  onClick,
  name,
  src,
  className
}) => {
  const shortName = getNameInitial(name);
  const [error, setError] = useState10(false);
  const loadImg = !error && src;
  const style = loadImg ? void 0 : { background: color };
  return /* @__PURE__ */ jsx44("div", { className: clsx17("Avatar", className), style, onClick, children: loadImg ? /* @__PURE__ */ jsx44(
    "img",
    {
      className: "Avatar-img",
      src,
      alt: shortName,
      referrerPolicy: "no-referrer",
      onError: () => setError(true)
    }
  ) : shortName });
};

// actions/actionNavigate.tsx
import clsx18 from "clsx";
import { jsx as jsx45, jsxs as jsxs25 } from "react/jsx-runtime";
var actionGoToCollaborator = register({
  name: "goToCollaborator",
  label: "Go to a collaborator",
  viewMode: true,
  trackEvent: { category: "collab" },
  perform: (_elements, appState, collaborator) => {
    if (!collaborator.socketId || appState.userToFollow?.socketId === collaborator.socketId || collaborator.isCurrentUser) {
      return {
        appState: {
          ...appState,
          userToFollow: null
        },
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
    return {
      appState: {
        ...appState,
        userToFollow: {
          socketId: collaborator.socketId,
          username: collaborator.username || ""
        },
        // Close mobile menu
        openMenu: appState.openMenu === "canvas" ? null : appState.openMenu
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  PanelComponent: ({ updateData, data, appState }) => {
    const { socketId, collaborator, withName, isBeingFollowed } = data;
    const background = getClientColor(socketId, collaborator);
    const statusClassNames = clsx18({
      "is-followed": isBeingFollowed,
      "is-current-user": collaborator.isCurrentUser === true,
      "is-speaking": collaborator.isSpeaking,
      "is-in-call": collaborator.isInCall,
      "is-muted": collaborator.isMuted
    });
    const statusIconJSX = collaborator.isInCall ? collaborator.isSpeaking ? /* @__PURE__ */ jsxs25(
      "div",
      {
        className: "UserList__collaborator-status-icon-speaking-indicator",
        title: t("userList.hint.isSpeaking"),
        children: [
          /* @__PURE__ */ jsx45("div", {}),
          /* @__PURE__ */ jsx45("div", {}),
          /* @__PURE__ */ jsx45("div", {})
        ]
      }
    ) : collaborator.isMuted ? /* @__PURE__ */ jsx45(
      "div",
      {
        className: "UserList__collaborator-status-icon-microphone-muted",
        title: t("userList.hint.micMuted"),
        children: microphoneMutedIcon
      }
    ) : /* @__PURE__ */ jsx45("div", { title: t("userList.hint.inCall"), children: microphoneIcon }) : null;
    return withName ? /* @__PURE__ */ jsxs25(
      "div",
      {
        className: `dropdown-menu-item dropdown-menu-item-base UserList__collaborator ${statusClassNames}`,
        style: { [`--avatar-size`]: "1.5rem" },
        onClick: () => updateData(collaborator),
        children: [
          /* @__PURE__ */ jsx45(
            Avatar,
            {
              color: background,
              onClick: () => {
              },
              name: collaborator.username || "",
              src: collaborator.avatarUrl,
              className: statusClassNames
            }
          ),
          /* @__PURE__ */ jsx45("div", { className: "UserList__collaborator-name", children: collaborator.username }),
          /* @__PURE__ */ jsxs25("div", { className: "UserList__collaborator-status-icons", "aria-hidden": true, children: [
            isBeingFollowed && /* @__PURE__ */ jsx45(
              "div",
              {
                className: "UserList__collaborator-status-icon-is-followed",
                title: t("userList.hint.followStatus"),
                children: eyeIcon
              }
            ),
            statusIconJSX
          ] })
        ]
      }
    ) : /* @__PURE__ */ jsxs25(
      "div",
      {
        className: `UserList__collaborator UserList__collaborator--avatar-only ${statusClassNames}`,
        children: [
          /* @__PURE__ */ jsx45(
            Avatar,
            {
              color: background,
              onClick: () => {
                updateData(collaborator);
              },
              name: collaborator.username || "",
              src: collaborator.avatarUrl,
              className: statusClassNames
            }
          ),
          statusIconJSX && /* @__PURE__ */ jsx45("div", { className: "UserList__collaborator-status-icon", children: statusIconJSX })
        ]
      }
    );
  }
});

// actions/actionAddToLibrary.ts
var actionAddToLibrary = register({
  name: "addToLibrary",
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true,
      includeElementsInFrames: true
    });
    for (const type of LIBRARY_DISABLED_TYPES) {
      if (selectedElements.some((element) => element.type === type)) {
        return {
          captureUpdate: CaptureUpdateAction.EVENTUALLY,
          appState: {
            ...appState,
            errorMessage: t(`errors.libraryElementTypeError.${type}`)
          }
        };
      }
    }
    return app.library.getLatestLibrary().then((items) => {
      return app.library.setLibrary([
        {
          id: randomId(),
          status: "unpublished",
          elements: selectedElements.map(deepCopyElement),
          created: Date.now()
        },
        ...items
      ]);
    }).then(() => {
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          toast: { message: t("toast.addedToLibrary") }
        }
      };
    }).catch((error) => {
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          errorMessage: error.message
        }
      };
    });
  },
  label: "labels.addToLibrary"
});

// align.ts
var alignElements = (selectedElements, elementsMap, alignment, scene) => {
  const groups = getMaximumGroups(
    selectedElements,
    elementsMap
  );
  const selectionBoundingBox = getCommonBoundingBox(selectedElements);
  return groups.flatMap((group) => {
    const translation = calculateTranslation(
      group,
      selectionBoundingBox,
      alignment
    );
    return group.map((element) => {
      const updatedEle = mutateElement(element, {
        x: element.x + translation.x,
        y: element.y + translation.y
      });
      updateBoundElements(element, scene.getNonDeletedElementsMap(), {
        simultaneouslyUpdated: group
      });
      return updatedEle;
    });
  });
};
var calculateTranslation = (group, selectionBoundingBox, { axis, position }) => {
  const groupBoundingBox = getCommonBoundingBox(group);
  const [min, max] = axis === "x" ? ["minX", "maxX"] : ["minY", "maxY"];
  const noTranslation = { x: 0, y: 0 };
  if (position === "start") {
    return {
      ...noTranslation,
      [axis]: selectionBoundingBox[min] - groupBoundingBox[min]
    };
  } else if (position === "end") {
    return {
      ...noTranslation,
      [axis]: selectionBoundingBox[max] - groupBoundingBox[max]
    };
  }
  return {
    ...noTranslation,
    [axis]: (selectionBoundingBox[min] + selectionBoundingBox[max]) / 2 - (groupBoundingBox[min] + groupBoundingBox[max]) / 2
  };
};

// actions/actionAlign.tsx
import { jsx as jsx46 } from "react/jsx-runtime";
var alignActionsPredicate = (appState, app) => {
  const selectedElements = app.scene.getSelectedElements(appState);
  return selectedElements.length > 1 && // TODO enable aligning frames when implemented properly
  !selectedElements.some((el) => isFrameLikeElement(el));
};
var alignSelectedElements = (elements, appState, app, alignment) => {
  const selectedElements = app.scene.getSelectedElements(appState);
  const elementsMap = arrayToMap(elements);
  const updatedElements = alignElements(
    selectedElements,
    elementsMap,
    alignment,
    app.scene
  );
  const updatedElementsMap = arrayToMap(updatedElements);
  return updateFrameMembershipOfSelectedElements(
    elements.map((element) => updatedElementsMap.get(element.id) || element),
    appState,
    app
  );
};
var actionAlignTop = register({
  name: "alignTop",
  label: "labels.alignTop",
  icon: AlignTopIcon,
  trackEvent: { category: "element" },
  predicate: (elements, appState, appProps, app) => alignActionsPredicate(appState, app),
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: alignSelectedElements(elements, appState, app, {
        position: "start",
        axis: "y"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_UP,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx46(
    ToolButton,
    {
      hidden: !alignActionsPredicate(appState, app),
      type: "button",
      icon: AlignTopIcon,
      onClick: () => updateData(null),
      title: `${t("labels.alignTop")} \u2014 ${getShortcutKey(
        "CtrlOrCmd+Shift+Up"
      )}`,
      "aria-label": t("labels.alignTop"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var actionAlignBottom = register({
  name: "alignBottom",
  label: "labels.alignBottom",
  icon: AlignBottomIcon,
  trackEvent: { category: "element" },
  predicate: (elements, appState, appProps, app) => alignActionsPredicate(appState, app),
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: alignSelectedElements(elements, appState, app, {
        position: "end",
        axis: "y"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_DOWN,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx46(
    ToolButton,
    {
      hidden: !alignActionsPredicate(appState, app),
      type: "button",
      icon: AlignBottomIcon,
      onClick: () => updateData(null),
      title: `${t("labels.alignBottom")} \u2014 ${getShortcutKey(
        "CtrlOrCmd+Shift+Down"
      )}`,
      "aria-label": t("labels.alignBottom"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var actionAlignLeft = register({
  name: "alignLeft",
  label: "labels.alignLeft",
  icon: AlignLeftIcon,
  trackEvent: { category: "element" },
  predicate: (elements, appState, appProps, app) => alignActionsPredicate(appState, app),
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: alignSelectedElements(elements, appState, app, {
        position: "start",
        axis: "x"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_LEFT,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx46(
    ToolButton,
    {
      hidden: !alignActionsPredicate(appState, app),
      type: "button",
      icon: AlignLeftIcon,
      onClick: () => updateData(null),
      title: `${t("labels.alignLeft")} \u2014 ${getShortcutKey(
        "CtrlOrCmd+Shift+Left"
      )}`,
      "aria-label": t("labels.alignLeft"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var actionAlignRight = register({
  name: "alignRight",
  label: "labels.alignRight",
  icon: AlignRightIcon,
  trackEvent: { category: "element" },
  predicate: (elements, appState, appProps, app) => alignActionsPredicate(appState, app),
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: alignSelectedElements(elements, appState, app, {
        position: "end",
        axis: "x"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === KEYS.ARROW_RIGHT,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx46(
    ToolButton,
    {
      hidden: !alignActionsPredicate(appState, app),
      type: "button",
      icon: AlignRightIcon,
      onClick: () => updateData(null),
      title: `${t("labels.alignRight")} \u2014 ${getShortcutKey(
        "CtrlOrCmd+Shift+Right"
      )}`,
      "aria-label": t("labels.alignRight"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var actionAlignVerticallyCentered = register({
  name: "alignVerticallyCentered",
  label: "labels.centerVertically",
  icon: CenterVerticallyIcon,
  trackEvent: { category: "element" },
  predicate: (elements, appState, appProps, app) => alignActionsPredicate(appState, app),
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: alignSelectedElements(elements, appState, app, {
        position: "center",
        axis: "y"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx46(
    ToolButton,
    {
      hidden: !alignActionsPredicate(appState, app),
      type: "button",
      icon: CenterVerticallyIcon,
      onClick: () => updateData(null),
      title: t("labels.centerVertically"),
      "aria-label": t("labels.centerVertically"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var actionAlignHorizontallyCentered = register({
  name: "alignHorizontallyCentered",
  label: "labels.centerHorizontally",
  icon: CenterHorizontallyIcon,
  trackEvent: { category: "element" },
  predicate: (elements, appState, appProps, app) => alignActionsPredicate(appState, app),
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: alignSelectedElements(elements, appState, app, {
        position: "center",
        axis: "x"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx46(
    ToolButton,
    {
      hidden: !alignActionsPredicate(appState, app),
      type: "button",
      icon: CenterHorizontallyIcon,
      onClick: () => updateData(null),
      title: t("labels.centerHorizontally"),
      "aria-label": t("labels.centerHorizontally"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});

// distribute.ts
var distributeElements = (selectedElements, elementsMap, distribution) => {
  const [start, mid, end, extent] = distribution.axis === "x" ? ["minX", "midX", "maxX", "width"] : ["minY", "midY", "maxY", "height"];
  const bounds = getCommonBoundingBox(selectedElements);
  const groups = getMaximumGroups(selectedElements, elementsMap).map((group) => [group, getCommonBoundingBox(group)]).sort((a, b) => a[1][mid] - b[1][mid]);
  let span = 0;
  for (const group of groups) {
    span += group[1][extent];
  }
  const step = (bounds[extent] - span) / (groups.length - 1);
  if (step < 0) {
    const index0 = groups.findIndex((g) => g[1][start] === bounds[start]);
    const index1 = groups.findIndex((g) => g[1][end] === bounds[end]);
    const step2 = (groups[index1][1][mid] - groups[index0][1][mid]) / (groups.length - 1);
    let pos2 = groups[index0][1][mid];
    return groups.flatMap(([group, box], index) => {
      const translation = {
        x: 0,
        y: 0
      };
      if (index !== index0 && index !== index1) {
        pos2 += step2;
        translation[distribution.axis] = pos2 - box[mid];
      }
      return group.map(
        (element) => newElementWith(element, {
          x: element.x + translation.x,
          y: element.y + translation.y
        })
      );
    });
  }
  let pos = bounds[start];
  return groups.flatMap(([group, box]) => {
    const translation = {
      x: 0,
      y: 0
    };
    translation[distribution.axis] = pos - box[start];
    pos += step;
    pos += box[extent];
    return group.map(
      (element) => newElementWith(element, {
        x: element.x + translation.x,
        y: element.y + translation.y
      })
    );
  });
};

// actions/actionDistribute.tsx
import { jsx as jsx47 } from "react/jsx-runtime";
var enableActionGroup2 = (appState, app) => {
  const selectedElements = app.scene.getSelectedElements(appState);
  return selectedElements.length > 1 && // TODO enable distributing frames when implemented properly
  !selectedElements.some((el) => isFrameLikeElement(el));
};
var distributeSelectedElements = (elements, appState, app, distribution) => {
  const selectedElements = app.scene.getSelectedElements(appState);
  const updatedElements = distributeElements(
    selectedElements,
    app.scene.getNonDeletedElementsMap(),
    distribution
  );
  const updatedElementsMap = arrayToMap(updatedElements);
  return updateFrameMembershipOfSelectedElements(
    elements.map((element) => updatedElementsMap.get(element.id) || element),
    appState,
    app
  );
};
var distributeHorizontally = register({
  name: "distributeHorizontally",
  label: "labels.distributeHorizontally",
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: distributeSelectedElements(elements, appState, app, {
        space: "between",
        axis: "x"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.H,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx47(
    ToolButton,
    {
      hidden: !enableActionGroup2(appState, app),
      type: "button",
      icon: DistributeHorizontallyIcon,
      onClick: () => updateData(null),
      title: `${t("labels.distributeHorizontally")} \u2014 ${getShortcutKey(
        "Alt+H"
      )}`,
      "aria-label": t("labels.distributeHorizontally"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});
var distributeVertically = register({
  name: "distributeVertically",
  label: "labels.distributeVertically",
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    return {
      appState,
      elements: distributeSelectedElements(elements, appState, app, {
        space: "between",
        axis: "y"
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
  PanelComponent: ({ elements, appState, updateData, app }) => /* @__PURE__ */ jsx47(
    ToolButton,
    {
      hidden: !enableActionGroup2(appState, app),
      type: "button",
      icon: DistributeVerticallyIcon,
      onClick: () => updateData(null),
      title: `${t("labels.distributeVertically")} \u2014 ${getShortcutKey("Alt+V")}`,
      "aria-label": t("labels.distributeVertically"),
      visible: isSomeElementSelected(getNonDeletedElements(elements), appState)
    }
  )
});

// actions/actionFlip.ts
var actionFlipHorizontal = register({
  name: "flipHorizontal",
  label: "labels.flipHorizontal",
  icon: flipHorizontal,
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    return {
      elements: updateFrameMembershipOfSelectedElements(
        flipSelectedElements(
          elements,
          app.scene.getNonDeletedElementsMap(),
          appState,
          "horizontal",
          app
        ),
        appState,
        app
      ),
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event.shiftKey && event.code === CODES.H
});
var actionFlipVertical = register({
  name: "flipVertical",
  label: "labels.flipVertical",
  icon: flipVertical,
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    return {
      elements: updateFrameMembershipOfSelectedElements(
        flipSelectedElements(
          elements,
          app.scene.getNonDeletedElementsMap(),
          appState,
          "vertical",
          app
        ),
        appState,
        app
      ),
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event) => event.shiftKey && event.code === CODES.V && !event[KEYS.CTRL_OR_CMD]
});
var flipSelectedElements = (elements, elementsMap, appState, flipDirection, app) => {
  const selectedElements = getSelectedElements(
    getNonDeletedElements(elements),
    appState,
    {
      includeBoundTextElement: true,
      includeElementsInFrames: true
    }
  );
  const updatedElements = flipElements(
    selectedElements,
    elementsMap,
    appState,
    flipDirection,
    app
  );
  const updatedElementsMap = arrayToMap(updatedElements);
  return elements.map(
    (element) => updatedElementsMap.get(element.id) || element
  );
};
var flipElements = (selectedElements, elementsMap, appState, flipDirection, app) => {
  if (selectedElements.every(
    (element) => isArrowElement(element) && (element.startBinding || element.endBinding)
  )) {
    return selectedElements.map((element) => {
      const _element = element;
      return newElementWith(_element, {
        startArrowhead: _element.endArrowhead,
        endArrowhead: _element.startArrowhead
      });
    });
  }
  const { midX, midY } = getCommonBoundingBox(selectedElements);
  resizeMultipleElements(
    selectedElements,
    elementsMap,
    "nw",
    app.scene,
    new Map(
      Array.from(elementsMap.values()).map((element) => [
        element.id,
        deepCopyElement(element)
      ])
    ),
    {
      flipByX: flipDirection === "horizontal",
      flipByY: flipDirection === "vertical",
      shouldResizeFromCenter: true,
      shouldMaintainAspectRatio: true
    }
  );
  bindOrUnbindLinearElements(
    selectedElements.filter(isLinearElement),
    elementsMap,
    app.scene.getNonDeletedElements(),
    app.scene,
    isBindingEnabled(appState),
    [],
    appState.zoom
  );
  const { elbowArrows, otherElements } = selectedElements.reduce(
    (acc, element) => isElbowArrow(element) ? { ...acc, elbowArrows: acc.elbowArrows.concat(element) } : { ...acc, otherElements: acc.otherElements.concat(element) },
    { elbowArrows: [], otherElements: [] }
  );
  const { midX: newMidX, midY: newMidY } = getCommonBoundingBox(selectedElements);
  const [diffX, diffY] = [midX - newMidX, midY - newMidY];
  otherElements.forEach(
    (element) => mutateElement(element, {
      x: element.x + diffX,
      y: element.y + diffY
    })
  );
  elbowArrows.forEach(
    (element) => mutateElement(element, {
      x: element.x + diffX,
      y: element.y + diffY
    })
  );
  return selectedElements;
};

// actions/actionClipboard.tsx
var actionCopy = register({
  name: "copy",
  label: "labels.copy",
  icon: DuplicateIcon,
  trackEvent: { category: "element" },
  perform: async (elements, appState, event, app) => {
    const elementsToCopy = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true,
      includeElementsInFrames: true
    });
    try {
      await copyToClipboard(elementsToCopy, app.files, event);
    } catch (error) {
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          errorMessage: error.message
        }
      };
    }
    return {
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  // don't supply a shortcut since we handle this conditionally via onCopy event
  keyTest: void 0
});
var actionPaste = register({
  name: "paste",
  label: "labels.paste",
  trackEvent: { category: "element" },
  perform: async (elements, appState, data, app) => {
    let types;
    try {
      types = await readSystemClipboard();
    } catch (error) {
      if (error.name === "AbortError" || error.name === "NotAllowedError") {
        return false;
      }
      console.error(`actionPaste ${error.name}: ${error.message}`);
      if (isFirefox) {
        return {
          captureUpdate: CaptureUpdateAction.EVENTUALLY,
          appState: {
            ...appState,
            errorMessage: t("hints.firefox_clipboard_write")
          }
        };
      }
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          errorMessage: t("errors.asyncPasteFailedOnRead")
        }
      };
    }
    try {
      app.pasteFromClipboard(createPasteEvent({ types }));
    } catch (error) {
      console.error(error);
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY,
        appState: {
          ...appState,
          errorMessage: t("errors.asyncPasteFailedOnParse")
        }
      };
    }
    return {
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  // don't supply a shortcut since we handle this conditionally via onCopy event
  keyTest: void 0
});
var actionCut = register({
  name: "cut",
  label: "labels.cut",
  icon: cutIcon,
  trackEvent: { category: "element" },
  perform: (elements, appState, event, app) => {
    actionCopy.perform(elements, appState, event, app);
    return actionDeleteSelected.perform(elements, appState, null, app);
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X
});
var actionCopyAsSvg = register({
  name: "copyAsSvg",
  label: "labels.copyAsSvg",
  icon: svgIcon,
  trackEvent: { category: "element" },
  perform: async (elements, appState, _data, app) => {
    if (!app.canvas) {
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
    const { exportedElements, exportingFrame } = prepareElementsForExport(
      elements,
      appState,
      true
    );
    try {
      await exportCanvas(
        "clipboard-svg",
        exportedElements,
        appState,
        app.files,
        {
          ...appState,
          exportingFrame,
          name: app.getName()
        }
      );
      const selectedElements = app.scene.getSelectedElements({
        selectedElementIds: appState.selectedElementIds,
        includeBoundTextElement: true,
        includeElementsInFrames: true
      });
      return {
        appState: {
          toast: {
            message: t("toast.copyToClipboardAsSvg", {
              exportSelection: selectedElements.length ? t("toast.selection") : t("toast.canvas"),
              exportColorScheme: appState.exportWithDarkMode ? t("buttons.darkMode") : t("buttons.lightMode")
            })
          }
        },
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    } catch (error) {
      console.error(error);
      return {
        appState: {
          errorMessage: error.message
        },
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
  },
  predicate: (elements) => {
    return probablySupportsClipboardWriteText && elements.length > 0;
  },
  keywords: ["svg", "clipboard", "copy"]
});
var actionCopyAsPng = register({
  name: "copyAsPng",
  label: "labels.copyAsPng",
  icon: pngIcon,
  trackEvent: { category: "element" },
  perform: async (elements, appState, _data, app) => {
    if (!app.canvas) {
      return {
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
    const selectedElements = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true,
      includeElementsInFrames: true
    });
    const { exportedElements, exportingFrame } = prepareElementsForExport(
      elements,
      appState,
      true
    );
    try {
      await exportCanvas("clipboard", exportedElements, appState, app.files, {
        ...appState,
        exportingFrame,
        name: app.getName()
      });
      return {
        appState: {
          ...appState,
          toast: {
            message: t("toast.copyToClipboardAsPng", {
              exportSelection: selectedElements.length ? t("toast.selection") : t("toast.canvas"),
              exportColorScheme: appState.exportWithDarkMode ? t("buttons.darkMode") : t("buttons.lightMode")
            })
          }
        },
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    } catch (error) {
      console.error(error);
      return {
        appState: {
          ...appState,
          errorMessage: error.message
        },
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
  },
  predicate: (elements) => {
    return probablySupportsClipboardBlob && elements.length > 0;
  },
  keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey,
  keywords: ["png", "clipboard", "copy"]
});
var copyText = register({
  name: "copyText",
  label: "labels.copyText",
  trackEvent: { category: "element" },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true
    });
    try {
      copyTextToSystemClipboard(getTextFromElements(selectedElements));
    } catch (e) {
      throw new Error(t("errors.copyToSystemClipboardFailed"));
    }
    return {
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  predicate: (elements, appState, _, app) => {
    return probablySupportsClipboardWriteText && app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true
    }).some(isTextElement);
  },
  keywords: ["text", "clipboard", "copy"]
});

// actions/actionToggleGridMode.tsx
var actionToggleGridMode = register({
  name: "gridMode",
  icon: gridIcon,
  keywords: ["snap"],
  label: "labels.toggleGrid",
  viewMode: true,
  trackEvent: {
    category: "canvas",
    predicate: (appState) => appState.gridModeEnabled
  },
  perform(elements, appState) {
    return {
      appState: {
        ...appState,
        gridModeEnabled: !this.checked(appState),
        objectsSnapModeEnabled: false
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.gridModeEnabled,
  predicate: (element, appState, props) => {
    return props.gridModeEnabled === void 0;
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE
});

// actions/actionToggleZenMode.tsx
var actionToggleZenMode = register({
  name: "zenMode",
  label: "buttons.zenMode",
  icon: coffeeIcon,
  paletteName: "Toggle zen mode",
  viewMode: true,
  trackEvent: {
    category: "canvas",
    predicate: (appState) => !appState.zenModeEnabled
  },
  perform(elements, appState) {
    return {
      appState: {
        ...appState,
        zenModeEnabled: !this.checked(appState)
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.zenModeEnabled,
  predicate: (elements, appState, appProps) => {
    return typeof appProps.zenModeEnabled === "undefined";
  },
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z
});

// actions/actionToggleObjectsSnapMode.tsx
var actionToggleObjectsSnapMode = register({
  name: "objectsSnapMode",
  label: "buttons.objectsSnapMode",
  icon: magnetIcon,
  viewMode: false,
  trackEvent: {
    category: "canvas",
    predicate: (appState) => !appState.objectsSnapModeEnabled
  },
  perform(elements, appState) {
    return {
      appState: {
        ...appState,
        objectsSnapModeEnabled: !this.checked(appState),
        gridModeEnabled: false
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.objectsSnapModeEnabled,
  predicate: (elements, appState, appProps) => {
    return typeof appProps.objectsSnapModeEnabled === "undefined";
  },
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.S
});

// actions/actionToggleStats.tsx
var actionToggleStats = register({
  name: "stats",
  label: "stats.fullTitle",
  icon: abacusIcon,
  paletteName: "Toggle stats",
  viewMode: true,
  trackEvent: { category: "menu" },
  keywords: ["edit", "attributes", "customize"],
  perform(elements, appState) {
    return {
      appState: {
        ...appState,
        stats: { ...appState.stats, open: !this.checked(appState) }
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.stats.open,
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.SLASH
});

// actions/actionBoundText.tsx
var actionUnbindText = register({
  name: "unbindText",
  label: "labels.unbindText",
  trackEvent: { category: "element" },
  predicate: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    return selectedElements.some((element) => hasBoundTextElement(element));
  },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    const elementsMap = app.scene.getNonDeletedElementsMap();
    selectedElements.forEach((element) => {
      const boundTextElement = getBoundTextElement(element, elementsMap);
      if (boundTextElement) {
        const { width, height } = measureText(
          boundTextElement.originalText,
          getFontString(boundTextElement),
          boundTextElement.lineHeight
        );
        const originalContainerHeight = getOriginalContainerHeightFromCache(
          element.id
        );
        resetOriginalContainerCache(element.id);
        const { x, y } = computeBoundTextPosition(
          element,
          boundTextElement,
          elementsMap
        );
        mutateElement(boundTextElement, {
          containerId: null,
          width,
          height,
          text: boundTextElement.originalText,
          x,
          y
        });
        mutateElement(element, {
          boundElements: element.boundElements?.filter(
            (ele) => ele.id !== boundTextElement.id
          ),
          height: originalContainerHeight ? originalContainerHeight : element.height
        });
      }
    });
    return {
      elements,
      appState,
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  }
});
var actionBindText = register({
  name: "bindText",
  label: "labels.bindText",
  trackEvent: { category: "element" },
  predicate: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    if (selectedElements.length === 2) {
      const textElement = isTextElement(selectedElements[0]) || isTextElement(selectedElements[1]);
      let bindingContainer;
      if (isTextBindableContainer(selectedElements[0])) {
        bindingContainer = selectedElements[0];
      } else if (isTextBindableContainer(selectedElements[1])) {
        bindingContainer = selectedElements[1];
      }
      if (textElement && bindingContainer && getBoundTextElement(
        bindingContainer,
        app.scene.getNonDeletedElementsMap()
      ) === null) {
        return true;
      }
    }
    return false;
  },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    let textElement;
    let container;
    if (isTextElement(selectedElements[0]) && isTextBindableContainer(selectedElements[1])) {
      textElement = selectedElements[0];
      container = selectedElements[1];
    } else {
      textElement = selectedElements[1];
      container = selectedElements[0];
    }
    mutateElement(textElement, {
      containerId: container.id,
      verticalAlign: VERTICAL_ALIGN.MIDDLE,
      textAlign: TEXT_ALIGN.CENTER,
      autoResize: true
    });
    mutateElement(container, {
      boundElements: (container.boundElements || []).concat({
        type: "text",
        id: textElement.id
      })
    });
    const originalContainerHeight = container.height;
    redrawTextBoundingBox(
      textElement,
      container,
      app.scene.getNonDeletedElementsMap()
    );
    updateOriginalContainerCache(container.id, originalContainerHeight);
    return {
      elements: pushTextAboveContainer(elements, container, textElement),
      appState: { ...appState, selectedElementIds: { [container.id]: true } },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  }
});
var pushTextAboveContainer = (elements, container, textElement) => {
  const updatedElements = elements.slice();
  const textElementIndex = updatedElements.findIndex(
    (ele) => ele.id === textElement.id
  );
  updatedElements.splice(textElementIndex, 1);
  const containerIndex = updatedElements.findIndex(
    (ele) => ele.id === container.id
  );
  updatedElements.splice(containerIndex + 1, 0, textElement);
  syncMovedIndices(updatedElements, arrayToMap([container, textElement]));
  return updatedElements;
};
var pushContainerBelowText = (elements, container, textElement) => {
  const updatedElements = elements.slice();
  const containerIndex = updatedElements.findIndex(
    (ele) => ele.id === container.id
  );
  updatedElements.splice(containerIndex, 1);
  const textElementIndex = updatedElements.findIndex(
    (ele) => ele.id === textElement.id
  );
  updatedElements.splice(textElementIndex, 0, container);
  syncMovedIndices(updatedElements, arrayToMap([container, textElement]));
  return updatedElements;
};
var actionWrapTextInContainer = register({
  name: "wrapTextInContainer",
  label: "labels.createContainerFromText",
  trackEvent: { category: "element" },
  predicate: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    const areTextElements = selectedElements.every((el) => isTextElement(el));
    return selectedElements.length > 0 && areTextElements;
  },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    let updatedElements = elements.slice();
    const containerIds = {};
    for (const textElement of selectedElements) {
      if (isTextElement(textElement)) {
        const container = newElement({
          type: "rectangle",
          backgroundColor: appState.currentItemBackgroundColor,
          boundElements: [
            ...textElement.boundElements || [],
            { id: textElement.id, type: "text" }
          ],
          angle: textElement.angle,
          fillStyle: appState.currentItemFillStyle,
          strokeColor: appState.currentItemStrokeColor,
          roughness: appState.currentItemRoughness,
          strokeWidth: appState.currentItemStrokeWidth,
          strokeStyle: appState.currentItemStrokeStyle,
          roundness: appState.currentItemRoundness === "round" ? {
            type: isUsingAdaptiveRadius("rectangle") ? ROUNDNESS.ADAPTIVE_RADIUS : ROUNDNESS.PROPORTIONAL_RADIUS
          } : null,
          opacity: 100,
          locked: false,
          x: textElement.x - BOUND_TEXT_PADDING,
          y: textElement.y - BOUND_TEXT_PADDING,
          width: computeContainerDimensionForBoundText(
            textElement.width,
            "rectangle"
          ),
          height: computeContainerDimensionForBoundText(
            textElement.height,
            "rectangle"
          ),
          groupIds: textElement.groupIds,
          frameId: textElement.frameId
        });
        if (textElement.boundElements?.length) {
          const linearElementIds = textElement.boundElements.filter((ele) => ele.type === "arrow").map((el) => el.id);
          const linearElements = updatedElements.filter(
            (ele) => linearElementIds.includes(ele.id)
          );
          linearElements.forEach((ele) => {
            let startBinding = ele.startBinding;
            let endBinding = ele.endBinding;
            if (startBinding?.elementId === textElement.id) {
              startBinding = {
                ...startBinding,
                elementId: container.id
              };
            }
            if (endBinding?.elementId === textElement.id) {
              endBinding = { ...endBinding, elementId: container.id };
            }
            if (startBinding || endBinding) {
              mutateElement(ele, { startBinding, endBinding }, false);
            }
          });
        }
        mutateElement(
          textElement,
          {
            containerId: container.id,
            verticalAlign: VERTICAL_ALIGN.MIDDLE,
            boundElements: null,
            textAlign: TEXT_ALIGN.CENTER,
            autoResize: true
          },
          false
        );
        redrawTextBoundingBox(
          textElement,
          container,
          app.scene.getNonDeletedElementsMap()
        );
        updatedElements = pushContainerBelowText(
          [...updatedElements, container],
          container,
          textElement
        );
        containerIds[container.id] = true;
      }
    }
    return {
      elements: updatedElements,
      appState: {
        ...appState,
        selectedElementIds: containerIds
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  }
});

// components/hyperlink/Hyperlink.tsx
import {
  useCallback as useCallback4,
  useEffect as useEffect17,
  useLayoutEffect as useLayoutEffect2,
  useRef as useRef12,
  useState as useState11
} from "react";
import clsx19 from "clsx";
import { jsx as jsx48, jsxs as jsxs26 } from "react/jsx-runtime";
var POPUP_WIDTH = 380;
var POPUP_HEIGHT = 42;
var POPUP_PADDING = 5;
var SPACE_BOTTOM = 85;
var AUTO_HIDE_TIMEOUT = 500;
var IS_HYPERLINK_TOOLTIP_VISIBLE = false;
var embeddableLinkCache = /* @__PURE__ */ new Map();
var Hyperlink = ({
  element,
  elementsMap,
  setAppState,
  onLinkOpen,
  setToast,
  updateEmbedValidationStatus
}) => {
  const appState = useExcalidrawAppState();
  const appProps = useAppProps();
  const device = useDevice();
  const linkVal = element.link || "";
  const [inputVal, setInputVal] = useState11(linkVal);
  const inputRef = useRef12(null);
  const isEditing = appState.showHyperlinkPopup === "editor";
  const handleSubmit = useCallback4(() => {
    if (!inputRef.current) {
      return;
    }
    const link = normalizeLink(inputRef.current.value) || null;
    if (!element.link && link) {
      trackEvent("hyperlink", "create");
    }
    if (isEmbeddableElement(element)) {
      if (appState.activeEmbeddable?.element === element) {
        setAppState({ activeEmbeddable: null });
      }
      if (!link) {
        mutateElement(element, {
          link: null
        });
        updateEmbedValidationStatus(element, false);
        return;
      }
      if (!embeddableURLValidator(link, appProps.validateEmbeddable)) {
        if (link) {
          setToast({ message: t("toast.unableToEmbed"), closable: true });
        }
        element.link && embeddableLinkCache.set(element.id, element.link);
        mutateElement(element, {
          link
        });
        updateEmbedValidationStatus(element, false);
      } else {
        const { width, height } = element;
        const embedLink = getEmbedLink(link);
        if (embedLink?.error instanceof URIError) {
          setToast({
            message: t("toast.unrecognizedLinkFormat"),
            closable: true
          });
        }
        const ar = embedLink ? embedLink.intrinsicSize.w / embedLink.intrinsicSize.h : 1;
        const hasLinkChanged = embeddableLinkCache.get(element.id) !== element.link;
        mutateElement(element, {
          ...hasLinkChanged ? {
            width: embedLink?.type === "video" ? width > height ? width : height * ar : width,
            height: embedLink?.type === "video" ? width > height ? width / ar : height : height
          } : {},
          link
        });
        updateEmbedValidationStatus(element, true);
        if (embeddableLinkCache.has(element.id)) {
          embeddableLinkCache.delete(element.id);
        }
      }
    } else {
      mutateElement(element, { link });
    }
  }, [
    element,
    setToast,
    appProps.validateEmbeddable,
    appState.activeEmbeddable,
    setAppState,
    updateEmbedValidationStatus
  ]);
  useLayoutEffect2(() => {
    return () => {
      handleSubmit();
    };
  }, [handleSubmit]);
  useEffect17(() => {
    if (isEditing && inputRef?.current && !(device.viewport.isMobile || device.isTouchScreen)) {
      inputRef.current.select();
    }
  }, [isEditing, device.viewport.isMobile, device.isTouchScreen]);
  useEffect17(() => {
    let timeoutId = null;
    const handlePointerMove = (event) => {
      if (isEditing) {
        return;
      }
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
      const shouldHide = shouldHideLinkPopup(
        element,
        elementsMap,
        appState,
        pointFrom(event.clientX, event.clientY)
      );
      if (shouldHide) {
        timeoutId = window.setTimeout(() => {
          setAppState({ showHyperlinkPopup: false });
        }, AUTO_HIDE_TIMEOUT);
      }
    };
    window.addEventListener("pointermove" /* POINTER_MOVE */, handlePointerMove, false);
    return () => {
      window.removeEventListener("pointermove" /* POINTER_MOVE */, handlePointerMove, false);
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
    };
  }, [appState, element, isEditing, setAppState, elementsMap]);
  const handleRemove = useCallback4(() => {
    trackEvent("hyperlink", "delete");
    mutateElement(element, { link: null });
    setAppState({ showHyperlinkPopup: false });
  }, [setAppState, element]);
  const onEdit = () => {
    trackEvent("hyperlink", "edit", "popup-ui");
    setAppState({ showHyperlinkPopup: "editor" });
  };
  const { x, y } = getCoordsForPopover(element, appState, elementsMap);
  if (appState.contextMenu || appState.selectedElementsAreBeingDragged || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
    return null;
  }
  return /* @__PURE__ */ jsxs26(
    "div",
    {
      className: "excalidraw-hyperlinkContainer",
      style: {
        top: `${y}px`,
        left: `${x}px`,
        width: POPUP_WIDTH,
        padding: POPUP_PADDING
      },
      children: [
        isEditing ? /* @__PURE__ */ jsx48(
          "input",
          {
            className: clsx19("excalidraw-hyperlinkContainer-input"),
            placeholder: t("labels.link.hint"),
            ref: inputRef,
            value: inputVal,
            onChange: (event) => setInputVal(event.target.value),
            autoFocus: true,
            onKeyDown: (event) => {
              event.stopPropagation();
              if (event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K) {
                event.preventDefault();
              }
              if (event.key === KEYS.ENTER || event.key === KEYS.ESCAPE) {
                handleSubmit();
                setAppState({ showHyperlinkPopup: "info" });
              }
            }
          }
        ) : element.link ? /* @__PURE__ */ jsx48(
          "a",
          {
            href: normalizeLink(element.link || ""),
            className: "excalidraw-hyperlinkContainer-link",
            target: isLocalLink(element.link) ? "_self" : "_blank",
            onClick: (event) => {
              if (element.link && onLinkOpen) {
                const customEvent = wrapEvent(
                  "excalidraw-link" /* EXCALIDRAW_LINK */,
                  event.nativeEvent
                );
                onLinkOpen(
                  {
                    ...element,
                    link: normalizeLink(element.link)
                  },
                  customEvent
                );
                if (customEvent.defaultPrevented) {
                  event.preventDefault();
                }
              }
            },
            rel: "noopener noreferrer",
            children: element.link
          }
        ) : /* @__PURE__ */ jsx48("div", { className: "excalidraw-hyperlinkContainer-link", children: t("labels.link.empty") }),
        /* @__PURE__ */ jsxs26("div", { className: "excalidraw-hyperlinkContainer__buttons", children: [
          !isEditing && /* @__PURE__ */ jsx48(
            ToolButton,
            {
              type: "button",
              title: t("buttons.edit"),
              "aria-label": t("buttons.edit"),
              label: t("buttons.edit"),
              onClick: onEdit,
              className: "excalidraw-hyperlinkContainer--edit",
              icon: FreedrawIcon
            }
          ),
          /* @__PURE__ */ jsx48(
            ToolButton,
            {
              type: "button",
              title: t("labels.linkToElement"),
              "aria-label": t("labels.linkToElement"),
              label: t("labels.linkToElement"),
              onClick: () => {
                setAppState({
                  openDialog: {
                    name: "elementLinkSelector",
                    sourceElementId: element.id
                  }
                });
              },
              icon: elementLinkIcon
            }
          ),
          linkVal && !isEmbeddableElement(element) && /* @__PURE__ */ jsx48(
            ToolButton,
            {
              type: "button",
              title: t("buttons.remove"),
              "aria-label": t("buttons.remove"),
              label: t("buttons.remove"),
              onClick: handleRemove,
              className: "excalidraw-hyperlinkContainer--remove",
              icon: TrashIcon
            }
          )
        ] })
      ]
    }
  );
};
var getCoordsForPopover = (element, appState, elementsMap) => {
  const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
  const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
    { sceneX: x1 + element.width / 2, sceneY: y1 },
    appState
  );
  const x = viewportX - appState.offsetLeft - POPUP_WIDTH / 2;
  const y = viewportY - appState.offsetTop - SPACE_BOTTOM;
  return { x, y };
};
var getContextMenuLabel = (elements, appState) => {
  const selectedElements = getSelectedElements(elements, appState);
  const label = isEmbeddableElement(selectedElements[0]) ? "labels.link.editEmbed" : selectedElements[0]?.link ? "labels.link.edit" : "labels.link.create";
  return label;
};
var HYPERLINK_TOOLTIP_TIMEOUT_ID = null;
var showHyperlinkTooltip = (element, appState, elementsMap) => {
  if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
    clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
  }
  HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
    () => renderTooltip(element, appState, elementsMap),
    HYPERLINK_TOOLTIP_DELAY
  );
};
var renderTooltip = (element, appState, elementsMap) => {
  if (!element.link) {
    return;
  }
  const tooltipDiv = getTooltipDiv();
  tooltipDiv.classList.add("excalidraw-tooltip--visible");
  tooltipDiv.style.maxWidth = "20rem";
  tooltipDiv.textContent = isElementLink(element.link) ? t("labels.link.goToElement") : element.link;
  const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
  const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
    [x1, y1, x2, y2],
    element.angle,
    appState
  );
  const linkViewportCoords = sceneCoordsToViewportCoords(
    { sceneX: linkX, sceneY: linkY },
    appState
  );
  updateTooltipPosition(
    tooltipDiv,
    {
      left: linkViewportCoords.x,
      top: linkViewportCoords.y,
      width: linkWidth,
      height: linkHeight
    },
    "top"
  );
  trackEvent("hyperlink", "tooltip", "link-icon");
  IS_HYPERLINK_TOOLTIP_VISIBLE = true;
};
var hideHyperlinkToolip = () => {
  if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
    clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
  }
  if (IS_HYPERLINK_TOOLTIP_VISIBLE) {
    IS_HYPERLINK_TOOLTIP_VISIBLE = false;
    getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
  }
};
var shouldHideLinkPopup = (element, elementsMap, appState, [clientX, clientY]) => {
  const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
    { clientX, clientY },
    appState
  );
  const threshold = 15 / appState.zoom.value;
  if (hitElementBoundingBox(sceneX, sceneY, element, elementsMap)) {
    return false;
  }
  const [x1, y1, x2] = getElementAbsoluteCoords(element, elementsMap);
  if (sceneX >= x1 && sceneX <= x2 && sceneY >= y1 - SPACE_BOTTOM && sceneY <= y1) {
    return false;
  }
  const { x: popoverX, y: popoverY } = getCoordsForPopover(
    element,
    appState,
    elementsMap
  );
  if (clientX >= popoverX - threshold && clientX <= popoverX + POPUP_WIDTH + POPUP_PADDING * 2 + threshold && clientY >= popoverY - threshold && clientY <= popoverY + threshold + POPUP_PADDING * 2 + POPUP_HEIGHT) {
    return false;
  }
  return true;
};

// actions/actionLink.tsx
import { jsx as jsx49 } from "react/jsx-runtime";
var actionLink = register({
  name: "hyperlink",
  label: (elements, appState) => getContextMenuLabel(elements, appState),
  icon: LinkIcon,
  perform: (elements, appState) => {
    if (appState.showHyperlinkPopup === "editor") {
      return false;
    }
    return {
      elements,
      appState: {
        ...appState,
        showHyperlinkPopup: "editor",
        openMenu: null
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  trackEvent: { category: "hyperlink", action: "click" },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
  predicate: (elements, appState) => {
    const selectedElements = getSelectedElements(elements, appState);
    return selectedElements.length === 1;
  },
  PanelComponent: ({ elements, appState, updateData }) => {
    const selectedElements = getSelectedElements(elements, appState);
    return /* @__PURE__ */ jsx49(
      ToolButton,
      {
        type: "button",
        icon: LinkIcon,
        "aria-label": t(getContextMenuLabel(elements, appState)),
        title: `${isEmbeddableElement(elements[0]) ? t("labels.link.labelEmbed") : t("labels.link.label")} - ${getShortcutKey("CtrlOrCmd+K")}`,
        onClick: () => updateData(null),
        selected: selectedElements.length === 1 && !!selectedElements[0].link
      }
    );
  }
});

// actions/actionElementLock.ts
var shouldLock = (elements) => elements.every((el) => !el.locked);
var actionToggleElementLock = register({
  name: "toggleElementLock",
  label: (elements, appState, app) => {
    const selected = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: false
    });
    if (selected.length === 1 && !isFrameLikeElement(selected[0])) {
      return selected[0].locked ? "labels.elementLock.unlock" : "labels.elementLock.lock";
    }
    return shouldLock(selected) ? "labels.elementLock.lockAll" : "labels.elementLock.unlockAll";
  },
  icon: (appState, elements) => {
    const selectedElements = getSelectedElements(elements, appState);
    return shouldLock(selectedElements) ? LockedIcon : UnlockedIcon;
  },
  trackEvent: { category: "element" },
  predicate: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    return selectedElements.length > 0 && !selectedElements.some((element) => element.locked && element.frameId);
  },
  perform: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true,
      includeElementsInFrames: true
    });
    if (!selectedElements.length) {
      return false;
    }
    const nextLockState = shouldLock(selectedElements);
    const selectedElementsMap = arrayToMap(selectedElements);
    return {
      elements: elements.map((element) => {
        if (!selectedElementsMap.has(element.id)) {
          return element;
        }
        return newElementWith(element, { locked: nextLockState });
      }),
      appState: {
        ...appState,
        selectedLinearElement: nextLockState ? null : appState.selectedLinearElement
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  keyTest: (event, appState, elements, app) => {
    return event.key.toLocaleLowerCase() === KEYS.L && event[KEYS.CTRL_OR_CMD] && event.shiftKey && app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: false
    }).length > 0;
  }
});
var actionUnlockAllElements = register({
  name: "unlockAllElements",
  paletteName: "Unlock all elements",
  trackEvent: { category: "canvas" },
  viewMode: false,
  icon: UnlockedIcon,
  predicate: (elements, appState) => {
    const selectedElements = getSelectedElements(elements, appState);
    return selectedElements.length === 0 && elements.some((element) => element.locked);
  },
  perform: (elements, appState) => {
    const lockedElements = elements.filter((el) => el.locked);
    return {
      elements: elements.map((element) => {
        if (element.locked) {
          return newElementWith(element, { locked: false });
        }
        return element;
      }),
      appState: {
        ...appState,
        selectedElementIds: Object.fromEntries(
          lockedElements.map((el) => [el.id, true])
        )
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  label: "labels.elementLock.unlockAll"
});

// components/CommandPalette/CommandPalette.tsx
import { useEffect as useEffect28, useRef as useRef22, useState as useState23 } from "react";

// components/Dialog.tsx
import clsx28 from "clsx";
import { useEffect as useEffect27, useState as useState20 } from "react";

// hooks/useCallbackRefState.ts
import { useCallback as useCallback5, useState as useState12 } from "react";
var useCallbackRefState = () => {
  const [refValue, setRefValue] = useState12(null);
  const refCallback = useCallback5((value) => setRefValue(value), []);
  return [refValue, refCallback];
};

// components/Modal.tsx
import { createPortal as createPortal2 } from "react-dom";
import clsx20 from "clsx";
import { useRef as useRef13 } from "react";
import { jsx as jsx50, jsxs as jsxs27 } from "react/jsx-runtime";
var Modal = (props) => {
  const { closeOnClickOutside = true } = props;
  const modalRoot = useCreatePortalContainer({
    className: "excalidraw-modal-container"
  });
  const animationsDisabledRef = useRef13(
    document.body.classList.contains("excalidraw-animations-disabled")
  );
  if (!modalRoot) {
    return null;
  }
  const handleKeydown = (event) => {
    if (event.key === KEYS.ESCAPE) {
      event.nativeEvent.stopImmediatePropagation();
      event.stopPropagation();
      props.onCloseRequest();
    }
  };
  return createPortal2(
    /* @__PURE__ */ jsxs27(
      "div",
      {
        className: clsx20("Modal", props.className, {
          "animations-disabled": animationsDisabledRef.current
        }),
        role: "dialog",
        "aria-modal": "true",
        onKeyDown: handleKeydown,
        "aria-labelledby": props.labelledBy,
        "data-prevent-outside-click": true,
        children: [
          /* @__PURE__ */ jsx50(
            "div",
            {
              className: "Modal__background",
              onClick: closeOnClickOutside ? props.onCloseRequest : void 0
            }
          ),
          /* @__PURE__ */ jsx50(
            "div",
            {
              className: "Modal__content",
              style: { "--max-width": `${props.maxWidth}px` },
              tabIndex: 0,
              children: props.children
            }
          )
        ]
      }
    ),
    modalRoot
  );
};

// components/LibraryMenu.tsx
import {
  useState as useState19,
  useCallback as useCallback10,
  useMemo as useMemo6,
  useEffect as useEffect26,
  memo as memo3,
  useRef as useRef19
} from "react";

// data/library.ts
import { useEffect as useEffect19, useRef as useRef14 } from "react";

// hooks/useLibraryItemSvg.ts
import { useEffect as useEffect18, useState as useState13 } from "react";
var libraryItemSvgsCache = atom(/* @__PURE__ */ new Map());
var exportLibraryItemToSvg = async (elements) => {
  return await exportToSvg2({
    elements,
    appState: {
      exportBackground: false,
      viewBackgroundColor: COLOR_PALETTE.white
    },
    files: null,
    renderEmbeddables: false,
    skipInliningFonts: true
  });
};
var useLibraryItemSvg = (id, elements, svgCache) => {
  const [svg, setSvg] = useState13();
  useEffect18(() => {
    if (elements) {
      if (id) {
        const cachedSvg = svgCache.get(id);
        if (cachedSvg) {
          setSvg(cachedSvg);
        } else {
          (async () => {
            const exportedSvg = await exportLibraryItemToSvg(elements);
            exportedSvg.querySelector(".style-fonts")?.remove();
            if (exportedSvg) {
              svgCache.set(id, exportedSvg);
              setSvg(exportedSvg);
            }
          })();
        }
      } else {
        (async () => {
          const exportedSvg = await exportLibraryItemToSvg(elements);
          setSvg(exportedSvg);
        })();
      }
    }
  }, [id, elements, svgCache, setSvg]);
  return svg;
};
var useLibraryCache = () => {
  const [svgCache] = useAtom(libraryItemSvgsCache);
  const clearLibraryCache = () => svgCache.clear();
  const deleteItemsFromLibraryCache = (items) => {
    items.forEach((item) => svgCache.delete(item));
  };
  return {
    clearLibraryCache,
    deleteItemsFromLibraryCache,
    svgCache
  };
};

// queue.ts
var Queue = class {
  constructor() {
    __publicField(this, "jobs", []);
    __publicField(this, "running", false);
  }
  tick() {
    if (this.running) {
      return;
    }
    const job = this.jobs.shift();
    if (job) {
      this.running = true;
      job.promise.resolve(
        promiseTry(job.jobFactory, ...job.args).finally(() => {
          this.running = false;
          this.tick();
        })
      );
    } else {
      this.running = false;
    }
  }
  push(jobFactory, ...args) {
    const promise = resolvablePromise();
    this.jobs.push({ jobFactory, promise, args });
    this.tick();
    return promise;
  }
};

// data/library.ts
var ALLOWED_LIBRARY_URLS = [
  "excalidraw.com",
  // when installing from github PRs
  "raw.githubusercontent.com/excalidraw/excalidraw-libraries"
];
var onLibraryUpdateEmitter = new Emitter();
var libraryItemsAtom = atom({ status: "loaded", isInitialized: false, libraryItems: [] });
var cloneLibraryItems = (libraryItems) => cloneJSON(libraryItems);
var isUniqueItem = (existingLibraryItems, targetLibraryItem) => {
  return !existingLibraryItems.find((libraryItem) => {
    if (libraryItem.elements.length !== targetLibraryItem.elements.length) {
      return false;
    }
    return libraryItem.elements.every((libItemExcalidrawItem, idx) => {
      return libItemExcalidrawItem.id === targetLibraryItem.elements[idx].id && libItemExcalidrawItem.versionNonce === targetLibraryItem.elements[idx].versionNonce;
    });
  });
};
var mergeLibraryItems = (localItems, otherItems) => {
  const newItems = [];
  for (const item of otherItems) {
    if (isUniqueItem(localItems, item)) {
      newItems.push(item);
    }
  }
  return [...newItems, ...localItems];
};
var createLibraryUpdate = (prevLibraryItems, nextLibraryItems) => {
  const nextItemsMap = arrayToMap(nextLibraryItems);
  const update = {
    deletedItems: /* @__PURE__ */ new Map(),
    addedItems: /* @__PURE__ */ new Map()
  };
  for (const item of prevLibraryItems) {
    if (!nextItemsMap.has(item.id)) {
      update.deletedItems.set(item.id, item);
    }
  }
  const prevItemsMap = arrayToMap(prevLibraryItems);
  for (const item of nextLibraryItems) {
    if (!prevItemsMap.has(item.id)) {
      update.addedItems.set(item.id, item);
    }
  }
  return update;
};
var Library = class {
  constructor(app) {
    /** latest libraryItems */
    __publicField(this, "currLibraryItems", []);
    /** snapshot of library items since last onLibraryChange call */
    __publicField(this, "prevLibraryItems", cloneLibraryItems(this.currLibraryItems));
    __publicField(this, "app");
    __publicField(this, "updateQueue", []);
    __publicField(this, "getLastUpdateTask", () => {
      return this.updateQueue[this.updateQueue.length - 1];
    });
    __publicField(this, "notifyListeners", () => {
      if (this.updateQueue.length > 0) {
        editorJotaiStore.set(libraryItemsAtom, (s) => ({
          status: "loading",
          libraryItems: this.currLibraryItems,
          isInitialized: s.isInitialized
        }));
      } else {
        editorJotaiStore.set(libraryItemsAtom, {
          status: "loaded",
          libraryItems: this.currLibraryItems,
          isInitialized: true
        });
        try {
          const prevLibraryItems = this.prevLibraryItems;
          this.prevLibraryItems = cloneLibraryItems(this.currLibraryItems);
          const nextLibraryItems = cloneLibraryItems(this.currLibraryItems);
          this.app.props.onLibraryChange?.(nextLibraryItems);
          onLibraryUpdateEmitter.trigger(
            createLibraryUpdate(prevLibraryItems, nextLibraryItems),
            nextLibraryItems
          );
        } catch (error) {
          console.error(error);
        }
      }
    });
    /** call on excalidraw instance unmount */
    __publicField(this, "destroy", () => {
      this.updateQueue = [];
      this.currLibraryItems = [];
      editorJotaiStore.set(libraryItemSvgsCache, /* @__PURE__ */ new Map());
    });
    __publicField(this, "resetLibrary", () => {
      return this.setLibrary([]);
    });
    /**
     * @returns latest cloned libraryItems. Awaits all in-progress updates first.
     */
    __publicField(this, "getLatestLibrary", () => {
      return new Promise(async (resolve) => {
        try {
          const libraryItems = await (this.getLastUpdateTask() || this.currLibraryItems);
          if (this.updateQueue.length > 0) {
            resolve(this.getLatestLibrary());
          } else {
            resolve(cloneLibraryItems(libraryItems));
          }
        } catch (error) {
          return resolve(this.currLibraryItems);
        }
      });
    });
    // NOTE this is a high-level public API (exposed on ExcalidrawAPI) with
    // a slight overhead (always restoring library items). For internal use
    // where merging isn't needed, use `library.setLibrary()` directly.
    __publicField(this, "updateLibrary", async ({
      libraryItems,
      prompt = false,
      merge = false,
      openLibraryMenu = false,
      defaultStatus = "unpublished"
    }) => {
      if (openLibraryMenu) {
        this.app.setState({
          openSidebar: { name: DEFAULT_SIDEBAR.name, tab: LIBRARY_SIDEBAR_TAB }
        });
      }
      return this.setLibrary(() => {
        return new Promise(async (resolve, reject) => {
          try {
            const source = await (typeof libraryItems === "function" && !(libraryItems instanceof Blob) ? libraryItems(this.currLibraryItems) : libraryItems);
            let nextItems;
            if (source instanceof Blob) {
              nextItems = await loadLibraryFromBlob(source, defaultStatus);
            } else {
              nextItems = restoreLibraryItems(source, defaultStatus);
            }
            if (!prompt || window.confirm(
              t("alerts.confirmAddLibrary", {
                numShapes: nextItems.length
              })
            )) {
              if (prompt) {
                this.app.focusContainer();
              }
              if (merge) {
                resolve(mergeLibraryItems(this.currLibraryItems, nextItems));
              } else {
                resolve(nextItems);
              }
            } else {
              reject(new AbortError());
            }
          } catch (error) {
            reject(error);
          }
        });
      });
    });
    __publicField(this, "setLibrary", (libraryItems) => {
      const task = new Promise(async (resolve, reject) => {
        try {
          await this.getLastUpdateTask();
          if (typeof libraryItems === "function") {
            libraryItems = libraryItems(this.currLibraryItems);
          }
          this.currLibraryItems = cloneLibraryItems(await libraryItems);
          resolve(this.currLibraryItems);
        } catch (error) {
          reject(error);
        }
      }).catch((error) => {
        if (error.name === "AbortError") {
          console.warn("Library update aborted by user");
          return this.currLibraryItems;
        }
        throw error;
      }).finally(() => {
        this.updateQueue = this.updateQueue.filter((_task) => _task !== task);
        this.notifyListeners();
      });
      this.updateQueue.push(task);
      this.notifyListeners();
      return task;
    });
    this.app = app;
  }
};
var library_default = Library;
var distributeLibraryItemsOnSquareGrid = (libraryItems) => {
  const PADDING = 50;
  const ITEMS_PER_ROW = Math.ceil(Math.sqrt(libraryItems.length));
  const resElements = [];
  const getMaxHeightPerRow = (row2) => {
    const maxHeight = libraryItems.slice(row2 * ITEMS_PER_ROW, row2 * ITEMS_PER_ROW + ITEMS_PER_ROW).reduce((acc, item) => {
      const { height } = getCommonBoundingBox(item.elements);
      return Math.max(acc, height);
    }, 0);
    return maxHeight;
  };
  const getMaxWidthPerCol = (targetCol) => {
    let index2 = 0;
    let currCol = 0;
    let maxWidth = 0;
    for (const item of libraryItems) {
      if (index2 % ITEMS_PER_ROW === 0) {
        currCol = 0;
      }
      if (currCol === targetCol) {
        const { width } = getCommonBoundingBox(item.elements);
        maxWidth = Math.max(maxWidth, width);
      }
      index2++;
      currCol++;
    }
    return maxWidth;
  };
  let colOffsetX = 0;
  let rowOffsetY = 0;
  let maxHeightCurrRow = 0;
  let maxWidthCurrCol = 0;
  let index = 0;
  let col = 0;
  let row = 0;
  for (const item of libraryItems) {
    if (index && index % ITEMS_PER_ROW === 0) {
      rowOffsetY += maxHeightCurrRow + PADDING;
      colOffsetX = 0;
      col = 0;
      row++;
    }
    if (col === 0) {
      maxHeightCurrRow = getMaxHeightPerRow(row);
    }
    maxWidthCurrCol = getMaxWidthPerCol(col);
    const { minX, minY, width, height } = getCommonBoundingBox(item.elements);
    const offsetCenterX = (maxWidthCurrCol - width) / 2;
    const offsetCenterY = (maxHeightCurrRow - height) / 2;
    resElements.push(
      ...item.elements.map((element) => ({
        ...element,
        x: element.x + // offset for column
        colOffsetX + // offset to center in given square grid
        offsetCenterX - // subtract minX so that given item starts at 0 coord
        minX,
        y: element.y + // offset for row
        rowOffsetY + // offset to center in given square grid
        offsetCenterY - // subtract minY so that given item starts at 0 coord
        minY
      }))
    );
    colOffsetX += maxWidthCurrCol + PADDING;
    index++;
    col++;
  }
  return resElements;
};
var validateLibraryUrl = (libraryUrl, validator = ALLOWED_LIBRARY_URLS) => {
  if (typeof validator === "function" ? validator(libraryUrl) : validator.some((allowedUrlDef) => {
    const allowedUrl = new URL(
      `https://${allowedUrlDef.replace(/^https?:\/\//, "")}`
    );
    const { hostname, pathname } = new URL(libraryUrl);
    return new RegExp(`(^|\\.)${allowedUrl.hostname}$`).test(hostname) && new RegExp(
      `^${allowedUrl.pathname.replace(/\/+$/, "")}(/+|$)`
    ).test(pathname);
  })) {
    return true;
  }
  throw new Error(`Invalid or disallowed library URL: "${libraryUrl}"`);
};
var parseLibraryTokensFromUrl = () => {
  const libraryUrl = (
    // current
    new URLSearchParams(window.location.hash.slice(1)).get(
      URL_HASH_KEYS.addLibrary
    ) || // legacy, kept for compat reasons
    new URLSearchParams(window.location.search).get(URL_QUERY_KEYS.addLibrary)
  );
  const idToken = libraryUrl ? new URLSearchParams(window.location.hash.slice(1)).get("token") : null;
  return libraryUrl ? { libraryUrl, idToken } : null;
};
var _AdapterTransaction = class _AdapterTransaction {
  constructor(adapter) {
    // ------------------
    __publicField(this, "adapter");
    this.adapter = adapter;
  }
  static async getLibraryItems(adapter, source, _queue = true) {
    const task = () => new Promise(async (resolve, reject) => {
      try {
        const data = await adapter.load({ source });
        resolve(restoreLibraryItems(data?.libraryItems || [], "published"));
      } catch (error) {
        reject(error);
      }
    });
    if (_queue) {
      return _AdapterTransaction.queue.push(task);
    }
    return task();
  }
  getLibraryItems(source) {
    return _AdapterTransaction.getLibraryItems(this.adapter, source, false);
  }
};
__publicField(_AdapterTransaction, "queue", new Queue());
__publicField(_AdapterTransaction, "run", async (adapter, fn) => {
  const transaction = new _AdapterTransaction(adapter);
  return _AdapterTransaction.queue.push(() => fn(transaction));
});
var AdapterTransaction = _AdapterTransaction;
var lastSavedLibraryItemsHash = 0;
var librarySaveCounter = 0;
var getLibraryItemsHash = (items) => {
  return hashString(
    items.map((item) => {
      return `${item.id}:${hashElementsVersion(item.elements)}`;
    }).sort().join()
  );
};
var persistLibraryUpdate = async (adapter, update) => {
  try {
    librarySaveCounter++;
    return await AdapterTransaction.run(adapter, async (transaction) => {
      const nextLibraryItemsMap = arrayToMap(
        await transaction.getLibraryItems("save")
      );
      for (const [id] of update.deletedItems) {
        nextLibraryItemsMap.delete(id);
      }
      const addedItems = [];
      for (const [id, item] of update.addedItems) {
        if (nextLibraryItemsMap.has(id)) {
          nextLibraryItemsMap.set(id, item);
        } else {
          addedItems.push(item);
        }
      }
      const nextLibraryItems = addedItems.concat(
        Array.from(nextLibraryItemsMap.values())
      );
      const version = getLibraryItemsHash(nextLibraryItems);
      if (version !== lastSavedLibraryItemsHash) {
        await adapter.save({ libraryItems: nextLibraryItems });
      }
      lastSavedLibraryItemsHash = version;
      return nextLibraryItems;
    });
  } finally {
    librarySaveCounter--;
  }
};
var useHandleLibrary = (opts) => {
  const { excalidrawAPI } = opts;
  const optsRef = useRef14(opts);
  optsRef.current = opts;
  const isLibraryLoadedRef = useRef14(false);
  useEffect19(() => {
    if (!excalidrawAPI) {
      return;
    }
    isLibraryLoadedRef.current = false;
    const importLibraryFromURL = async ({
      libraryUrl,
      idToken
    }) => {
      const libraryPromise = new Promise(async (resolve, reject) => {
        try {
          libraryUrl = decodeURIComponent(libraryUrl);
          libraryUrl = toValidURL(libraryUrl);
          validateLibraryUrl(libraryUrl, optsRef.current.validateLibraryUrl);
          const request = await fetch(libraryUrl);
          const blob = await request.blob();
          resolve(blob);
        } catch (error) {
          reject(error);
        }
      });
      const shouldPrompt = idToken !== excalidrawAPI.id;
      await (shouldPrompt && document.hidden ? new Promise((resolve) => {
        window.addEventListener("focus", () => resolve(), {
          once: true
        });
      }) : null);
      try {
        await excalidrawAPI.updateLibrary({
          libraryItems: libraryPromise,
          prompt: shouldPrompt,
          merge: true,
          defaultStatus: "published",
          openLibraryMenu: true
        });
      } catch (error) {
        excalidrawAPI.updateScene({
          appState: {
            errorMessage: error.message
          }
        });
        throw error;
      } finally {
        if (window.location.hash.includes(URL_HASH_KEYS.addLibrary)) {
          const hash = new URLSearchParams(window.location.hash.slice(1));
          hash.delete(URL_HASH_KEYS.addLibrary);
          window.history.replaceState({}, APP_NAME, `#${hash.toString()}`);
        } else if (window.location.search.includes(URL_QUERY_KEYS.addLibrary)) {
          const query = new URLSearchParams(window.location.search);
          query.delete(URL_QUERY_KEYS.addLibrary);
          window.history.replaceState({}, APP_NAME, `?${query.toString()}`);
        }
      }
    };
    const onHashChange = (event) => {
      event.preventDefault();
      const libraryUrlTokens2 = parseLibraryTokensFromUrl();
      if (libraryUrlTokens2) {
        event.stopImmediatePropagation();
        window.history.replaceState({}, "", event.oldURL);
        importLibraryFromURL(libraryUrlTokens2);
      }
    };
    const libraryUrlTokens = parseLibraryTokensFromUrl();
    if (libraryUrlTokens) {
      importLibraryFromURL(libraryUrlTokens);
    }
    if ("getInitialLibraryItems" in optsRef.current && optsRef.current.getInitialLibraryItems) {
      console.warn(
        "useHandleLibrar `opts.getInitialLibraryItems` is deprecated. Use `opts.adapter` instead."
      );
      Promise.resolve(optsRef.current.getInitialLibraryItems()).then((libraryItems) => {
        excalidrawAPI.updateLibrary({
          libraryItems,
          // merge with current library items because we may have already
          // populated it (e.g. by installing 3rd party library which can
          // happen before the DB data is loaded)
          merge: true
        });
      }).catch((error) => {
        console.error(
          `UseHandeLibrary getInitialLibraryItems failed: ${error?.message}`
        );
      });
    }
    if ("adapter" in optsRef.current && optsRef.current.adapter) {
      const adapter = optsRef.current.adapter;
      const migrationAdapter = optsRef.current.migrationAdapter;
      const initDataPromise = resolvablePromise();
      if (migrationAdapter) {
        initDataPromise.resolve(
          promiseTry(migrationAdapter.load).then(async (libraryData) => {
            let restoredData = null;
            try {
              if (!libraryData) {
                return AdapterTransaction.getLibraryItems(adapter, "load");
              }
              restoredData = restoreLibraryItems(
                libraryData.libraryItems || [],
                "published"
              );
              const nextItems = await persistLibraryUpdate(
                adapter,
                createLibraryUpdate([], restoredData)
              );
              try {
                await migrationAdapter.clear();
              } catch (error) {
                console.error(
                  `couldn't delete legacy library data: ${error.message}`
                );
              }
              return nextItems;
            } catch (error) {
              console.error(
                `couldn't migrate legacy library data: ${error.message}`
              );
              return restoredData;
            }
          }).catch((error) => {
            console.error(`error during library migration: ${error.message}`);
            return AdapterTransaction.getLibraryItems(adapter, "load");
          })
        );
      } else {
        initDataPromise.resolve(
          promiseTry(AdapterTransaction.getLibraryItems, adapter, "load")
        );
      }
      excalidrawAPI.updateLibrary({
        libraryItems: initDataPromise.then((libraryItems) => {
          const _libraryItems = libraryItems || [];
          lastSavedLibraryItemsHash = getLibraryItemsHash(_libraryItems);
          return _libraryItems;
        }),
        // merge with current library items because we may have already
        // populated it (e.g. by installing 3rd party library which can
        // happen before the DB data is loaded)
        merge: true
      }).finally(() => {
        isLibraryLoadedRef.current = true;
      });
    }
    window.addEventListener("hashchange" /* HASHCHANGE */, onHashChange);
    return () => {
      window.removeEventListener("hashchange" /* HASHCHANGE */, onHashChange);
    };
  }, [
    // important this useEffect only depends on excalidrawAPI so it only reruns
    // on editor remounts (the excalidrawAPI changes)
    excalidrawAPI
  ]);
  useEffect19(
    () => {
      const unsubOnLibraryUpdate = onLibraryUpdateEmitter.on(
        async (update, nextLibraryItems) => {
          const isLoaded = isLibraryLoadedRef.current;
          const adapter = "adapter" in optsRef.current && optsRef.current.adapter || null;
          try {
            if (adapter) {
              if (
                // if nextLibraryItems hash identical to previously saved hash,
                // exit early, even if actual upstream state ends up being
                // different (e.g. has more data than we have locally), as it'd
                // be low-impact scenario.
                lastSavedLibraryItemsHash !== getLibraryItemsHash(nextLibraryItems)
              ) {
                await persistLibraryUpdate(adapter, update);
              }
            }
          } catch (error) {
            console.error(
              `couldn't persist library update: ${error.message}`,
              update
            );
            if (isLoaded && optsRef.current.excalidrawAPI) {
              optsRef.current.excalidrawAPI.updateScene({
                appState: {
                  errorMessage: t("errors.saveLibraryError")
                }
              });
            }
          }
        }
      );
      const onUnload = (event) => {
        if (librarySaveCounter) {
          preventUnload(event);
        }
      };
      window.addEventListener("beforeunload" /* BEFORE_UNLOAD */, onUnload);
      return () => {
        window.removeEventListener("beforeunload" /* BEFORE_UNLOAD */, onUnload);
        unsubOnLibraryUpdate();
        lastSavedLibraryItemsHash = 0;
        librarySaveCounter = 0;
      };
    },
    [
      // this effect must not have any deps so it doesn't rerun
    ]
  );
};

// components/LibraryMenuItems.tsx
import {
  useCallback as useCallback9,
  useEffect as useEffect25,
  useMemo as useMemo5,
  useRef as useRef18,
  useState as useState18
} from "react";

// components/Stack.tsx
import { forwardRef as forwardRef2 } from "react";
import clsx21 from "clsx";
import { jsx as jsx51 } from "react/jsx-runtime";
var RowStack = forwardRef2(
  ({ children, gap, align, justifyContent, className, style }, ref) => {
    return /* @__PURE__ */ jsx51(
      "div",
      {
        className: clsx21("Stack Stack_horizontal", className),
        style: {
          "--gap": gap,
          alignItems: align,
          justifyContent,
          ...style
        },
        ref,
        children
      }
    );
  }
);
var ColStack = forwardRef2(
  ({ children, gap, align, justifyContent, className, style }, ref) => {
    return /* @__PURE__ */ jsx51(
      "div",
      {
        className: clsx21("Stack Stack_vertical", className),
        style: {
          "--gap": gap,
          justifyItems: align,
          justifyContent,
          ...style
        },
        ref,
        children
      }
    );
  }
);
var Stack_default = {
  Row: RowStack,
  Col: ColStack
};

// components/LibraryMenuBrowseButton.tsx
import { jsx as jsx52 } from "react/jsx-runtime";
var LibraryMenuBrowseButton = ({
  theme,
  id,
  libraryReturnUrl
}) => {
  const referrer = libraryReturnUrl || window.location.origin + window.location.pathname;
  return /* @__PURE__ */ jsx52(
    "a",
    {
      className: "library-menu-browse-button",
      href: `${define_import_meta_env_default.VITE_APP_LIBRARY_URL}?target=${window.name || "_blank"}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${VERSIONS.excalidrawLibrary}`,
      target: "_excalidraw_libraries",
      children: t("labels.libraries")
    }
  );
};
var LibraryMenuBrowseButton_default = LibraryMenuBrowseButton;

// components/LibraryMenuControlButtons.tsx
import clsx22 from "clsx";
import { jsx as jsx53, jsxs as jsxs28 } from "react/jsx-runtime";
var LibraryMenuControlButtons = ({
  libraryReturnUrl,
  theme,
  id,
  style,
  children,
  className
}) => {
  return /* @__PURE__ */ jsxs28(
    "div",
    {
      className: clsx22("library-menu-control-buttons", className),
      style,
      children: [
        /* @__PURE__ */ jsx53(
          LibraryMenuBrowseButton_default,
          {
            id,
            libraryReturnUrl,
            theme
          }
        ),
        children
      ]
    }
  );
};

// components/LibraryMenuHeaderContent.tsx
import { useCallback as useCallback7, useState as useState15 } from "react";

// components/Trans.tsx
import React19 from "react";
var SPLIT_REGEX = /({{[\w-]+}})|(<[\w-]+>)|(<\/[\w-]+>)/g;
var KEY_REGEXP = /{{([\w-]+)}}/;
var TAG_START_REGEXP = /<([\w-]+)>/;
var TAG_END_REGEXP = /<\/([\w-]+)>/;
var getTransChildren = (format, props) => {
  const stack = [
    {
      name: "",
      children: []
    }
  ];
  format.split(SPLIT_REGEX).filter(Boolean).forEach((match) => {
    const tagStartMatch = match.match(TAG_START_REGEXP);
    const tagEndMatch = match.match(TAG_END_REGEXP);
    const keyMatch = match.match(KEY_REGEXP);
    if (tagStartMatch !== null) {
      const name = tagStartMatch[1];
      if (props.hasOwnProperty(name)) {
        stack.push({
          name,
          children: []
        });
      } else {
        console.warn(
          `Trans: missed to pass in prop ${name} for interpolating ${format}`
        );
      }
    } else if (tagEndMatch !== null) {
      const name = tagEndMatch[1];
      if (name === stack[stack.length - 1].name) {
        const item = stack.pop();
        const itemChildren = React19.createElement(
          React19.Fragment,
          {},
          ...item.children
        );
        const fn = props[item.name];
        if (typeof fn === "function") {
          stack[stack.length - 1].children.push(fn(itemChildren));
        }
      } else {
        console.warn(
          `Trans: unexpected end tag ${match} for interpolating ${format}`
        );
      }
    } else if (keyMatch !== null) {
      const name = keyMatch[1];
      if (props.hasOwnProperty(name)) {
        stack[stack.length - 1].children.push(props[name]);
      } else {
        console.warn(
          `Trans: key ${name} not in props for interpolating ${format}`
        );
      }
    } else {
      stack[stack.length - 1].children.push(match);
    }
  });
  if (stack.length !== 1) {
    console.warn(`Trans: stack not empty for interpolating ${format}`);
  }
  return stack[0].children;
};
var Trans = ({
  i18nKey,
  children,
  ...props
}) => {
  const { t: t2 } = useI18n();
  return React19.createElement(
    React19.Fragment,
    {},
    ...getTransChildren(t2(i18nKey), props)
  );
};
var Trans_default = Trans;

// components/ConfirmDialog.tsx
import { flushSync } from "react-dom";

// components/DialogActionButton.tsx
import clsx23 from "clsx";
import { jsx as jsx54, jsxs as jsxs29 } from "react/jsx-runtime";
var DialogActionButton = ({
  label,
  onClick,
  className,
  children,
  actionType,
  type = "button",
  isLoading,
  ...rest
}) => {
  const cs = actionType ? `Dialog__action-button--${actionType}` : "";
  return /* @__PURE__ */ jsxs29(
    "button",
    {
      className: clsx23("Dialog__action-button", cs, className),
      type,
      "aria-label": label,
      onClick,
      ...rest,
      children: [
        children && /* @__PURE__ */ jsx54("div", { style: isLoading ? { visibility: "hidden" } : {}, children }),
        /* @__PURE__ */ jsx54("div", { style: isLoading ? { visibility: "hidden" } : {}, children: label }),
        isLoading && /* @__PURE__ */ jsx54("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx54(Spinner_default, {}) })
      ]
    }
  );
};
var DialogActionButton_default = DialogActionButton;

// components/ConfirmDialog.tsx
import { jsx as jsx55, jsxs as jsxs30 } from "react/jsx-runtime";
var ConfirmDialog = (props) => {
  const {
    onConfirm,
    onCancel,
    children,
    confirmText = t("buttons.confirm"),
    cancelText = t("buttons.cancel"),
    className = "",
    ...rest
  } = props;
  const setAppState = useExcalidrawSetAppState();
  const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
  const { container } = useExcalidrawContainer();
  return /* @__PURE__ */ jsxs30(
    Dialog,
    {
      onCloseRequest: onCancel,
      size: "small",
      ...rest,
      className: `confirm-dialog ${className}`,
      children: [
        children,
        /* @__PURE__ */ jsxs30("div", { className: "confirm-dialog-buttons", children: [
          /* @__PURE__ */ jsx55(
            DialogActionButton_default,
            {
              label: cancelText,
              onClick: () => {
                setAppState({ openMenu: null });
                setIsLibraryMenuOpen(false);
                flushSync(() => {
                  onCancel();
                });
                container?.focus();
              }
            }
          ),
          /* @__PURE__ */ jsx55(
            DialogActionButton_default,
            {
              label: confirmText,
              onClick: () => {
                setAppState({ openMenu: null });
                setIsLibraryMenuOpen(false);
                flushSync(() => {
                  onConfirm();
                });
                container?.focus();
              },
              actionType: "danger"
            }
          )
        ] })
      ]
    }
  );
};
var ConfirmDialog_default = ConfirmDialog;

// components/PublishLibrary.tsx
import { useCallback as useCallback6, useEffect as useEffect20, useRef as useRef15, useState as useState14 } from "react";
import OpenColor from "open-color";

// data/EditorLocalStorage.ts
var EditorLocalStorage = class {
  static has(key) {
    try {
      return !!window.localStorage.getItem(key);
    } catch (error) {
      console.warn(`localStorage.getItem error: ${error.message}`);
      return false;
    }
  }
  static get(key) {
    try {
      const value = window.localStorage.getItem(key);
      if (value) {
        return JSON.parse(value);
      }
      return null;
    } catch (error) {
      console.warn(`localStorage.getItem error: ${error.message}`);
      return null;
    }
  }
};
__publicField(EditorLocalStorage, "set", (key, value) => {
  try {
    window.localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch (error) {
    console.warn(`localStorage.setItem error: ${error.message}`);
    return false;
  }
});
__publicField(EditorLocalStorage, "delete", (name) => {
  try {
    window.localStorage.removeItem(name);
  } catch (error) {
    console.warn(`localStorage.removeItem error: ${error.message}`);
  }
});

// components/PublishLibrary.tsx
import { jsx as jsx56, jsxs as jsxs31 } from "react/jsx-runtime";
var generatePreviewImage = async (libraryItems) => {
  const MAX_ITEMS_PER_ROW = 6;
  const BOX_SIZE = 128;
  const BOX_PADDING = Math.round(BOX_SIZE / 16);
  const BORDER_WIDTH = Math.max(Math.round(BOX_SIZE / 64), 2);
  const rows = chunk(libraryItems, MAX_ITEMS_PER_ROW);
  const canvas = document.createElement("canvas");
  canvas.width = rows[0].length * BOX_SIZE + (rows[0].length + 1) * (BOX_PADDING * 2) - BOX_PADDING * 2;
  canvas.height = rows.length * BOX_SIZE + (rows.length + 1) * (BOX_PADDING * 2) - BOX_PADDING * 2;
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = OpenColor.white;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  for (const [index, item] of libraryItems.entries()) {
    const itemCanvas = await exportToCanvas2({
      elements: item.elements,
      files: null,
      maxWidthOrHeight: BOX_SIZE
    });
    const { width, height } = itemCanvas;
    const rowOffset = Math.floor(index / MAX_ITEMS_PER_ROW) * (BOX_SIZE + BOX_PADDING * 2);
    const colOffset = index % MAX_ITEMS_PER_ROW * (BOX_SIZE + BOX_PADDING * 2);
    ctx.drawImage(
      itemCanvas,
      colOffset + (BOX_SIZE - width) / 2 + BOX_PADDING,
      rowOffset + (BOX_SIZE - height) / 2 + BOX_PADDING
    );
    ctx.lineWidth = BORDER_WIDTH;
    ctx.strokeStyle = OpenColor.gray[4];
    ctx.strokeRect(
      colOffset + BOX_PADDING / 2,
      rowOffset + BOX_PADDING / 2,
      BOX_SIZE + BOX_PADDING,
      BOX_SIZE + BOX_PADDING
    );
  }
  return await resizeImageFile(
    new File([await canvasToBlob(canvas)], "preview", { type: MIME_TYPES.png }),
    {
      outputType: MIME_TYPES.jpg,
      maxWidthOrHeight: 5e3
    }
  );
};
var SingleLibraryItem = ({
  libItem,
  appState,
  index,
  onChange,
  onRemove
}) => {
  const svgRef = useRef15(null);
  const inputRef = useRef15(null);
  useEffect20(() => {
    const node = svgRef.current;
    if (!node) {
      return;
    }
    (async () => {
      const svg = await exportToSvg2({
        elements: libItem.elements,
        appState: {
          ...appState,
          viewBackgroundColor: OpenColor.white,
          exportBackground: true
        },
        files: null,
        skipInliningFonts: true
      });
      node.innerHTML = svg.outerHTML;
    })();
  }, [libItem.elements, appState]);
  return /* @__PURE__ */ jsxs31("div", { className: "single-library-item", children: [
    libItem.status === "published" && /* @__PURE__ */ jsx56("span", { className: "single-library-item-status", children: t("labels.statusPublished") }),
    /* @__PURE__ */ jsx56("div", { ref: svgRef, className: "single-library-item__svg" }),
    /* @__PURE__ */ jsx56(
      ToolButton,
      {
        "aria-label": t("buttons.remove"),
        type: "button",
        icon: CloseIcon,
        className: "single-library-item--remove",
        onClick: onRemove.bind(null, libItem.id),
        title: t("buttons.remove")
      }
    ),
    /* @__PURE__ */ jsxs31(
      "div",
      {
        style: {
          display: "flex",
          margin: "0.8rem 0",
          width: "100%",
          fontSize: "14px",
          fontWeight: 500,
          flexDirection: "column"
        },
        children: [
          /* @__PURE__ */ jsxs31(
            "label",
            {
              style: {
                display: "flex",
                justifyContent: "space-between",
                flexDirection: "column"
              },
              children: [
                /* @__PURE__ */ jsxs31("div", { style: { padding: "0.5em 0" }, children: [
                  /* @__PURE__ */ jsx56("span", { style: { fontWeight: 500, color: OpenColor.gray[6] }, children: t("publishDialog.itemName") }),
                  /* @__PURE__ */ jsx56("span", { "aria-hidden": "true", className: "required", children: "*" })
                ] }),
                /* @__PURE__ */ jsx56(
                  "input",
                  {
                    type: "text",
                    ref: inputRef,
                    style: { width: "80%", padding: "0.2rem" },
                    defaultValue: libItem.name,
                    placeholder: "Item name",
                    onChange: (event) => {
                      onChange(event.target.value, index);
                    }
                  }
                )
              ]
            }
          ),
          /* @__PURE__ */ jsx56("span", { className: "error", children: libItem.error })
        ]
      }
    )
  ] });
};
var PublishLibrary = ({
  onClose,
  libraryItems,
  appState,
  onSuccess,
  onError,
  updateItemsInStorage,
  onRemove
}) => {
  const [libraryData, setLibraryData] = useState14({
    authorName: "",
    githubHandle: "",
    name: "",
    description: "",
    twitterHandle: "",
    website: ""
  });
  const [isSubmitting, setIsSubmitting] = useState14(false);
  useEffect20(() => {
    const data = EditorLocalStorage.get(
      EDITOR_LS_KEYS.PUBLISH_LIBRARY
    );
    if (data) {
      setLibraryData(data);
    }
  }, []);
  const [clonedLibItems, setClonedLibItems] = useState14(
    libraryItems.slice()
  );
  useEffect20(() => {
    setClonedLibItems(libraryItems.slice());
  }, [libraryItems]);
  const onInputChange = (event) => {
    setLibraryData({
      ...libraryData,
      [event.target.name]: event.target.value
    });
  };
  const onSubmit = async (event) => {
    event.preventDefault();
    setIsSubmitting(true);
    const erroredLibItems = [];
    let isError = false;
    clonedLibItems.forEach((libItem) => {
      let error = "";
      if (!libItem.name) {
        error = t("publishDialog.errors.required");
        isError = true;
      }
      erroredLibItems.push({ ...libItem, error });
    });
    if (isError) {
      setClonedLibItems(erroredLibItems);
      setIsSubmitting(false);
      return;
    }
    const previewImage = await generatePreviewImage(clonedLibItems);
    const libContent = {
      type: EXPORT_DATA_TYPES.excalidrawLibrary,
      version: VERSIONS.excalidrawLibrary,
      source: EXPORT_SOURCE,
      libraryItems: clonedLibItems
    };
    const content = JSON.stringify(libContent, null, 2);
    const lib = new Blob([content], { type: "application/json" });
    const formData = new FormData();
    formData.append("excalidrawLib", lib);
    formData.append("previewImage", previewImage);
    formData.append("previewImageType", previewImage.type);
    formData.append("title", libraryData.name);
    formData.append("authorName", libraryData.authorName);
    formData.append("githubHandle", libraryData.githubHandle);
    formData.append("name", libraryData.name);
    formData.append("description", libraryData.description);
    formData.append("twitterHandle", libraryData.twitterHandle);
    formData.append("website", libraryData.website);
    fetch(`${define_import_meta_env_default.VITE_APP_LIBRARY_BACKEND}/submit`, {
      method: "post",
      body: formData
    }).then(
      (response) => {
        if (response.ok) {
          return response.json().then(({ url }) => {
            EditorLocalStorage.delete(EDITOR_LS_KEYS.PUBLISH_LIBRARY);
            onSuccess({
              url,
              authorName: libraryData.authorName,
              items: clonedLibItems
            });
          });
        }
        return response.json().catch(() => {
          throw new Error(response.statusText || "something went wrong");
        }).then((error) => {
          throw new Error(
            error.message || response.statusText || "something went wrong"
          );
        });
      },
      (err) => {
        console.error(err);
        onError(err);
        setIsSubmitting(false);
      }
    ).catch((err) => {
      console.error(err);
      onError(err);
      setIsSubmitting(false);
    });
  };
  const renderLibraryItems = () => {
    const items = [];
    clonedLibItems.forEach((libItem, index) => {
      items.push(
        /* @__PURE__ */ jsx56("div", { className: "single-library-item-wrapper", children: /* @__PURE__ */ jsx56(
          SingleLibraryItem,
          {
            libItem,
            appState,
            index,
            onChange: (val, index2) => {
              const items2 = clonedLibItems.slice();
              items2[index2].name = val;
              setClonedLibItems(items2);
            },
            onRemove
          }
        ) }, index)
      );
    });
    return /* @__PURE__ */ jsx56("div", { className: "selected-library-items", children: items });
  };
  const onDialogClose = useCallback6(() => {
    updateItemsInStorage(clonedLibItems);
    EditorLocalStorage.set(EDITOR_LS_KEYS.PUBLISH_LIBRARY, libraryData);
    onClose();
  }, [clonedLibItems, onClose, updateItemsInStorage, libraryData]);
  const shouldRenderForm = !!libraryItems.length;
  const containsPublishedItems = libraryItems.some(
    (item) => item.status === "published"
  );
  return /* @__PURE__ */ jsx56(
    Dialog,
    {
      onCloseRequest: onDialogClose,
      title: t("publishDialog.title"),
      className: "publish-library",
      children: shouldRenderForm ? /* @__PURE__ */ jsxs31("form", { onSubmit, children: [
        /* @__PURE__ */ jsx56("div", { className: "publish-library-note", children: /* @__PURE__ */ jsx56(
          Trans_default,
          {
            i18nKey: "publishDialog.noteDescription",
            link: (el) => /* @__PURE__ */ jsx56(
              "a",
              {
                href: "https://libraries.excalidraw.com",
                target: "_blank",
                rel: "noopener noreferrer",
                children: el
              }
            )
          }
        ) }),
        /* @__PURE__ */ jsx56("span", { className: "publish-library-note", children: /* @__PURE__ */ jsx56(
          Trans_default,
          {
            i18nKey: "publishDialog.noteGuidelines",
            link: (el) => /* @__PURE__ */ jsx56(
              "a",
              {
                href: "https://github.com/excalidraw/excalidraw-libraries#guidelines",
                target: "_blank",
                rel: "noopener noreferrer",
                children: el
              }
            )
          }
        ) }),
        /* @__PURE__ */ jsx56("div", { className: "publish-library-note", children: t("publishDialog.noteItems") }),
        containsPublishedItems && /* @__PURE__ */ jsx56("span", { className: "publish-library-note publish-library-warning", children: t("publishDialog.republishWarning") }),
        renderLibraryItems(),
        /* @__PURE__ */ jsxs31("div", { className: "publish-library__fields", children: [
          /* @__PURE__ */ jsxs31("label", { children: [
            /* @__PURE__ */ jsxs31("div", { children: [
              /* @__PURE__ */ jsx56("span", { children: t("publishDialog.libraryName") }),
              /* @__PURE__ */ jsx56("span", { "aria-hidden": "true", className: "required", children: "*" })
            ] }),
            /* @__PURE__ */ jsx56(
              "input",
              {
                type: "text",
                name: "name",
                required: true,
                value: libraryData.name,
                onChange: onInputChange,
                placeholder: t("publishDialog.placeholder.libraryName")
              }
            )
          ] }),
          /* @__PURE__ */ jsxs31("label", { style: { alignItems: "flex-start" }, children: [
            /* @__PURE__ */ jsxs31("div", { children: [
              /* @__PURE__ */ jsx56("span", { children: t("publishDialog.libraryDesc") }),
              /* @__PURE__ */ jsx56("span", { "aria-hidden": "true", className: "required", children: "*" })
            ] }),
            /* @__PURE__ */ jsx56(
              "textarea",
              {
                name: "description",
                rows: 4,
                required: true,
                value: libraryData.description,
                onChange: onInputChange,
                placeholder: t("publishDialog.placeholder.libraryDesc")
              }
            )
          ] }),
          /* @__PURE__ */ jsxs31("label", { children: [
            /* @__PURE__ */ jsxs31("div", { children: [
              /* @__PURE__ */ jsx56("span", { children: t("publishDialog.authorName") }),
              /* @__PURE__ */ jsx56("span", { "aria-hidden": "true", className: "required", children: "*" })
            ] }),
            /* @__PURE__ */ jsx56(
              "input",
              {
                type: "text",
                name: "authorName",
                required: true,
                value: libraryData.authorName,
                onChange: onInputChange,
                placeholder: t("publishDialog.placeholder.authorName")
              }
            )
          ] }),
          /* @__PURE__ */ jsxs31("label", { children: [
            /* @__PURE__ */ jsx56("span", { children: t("publishDialog.githubUsername") }),
            /* @__PURE__ */ jsx56(
              "input",
              {
                type: "text",
                name: "githubHandle",
                value: libraryData.githubHandle,
                onChange: onInputChange,
                placeholder: t("publishDialog.placeholder.githubHandle")
              }
            )
          ] }),
          /* @__PURE__ */ jsxs31("label", { children: [
            /* @__PURE__ */ jsx56("span", { children: t("publishDialog.twitterUsername") }),
            /* @__PURE__ */ jsx56(
              "input",
              {
                type: "text",
                name: "twitterHandle",
                value: libraryData.twitterHandle,
                onChange: onInputChange,
                placeholder: t("publishDialog.placeholder.twitterHandle")
              }
            )
          ] }),
          /* @__PURE__ */ jsxs31("label", { children: [
            /* @__PURE__ */ jsx56("span", { children: t("publishDialog.website") }),
            /* @__PURE__ */ jsx56(
              "input",
              {
                type: "text",
                name: "website",
                pattern: "https?://.+",
                title: t("publishDialog.errors.website"),
                value: libraryData.website,
                onChange: onInputChange,
                placeholder: t("publishDialog.placeholder.website")
              }
            )
          ] }),
          /* @__PURE__ */ jsx56("span", { className: "publish-library-note", children: /* @__PURE__ */ jsx56(
            Trans_default,
            {
              i18nKey: "publishDialog.noteLicense",
              link: (el) => /* @__PURE__ */ jsx56(
                "a",
                {
                  href: "https://github.com/excalidraw/excalidraw-libraries/blob/main/LICENSE",
                  target: "_blank",
                  rel: "noopener noreferrer",
                  children: el
                }
              )
            }
          ) })
        ] }),
        /* @__PURE__ */ jsxs31("div", { className: "publish-library__buttons", children: [
          /* @__PURE__ */ jsx56(
            DialogActionButton_default,
            {
              label: t("buttons.cancel"),
              onClick: onDialogClose,
              "data-testid": "cancel-clear-canvas-button"
            }
          ),
          /* @__PURE__ */ jsx56(
            DialogActionButton_default,
            {
              type: "submit",
              label: t("buttons.submit"),
              actionType: "primary",
              isLoading: isSubmitting
            }
          )
        ] })
      ] }) : /* @__PURE__ */ jsx56("p", { style: { padding: "1em", textAlign: "center", fontWeight: 500 }, children: t("publishDialog.atleastOneLibItem") })
    }
  );
};
var PublishLibrary_default = PublishLibrary;

// components/dropdownMenu/DropdownMenuTrigger.tsx
import clsx24 from "clsx";
import { jsx as jsx57 } from "react/jsx-runtime";
var MenuTrigger = ({
  className = "",
  children,
  onToggle,
  title,
  ...rest
}) => {
  const device = useDevice();
  const classNames = clsx24(
    `dropdown-menu-button ${className}`,
    "zen-mode-transition",
    {
      "dropdown-menu-button--mobile": device.editor.isMobile
    }
  ).trim();
  return /* @__PURE__ */ jsx57(
    "button",
    {
      "data-prevent-outside-click": true,
      className: classNames,
      onClick: onToggle,
      type: "button",
      "data-testid": "dropdown-menu-button",
      title,
      ...rest,
      children
    }
  );
};
var DropdownMenuTrigger_default = MenuTrigger;
MenuTrigger.displayName = "DropdownMenuTrigger";

// components/dropdownMenu/DropdownMenuSeparator.tsx
import { jsx as jsx58 } from "react/jsx-runtime";
var MenuSeparator = () => /* @__PURE__ */ jsx58(
  "div",
  {
    style: {
      height: "1px",
      backgroundColor: "var(--default-border-color)",
      margin: ".5rem 0"
    }
  }
);
var DropdownMenuSeparator_default = MenuSeparator;
MenuSeparator.displayName = "DropdownMenuSeparator";

// components/dropdownMenu/DropdownMenuContent.tsx
import clsx25 from "clsx";
import { useEffect as useEffect21, useRef as useRef16 } from "react";
import { jsx as jsx59 } from "react/jsx-runtime";
var MenuContent = ({
  children,
  onClickOutside,
  className = "",
  onSelect,
  style
}) => {
  const device = useDevice();
  const menuRef = useRef16(null);
  const callbacksRef = useStable({ onClickOutside });
  useOutsideClick(menuRef, () => {
    callbacksRef.onClickOutside?.();
  });
  useEffect21(() => {
    const onKeyDown = (event) => {
      if (event.key === KEYS.ESCAPE) {
        event.stopImmediatePropagation();
        callbacksRef.onClickOutside?.();
      }
    };
    const option = {
      // so that we can stop propagation of the event before it reaches
      // event handlers that were bound before this one
      capture: true
    };
    document.addEventListener("keydown" /* KEYDOWN */, onKeyDown, option);
    return () => {
      document.removeEventListener("keydown" /* KEYDOWN */, onKeyDown, option);
    };
  }, [callbacksRef]);
  const classNames = clsx25(`dropdown-menu ${className}`, {
    "dropdown-menu--mobile": device.editor.isMobile
  }).trim();
  return /* @__PURE__ */ jsx59(DropdownMenuContentPropsContext.Provider, { value: { onSelect }, children: /* @__PURE__ */ jsx59(
    "div",
    {
      ref: menuRef,
      className: classNames,
      style,
      "data-testid": "dropdown-menu",
      children: device.editor.isMobile ? /* @__PURE__ */ jsx59(Stack_default.Col, { className: "dropdown-menu-container", children }) : /* @__PURE__ */ jsx59(
        Island,
        {
          className: "dropdown-menu-container",
          padding: 2,
          style: { zIndex: 2 },
          children
        }
      )
    }
  ) });
};
MenuContent.displayName = "DropdownMenuContent";
var DropdownMenuContent_default = MenuContent;

// components/dropdownMenu/DropdownMenuItemLink.tsx
import { jsx as jsx60 } from "react/jsx-runtime";
var DropdownMenuItemLink = ({
  icon,
  shortcut,
  href,
  children,
  onSelect,
  className = "",
  selected,
  rel = "noreferrer",
  ...rest
}) => {
  const handleClick = useHandleDropdownMenuItemClick(rest.onClick, onSelect);
  return /* @__PURE__ */ jsx60(
    "a",
    {
      ...rest,
      href,
      target: "_blank",
      rel: "noreferrer",
      className: getDropdownMenuItemClassName(className, selected),
      title: rest.title ?? rest["aria-label"],
      onClick: handleClick,
      children: /* @__PURE__ */ jsx60(DropdownMenuItemContent_default, { icon, shortcut, children })
    }
  );
};
var DropdownMenuItemLink_default = DropdownMenuItemLink;
DropdownMenuItemLink.displayName = "DropdownMenuItemLink";

// components/dropdownMenu/DropdownMenuItemCustom.tsx
import { jsx as jsx61 } from "react/jsx-runtime";
var DropdownMenuItemCustom = ({
  children,
  className = "",
  selected,
  ...rest
}) => {
  return /* @__PURE__ */ jsx61(
    "div",
    {
      ...rest,
      className: `dropdown-menu-item-base dropdown-menu-item-custom ${className} ${selected ? `dropdown-menu-item--selected` : ``}`.trim(),
      children
    }
  );
};
var DropdownMenuItemCustom_default = DropdownMenuItemCustom;

// components/dropdownMenu/dropdownMenuUtils.ts
import React21 from "react";
var getMenuTriggerComponent = (children) => {
  const comp = React21.Children.toArray(children).find(
    (child) => React21.isValidElement(child) && typeof child.type !== "string" && //@ts-ignore
    child?.type.displayName && //@ts-ignore
    child.type.displayName === "DropdownMenuTrigger"
  );
  if (!comp) {
    return null;
  }
  return comp;
};
var getMenuContentComponent = (children) => {
  const comp = React21.Children.toArray(children).find(
    (child) => React21.isValidElement(child) && typeof child.type !== "string" && //@ts-ignore
    child?.type.displayName && //@ts-ignore
    child.type.displayName === "DropdownMenuContent"
  );
  if (!comp) {
    return null;
  }
  return comp;
};

// components/dropdownMenu/DropdownMenu.tsx
import { Fragment as Fragment6, jsxs as jsxs32 } from "react/jsx-runtime";
var DropdownMenu = ({
  children,
  open
}) => {
  const MenuTriggerComp = getMenuTriggerComponent(children);
  const MenuContentComp = getMenuContentComponent(children);
  return /* @__PURE__ */ jsxs32(Fragment6, { children: [
    MenuTriggerComp,
    open && MenuContentComp
  ] });
};
DropdownMenu.Trigger = DropdownMenuTrigger_default;
DropdownMenu.Content = DropdownMenuContent_default;
DropdownMenu.Item = DropdownMenuItem_default;
DropdownMenu.ItemLink = DropdownMenuItemLink_default;
DropdownMenu.ItemCustom = DropdownMenuItemCustom_default;
DropdownMenu.Group = DropdownMenuGroup_default;
DropdownMenu.Separator = DropdownMenuSeparator_default;
var DropdownMenu_default = DropdownMenu;
DropdownMenu.displayName = "DropdownMenu";

// components/LibraryMenuHeaderContent.tsx
import clsx26 from "clsx";
import { jsx as jsx62, jsxs as jsxs33 } from "react/jsx-runtime";
var getSelectedItems = (libraryItems, selectedItems) => libraryItems.filter((item) => selectedItems.includes(item.id));
var LibraryDropdownMenuButton = ({
  setAppState,
  selectedItems,
  library,
  onRemoveFromLibrary,
  resetLibrary,
  onSelectItems,
  appState,
  className
}) => {
  const [libraryItemsData] = useAtom(libraryItemsAtom);
  const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(
    isLibraryMenuOpenAtom
  );
  const renderRemoveLibAlert = () => {
    const content = selectedItems.length ? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length }) : t("alerts.resetLibrary");
    const title = selectedItems.length ? t("confirmDialog.removeItemsFromLib") : t("confirmDialog.resetLibrary");
    return /* @__PURE__ */ jsx62(
      ConfirmDialog_default,
      {
        onConfirm: () => {
          if (selectedItems.length) {
            onRemoveFromLibrary();
          } else {
            resetLibrary();
          }
          setShowRemoveLibAlert(false);
        },
        onCancel: () => {
          setShowRemoveLibAlert(false);
        },
        title,
        children: /* @__PURE__ */ jsx62("p", { children: content })
      }
    );
  };
  const [showRemoveLibAlert, setShowRemoveLibAlert] = useState15(false);
  const itemsSelected = !!selectedItems.length;
  const items = itemsSelected ? libraryItemsData.libraryItems.filter(
    (item) => selectedItems.includes(item.id)
  ) : libraryItemsData.libraryItems;
  const resetLabel = itemsSelected ? t("buttons.remove") : t("buttons.resetLibrary");
  const [showPublishLibraryDialog, setShowPublishLibraryDialog] = useState15(false);
  const [publishLibSuccess, setPublishLibSuccess] = useState15(null);
  const renderPublishSuccess = useCallback7(() => {
    return /* @__PURE__ */ jsxs33(
      Dialog,
      {
        onCloseRequest: () => setPublishLibSuccess(null),
        title: t("publishSuccessDialog.title"),
        className: "publish-library-success",
        size: "small",
        children: [
          /* @__PURE__ */ jsx62("p", { children: /* @__PURE__ */ jsx62(
            Trans_default,
            {
              i18nKey: "publishSuccessDialog.content",
              authorName: publishLibSuccess.authorName,
              link: (el) => /* @__PURE__ */ jsx62(
                "a",
                {
                  href: publishLibSuccess?.url,
                  target: "_blank",
                  rel: "noopener noreferrer",
                  children: el
                }
              )
            }
          ) }),
          /* @__PURE__ */ jsx62(
            ToolButton,
            {
              type: "button",
              title: t("buttons.close"),
              "aria-label": t("buttons.close"),
              label: t("buttons.close"),
              onClick: () => setPublishLibSuccess(null),
              "data-testid": "publish-library-success-close",
              className: "publish-library-success-close"
            }
          )
        ]
      }
    );
  }, [setPublishLibSuccess, publishLibSuccess]);
  const onPublishLibSuccess = (data, libraryItems) => {
    setShowPublishLibraryDialog(false);
    setPublishLibSuccess({ url: data.url, authorName: data.authorName });
    const nextLibItems = libraryItems.slice();
    nextLibItems.forEach((libItem) => {
      if (selectedItems.includes(libItem.id)) {
        libItem.status = "published";
      }
    });
    library.setLibrary(nextLibItems);
  };
  const onLibraryImport = async () => {
    try {
      await library.updateLibrary({
        libraryItems: fileOpen({
          description: "Excalidraw library files"
          // ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
          // gets resolved. Else, iOS users cannot open `.excalidraw` files.
          /*
            extensions: [".json", ".excalidrawlib"],
            */
        }),
        merge: true,
        openLibraryMenu: true
      });
    } catch (error) {
      if (error?.name === "AbortError") {
        console.warn(error);
        return;
      }
      setAppState({ errorMessage: t("errors.importLibraryError") });
    }
  };
  const onLibraryExport = async () => {
    const libraryItems = itemsSelected ? items : await library.getLatestLibrary();
    saveLibraryAsJSON(libraryItems).catch(muteFSAbortError).catch((error) => {
      setAppState({ errorMessage: error.message });
    });
  };
  const renderLibraryMenu = () => {
    return /* @__PURE__ */ jsxs33(DropdownMenu_default, { open: isLibraryMenuOpen, children: [
      /* @__PURE__ */ jsx62(
        DropdownMenu_default.Trigger,
        {
          onToggle: () => setIsLibraryMenuOpen(!isLibraryMenuOpen),
          children: DotsIcon
        }
      ),
      /* @__PURE__ */ jsxs33(
        DropdownMenu_default.Content,
        {
          onClickOutside: () => setIsLibraryMenuOpen(false),
          onSelect: () => setIsLibraryMenuOpen(false),
          className: "library-menu",
          children: [
            !itemsSelected && /* @__PURE__ */ jsx62(
              DropdownMenu_default.Item,
              {
                onSelect: onLibraryImport,
                icon: LoadIcon,
                "data-testid": "lib-dropdown--load",
                children: t("buttons.load")
              }
            ),
            !!items.length && /* @__PURE__ */ jsx62(
              DropdownMenu_default.Item,
              {
                onSelect: onLibraryExport,
                icon: ExportIcon,
                "data-testid": "lib-dropdown--export",
                children: t("buttons.export")
              }
            ),
            !!items.length && /* @__PURE__ */ jsx62(
              DropdownMenu_default.Item,
              {
                onSelect: () => setShowRemoveLibAlert(true),
                icon: TrashIcon,
                children: resetLabel
              }
            ),
            itemsSelected && /* @__PURE__ */ jsx62(
              DropdownMenu_default.Item,
              {
                icon: publishIcon,
                onSelect: () => setShowPublishLibraryDialog(true),
                "data-testid": "lib-dropdown--remove",
                children: t("buttons.publishLibrary")
              }
            )
          ]
        }
      )
    ] });
  };
  return /* @__PURE__ */ jsxs33("div", { className: clsx26("library-menu-dropdown-container", className), children: [
    renderLibraryMenu(),
    selectedItems.length > 0 && /* @__PURE__ */ jsx62("div", { className: "library-actions-counter", children: selectedItems.length }),
    showRemoveLibAlert && renderRemoveLibAlert(),
    showPublishLibraryDialog && /* @__PURE__ */ jsx62(
      PublishLibrary_default,
      {
        onClose: () => setShowPublishLibraryDialog(false),
        libraryItems: getSelectedItems(
          libraryItemsData.libraryItems,
          selectedItems
        ),
        appState,
        onSuccess: (data) => onPublishLibSuccess(data, libraryItemsData.libraryItems),
        onError: (error) => window.alert(error),
        updateItemsInStorage: () => library.setLibrary(libraryItemsData.libraryItems),
        onRemove: (id) => onSelectItems(selectedItems.filter((_id) => _id !== id))
      }
    ),
    publishLibSuccess && renderPublishSuccess()
  ] });
};
var LibraryDropdownMenu = ({
  selectedItems,
  onSelectItems,
  className
}) => {
  const { library } = useApp();
  const { clearLibraryCache, deleteItemsFromLibraryCache } = useLibraryCache();
  const appState = useUIAppState();
  const setAppState = useExcalidrawSetAppState();
  const [libraryItemsData] = useAtom(libraryItemsAtom);
  const removeFromLibrary = async (libraryItems) => {
    const nextItems = libraryItems.filter(
      (item) => !selectedItems.includes(item.id)
    );
    library.setLibrary(nextItems).catch(() => {
      setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
    });
    deleteItemsFromLibraryCache(selectedItems);
    onSelectItems([]);
  };
  const resetLibrary = () => {
    library.resetLibrary();
    clearLibraryCache();
  };
  return /* @__PURE__ */ jsx62(
    LibraryDropdownMenuButton,
    {
      appState,
      setAppState,
      selectedItems,
      onSelectItems,
      library,
      onRemoveFromLibrary: () => removeFromLibrary(libraryItemsData.libraryItems),
      resetLibrary,
      className
    }
  );
};

// components/LibraryMenuSection.tsx
import { memo as memo2, useEffect as useEffect23, useState as useState17 } from "react";

// components/LibraryUnit.tsx
import clsx27 from "clsx";
import { memo, useEffect as useEffect22, useRef as useRef17, useState as useState16 } from "react";
import { jsx as jsx63, jsxs as jsxs34 } from "react/jsx-runtime";
var LibraryUnit = memo(
  ({
    id,
    elements,
    isPending,
    onClick,
    selected,
    onToggle,
    onDrag,
    svgCache
  }) => {
    const ref = useRef17(null);
    const svg = useLibraryItemSvg(id, elements, svgCache);
    useEffect22(() => {
      const node = ref.current;
      if (!node) {
        return;
      }
      if (svg) {
        node.innerHTML = svg.outerHTML;
      }
      return () => {
        node.innerHTML = "";
      };
    }, [svg]);
    const [isHovered, setIsHovered] = useState16(false);
    const isMobile = useDevice().editor.isMobile;
    const adder = isPending && /* @__PURE__ */ jsx63("div", { className: "library-unit__adder", children: PlusIcon });
    return /* @__PURE__ */ jsxs34(
      "div",
      {
        className: clsx27("library-unit", {
          "library-unit__active": elements,
          "library-unit--hover": elements && isHovered,
          "library-unit--selected": selected,
          "library-unit--skeleton": !svg
        }),
        onMouseEnter: () => setIsHovered(true),
        onMouseLeave: () => setIsHovered(false),
        children: [
          /* @__PURE__ */ jsx63(
            "div",
            {
              className: clsx27("library-unit__dragger", {
                "library-unit__pulse": !!isPending
              }),
              ref,
              draggable: !!elements,
              onClick: !!elements || !!isPending ? (event) => {
                if (id && event.shiftKey) {
                  onToggle(id, event);
                } else {
                  onClick(id);
                }
              } : void 0,
              onDragStart: (event) => {
                if (!id) {
                  event.preventDefault();
                  return;
                }
                setIsHovered(false);
                onDrag(id, event);
              }
            }
          ),
          adder,
          id && elements && (isHovered || isMobile || selected) && /* @__PURE__ */ jsx63(
            CheckboxItem,
            {
              checked: selected,
              onChange: (checked, event) => onToggle(id, event),
              className: "library-unit__checkbox"
            }
          )
        ]
      }
    );
  }
);
var EmptyLibraryUnit = () => /* @__PURE__ */ jsx63("div", { className: "library-unit library-unit--skeleton" });

// hooks/useTransition.ts
import React22, { useCallback as useCallback8 } from "react";
function useTransitionPolyfill() {
  const startTransition = useCallback8((callback) => callback(), []);
  return [false, startTransition];
}
var useTransition = React22.useTransition || useTransitionPolyfill;

// components/LibraryMenuSection.tsx
import { Fragment as Fragment7, jsx as jsx64 } from "react/jsx-runtime";
var LibraryMenuSectionGrid = ({
  children
}) => {
  return /* @__PURE__ */ jsx64("div", { className: "library-menu-items-container__grid", children });
};
var LibraryMenuSection = memo2(
  ({
    items,
    onItemSelectToggle,
    onItemDrag,
    isItemSelected,
    onClick,
    svgCache,
    itemsRenderedPerBatch
  }) => {
    const [, startTransition] = useTransition();
    const [index, setIndex] = useState17(0);
    useEffect23(() => {
      if (index < items.length) {
        startTransition(() => {
          setIndex(index + itemsRenderedPerBatch);
        });
      }
    }, [index, items.length, startTransition, itemsRenderedPerBatch]);
    return /* @__PURE__ */ jsx64(Fragment7, { children: items.map((item, i) => {
      return i < index ? /* @__PURE__ */ jsx64(
        LibraryUnit,
        {
          elements: item?.elements,
          isPending: !item?.id && !!item?.elements,
          onClick,
          svgCache,
          id: item?.id,
          selected: isItemSelected(item.id),
          onToggle: onItemSelectToggle,
          onDrag: onItemDrag
        },
        item?.id ?? i
      ) : /* @__PURE__ */ jsx64(EmptyLibraryUnit, {}, i);
    }) });
  }
);

// hooks/useScrollPosition.ts
import { useEffect as useEffect24 } from "react";
import throttle from "lodash.throttle";
var scrollPositionAtom = atom(0);
var useScrollPosition = (elementRef) => {
  const [scrollPosition, setScrollPosition] = useAtom(scrollPositionAtom);
  useEffect24(() => {
    const { current: element } = elementRef;
    if (!element) {
      return;
    }
    const handleScroll = throttle(() => {
      const { scrollTop } = element;
      setScrollPosition(scrollTop);
    }, 200);
    element.addEventListener("scroll", handleScroll);
    return () => {
      handleScroll.cancel();
      element.removeEventListener("scroll", handleScroll);
    };
  }, [elementRef, setScrollPosition]);
  return scrollPosition;
};

// components/LibraryMenuItems.tsx
import { Fragment as Fragment8, jsx as jsx65, jsxs as jsxs35 } from "react/jsx-runtime";
var ITEMS_RENDERED_PER_BATCH = 17;
var CACHED_ITEMS_RENDERED_PER_BATCH = 64;
function LibraryMenuItems({
  isLoading,
  libraryItems,
  onAddToLibrary,
  onInsertLibraryItems,
  pendingElements,
  theme,
  id,
  libraryReturnUrl,
  onSelectItems,
  selectedItems
}) {
  const libraryContainerRef = useRef18(null);
  const scrollPosition = useScrollPosition(libraryContainerRef);
  useEffect25(() => {
    if (scrollPosition > 0) {
      libraryContainerRef.current?.scrollTo(0, scrollPosition);
    }
  }, []);
  const { svgCache } = useLibraryCache();
  const unpublishedItems = useMemo5(
    () => libraryItems.filter((item) => item.status !== "published"),
    [libraryItems]
  );
  const publishedItems = useMemo5(
    () => libraryItems.filter((item) => item.status === "published"),
    [libraryItems]
  );
  const showBtn = !libraryItems.length && !pendingElements.length;
  const isLibraryEmpty = !pendingElements.length && !unpublishedItems.length && !publishedItems.length;
  const [lastSelectedItem, setLastSelectedItem] = useState18(null);
  const onItemSelectToggle = useCallback9(
    (id2, event) => {
      const shouldSelect = !selectedItems.includes(id2);
      const orderedItems = [...unpublishedItems, ...publishedItems];
      if (shouldSelect) {
        if (event.shiftKey && lastSelectedItem) {
          const rangeStart = orderedItems.findIndex(
            (item) => item.id === lastSelectedItem
          );
          const rangeEnd = orderedItems.findIndex((item) => item.id === id2);
          if (rangeStart === -1 || rangeEnd === -1) {
            onSelectItems([...selectedItems, id2]);
            return;
          }
          const selectedItemsMap = arrayToMap(selectedItems);
          const nextSelectedIds = orderedItems.reduce(
            (acc, item, idx) => {
              if (idx >= rangeStart && idx <= rangeEnd || selectedItemsMap.has(item.id)) {
                acc.push(item.id);
              }
              return acc;
            },
            []
          );
          onSelectItems(nextSelectedIds);
        } else {
          onSelectItems([...selectedItems, id2]);
        }
        setLastSelectedItem(id2);
      } else {
        setLastSelectedItem(null);
        onSelectItems(selectedItems.filter((_id) => _id !== id2));
      }
    },
    [
      lastSelectedItem,
      onSelectItems,
      publishedItems,
      selectedItems,
      unpublishedItems
    ]
  );
  const getInsertedElements = useCallback9(
    (id2) => {
      let targetElements;
      if (selectedItems.includes(id2)) {
        targetElements = libraryItems.filter(
          (item) => selectedItems.includes(item.id)
        );
      } else {
        targetElements = libraryItems.filter((item) => item.id === id2);
      }
      return targetElements.map((item) => {
        return {
          ...item,
          // duplicate each library item before inserting on canvas to confine
          // ids and bindings to each library item. See #6465
          elements: duplicateElements(item.elements, { randomizeSeed: true })
        };
      });
    },
    [libraryItems, selectedItems]
  );
  const onItemDrag = useCallback9(
    (id2, event) => {
      event.dataTransfer.setData(
        MIME_TYPES.excalidrawlib,
        serializeLibraryAsJSON(getInsertedElements(id2))
      );
    },
    [getInsertedElements]
  );
  const isItemSelected = useCallback9(
    (id2) => {
      if (!id2) {
        return false;
      }
      return selectedItems.includes(id2);
    },
    [selectedItems]
  );
  const onAddToLibraryClick = useCallback9(() => {
    onAddToLibrary(pendingElements);
  }, [pendingElements, onAddToLibrary]);
  const onItemClick = useCallback9(
    (id2) => {
      if (id2) {
        onInsertLibraryItems(getInsertedElements(id2));
      }
    },
    [getInsertedElements, onInsertLibraryItems]
  );
  const itemsRenderedPerBatch = svgCache.size >= libraryItems.length ? CACHED_ITEMS_RENDERED_PER_BATCH : ITEMS_RENDERED_PER_BATCH;
  return /* @__PURE__ */ jsxs35(
    "div",
    {
      className: "library-menu-items-container",
      style: pendingElements.length || unpublishedItems.length || publishedItems.length ? { justifyContent: "flex-start" } : { borderBottom: 0 },
      children: [
        !isLibraryEmpty && /* @__PURE__ */ jsx65(
          LibraryDropdownMenu,
          {
            selectedItems,
            onSelectItems,
            className: "library-menu-dropdown-container--in-heading"
          }
        ),
        /* @__PURE__ */ jsxs35(
          Stack_default.Col,
          {
            className: "library-menu-items-container__items",
            align: "start",
            gap: 1,
            style: {
              flex: publishedItems.length > 0 ? 1 : "0 1 auto",
              marginBottom: 0
            },
            ref: libraryContainerRef,
            children: [
              /* @__PURE__ */ jsxs35(Fragment8, { children: [
                !isLibraryEmpty && /* @__PURE__ */ jsx65("div", { className: "library-menu-items-container__header", children: t("labels.personalLib") }),
                isLoading && /* @__PURE__ */ jsx65(
                  "div",
                  {
                    style: {
                      position: "absolute",
                      top: "var(--container-padding-y)",
                      right: "var(--container-padding-x)",
                      transform: "translateY(50%)"
                    },
                    children: /* @__PURE__ */ jsx65(Spinner_default, {})
                  }
                ),
                !pendingElements.length && !unpublishedItems.length ? /* @__PURE__ */ jsxs35("div", { className: "library-menu-items__no-items", children: [
                  /* @__PURE__ */ jsx65("div", { className: "library-menu-items__no-items__label", children: t("library.noItems") }),
                  /* @__PURE__ */ jsx65("div", { className: "library-menu-items__no-items__hint", children: publishedItems.length > 0 ? t("library.hint_emptyPrivateLibrary") : t("library.hint_emptyLibrary") })
                ] }) : /* @__PURE__ */ jsxs35(LibraryMenuSectionGrid, { children: [
                  pendingElements.length > 0 && /* @__PURE__ */ jsx65(
                    LibraryMenuSection,
                    {
                      itemsRenderedPerBatch,
                      items: [{ id: null, elements: pendingElements }],
                      onItemSelectToggle,
                      onItemDrag,
                      onClick: onAddToLibraryClick,
                      isItemSelected,
                      svgCache
                    }
                  ),
                  /* @__PURE__ */ jsx65(
                    LibraryMenuSection,
                    {
                      itemsRenderedPerBatch,
                      items: unpublishedItems,
                      onItemSelectToggle,
                      onItemDrag,
                      onClick: onItemClick,
                      isItemSelected,
                      svgCache
                    }
                  )
                ] })
              ] }),
              /* @__PURE__ */ jsxs35(Fragment8, { children: [
                (publishedItems.length > 0 || pendingElements.length > 0 || unpublishedItems.length > 0) && /* @__PURE__ */ jsx65("div", { className: "library-menu-items-container__header library-menu-items-container__header--excal", children: t("labels.excalidrawLib") }),
                publishedItems.length > 0 ? /* @__PURE__ */ jsx65(LibraryMenuSectionGrid, { children: /* @__PURE__ */ jsx65(
                  LibraryMenuSection,
                  {
                    itemsRenderedPerBatch,
                    items: publishedItems,
                    onItemSelectToggle,
                    onItemDrag,
                    onClick: onItemClick,
                    isItemSelected,
                    svgCache
                  }
                ) }) : unpublishedItems.length > 0 ? /* @__PURE__ */ jsx65(
                  "div",
                  {
                    style: {
                      margin: "1rem 0",
                      display: "flex",
                      flexDirection: "column",
                      alignItems: "center",
                      justifyContent: "center",
                      width: "100%",
                      fontSize: ".9rem"
                    },
                    children: t("library.noItems")
                  }
                ) : null
              ] }),
              showBtn && /* @__PURE__ */ jsx65(
                LibraryMenuControlButtons,
                {
                  style: { padding: "16px 0", width: "100%" },
                  id,
                  libraryReturnUrl,
                  theme,
                  children: /* @__PURE__ */ jsx65(
                    LibraryDropdownMenu,
                    {
                      selectedItems,
                      onSelectItems
                    }
                  )
                }
              )
            ]
          }
        )
      ]
    }
  );
}

// components/LibraryMenu.tsx
import { jsx as jsx66, jsxs as jsxs36 } from "react/jsx-runtime";
var isLibraryMenuOpenAtom = atom(false);
var LibraryMenuWrapper = ({ children }) => {
  return /* @__PURE__ */ jsx66("div", { className: "layer-ui__library", children });
};
var LibraryMenuContent = memo3(
  ({
    onInsertLibraryItems,
    pendingElements,
    onAddToLibrary,
    setAppState,
    libraryReturnUrl,
    library,
    id,
    theme,
    selectedItems,
    onSelectItems
  }) => {
    const [libraryItemsData] = useAtom(libraryItemsAtom);
    const _onAddToLibrary = useCallback10(
      (elements) => {
        const addToLibrary = async (processedElements, libraryItems2) => {
          trackEvent("element", "addToLibrary", "ui");
          for (const type of LIBRARY_DISABLED_TYPES) {
            if (processedElements.some((element) => element.type === type)) {
              return setAppState({
                errorMessage: t(`errors.libraryElementTypeError.${type}`)
              });
            }
          }
          const nextItems = [
            {
              status: "unpublished",
              elements: processedElements,
              id: randomId(),
              created: Date.now()
            },
            ...libraryItems2
          ];
          onAddToLibrary();
          library.setLibrary(nextItems).catch(() => {
            setAppState({ errorMessage: t("alerts.errorAddingToLibrary") });
          });
        };
        addToLibrary(elements, libraryItemsData.libraryItems);
      },
      [onAddToLibrary, library, setAppState, libraryItemsData.libraryItems]
    );
    const libraryItems = useMemo6(
      () => libraryItemsData.libraryItems,
      [libraryItemsData]
    );
    if (libraryItemsData.status === "loading" && !libraryItemsData.isInitialized) {
      return /* @__PURE__ */ jsx66(LibraryMenuWrapper, { children: /* @__PURE__ */ jsx66("div", { className: "layer-ui__library-message", children: /* @__PURE__ */ jsxs36("div", { children: [
        /* @__PURE__ */ jsx66(Spinner_default, { size: "2em" }),
        /* @__PURE__ */ jsx66("span", { children: t("labels.libraryLoadingMessage") })
      ] }) }) });
    }
    const showBtn = libraryItemsData.libraryItems.length > 0 || pendingElements.length > 0;
    return /* @__PURE__ */ jsxs36(LibraryMenuWrapper, { children: [
      /* @__PURE__ */ jsx66(
        LibraryMenuItems,
        {
          isLoading: libraryItemsData.status === "loading",
          libraryItems,
          onAddToLibrary: _onAddToLibrary,
          onInsertLibraryItems,
          pendingElements,
          id,
          libraryReturnUrl,
          theme,
          onSelectItems,
          selectedItems
        }
      ),
      showBtn && /* @__PURE__ */ jsx66(
        LibraryMenuControlButtons,
        {
          className: "library-menu-control-buttons--at-bottom",
          style: { padding: "16px 12px 0 12px" },
          id,
          libraryReturnUrl,
          theme
        }
      )
    ] });
  }
);
var getPendingElements = (elements, selectedElementIds) => ({
  elements,
  pending: getSelectedElements(
    elements,
    { selectedElementIds },
    {
      includeBoundTextElement: true,
      includeElementsInFrames: true
    }
  ),
  selectedElementIds
});
var usePendingElementsMemo = (appState, app) => {
  const elements = useExcalidrawElements();
  const [state, setState] = useState19(
    () => getPendingElements(elements, appState.selectedElementIds)
  );
  const selectedElementVersions = useRef19(
    /* @__PURE__ */ new Map()
  );
  useEffect26(() => {
    for (const element of state.pending) {
      selectedElementVersions.current.set(element.id, element.version);
    }
  }, [state.pending]);
  useEffect26(() => {
    if (
      // Only update once pointer is released.
      // Reading directly from app.state to make it clear it's not reactive
      // (hence, there's potential for stale state)
      app.state.cursorButton === "up" && app.state.activeTool.type === "selection"
    ) {
      setState((prev) => {
        if (!isShallowEqual(prev.selectedElementIds, appState.selectedElementIds)) {
          selectedElementVersions.current.clear();
          return getPendingElements(elements, appState.selectedElementIds);
        }
        const elementsMap = app.scene.getNonDeletedElementsMap();
        for (const id of Object.keys(appState.selectedElementIds)) {
          const currVersion = elementsMap.get(id)?.version;
          if (currVersion && currVersion !== selectedElementVersions.current.get(id)) {
            return getPendingElements(elements, appState.selectedElementIds);
          }
        }
        return prev;
      });
    }
  }, [
    app,
    app.state.cursorButton,
    app.state.activeTool.type,
    appState.selectedElementIds,
    elements
  ]);
  return state.pending;
};
var LibraryMenu = memo3(() => {
  const app = useApp();
  const { onInsertElements } = app;
  const appProps = useAppProps();
  const appState = useUIAppState();
  const setAppState = useExcalidrawSetAppState();
  const [selectedItems, setSelectedItems] = useState19([]);
  const memoizedLibrary = useMemo6(() => app.library, [app.library]);
  const pendingElements = usePendingElementsMemo(appState, app);
  const onInsertLibraryItems = useCallback10(
    (libraryItems) => {
      onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
    },
    [onInsertElements]
  );
  const deselectItems = useCallback10(() => {
    setAppState({
      selectedElementIds: {},
      selectedGroupIds: {},
      activeEmbeddable: null
    });
  }, [setAppState]);
  return /* @__PURE__ */ jsx66(
    LibraryMenuContent,
    {
      pendingElements,
      onInsertLibraryItems,
      onAddToLibrary: deselectItems,
      setAppState,
      libraryReturnUrl: appProps.libraryReturnUrl,
      library: memoizedLibrary,
      id: app.id,
      theme: appState.theme,
      selectedItems,
      onSelectItems: setSelectedItems
    }
  );
});

// components/Dialog.tsx
import { jsx as jsx67, jsxs as jsxs37 } from "react/jsx-runtime";
function getDialogSize(size) {
  if (size && typeof size === "number") {
    return size;
  }
  switch (size) {
    case "small":
      return 550;
    case "wide":
      return 1024;
    case "regular":
    default:
      return 800;
  }
}
var Dialog = (props) => {
  const [islandNode, setIslandNode] = useCallbackRefState();
  const [lastActiveElement] = useState20(document.activeElement);
  const { id } = useExcalidrawContainer();
  const isFullscreen = useDevice().viewport.isMobile;
  useEffect27(() => {
    if (!islandNode) {
      return;
    }
    const focusableElements = queryFocusableElements(islandNode);
    setTimeout(() => {
      if (focusableElements.length > 0 && props.autofocus !== false) {
        (focusableElements[1] || focusableElements[0]).focus();
      }
    });
    const handleKeyDown = (event) => {
      if (event.key === KEYS.TAB) {
        const focusableElements2 = queryFocusableElements(islandNode);
        const { activeElement } = document;
        const currentIndex = focusableElements2.findIndex(
          (element) => element === activeElement
        );
        if (currentIndex === 0 && event.shiftKey) {
          focusableElements2[focusableElements2.length - 1].focus();
          event.preventDefault();
        } else if (currentIndex === focusableElements2.length - 1 && !event.shiftKey) {
          focusableElements2[0].focus();
          event.preventDefault();
        }
      }
    };
    islandNode.addEventListener("keydown", handleKeyDown);
    return () => islandNode.removeEventListener("keydown", handleKeyDown);
  }, [islandNode, props.autofocus]);
  const setAppState = useExcalidrawSetAppState();
  const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
  const onClose = () => {
    setAppState({ openMenu: null });
    setIsLibraryMenuOpen(false);
    lastActiveElement.focus();
    props.onCloseRequest();
  };
  return /* @__PURE__ */ jsx67(
    Modal,
    {
      className: clsx28("Dialog", props.className, {
        "Dialog--fullscreen": isFullscreen
      }),
      labelledBy: "dialog-title",
      maxWidth: getDialogSize(props.size),
      onCloseRequest: onClose,
      closeOnClickOutside: props.closeOnClickOutside,
      children: /* @__PURE__ */ jsxs37(Island, { ref: setIslandNode, children: [
        props.title && /* @__PURE__ */ jsx67("h2", { id: `${id}-dialog-title`, className: "Dialog__title", children: /* @__PURE__ */ jsx67("span", { className: "Dialog__titleContent", children: props.title }) }),
        isFullscreen && /* @__PURE__ */ jsx67(
          "button",
          {
            className: "Dialog__close",
            onClick: onClose,
            title: t("buttons.close"),
            "aria-label": t("buttons.close"),
            type: "button",
            children: CloseIcon
          }
        ),
        /* @__PURE__ */ jsx67("div", { className: "Dialog__content", children: props.children })
      ] })
    }
  );
};

// components/TextField.tsx
import {
  forwardRef as forwardRef3,
  useRef as useRef20,
  useImperativeHandle,
  useLayoutEffect as useLayoutEffect3,
  useState as useState21
} from "react";
import clsx30 from "clsx";

// components/Button.tsx
import clsx29 from "clsx";
import { jsx as jsx68 } from "react/jsx-runtime";
var Button = ({
  type = "button",
  onSelect,
  selected,
  children,
  className = "",
  ...rest
}) => {
  return /* @__PURE__ */ jsx68(
    "button",
    {
      onClick: composeEventHandlers(rest.onClick, (event) => {
        onSelect();
      }),
      type,
      className: clsx29("excalidraw-button", className, { selected }),
      ...rest,
      children
    }
  );
};

// components/TextField.tsx
import { jsx as jsx69, jsxs as jsxs38 } from "react/jsx-runtime";
var TextField = forwardRef3(
  ({
    onChange,
    label,
    fullWidth,
    placeholder,
    readonly,
    selectOnRender,
    onKeyDown,
    isRedacted = false,
    icon,
    className,
    ...rest
  }, ref) => {
    const innerRef = useRef20(null);
    useImperativeHandle(ref, () => innerRef.current);
    useLayoutEffect3(() => {
      if (selectOnRender) {
        innerRef.current?.focus();
        innerRef.current?.select();
      }
    }, [selectOnRender]);
    const [isTemporarilyUnredacted, setIsTemporarilyUnredacted] = useState21(false);
    return /* @__PURE__ */ jsxs38(
      "div",
      {
        className: clsx30("ExcTextField", className, {
          "ExcTextField--fullWidth": fullWidth,
          "ExcTextField--hasIcon": !!icon
        }),
        onClick: () => {
          innerRef.current?.focus();
        },
        children: [
          icon,
          label && /* @__PURE__ */ jsx69("div", { className: "ExcTextField__label", children: label }),
          /* @__PURE__ */ jsxs38(
            "div",
            {
              className: clsx30("ExcTextField__input", {
                "ExcTextField__input--readonly": readonly
              }),
              children: [
                /* @__PURE__ */ jsx69(
                  "input",
                  {
                    className: clsx30({
                      "is-redacted": "value" in rest && rest.value && isRedacted && !isTemporarilyUnredacted
                    }),
                    readOnly: readonly,
                    value: "value" in rest ? rest.value : void 0,
                    defaultValue: "defaultValue" in rest ? rest.defaultValue : void 0,
                    placeholder,
                    ref: innerRef,
                    onChange: (event) => onChange?.(event.target.value),
                    onKeyDown
                  }
                ),
                isRedacted && /* @__PURE__ */ jsx69(
                  Button,
                  {
                    onSelect: () => setIsTemporarilyUnredacted(!isTemporarilyUnredacted),
                    style: { border: 0, userSelect: "none" },
                    children: isTemporarilyUnredacted ? eyeClosedIcon : eyeIcon
                  }
                )
              ]
            }
          )
        ]
      }
    );
  }
);

// components/CommandPalette/CommandPalette.tsx
import clsx32 from "clsx";

// actions/shortcuts.ts
var shortcutMap = {
  toggleTheme: [getShortcutKey("Shift+Alt+D")],
  saveScene: [getShortcutKey("CtrlOrCmd+S")],
  loadScene: [getShortcutKey("CtrlOrCmd+O")],
  clearCanvas: [getShortcutKey("CtrlOrCmd+Delete")],
  imageExport: [getShortcutKey("CtrlOrCmd+Shift+E")],
  commandPalette: [
    getShortcutKey("CtrlOrCmd+/"),
    getShortcutKey("CtrlOrCmd+Shift+P")
  ],
  cut: [getShortcutKey("CtrlOrCmd+X")],
  copy: [getShortcutKey("CtrlOrCmd+C")],
  paste: [getShortcutKey("CtrlOrCmd+V")],
  copyStyles: [getShortcutKey("CtrlOrCmd+Alt+C")],
  pasteStyles: [getShortcutKey("CtrlOrCmd+Alt+V")],
  selectAll: [getShortcutKey("CtrlOrCmd+A")],
  deleteSelectedElements: [getShortcutKey("Delete")],
  duplicateSelection: [
    getShortcutKey("CtrlOrCmd+D"),
    getShortcutKey(`Alt+${t("helpDialog.drag")}`)
  ],
  sendBackward: [getShortcutKey("CtrlOrCmd+[")],
  bringForward: [getShortcutKey("CtrlOrCmd+]")],
  sendToBack: [
    isDarwin ? getShortcutKey("CtrlOrCmd+Alt+[") : getShortcutKey("CtrlOrCmd+Shift+[")
  ],
  bringToFront: [
    isDarwin ? getShortcutKey("CtrlOrCmd+Alt+]") : getShortcutKey("CtrlOrCmd+Shift+]")
  ],
  copyAsPng: [getShortcutKey("Shift+Alt+C")],
  group: [getShortcutKey("CtrlOrCmd+G")],
  ungroup: [getShortcutKey("CtrlOrCmd+Shift+G")],
  gridMode: [getShortcutKey("CtrlOrCmd+'")],
  zenMode: [getShortcutKey("Alt+Z")],
  objectsSnapMode: [getShortcutKey("Alt+S")],
  stats: [getShortcutKey("Alt+/")],
  addToLibrary: [],
  flipHorizontal: [getShortcutKey("Shift+H")],
  flipVertical: [getShortcutKey("Shift+V")],
  viewMode: [getShortcutKey("Alt+R")],
  hyperlink: [getShortcutKey("CtrlOrCmd+K")],
  toggleElementLock: [getShortcutKey("CtrlOrCmd+Shift+L")],
  resetZoom: [getShortcutKey("CtrlOrCmd+0")],
  zoomOut: [getShortcutKey("CtrlOrCmd+-")],
  zoomIn: [getShortcutKey("CtrlOrCmd++")],
  zoomToFitSelection: [getShortcutKey("Shift+3")],
  zoomToFit: [getShortcutKey("Shift+1")],
  zoomToFitSelectionInViewport: [getShortcutKey("Shift+2")],
  toggleEraserTool: [getShortcutKey("E")],
  toggleHandTool: [getShortcutKey("H")],
  setFrameAsActiveTool: [getShortcutKey("F")],
  saveFileToDisk: [getShortcutKey("CtrlOrCmd+S")],
  saveToActiveFile: [getShortcutKey("CtrlOrCmd+S")],
  toggleShortcuts: [getShortcutKey("?")],
  searchMenu: [getShortcutKey("CtrlOrCmd+F")],
  wrapSelectionInFrame: []
};
var getShortcutFromShortcutName = (name, idx = 0) => {
  const shortcuts = shortcutMap[name];
  return shortcuts && shortcuts.length > 0 ? shortcuts[idx] || shortcuts[0] : "";
};

// components/CommandPalette/CommandPalette.tsx
import fuzzy from "fuzzy";

// deburr.ts
var rsComboMarksRange = "\\u0300-\\u036f";
var reComboHalfMarksRange = "\\ufe20-\\ufe2f";
var rsComboSymbolsRange = "\\u20d0-\\u20ff";
var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange;
var rsCombo = `[${rsComboRange}]`;
var reComboMark = RegExp(rsCombo, "g");
var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
var deburredLetters = {
  "\xC0": "A",
  "\xC1": "A",
  "\xC2": "A",
  "\xC3": "A",
  "\xC4": "A",
  "\xC5": "A",
  "\xE0": "a",
  "\xE1": "a",
  "\xE2": "a",
  "\xE3": "a",
  "\xE4": "a",
  "\xE5": "a",
  "\xC7": "C",
  "\xE7": "c",
  "\xD0": "D",
  "\xF0": "d",
  "\xC8": "E",
  "\xC9": "E",
  "\xCA": "E",
  "\xCB": "E",
  "\xE8": "e",
  "\xE9": "e",
  "\xEA": "e",
  "\xEB": "e",
  "\xCC": "I",
  "\xCD": "I",
  "\xCE": "I",
  "\xCF": "I",
  "\xEC": "i",
  "\xED": "i",
  "\xEE": "i",
  "\xEF": "i",
  "\xD1": "N",
  "\xF1": "n",
  "\xD2": "O",
  "\xD3": "O",
  "\xD4": "O",
  "\xD5": "O",
  "\xD6": "O",
  "\xD8": "O",
  "\xF2": "o",
  "\xF3": "o",
  "\xF4": "o",
  "\xF5": "o",
  "\xF6": "o",
  "\xF8": "o",
  "\xD9": "U",
  "\xDA": "U",
  "\xDB": "U",
  "\xDC": "U",
  "\xF9": "u",
  "\xFA": "u",
  "\xFB": "u",
  "\xFC": "u",
  "\xDD": "Y",
  "\xFD": "y",
  "\xFF": "y",
  // normaly Ae/ae
  "\xC6": "E",
  "\xE6": "e",
  // normally Th/th
  "\xDE": "T",
  "\xFE": "t",
  // normally ss
  "\xDF": "s",
  "\u0100": "A",
  "\u0102": "A",
  "\u0104": "A",
  "\u0101": "a",
  "\u0103": "a",
  "\u0105": "a",
  "\u0106": "C",
  "\u0108": "C",
  "\u010A": "C",
  "\u010C": "C",
  "\u0107": "c",
  "\u0109": "c",
  "\u010B": "c",
  "\u010D": "c",
  "\u010E": "D",
  "\u0110": "D",
  "\u010F": "d",
  "\u0111": "d",
  "\u0112": "E",
  "\u0114": "E",
  "\u0116": "E",
  "\u0118": "E",
  "\u011A": "E",
  "\u0113": "e",
  "\u0115": "e",
  "\u0117": "e",
  "\u0119": "e",
  "\u011B": "e",
  "\u011C": "G",
  "\u011E": "G",
  "\u0120": "G",
  "\u0122": "G",
  "\u011D": "g",
  "\u011F": "g",
  "\u0121": "g",
  "\u0123": "g",
  "\u0124": "H",
  "\u0126": "H",
  "\u0125": "h",
  "\u0127": "h",
  "\u0128": "I",
  "\u012A": "I",
  "\u012C": "I",
  "\u012E": "I",
  "\u0130": "I",
  "\u0129": "i",
  "\u012B": "i",
  "\u012D": "i",
  "\u012F": "i",
  "\u0131": "i",
  "\u0134": "J",
  "\u0135": "j",
  "\u0136": "K",
  "\u0137": "k",
  "\u0138": "k",
  "\u0139": "L",
  "\u013B": "L",
  "\u013D": "L",
  "\u013F": "L",
  "\u0141": "L",
  "\u013A": "l",
  "\u013C": "l",
  "\u013E": "l",
  "\u0140": "l",
  "\u0142": "l",
  "\u0143": "N",
  "\u0145": "N",
  "\u0147": "N",
  "\u014A": "N",
  "\u0144": "n",
  "\u0146": "n",
  "\u0148": "n",
  "\u014B": "n",
  "\u014C": "O",
  "\u014E": "O",
  "\u0150": "O",
  "\u014D": "o",
  "\u014F": "o",
  "\u0151": "o",
  "\u0154": "R",
  "\u0156": "R",
  "\u0158": "R",
  "\u0155": "r",
  "\u0157": "r",
  "\u0159": "r",
  "\u015A": "S",
  "\u015C": "S",
  "\u015E": "S",
  "\u0160": "S",
  "\u015B": "s",
  "\u015D": "s",
  "\u015F": "s",
  "\u0161": "s",
  "\u0162": "T",
  "\u0164": "T",
  "\u0166": "T",
  "\u0163": "t",
  "\u0165": "t",
  "\u0167": "t",
  "\u0168": "U",
  "\u016A": "U",
  "\u016C": "U",
  "\u016E": "U",
  "\u0170": "U",
  "\u0172": "U",
  "\u0169": "u",
  "\u016B": "u",
  "\u016D": "u",
  "\u016F": "u",
  "\u0171": "u",
  "\u0173": "u",
  "\u0174": "W",
  "\u0175": "w",
  "\u0176": "Y",
  "\u0177": "y",
  "\u0178": "Y",
  "\u0179": "Z",
  "\u017B": "Z",
  "\u017D": "Z",
  "\u017A": "z",
  "\u017C": "z",
  "\u017E": "z",
  // normally IJ/ij
  "\u0132": "I",
  "\u0133": "i",
  // normally OE/oe
  "\u0152": "E",
  "\u0153": "e",
  // normally "'n"
  "\u0149": "n",
  "\u017F": "s"
};
var deburr = (str) => {
  return str.replace(reLatin, (key) => {
    return deburredLetters[key] || key;
  }).replace(reComboMark, "");
};

// components/Actions.tsx
import { useState as useState22 } from "react";
import clsx31 from "clsx";

// context/tunnels.ts
import React27 from "react";
import tunnel from "tunnel-rat";
import { createIsolation as createIsolation2 } from "jotai-scope";
var TunnelsContext = React27.createContext(null);
var useTunnels = () => React27.useContext(TunnelsContext);
var tunnelsJotai = createIsolation2();
var useInitializeTunnels = () => {
  return React27.useMemo(() => {
    return {
      MainMenuTunnel: tunnel(),
      WelcomeScreenMenuHintTunnel: tunnel(),
      WelcomeScreenToolbarHintTunnel: tunnel(),
      WelcomeScreenHelpHintTunnel: tunnel(),
      WelcomeScreenCenterTunnel: tunnel(),
      FooterCenterTunnel: tunnel(),
      DefaultSidebarTriggerTunnel: tunnel(),
      DefaultSidebarTabTriggersTunnel: tunnel(),
      OverwriteConfirmDialogTunnel: tunnel(),
      TTDDialogTriggerTunnel: tunnel(),
      tunnelsJotai
    };
  }, []);
};

// components/Actions.tsx
import { Fragment as Fragment9, jsx as jsx70, jsxs as jsxs39 } from "react/jsx-runtime";
var canChangeStrokeColor = (appState, targetElements) => {
  let commonSelectedType = targetElements[0]?.type || null;
  for (const element of targetElements) {
    if (element.type !== commonSelectedType) {
      commonSelectedType = null;
      break;
    }
  }
  return hasStrokeColor(appState.activeTool.type) && appState.activeTool.type !== "image" && commonSelectedType !== "image" && commonSelectedType !== "frame" && commonSelectedType !== "magicframe" || targetElements.some((element) => hasStrokeColor(element.type));
};
var canChangeBackgroundColor = (appState, targetElements) => {
  return hasBackground(appState.activeTool.type) || targetElements.some((element) => hasBackground(element.type));
};
var SelectedShapeActions = ({
  appState,
  elementsMap,
  renderAction,
  app
}) => {
  const targetElements = getTargetElements(elementsMap, appState);
  let isSingleElementBoundContainer = false;
  if (targetElements.length === 2 && (hasBoundTextElement(targetElements[0]) || hasBoundTextElement(targetElements[1]))) {
    isSingleElementBoundContainer = true;
  }
  const isEditingTextOrNewElement = Boolean(
    appState.editingTextElement || appState.newElement
  );
  const device = useDevice();
  const isRTL = document.documentElement.getAttribute("dir") === "rtl";
  const showFillIcons = hasBackground(appState.activeTool.type) && !isTransparent(appState.currentItemBackgroundColor) || targetElements.some(
    (element) => hasBackground(element.type) && !isTransparent(element.backgroundColor)
  );
  const showLinkIcon = targetElements.length === 1 || isSingleElementBoundContainer;
  const showLineEditorAction = !appState.editingLinearElement && targetElements.length === 1 && isLinearElement(targetElements[0]) && !isElbowArrow(targetElements[0]);
  const showCropEditorAction = !appState.croppingElementId && targetElements.length === 1 && isImageElement(targetElements[0]);
  const showAlignActions = !isSingleElementBoundContainer && alignActionsPredicate(appState, app);
  return /* @__PURE__ */ jsxs39("div", { className: "panelColumn", children: [
    /* @__PURE__ */ jsx70("div", { children: canChangeStrokeColor(appState, targetElements) && renderAction("changeStrokeColor") }),
    canChangeBackgroundColor(appState, targetElements) && /* @__PURE__ */ jsx70("div", { children: renderAction("changeBackgroundColor") }),
    showFillIcons && renderAction("changeFillStyle"),
    (hasStrokeWidth(appState.activeTool.type) || targetElements.some((element) => hasStrokeWidth(element.type))) && renderAction("changeStrokeWidth"),
    (appState.activeTool.type === "freedraw" || targetElements.some((element) => element.type === "freedraw")) && renderAction("changeStrokeShape"),
    (hasStrokeStyle(appState.activeTool.type) || targetElements.some((element) => hasStrokeStyle(element.type))) && /* @__PURE__ */ jsxs39(Fragment9, { children: [
      renderAction("changeStrokeStyle"),
      renderAction("changeSloppiness")
    ] }),
    (canChangeRoundness(appState.activeTool.type) || targetElements.some((element) => canChangeRoundness(element.type))) && /* @__PURE__ */ jsx70(Fragment9, { children: renderAction("changeRoundness") }),
    (toolIsArrow(appState.activeTool.type) || targetElements.some((element) => toolIsArrow(element.type))) && /* @__PURE__ */ jsx70(Fragment9, { children: renderAction("changeArrowType") }),
    (appState.activeTool.type === "text" || targetElements.some(isTextElement)) && /* @__PURE__ */ jsxs39(Fragment9, { children: [
      renderAction("changeFontFamily"),
      renderAction("changeFontSize"),
      (appState.activeTool.type === "text" || suppportsHorizontalAlign(targetElements, elementsMap)) && renderAction("changeTextAlign")
    ] }),
    shouldAllowVerticalAlign(targetElements, elementsMap) && renderAction("changeVerticalAlign"),
    (canHaveArrowheads(appState.activeTool.type) || targetElements.some((element) => canHaveArrowheads(element.type))) && /* @__PURE__ */ jsx70(Fragment9, { children: renderAction("changeArrowhead") }),
    renderAction("changeOpacity"),
    /* @__PURE__ */ jsxs39("fieldset", { children: [
      /* @__PURE__ */ jsx70("legend", { children: t("labels.layers") }),
      /* @__PURE__ */ jsxs39("div", { className: "buttonList", children: [
        renderAction("sendToBack"),
        renderAction("sendBackward"),
        renderAction("bringForward"),
        renderAction("bringToFront")
      ] })
    ] }),
    showAlignActions && !isSingleElementBoundContainer && /* @__PURE__ */ jsxs39("fieldset", { children: [
      /* @__PURE__ */ jsx70("legend", { children: t("labels.align") }),
      /* @__PURE__ */ jsxs39("div", { className: "buttonList", children: [
        isRTL ? /* @__PURE__ */ jsxs39(Fragment9, { children: [
          renderAction("alignRight"),
          renderAction("alignHorizontallyCentered"),
          renderAction("alignLeft")
        ] }) : /* @__PURE__ */ jsxs39(Fragment9, { children: [
          renderAction("alignLeft"),
          renderAction("alignHorizontallyCentered"),
          renderAction("alignRight")
        ] }),
        targetElements.length > 2 && renderAction("distributeHorizontally"),
        /* @__PURE__ */ jsx70("div", { style: { flexBasis: "100%", height: 0 } }),
        /* @__PURE__ */ jsxs39(
          "div",
          {
            style: {
              display: "flex",
              flexWrap: "wrap",
              gap: ".5rem",
              marginTop: "-0.5rem"
            },
            children: [
              renderAction("alignTop"),
              renderAction("alignVerticallyCentered"),
              renderAction("alignBottom"),
              targetElements.length > 2 && renderAction("distributeVertically")
            ]
          }
        )
      ] })
    ] }),
    !isEditingTextOrNewElement && targetElements.length > 0 && /* @__PURE__ */ jsxs39("fieldset", { children: [
      /* @__PURE__ */ jsx70("legend", { children: t("labels.actions") }),
      /* @__PURE__ */ jsxs39("div", { className: "buttonList", children: [
        !device.editor.isMobile && renderAction("duplicateSelection"),
        !device.editor.isMobile && renderAction("deleteSelectedElements"),
        renderAction("group"),
        renderAction("ungroup"),
        showLinkIcon && renderAction("hyperlink"),
        showCropEditorAction && renderAction("cropEditor"),
        showLineEditorAction && renderAction("toggleLinearEditor")
      ] })
    ] })
  ] });
};
var ShapesSwitcher = ({
  activeTool,
  appState,
  app,
  UIOptions
}) => {
  const [isExtraToolsMenuOpen, setIsExtraToolsMenuOpen] = useState22(false);
  const frameToolSelected = activeTool.type === "frame";
  const laserToolSelected = activeTool.type === "laser";
  const embeddableToolSelected = activeTool.type === "embeddable";
  const { TTDDialogTriggerTunnel } = useTunnels();
  return /* @__PURE__ */ jsxs39(Fragment9, { children: [
    SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
      if (UIOptions.tools?.[value] === false) {
        return null;
      }
      const label = t(`toolBar.${value}`);
      const letter = key && capitalizeString(typeof key === "string" ? key : key[0]);
      const shortcut = letter ? `${letter} ${t("helpDialog.or")} ${numericKey}` : `${numericKey}`;
      return /* @__PURE__ */ jsx70(
        ToolButton,
        {
          className: clsx31("Shape", { fillable }),
          type: "radio",
          icon,
          checked: activeTool.type === value,
          name: "editor-current-shape",
          title: `${capitalizeString(label)} \u2014 ${shortcut}`,
          keyBindingLabel: numericKey || letter,
          "aria-label": capitalizeString(label),
          "aria-keyshortcuts": shortcut,
          "data-testid": `toolbar-${value}`,
          onPointerDown: ({ pointerType }) => {
            if (!appState.penDetected && pointerType === "pen") {
              app.togglePenMode(true);
            }
          },
          onChange: ({ pointerType }) => {
            if (appState.activeTool.type !== value) {
              trackEvent("toolbar", value, "ui");
            }
            if (value === "image") {
              app.setActiveTool({
                type: value,
                insertOnCanvasDirectly: pointerType !== "mouse"
              });
            } else {
              app.setActiveTool({ type: value });
            }
          }
        },
        value
      );
    }),
    /* @__PURE__ */ jsx70("div", { className: "App-toolbar__divider" }),
    /* @__PURE__ */ jsxs39(DropdownMenu_default, { open: isExtraToolsMenuOpen, children: [
      /* @__PURE__ */ jsx70(
        DropdownMenu_default.Trigger,
        {
          className: clsx31("App-toolbar__extra-tools-trigger", {
            "App-toolbar__extra-tools-trigger--selected": frameToolSelected || embeddableToolSelected || // in collab we're already highlighting the laser button
            // outside toolbar, so let's not highlight extra-tools button
            // on top of it
            laserToolSelected && !app.props.isCollaborating
          }),
          onToggle: () => setIsExtraToolsMenuOpen(!isExtraToolsMenuOpen),
          title: t("toolBar.extraTools"),
          children: extraToolsIcon
        }
      ),
      /* @__PURE__ */ jsxs39(
        DropdownMenu_default.Content,
        {
          onClickOutside: () => setIsExtraToolsMenuOpen(false),
          onSelect: () => setIsExtraToolsMenuOpen(false),
          className: "App-toolbar__extra-tools-dropdown",
          children: [
            /* @__PURE__ */ jsx70(
              DropdownMenu_default.Item,
              {
                onSelect: () => app.setActiveTool({ type: "frame" }),
                icon: frameToolIcon,
                shortcut: KEYS.F.toLocaleUpperCase(),
                "data-testid": "toolbar-frame",
                selected: frameToolSelected,
                children: t("toolBar.frame")
              }
            ),
            /* @__PURE__ */ jsx70(
              DropdownMenu_default.Item,
              {
                onSelect: () => app.setActiveTool({ type: "embeddable" }),
                icon: EmbedIcon,
                "data-testid": "toolbar-embeddable",
                selected: embeddableToolSelected,
                children: t("toolBar.embeddable")
              }
            ),
            /* @__PURE__ */ jsx70(
              DropdownMenu_default.Item,
              {
                onSelect: () => app.setActiveTool({ type: "laser" }),
                icon: laserPointerToolIcon,
                "data-testid": "toolbar-laser",
                selected: laserToolSelected,
                shortcut: KEYS.K.toLocaleUpperCase(),
                children: t("toolBar.laser")
              }
            ),
            /* @__PURE__ */ jsx70("div", { style: { margin: "6px 0", fontSize: 14, fontWeight: 600 }, children: "Generate" }),
            app.props.aiEnabled !== false && /* @__PURE__ */ jsx70(TTDDialogTriggerTunnel.Out, {}),
            /* @__PURE__ */ jsx70(
              DropdownMenu_default.Item,
              {
                onSelect: () => app.setOpenDialog({ name: "ttd", tab: "mermaid" }),
                icon: mermaidLogoIcon,
                "data-testid": "toolbar-embeddable",
                children: t("toolBar.mermaidToExcalidraw")
              }
            ),
            app.props.aiEnabled !== false && app.plugins.diagramToCode && /* @__PURE__ */ jsx70(Fragment9, { children: /* @__PURE__ */ jsxs39(
              DropdownMenu_default.Item,
              {
                onSelect: () => app.onMagicframeToolSelect(),
                icon: MagicIcon,
                "data-testid": "toolbar-magicframe",
                children: [
                  t("toolBar.magicframe"),
                  /* @__PURE__ */ jsx70(DropdownMenu_default.Item.Badge, { children: "AI" })
                ]
              }
            ) })
          ]
        }
      )
    ] })
  ] });
};
var ZoomActions = ({
  renderAction,
  zoom
}) => /* @__PURE__ */ jsx70(Stack_default.Col, { gap: 1, className: CLASSES.ZOOM_ACTIONS, children: /* @__PURE__ */ jsxs39(Stack_default.Row, { align: "center", children: [
  renderAction("zoomOut"),
  renderAction("resetZoom"),
  renderAction("zoomIn")
] }) });
var UndoRedoActions = ({
  renderAction,
  className
}) => /* @__PURE__ */ jsxs39("div", { className: `undo-redo-buttons ${className}`, children: [
  /* @__PURE__ */ jsx70("div", { className: "undo-button-container", children: /* @__PURE__ */ jsx70(Tooltip, { label: t("buttons.undo"), children: renderAction("undo") }) }),
  /* @__PURE__ */ jsx70("div", { className: "redo-button-container", children: /* @__PURE__ */ jsxs39(Tooltip, { label: t("buttons.redo"), children: [
    " ",
    renderAction("redo")
  ] }) })
] });
var ExitZenModeAction = ({
  actionManager,
  showExitZenModeBtn
}) => /* @__PURE__ */ jsx70(
  "button",
  {
    type: "button",
    className: clsx31("disable-zen-mode", {
      "disable-zen-mode--visible": showExitZenModeBtn
    }),
    onClick: () => actionManager.executeAction(actionToggleZenMode),
    children: t("buttons.exitZenMode")
  }
);
var FinalizeAction = ({
  renderAction,
  className
}) => /* @__PURE__ */ jsx70("div", { className: `finalize-button ${className}`, children: renderAction("finalize", { size: "small" }) });

// hooks/useStableCallback.ts
import { useRef as useRef21 } from "react";
var useStableCallback = (userFn) => {
  const stableRef = useRef21({ userFn });
  stableRef.current.userFn = userFn;
  if (!stableRef.current.stableFn) {
    stableRef.current.stableFn = (...args) => stableRef.current.userFn(...args);
  }
  return stableRef.current.stableFn;
};

// components/ActiveConfirmDialog.tsx
import { jsx as jsx71, jsxs as jsxs40 } from "react/jsx-runtime";
var activeConfirmDialogAtom = atom(null);
var ActiveConfirmDialog = () => {
  const [activeConfirmDialog, setActiveConfirmDialog] = useAtom(
    activeConfirmDialogAtom
  );
  const actionManager = useExcalidrawActionManager();
  if (!activeConfirmDialog) {
    return null;
  }
  if (activeConfirmDialog === "clearCanvas") {
    return /* @__PURE__ */ jsx71(
      ConfirmDialog_default,
      {
        onConfirm: () => {
          actionManager.executeAction(actionClearCanvas);
          setActiveConfirmDialog(null);
        },
        onCancel: () => setActiveConfirmDialog(null),
        title: t("clearCanvasDialog.title"),
        children: /* @__PURE__ */ jsxs40("p", { className: "clear-canvas__content", children: [
          " ",
          t("alerts.clearReset")
        ] })
      }
    );
  }
  return null;
};

// components/CommandPalette/defaultCommandPaletteItems.ts
var defaultCommandPaletteItems_exports = {};
__export(defaultCommandPaletteItems_exports, {
  toggleTheme: () => toggleTheme
});
var toggleTheme = {
  ...actionToggleTheme,
  category: "App",
  label: "Toggle theme",
  perform: ({ actionManager }) => {
    actionManager.executeAction(actionToggleTheme, "commandPalette");
  }
};

// actions/actionElementLink.ts
var actionCopyElementLink = register({
  name: "copyElementLink",
  label: "labels.copyElementLink",
  icon: copyIcon,
  trackEvent: { category: "element" },
  perform: async (elements, appState, _, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    try {
      if (window.location) {
        const idAndType = getLinkIdAndTypeFromSelection(
          selectedElements,
          appState
        );
        if (idAndType) {
          await copyTextToSystemClipboard(
            app.props.generateLinkForSelection ? app.props.generateLinkForSelection(idAndType.id, idAndType.type) : defaultGetElementLinkFromSelection(
              idAndType.id,
              idAndType.type
            )
          );
          return {
            appState: {
              toast: {
                message: t("toast.elementLinkCopied"),
                closable: true
              }
            },
            captureUpdate: CaptureUpdateAction.EVENTUALLY
          };
        }
        return {
          appState,
          elements,
          app,
          captureUpdate: CaptureUpdateAction.EVENTUALLY
        };
      }
    } catch (error) {
      console.error(error);
    }
    return {
      appState,
      elements,
      app,
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  predicate: (elements, appState) => canCreateLinkFromElements(getSelectedElements(elements, appState))
});
var actionLinkToElement = register({
  name: "linkToElement",
  label: "labels.linkToElement",
  icon: elementLinkIcon,
  perform: (elements, appState, _, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    if (selectedElements.length !== 1 || !canCreateLinkFromElements(selectedElements)) {
      return {
        elements,
        appState,
        app,
        captureUpdate: CaptureUpdateAction.EVENTUALLY
      };
    }
    return {
      appState: {
        ...appState,
        openDialog: {
          name: "elementLinkSelector",
          sourceElementId: getSelectedElements(elements, appState)[0].id
        }
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  predicate: (elements, appState, appProps, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    return appState.openDialog?.name !== "elementLinkSelector" && selectedElements.length === 1 && canCreateLinkFromElements(selectedElements);
  },
  trackEvent: false
});

// components/CommandPalette/CommandPalette.tsx
import { jsx as jsx72, jsxs as jsxs41 } from "react/jsx-runtime";
var lastUsedPaletteItem = atom(null);
var DEFAULT_CATEGORIES = {
  app: "App",
  export: "Export",
  tools: "Tools",
  editor: "Editor",
  elements: "Elements",
  links: "Links"
};
var getCategoryOrder = (category) => {
  switch (category) {
    case DEFAULT_CATEGORIES.app:
      return 1;
    case DEFAULT_CATEGORIES.export:
      return 2;
    case DEFAULT_CATEGORIES.editor:
      return 3;
    case DEFAULT_CATEGORIES.tools:
      return 4;
    case DEFAULT_CATEGORIES.elements:
      return 5;
    case DEFAULT_CATEGORIES.links:
      return 6;
    default:
      return 10;
  }
};
var CommandShortcutHint = ({
  shortcut,
  className,
  children
}) => {
  const shortcuts = shortcut.replace("++", "+$").split("+");
  return /* @__PURE__ */ jsxs41("div", { className: clsx32("shortcut", className), children: [
    shortcuts.map((item, idx) => {
      return /* @__PURE__ */ jsx72("div", { className: "shortcut-wrapper", children: /* @__PURE__ */ jsx72("div", { className: "shortcut-key", children: item === "$" ? "+" : item }) }, item);
    }),
    /* @__PURE__ */ jsx72("div", { className: "shortcut-desc", children })
  ] });
};
var isCommandPaletteToggleShortcut = (event) => {
  return !event.altKey && event[KEYS.CTRL_OR_CMD] && (event.shiftKey && event.key.toLowerCase() === KEYS.P || event.key === KEYS.SLASH);
};
var CommandPalette = Object.assign(
  (props) => {
    const uiAppState = useUIAppState();
    const setAppState = useExcalidrawSetAppState();
    useEffect28(() => {
      const commandPaletteShortcut = (event) => {
        if (isCommandPaletteToggleShortcut(event)) {
          event.preventDefault();
          event.stopPropagation();
          setAppState((appState) => {
            const nextState = appState.openDialog?.name === "commandPalette" ? null : { name: "commandPalette" };
            if (nextState) {
              trackEvent("command_palette", "open", "shortcut");
            }
            return {
              openDialog: nextState
            };
          });
        }
      };
      window.addEventListener("keydown" /* KEYDOWN */, commandPaletteShortcut, {
        capture: true
      });
      return () => window.removeEventListener("keydown" /* KEYDOWN */, commandPaletteShortcut, {
        capture: true
      });
    }, [setAppState]);
    if (uiAppState.openDialog?.name !== "commandPalette") {
      return null;
    }
    return /* @__PURE__ */ jsx72(CommandPaletteInner, { ...props });
  },
  {
    defaultItems: defaultCommandPaletteItems_exports
  }
);
function CommandPaletteInner({
  customCommandPaletteItems
}) {
  const app = useApp();
  const uiAppState = useUIAppState();
  const setAppState = useExcalidrawSetAppState();
  const appProps = useAppProps();
  const actionManager = useExcalidrawActionManager();
  const [lastUsed, setLastUsed] = useAtom(lastUsedPaletteItem);
  const [allCommands, setAllCommands] = useState23(null);
  const inputRef = useRef22(null);
  const stableDeps = useStable({
    uiAppState,
    customCommandPaletteItems,
    appProps
  });
  useEffect28(() => {
    const { uiAppState: uiAppState2, customCommandPaletteItems: customCommandPaletteItems2, appProps: appProps2 } = stableDeps;
    const getActionLabel = (action) => {
      let label = "";
      if (action.label) {
        if (typeof action.label === "function") {
          label = t(
            action.label(
              app.scene.getNonDeletedElements(),
              uiAppState2,
              app
            )
          );
        } else {
          label = t(action.label);
        }
      }
      return label;
    };
    const getActionIcon = (action) => {
      if (typeof action.icon === "function") {
        return action.icon(uiAppState2, app.scene.getNonDeletedElements());
      }
      return action.icon;
    };
    let commandsFromActions = [];
    const actionToCommand = (action, category, transformer) => {
      const command = {
        label: getActionLabel(action),
        icon: getActionIcon(action),
        category,
        shortcut: getShortcutFromShortcutName(action.name),
        keywords: action.keywords,
        predicate: action.predicate,
        viewMode: action.viewMode,
        perform: () => {
          actionManager.executeAction(action, "commandPalette");
        }
      };
      return transformer ? transformer(command, action) : command;
    };
    if (uiAppState2 && app.scene && actionManager) {
      const elementsCommands = [
        actionManager.actions.group,
        actionManager.actions.ungroup,
        actionManager.actions.cut,
        actionManager.actions.copy,
        actionManager.actions.deleteSelectedElements,
        actionManager.actions.wrapSelectionInFrame,
        actionManager.actions.copyStyles,
        actionManager.actions.pasteStyles,
        actionManager.actions.bringToFront,
        actionManager.actions.bringForward,
        actionManager.actions.sendBackward,
        actionManager.actions.sendToBack,
        actionManager.actions.alignTop,
        actionManager.actions.alignBottom,
        actionManager.actions.alignLeft,
        actionManager.actions.alignRight,
        actionManager.actions.alignVerticallyCentered,
        actionManager.actions.alignHorizontallyCentered,
        actionManager.actions.duplicateSelection,
        actionManager.actions.flipHorizontal,
        actionManager.actions.flipVertical,
        actionManager.actions.zoomToFitSelection,
        actionManager.actions.zoomToFitSelectionInViewport,
        actionManager.actions.increaseFontSize,
        actionManager.actions.decreaseFontSize,
        actionManager.actions.toggleLinearEditor,
        actionManager.actions.cropEditor,
        actionLink,
        actionCopyElementLink,
        actionLinkToElement
      ].map(
        (action) => actionToCommand(
          action,
          DEFAULT_CATEGORIES.elements,
          (command, action2) => ({
            ...command,
            predicate: action2.predicate ? action2.predicate : (elements, appState, appProps3, app2) => {
              const selectedElements = getSelectedElements(
                elements,
                appState
              );
              return selectedElements.length > 0;
            }
          })
        )
      );
      const toolCommands = [
        actionManager.actions.toggleHandTool,
        actionManager.actions.setFrameAsActiveTool
      ].map((action) => actionToCommand(action, DEFAULT_CATEGORIES.tools));
      const editorCommands = [
        actionManager.actions.undo,
        actionManager.actions.redo,
        actionManager.actions.zoomIn,
        actionManager.actions.zoomOut,
        actionManager.actions.resetZoom,
        actionManager.actions.zoomToFit,
        actionManager.actions.zenMode,
        actionManager.actions.viewMode,
        actionManager.actions.gridMode,
        actionManager.actions.objectsSnapMode,
        actionManager.actions.toggleShortcuts,
        actionManager.actions.selectAll,
        actionManager.actions.toggleElementLock,
        actionManager.actions.unlockAllElements,
        actionManager.actions.stats
      ].map((action) => actionToCommand(action, DEFAULT_CATEGORIES.editor));
      const exportCommands = [
        actionManager.actions.saveToActiveFile,
        actionManager.actions.saveFileToDisk,
        actionManager.actions.copyAsPng,
        actionManager.actions.copyAsSvg
      ].map((action) => actionToCommand(action, DEFAULT_CATEGORIES.export));
      commandsFromActions = [
        ...elementsCommands,
        ...editorCommands,
        {
          label: getActionLabel(actionClearCanvas),
          icon: getActionIcon(actionClearCanvas),
          shortcut: getShortcutFromShortcutName(
            actionClearCanvas.name
          ),
          category: DEFAULT_CATEGORIES.editor,
          keywords: ["delete", "destroy"],
          viewMode: false,
          perform: () => {
            editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
          }
        },
        {
          label: t("buttons.exportImage"),
          category: DEFAULT_CATEGORIES.export,
          icon: ExportImageIcon,
          shortcut: getShortcutFromShortcutName("imageExport"),
          keywords: [
            "export",
            "image",
            "png",
            "jpeg",
            "svg",
            "clipboard",
            "picture"
          ],
          perform: () => {
            setAppState({ openDialog: { name: "imageExport" } });
          }
        },
        ...exportCommands
      ];
      const additionalCommands = [
        {
          label: t("toolBar.library"),
          category: DEFAULT_CATEGORIES.app,
          icon: LibraryIcon,
          viewMode: false,
          perform: () => {
            if (uiAppState2.openSidebar) {
              setAppState({
                openSidebar: null
              });
            } else {
              setAppState({
                openSidebar: {
                  name: DEFAULT_SIDEBAR.name,
                  tab: DEFAULT_SIDEBAR.defaultTab
                }
              });
            }
          }
        },
        {
          label: t("search.title"),
          category: DEFAULT_CATEGORIES.app,
          icon: searchIcon,
          viewMode: true,
          perform: () => {
            actionManager.executeAction(actionToggleSearchMenu);
          }
        },
        {
          label: t("labels.changeStroke"),
          keywords: ["color", "outline"],
          category: DEFAULT_CATEGORIES.elements,
          icon: bucketFillIcon,
          viewMode: false,
          predicate: (elements, appState) => {
            const selectedElements = getSelectedElements(elements, appState);
            return selectedElements.length > 0 && canChangeStrokeColor(appState, selectedElements);
          },
          perform: () => {
            setAppState((prevState) => ({
              openMenu: prevState.openMenu === "shape" ? null : "shape",
              openPopup: "elementStroke"
            }));
          }
        },
        {
          label: t("labels.changeBackground"),
          keywords: ["color", "fill"],
          icon: bucketFillIcon,
          category: DEFAULT_CATEGORIES.elements,
          viewMode: false,
          predicate: (elements, appState) => {
            const selectedElements = getSelectedElements(elements, appState);
            return selectedElements.length > 0 && canChangeBackgroundColor(appState, selectedElements);
          },
          perform: () => {
            setAppState((prevState) => ({
              openMenu: prevState.openMenu === "shape" ? null : "shape",
              openPopup: "elementBackground"
            }));
          }
        },
        {
          label: t("labels.canvasBackground"),
          keywords: ["color"],
          icon: bucketFillIcon,
          category: DEFAULT_CATEGORIES.editor,
          viewMode: false,
          perform: () => {
            setAppState((prevState) => ({
              openMenu: prevState.openMenu === "canvas" ? null : "canvas",
              openPopup: "canvasBackground"
            }));
          }
        },
        ...SHAPES.reduce((acc, shape) => {
          const { value, icon, key, numericKey } = shape;
          if (appProps2.UIOptions.tools?.[value] === false) {
            return acc;
          }
          const letter = key && capitalizeString(typeof key === "string" ? key : key[0]);
          const shortcut = letter || numericKey;
          const command = {
            label: t(`toolBar.${value}`),
            category: DEFAULT_CATEGORIES.tools,
            shortcut,
            icon,
            keywords: ["toolbar"],
            viewMode: false,
            perform: ({ event }) => {
              if (value === "image") {
                app.setActiveTool({
                  type: value,
                  insertOnCanvasDirectly: event.type === "keydown" /* KEYDOWN */
                });
              } else {
                app.setActiveTool({ type: value });
              }
            }
          };
          acc.push(command);
          return acc;
        }, []),
        ...toolCommands,
        {
          label: t("toolBar.lock"),
          category: DEFAULT_CATEGORIES.tools,
          icon: uiAppState2.activeTool.locked ? LockedIcon : UnlockedIcon,
          shortcut: KEYS.Q.toLocaleUpperCase(),
          viewMode: false,
          perform: () => {
            app.toggleLock();
          }
        },
        {
          label: `${t("labels.textToDiagram")}...`,
          category: DEFAULT_CATEGORIES.tools,
          icon: brainIconThin,
          viewMode: false,
          predicate: appProps2.aiEnabled,
          perform: () => {
            setAppState((state) => ({
              ...state,
              openDialog: {
                name: "ttd",
                tab: "text-to-diagram"
              }
            }));
          }
        },
        {
          label: `${t("toolBar.mermaidToExcalidraw")}...`,
          category: DEFAULT_CATEGORIES.tools,
          icon: mermaidLogoIcon,
          viewMode: false,
          predicate: appProps2.aiEnabled,
          perform: () => {
            setAppState((state) => ({
              ...state,
              openDialog: {
                name: "ttd",
                tab: "mermaid"
              }
            }));
          }
        }
        // {
        //   label: `${t("toolBar.magicframe")}...`,
        //   category: DEFAULT_CATEGORIES.tools,
        //   icon: MagicIconThin,
        //   viewMode: false,
        //   predicate: appProps.aiEnabled,
        //   perform: () => {
        //     app.onMagicframeToolSelect();
        //   },
        // },
      ];
      const allCommands2 = [
        ...commandsFromActions,
        ...additionalCommands,
        ...customCommandPaletteItems2 || []
      ].map((command) => {
        return {
          ...command,
          icon: command.icon || boltIcon,
          order: command.order ?? getCategoryOrder(command.category),
          haystack: `${deburr(command.label.toLocaleLowerCase())} ${command.keywords?.join(" ") || ""}`
        };
      });
      setAllCommands(allCommands2);
      setLastUsed(
        allCommands2.find((command) => command.label === lastUsed?.label) ?? null
      );
    }
  }, [
    stableDeps,
    app,
    actionManager,
    setAllCommands,
    lastUsed?.label,
    setLastUsed,
    setAppState
  ]);
  const [commandSearch, setCommandSearch] = useState23("");
  const [currentCommand, setCurrentCommand] = useState23(null);
  const [commandsByCategory, setCommandsByCategory] = useState23({});
  const closeCommandPalette = (cb) => {
    setAppState(
      {
        openDialog: null
      },
      cb
    );
    setCommandSearch("");
  };
  const executeCommand = (command, event) => {
    if (uiAppState.openDialog?.name === "commandPalette") {
      event.stopPropagation();
      event.preventDefault();
      document.body.classList.add("excalidraw-animations-disabled");
      closeCommandPalette(() => {
        command.perform({ actionManager, event });
        setLastUsed(command);
        requestAnimationFrame(() => {
          document.body.classList.remove("excalidraw-animations-disabled");
        });
      });
    }
  };
  const isCommandAvailable = useStableCallback(
    (command) => {
      if (command.viewMode === false && uiAppState.viewModeEnabled) {
        return false;
      }
      return typeof command.predicate === "function" ? command.predicate(
        app.scene.getNonDeletedElements(),
        uiAppState,
        appProps,
        app
      ) : command.predicate === void 0 || command.predicate;
    }
  );
  const handleKeyDown = useStableCallback((event) => {
    const ignoreAlphanumerics = isWritableElement(event.target) || isCommandPaletteToggleShortcut(event) || event.key === KEYS.ESCAPE;
    if (ignoreAlphanumerics && event.key !== KEYS.ARROW_UP && event.key !== KEYS.ARROW_DOWN && event.key !== KEYS.ENTER) {
      return;
    }
    const matchingCommands = Object.values(commandsByCategory).flat();
    const shouldConsiderLastUsed = lastUsed && !commandSearch && isCommandAvailable(lastUsed);
    if (event.key === KEYS.ARROW_UP) {
      event.preventDefault();
      const index = matchingCommands.findIndex(
        (item) => item.label === currentCommand?.label
      );
      if (shouldConsiderLastUsed) {
        if (index === 0) {
          setCurrentCommand(lastUsed);
          return;
        }
        if (currentCommand === lastUsed) {
          const nextItem2 = matchingCommands[matchingCommands.length - 1];
          if (nextItem2) {
            setCurrentCommand(nextItem2);
          }
          return;
        }
      }
      let nextIndex;
      if (index === -1) {
        nextIndex = matchingCommands.length - 1;
      } else {
        nextIndex = index === 0 ? matchingCommands.length - 1 : (index - 1) % matchingCommands.length;
      }
      const nextItem = matchingCommands[nextIndex];
      if (nextItem) {
        setCurrentCommand(nextItem);
      }
      return;
    }
    if (event.key === KEYS.ARROW_DOWN) {
      event.preventDefault();
      const index = matchingCommands.findIndex(
        (item) => item.label === currentCommand?.label
      );
      if (shouldConsiderLastUsed) {
        if (!currentCommand || index === matchingCommands.length - 1) {
          setCurrentCommand(lastUsed);
          return;
        }
        if (currentCommand === lastUsed) {
          const nextItem2 = matchingCommands[0];
          if (nextItem2) {
            setCurrentCommand(nextItem2);
          }
          return;
        }
      }
      const nextIndex = (index + 1) % matchingCommands.length;
      const nextItem = matchingCommands[nextIndex];
      if (nextItem) {
        setCurrentCommand(nextItem);
      }
      return;
    }
    if (event.key === KEYS.ENTER) {
      if (currentCommand) {
        setTimeout(() => {
          executeCommand(currentCommand, event);
        });
      }
    }
    if (ignoreAlphanumerics) {
      return;
    }
    event.stopPropagation();
    if (/^[a-zA-Z0-9]$/.test(event.key)) {
      inputRef?.current?.focus();
      return;
    }
    event.preventDefault();
  });
  useEffect28(() => {
    window.addEventListener("keydown" /* KEYDOWN */, handleKeyDown, {
      capture: true
    });
    return () => window.removeEventListener("keydown" /* KEYDOWN */, handleKeyDown, {
      capture: true
    });
  }, [handleKeyDown]);
  useEffect28(() => {
    if (!allCommands) {
      return;
    }
    const getNextCommandsByCategory = (commands) => {
      const nextCommandsByCategory = {};
      for (const command of commands) {
        if (nextCommandsByCategory[command.category]) {
          nextCommandsByCategory[command.category].push(command);
        } else {
          nextCommandsByCategory[command.category] = [command];
        }
      }
      return nextCommandsByCategory;
    };
    let matchingCommands = allCommands.filter(isCommandAvailable).sort((a, b) => a.order - b.order);
    const showLastUsed = !commandSearch && lastUsed && isCommandAvailable(lastUsed);
    if (!commandSearch) {
      setCommandsByCategory(
        getNextCommandsByCategory(
          showLastUsed ? matchingCommands.filter(
            (command) => command.label !== lastUsed?.label
          ) : matchingCommands
        )
      );
      setCurrentCommand(showLastUsed ? lastUsed : matchingCommands[0] || null);
      return;
    }
    const _query = deburr(
      commandSearch.toLocaleLowerCase().replace(/[<>_| -]/g, "")
    );
    matchingCommands = fuzzy.filter(_query, matchingCommands, {
      extract: (command) => command.haystack
    }).sort((a, b) => b.score - a.score).map((item) => item.original);
    setCommandsByCategory(getNextCommandsByCategory(matchingCommands));
    setCurrentCommand(matchingCommands[0] ?? null);
  }, [commandSearch, allCommands, isCommandAvailable, lastUsed]);
  return /* @__PURE__ */ jsxs41(
    Dialog,
    {
      onCloseRequest: () => closeCommandPalette(),
      closeOnClickOutside: true,
      title: false,
      size: 720,
      autofocus: true,
      className: "command-palette-dialog",
      children: [
        /* @__PURE__ */ jsx72(
          TextField,
          {
            value: commandSearch,
            placeholder: t("commandPalette.search.placeholder"),
            onChange: (value) => {
              setCommandSearch(value);
            },
            selectOnRender: true,
            ref: inputRef
          }
        ),
        !app.device.viewport.isMobile && /* @__PURE__ */ jsxs41("div", { className: "shortcuts-wrapper", children: [
          /* @__PURE__ */ jsx72(CommandShortcutHint, { shortcut: "\u2191\u2193", children: t("commandPalette.shortcuts.select") }),
          /* @__PURE__ */ jsx72(CommandShortcutHint, { shortcut: "\u21B5", children: t("commandPalette.shortcuts.confirm") }),
          /* @__PURE__ */ jsx72(CommandShortcutHint, { shortcut: getShortcutKey("Esc"), children: t("commandPalette.shortcuts.close") })
        ] }),
        /* @__PURE__ */ jsxs41("div", { className: "commands", children: [
          lastUsed && !commandSearch && /* @__PURE__ */ jsxs41("div", { className: "command-category", children: [
            /* @__PURE__ */ jsxs41("div", { className: "command-category-title", children: [
              t("commandPalette.recents"),
              /* @__PURE__ */ jsx72(
                "div",
                {
                  className: "icon",
                  style: {
                    marginLeft: "6px"
                  },
                  children: clockIcon
                }
              )
            ] }),
            /* @__PURE__ */ jsx72(
              CommandItem,
              {
                command: lastUsed,
                isSelected: lastUsed.label === currentCommand?.label,
                onClick: (event) => executeCommand(lastUsed, event),
                disabled: !isCommandAvailable(lastUsed),
                onMouseMove: () => setCurrentCommand(lastUsed),
                showShortcut: !app.device.viewport.isMobile,
                appState: uiAppState
              }
            )
          ] }),
          Object.keys(commandsByCategory).length > 0 ? Object.keys(commandsByCategory).map((category, idx) => {
            return /* @__PURE__ */ jsxs41("div", { className: "command-category", children: [
              /* @__PURE__ */ jsx72("div", { className: "command-category-title", children: category }),
              commandsByCategory[category].map((command) => /* @__PURE__ */ jsx72(
                CommandItem,
                {
                  command,
                  isSelected: command.label === currentCommand?.label,
                  onClick: (event) => executeCommand(command, event),
                  onMouseMove: () => setCurrentCommand(command),
                  showShortcut: !app.device.viewport.isMobile,
                  appState: uiAppState
                },
                command.label
              ))
            ] }, category);
          }) : allCommands ? /* @__PURE__ */ jsxs41("div", { className: "no-match", children: [
            /* @__PURE__ */ jsx72("div", { className: "icon", children: searchIcon }),
            " ",
            t("commandPalette.search.noMatch")
          ] }) : null
        ] })
      ]
    }
  );
}
var CommandItem = ({
  command,
  isSelected,
  disabled,
  onMouseMove,
  onClick,
  showShortcut,
  appState
}) => {
  const noop = () => {
  };
  return /* @__PURE__ */ jsxs41(
    "div",
    {
      className: clsx32("command-item", {
        "item-selected": isSelected,
        "item-disabled": disabled
      }),
      ref: (ref) => {
        if (isSelected && !disabled) {
          ref?.scrollIntoView?.({
            block: "nearest"
          });
        }
      },
      onClick: disabled ? noop : onClick,
      onMouseMove: disabled ? noop : onMouseMove,
      title: disabled ? t("commandPalette.itemNotAvailable") : "",
      children: [
        /* @__PURE__ */ jsxs41("div", { className: "name", children: [
          command.icon && /* @__PURE__ */ jsx72(
            InlineIcon,
            {
              icon: typeof command.icon === "function" ? command.icon(appState) : command.icon
            }
          ),
          command.label
        ] }),
        showShortcut && command.shortcut && /* @__PURE__ */ jsx72(CommandShortcutHint, { shortcut: command.shortcut })
      ]
    }
  );
};

// actions/actionLinearEditor.tsx
import { jsx as jsx73 } from "react/jsx-runtime";
var actionToggleLinearEditor = register({
  name: "toggleLinearEditor",
  category: DEFAULT_CATEGORIES.elements,
  label: (elements, appState, app) => {
    const selectedElement = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds
    })[0];
    return selectedElement?.type === "arrow" ? "labels.lineEditor.editArrow" : "labels.lineEditor.edit";
  },
  keywords: ["line"],
  trackEvent: {
    category: "element"
  },
  predicate: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    if (!appState.editingLinearElement && selectedElements.length === 1 && isLinearElement(selectedElements[0]) && !isElbowArrow(selectedElements[0])) {
      return true;
    }
    return false;
  },
  perform(elements, appState, _, app) {
    const selectedElement = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true
    })[0];
    const editingLinearElement = appState.editingLinearElement?.elementId === selectedElement.id ? null : new LinearElementEditor(selectedElement);
    return {
      appState: {
        ...appState,
        editingLinearElement
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  PanelComponent: ({ appState, updateData, app }) => {
    const selectedElement = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds
    })[0];
    const label = t(
      selectedElement.type === "arrow" ? "labels.lineEditor.editArrow" : "labels.lineEditor.edit"
    );
    return /* @__PURE__ */ jsx73(
      ToolButton,
      {
        type: "button",
        icon: lineEditorIcon,
        title: label,
        "aria-label": label,
        onClick: () => updateData(null)
      }
    );
  }
});

// actions/actionToggleSearchMenu.ts
var actionToggleSearchMenu = register({
  name: "searchMenu",
  icon: searchIcon,
  keywords: ["search", "find"],
  label: "search.title",
  viewMode: true,
  trackEvent: {
    category: "search_menu",
    action: "toggle",
    predicate: (appState) => appState.gridModeEnabled
  },
  perform(elements, appState, _, app) {
    if (appState.openSidebar?.name === DEFAULT_SIDEBAR.name && appState.openSidebar.tab === CANVAS_SEARCH_TAB) {
      const searchInput = app.excalidrawContainerValue.container?.querySelector(
        `.${CLASSES.SEARCH_MENU_INPUT_WRAPPER} input`
      );
      if (searchInput?.matches(":focus")) {
        return {
          appState: { ...appState, openSidebar: null },
          captureUpdate: CaptureUpdateAction.EVENTUALLY
        };
      }
      searchInput?.focus();
      searchInput?.select();
      return false;
    }
    return {
      appState: {
        ...appState,
        openSidebar: { name: DEFAULT_SIDEBAR.name, tab: CANVAS_SEARCH_TAB },
        openDialog: null
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.gridModeEnabled,
  predicate: (element, appState, props) => {
    return props.gridModeEnabled === void 0;
  },
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.F
});

// actions/actionCropEditor.tsx
import { jsx as jsx74 } from "react/jsx-runtime";
var actionToggleCropEditor = register({
  name: "cropEditor",
  label: "helpDialog.cropStart",
  icon: cropIcon,
  viewMode: true,
  trackEvent: { category: "menu" },
  keywords: ["image", "crop"],
  perform(elements, appState, _, app) {
    const selectedElement = app.scene.getSelectedElements({
      selectedElementIds: appState.selectedElementIds,
      includeBoundTextElement: true
    })[0];
    return {
      appState: {
        ...appState,
        isCropping: false,
        croppingElementId: selectedElement.id
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  },
  predicate: (elements, appState, _, app) => {
    const selectedElements = app.scene.getSelectedElements(appState);
    if (!appState.croppingElementId && selectedElements.length === 1 && isImageElement(selectedElements[0])) {
      return true;
    }
    return false;
  },
  PanelComponent: ({ appState, updateData, app }) => {
    const label = t("helpDialog.cropStart");
    return /* @__PURE__ */ jsx74(
      ToolButton,
      {
        type: "button",
        icon: cropIcon,
        title: label,
        "aria-label": label,
        onClick: () => updateData(null)
      }
    );
  }
});

// history.ts
var HistoryChangedEvent = class {
  constructor(isUndoStackEmpty = true, isRedoStackEmpty = true) {
    this.isUndoStackEmpty = isUndoStackEmpty;
    this.isRedoStackEmpty = isRedoStackEmpty;
  }
};
var History = class _History {
  constructor() {
    __publicField(this, "onHistoryChangedEmitter", new Emitter());
    __publicField(this, "undoStack", []);
    __publicField(this, "redoStack", []);
  }
  get isUndoStackEmpty() {
    return this.undoStack.length === 0;
  }
  get isRedoStackEmpty() {
    return this.redoStack.length === 0;
  }
  clear() {
    this.undoStack.length = 0;
    this.redoStack.length = 0;
  }
  /**
   * Record a local change which will go into the history
   */
  record(elementsChange, appStateChange) {
    const entry = HistoryEntry.create(appStateChange, elementsChange);
    if (!entry.isEmpty()) {
      this.undoStack.push(entry.inverse());
      if (!entry.elementsChange.isEmpty()) {
        this.redoStack.length = 0;
      }
      this.onHistoryChangedEmitter.trigger(
        new HistoryChangedEvent(this.isUndoStackEmpty, this.isRedoStackEmpty)
      );
    }
  }
  undo(elements, appState, snapshot) {
    return this.perform(
      elements,
      appState,
      snapshot,
      () => _History.pop(this.undoStack),
      (entry) => _History.push(this.redoStack, entry, elements)
    );
  }
  redo(elements, appState, snapshot) {
    return this.perform(
      elements,
      appState,
      snapshot,
      () => _History.pop(this.redoStack),
      (entry) => _History.push(this.undoStack, entry, elements)
    );
  }
  perform(elements, appState, snapshot, pop, push) {
    try {
      let historyEntry = pop();
      if (historyEntry === null) {
        return;
      }
      let nextElements = elements;
      let nextAppState = appState;
      let containsVisibleChange = false;
      while (historyEntry) {
        try {
          [nextElements, nextAppState, containsVisibleChange] = historyEntry.applyTo(nextElements, nextAppState, snapshot);
        } finally {
          push(historyEntry);
        }
        if (containsVisibleChange) {
          break;
        }
        historyEntry = pop();
      }
      return [nextElements, nextAppState];
    } finally {
      this.onHistoryChangedEmitter.trigger(
        new HistoryChangedEvent(this.isUndoStackEmpty, this.isRedoStackEmpty)
      );
    }
  }
  static pop(stack) {
    if (!stack.length) {
      return null;
    }
    const entry = stack.pop();
    if (entry !== void 0) {
      return entry;
    }
    return null;
  }
  static push(stack, entry, prevElements) {
    const updatedEntry = entry.inverse().applyLatestChanges(prevElements);
    return stack.push(updatedEntry);
  }
};
var HistoryEntry = class _HistoryEntry {
  constructor(appStateChange, elementsChange) {
    this.appStateChange = appStateChange;
    this.elementsChange = elementsChange;
  }
  static create(appStateChange, elementsChange) {
    return new _HistoryEntry(appStateChange, elementsChange);
  }
  inverse() {
    return new _HistoryEntry(
      this.appStateChange.inverse(),
      this.elementsChange.inverse()
    );
  }
  applyTo(elements, appState, snapshot) {
    const [nextElements, elementsContainVisibleChange] = this.elementsChange.applyTo(elements, snapshot.elements);
    const [nextAppState, appStateContainsVisibleChange] = this.appStateChange.applyTo(appState, nextElements);
    const appliedVisibleChanges = elementsContainVisibleChange || appStateContainsVisibleChange;
    return [nextElements, nextAppState, appliedVisibleChanges];
  }
  /**
   * Apply latest (remote) changes to the history entry, creates new instance of `HistoryEntry`.
   */
  applyLatestChanges(elements) {
    const updatedElementsChange = this.elementsChange.applyLatestChanges(elements);
    return _HistoryEntry.create(this.appStateChange, updatedElementsChange);
  }
  isEmpty() {
    return this.appStateChange.isEmpty() && this.elementsChange.isEmpty();
  }
};

// hooks/useEmitter.ts
import { useEffect as useEffect29, useState as useState24 } from "react";
var useEmitter = (emitter, initialState) => {
  const [event, setEvent] = useState24(initialState);
  useEffect29(() => {
    const unsubscribe = emitter.on((event2) => {
      setEvent(event2);
    });
    return () => {
      unsubscribe();
    };
  }, [emitter]);
  return event;
};

// actions/actionHistory.tsx
import { jsx as jsx75 } from "react/jsx-runtime";
var executeHistoryAction = (app, appState, updater) => {
  if (!appState.multiElement && !appState.resizingElement && !appState.editingTextElement && !appState.newElement && !appState.selectedElementsAreBeingDragged && !appState.selectionElement && !app.flowChartCreator.isCreatingChart) {
    const result = updater();
    if (!result) {
      return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
    }
    const [nextElementsMap, nextAppState] = result;
    const nextElements = Array.from(nextElementsMap.values());
    return {
      appState: nextAppState,
      elements: nextElements,
      captureUpdate: CaptureUpdateAction.NEVER
    };
  }
  return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
};
var createUndoAction = (history, store) => ({
  name: "undo",
  label: "buttons.undo",
  icon: UndoIcon,
  trackEvent: { category: "history" },
  viewMode: false,
  perform: (elements, appState, value, app) => executeHistoryAction(
    app,
    appState,
    () => history.undo(
      arrayToMap(elements),
      // TODO: #7348 refactor action manager to already include `SceneElementsMap`
      appState,
      store.snapshot
    )
  ),
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && matchKey(event, KEYS.Z) && !event.shiftKey,
  PanelComponent: ({ updateData, data }) => {
    const { isUndoStackEmpty } = useEmitter(
      history.onHistoryChangedEmitter,
      new HistoryChangedEvent(
        history.isUndoStackEmpty,
        history.isRedoStackEmpty
      )
    );
    return /* @__PURE__ */ jsx75(
      ToolButton,
      {
        type: "button",
        icon: UndoIcon,
        "aria-label": t("buttons.undo"),
        onClick: updateData,
        size: data?.size || "medium",
        disabled: isUndoStackEmpty,
        "data-testid": "button-undo"
      }
    );
  }
});
var createRedoAction = (history, store) => ({
  name: "redo",
  label: "buttons.redo",
  icon: RedoIcon,
  trackEvent: { category: "history" },
  viewMode: false,
  perform: (elements, appState, _, app) => executeHistoryAction(
    app,
    appState,
    () => history.redo(
      arrayToMap(elements),
      // TODO: #7348 refactor action manager to already include `SceneElementsMap`
      appState,
      store.snapshot
    )
  ),
  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.shiftKey && matchKey(event, KEYS.Z) || isWindows && event.ctrlKey && !event.shiftKey && matchKey(event, KEYS.Y),
  PanelComponent: ({ updateData, data }) => {
    const { isRedoStackEmpty } = useEmitter(
      history.onHistoryChangedEmitter,
      new HistoryChangedEvent(
        history.isUndoStackEmpty,
        history.isRedoStackEmpty
      )
    );
    return /* @__PURE__ */ jsx75(
      ToolButton,
      {
        type: "button",
        icon: RedoIcon,
        "aria-label": t("buttons.redo"),
        onClick: updateData,
        size: data?.size || "medium",
        disabled: isRedoStackEmpty,
        "data-testid": "button-redo"
      }
    );
  }
});

// actions/manager.tsx
import { jsx as jsx76 } from "react/jsx-runtime";
var trackAction = (action, source, appState, elements, app, value) => {
  if (action.trackEvent) {
    try {
      if (typeof action.trackEvent === "object") {
        const shouldTrack = action.trackEvent.predicate ? action.trackEvent.predicate(appState, elements, value) : true;
        if (shouldTrack) {
          trackEvent(
            action.trackEvent.category,
            action.trackEvent.action || action.name,
            `${source} (${app.device.editor.isMobile ? "mobile" : "desktop"})`
          );
        }
      }
    } catch (error) {
      console.error("error while logging action:", error);
    }
  }
};
var ActionManager = class {
  constructor(updater, getAppState, getElementsIncludingDeleted, app) {
    __publicField(this, "actions", {});
    __publicField(this, "updater");
    __publicField(this, "getAppState");
    __publicField(this, "getElementsIncludingDeleted");
    __publicField(this, "app");
    /**
     * @param data additional data sent to the PanelComponent
     */
    __publicField(this, "renderAction", (name, data) => {
      const canvasActions = this.app.props.UIOptions.canvasActions;
      if (this.actions[name] && "PanelComponent" in this.actions[name] && (name in canvasActions ? canvasActions[name] : true)) {
        const action = this.actions[name];
        const PanelComponent = action.PanelComponent;
        PanelComponent.displayName = "PanelComponent";
        const elements = this.getElementsIncludingDeleted();
        const appState = this.getAppState();
        const updateData = (formState) => {
          trackAction(action, "ui", appState, elements, this.app, formState);
          this.updater(
            action.perform(
              this.getElementsIncludingDeleted(),
              this.getAppState(),
              formState,
              this.app
            )
          );
        };
        return /* @__PURE__ */ jsx76(
          PanelComponent,
          {
            elements: this.getElementsIncludingDeleted(),
            appState: this.getAppState(),
            updateData,
            appProps: this.app.props,
            app: this.app,
            data
          }
        );
      }
      return null;
    });
    __publicField(this, "isActionEnabled", (action) => {
      const elements = this.getElementsIncludingDeleted();
      const appState = this.getAppState();
      return !action.predicate || action.predicate(elements, appState, this.app.props, this.app);
    });
    this.updater = (actionResult) => {
      if (isPromiseLike(actionResult)) {
        actionResult.then((actionResult2) => {
          return updater(actionResult2);
        });
      } else {
        return updater(actionResult);
      }
    };
    this.getAppState = getAppState;
    this.getElementsIncludingDeleted = getElementsIncludingDeleted;
    this.app = app;
  }
  registerAction(action) {
    this.actions[action.name] = action;
  }
  registerAll(actions2) {
    actions2.forEach((action) => this.registerAction(action));
  }
  handleKeyDown(event) {
    const canvasActions = this.app.props.UIOptions.canvasActions;
    const data = Object.values(this.actions).sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0)).filter(
      (action2) => (action2.name in canvasActions ? canvasActions[action2.name] : true) && action2.keyTest && action2.keyTest(
        event,
        this.getAppState(),
        this.getElementsIncludingDeleted(),
        this.app
      )
    );
    if (data.length !== 1) {
      if (data.length > 1) {
        console.warn("Canceling as multiple actions match this shortcut", data);
      }
      return false;
    }
    const action = data[0];
    if (this.getAppState().viewModeEnabled && action.viewMode !== true) {
      return false;
    }
    const elements = this.getElementsIncludingDeleted();
    const appState = this.getAppState();
    const value = null;
    trackAction(action, "keyboard", appState, elements, this.app, null);
    event.preventDefault();
    event.stopPropagation();
    this.updater(data[0].perform(elements, appState, value, this.app));
    return true;
  }
  executeAction(action, source = "api", value = null) {
    const elements = this.getElementsIncludingDeleted();
    const appState = this.getAppState();
    trackAction(action, source, appState, elements, this.app, value);
    this.updater(action.perform(elements, appState, value, this.app));
  }
};

// gesture.ts
var getCenter = (pointers) => {
  const allCoords = Array.from(pointers.values());
  return {
    x: sum(allCoords, (coords) => coords.x) / allCoords.length,
    y: sum(allCoords, (coords) => coords.y) / allCoords.length
  };
};
var getDistance = ([a, b]) => Math.hypot(a.x - b.x, a.y - b.y);
var sum = (array, mapper) => array.reduce((acc, item) => acc + mapper(item), 0);

// components/ContextMenu.tsx
import clsx33 from "clsx";

// components/Popover.tsx
import { useLayoutEffect as useLayoutEffect4, useRef as useRef23, useEffect as useEffect30 } from "react";
import { unstable_batchedUpdates } from "react-dom";
import { jsx as jsx77 } from "react/jsx-runtime";
var Popover6 = ({
  children,
  left,
  top,
  onCloseRequest,
  fitInViewport = false,
  offsetLeft = 0,
  offsetTop = 0,
  viewportWidth = window.innerWidth,
  viewportHeight = window.innerHeight
}) => {
  const popoverRef = useRef23(null);
  useEffect30(() => {
    const container = popoverRef.current;
    if (!container) {
      return;
    }
    if (!container.contains(document.activeElement)) {
      container.focus();
    }
    const handleKeyDown = (event) => {
      if (event.key === KEYS.TAB) {
        const focusableElements = queryFocusableElements(container);
        const { activeElement } = document;
        const currentIndex = focusableElements.findIndex(
          (element) => element === activeElement
        );
        if (activeElement === container) {
          if (event.shiftKey) {
            focusableElements[focusableElements.length - 1]?.focus();
          } else {
            focusableElements[0].focus();
          }
          event.preventDefault();
          event.stopImmediatePropagation();
        } else if (currentIndex === 0 && event.shiftKey) {
          focusableElements[focusableElements.length - 1]?.focus();
          event.preventDefault();
          event.stopImmediatePropagation();
        } else if (currentIndex === focusableElements.length - 1 && !event.shiftKey) {
          focusableElements[0]?.focus();
          event.preventDefault();
          event.stopImmediatePropagation();
        }
      }
    };
    container.addEventListener("keydown", handleKeyDown);
    return () => container.removeEventListener("keydown", handleKeyDown);
  }, []);
  const lastInitializedPosRef = useRef23(
    null
  );
  useLayoutEffect4(() => {
    if (fitInViewport && popoverRef.current && top != null && left != null) {
      const container = popoverRef.current;
      const { width, height } = container.getBoundingClientRect();
      if (lastInitializedPosRef.current?.top === top && lastInitializedPosRef.current?.left === left) {
        return;
      }
      lastInitializedPosRef.current = { top, left };
      if (width >= viewportWidth) {
        container.style.width = `${viewportWidth}px`;
        container.style.left = "0px";
        container.style.overflowX = "scroll";
      } else if (left + width - offsetLeft > viewportWidth) {
        container.style.left = `${viewportWidth - width - 10}px`;
      } else {
        container.style.left = `${left}px`;
      }
      if (height >= viewportHeight) {
        container.style.height = `${viewportHeight - 20}px`;
        container.style.top = "10px";
        container.style.overflowY = "scroll";
      } else if (top + height - offsetTop > viewportHeight) {
        container.style.top = `${viewportHeight - height}px`;
      } else {
        container.style.top = `${top}px`;
      }
    }
  }, [
    top,
    left,
    fitInViewport,
    viewportWidth,
    viewportHeight,
    offsetLeft,
    offsetTop
  ]);
  useEffect30(() => {
    if (onCloseRequest) {
      const handler = (event) => {
        if (!popoverRef.current?.contains(event.target)) {
          unstable_batchedUpdates(() => onCloseRequest(event));
        }
      };
      document.addEventListener("pointerdown", handler, false);
      return () => document.removeEventListener("pointerdown", handler, false);
    }
  }, [onCloseRequest]);
  return /* @__PURE__ */ jsx77("div", { className: "popover", ref: popoverRef, tabIndex: -1, children });
};

// components/ContextMenu.tsx
import React29 from "react";
import { jsx as jsx78, jsxs as jsxs42 } from "react/jsx-runtime";
var CONTEXT_MENU_SEPARATOR = "separator";
var ContextMenu = React29.memo(
  ({ actionManager, items, top, left, onClose }) => {
    const appState = useExcalidrawAppState();
    const elements = useExcalidrawElements();
    const filteredItems = items.reduce((acc, item) => {
      if (item && (item === CONTEXT_MENU_SEPARATOR || !item.predicate || item.predicate(
        elements,
        appState,
        actionManager.app.props,
        actionManager.app
      ))) {
        acc.push(item);
      }
      return acc;
    }, []);
    return /* @__PURE__ */ jsx78(
      Popover6,
      {
        onCloseRequest: () => {
          onClose();
        },
        top,
        left,
        fitInViewport: true,
        offsetLeft: appState.offsetLeft,
        offsetTop: appState.offsetTop,
        viewportWidth: appState.width,
        viewportHeight: appState.height,
        children: /* @__PURE__ */ jsx78(
          "ul",
          {
            className: "context-menu",
            onContextMenu: (event) => event.preventDefault(),
            children: filteredItems.map((item, idx) => {
              if (item === CONTEXT_MENU_SEPARATOR) {
                if (!filteredItems[idx - 1] || filteredItems[idx - 1] === CONTEXT_MENU_SEPARATOR) {
                  return null;
                }
                return /* @__PURE__ */ jsx78("hr", { className: "context-menu-item-separator" }, idx);
              }
              const actionName = item.name;
              let label = "";
              if (item.label) {
                if (typeof item.label === "function") {
                  label = t(
                    item.label(
                      elements,
                      appState,
                      actionManager.app
                    )
                  );
                } else {
                  label = t(item.label);
                }
              }
              return /* @__PURE__ */ jsx78(
                "li",
                {
                  "data-testid": actionName,
                  onClick: () => {
                    onClose(() => {
                      actionManager.executeAction(item, "contextMenu");
                    });
                  },
                  children: /* @__PURE__ */ jsxs42(
                    "button",
                    {
                      type: "button",
                      className: clsx33("context-menu-item", {
                        dangerous: actionName === "deleteSelectedElements",
                        checkmark: item.checked?.(appState)
                      }),
                      children: [
                        /* @__PURE__ */ jsx78("div", { className: "context-menu-item__label", children: label }),
                        /* @__PURE__ */ jsx78("kbd", { className: "context-menu-item__shortcut", children: actionName ? getShortcutFromShortcutName(actionName) : "" })
                      ]
                    }
                  )
                },
                idx
              );
            })
          }
        )
      }
    );
  }
);

// components/LayerUI.tsx
import clsx53 from "clsx";
import React40 from "react";

// components/ErrorDialog.tsx
import React30, { useState as useState25 } from "react";
import { Fragment as Fragment10, jsx as jsx79 } from "react/jsx-runtime";
var ErrorDialog = ({
  children,
  onClose
}) => {
  const [modalIsShown, setModalIsShown] = useState25(!!children);
  const { container: excalidrawContainer } = useExcalidrawContainer();
  const handleClose = React30.useCallback(() => {
    setModalIsShown(false);
    if (onClose) {
      onClose();
    }
    excalidrawContainer?.focus();
  }, [onClose, excalidrawContainer]);
  return /* @__PURE__ */ jsx79(Fragment10, { children: modalIsShown && /* @__PURE__ */ jsx79(
    Dialog,
    {
      size: "small",
      onCloseRequest: handleClose,
      title: t("errorDialog.title"),
      children: /* @__PURE__ */ jsx79("div", { style: { whiteSpace: "pre-wrap" }, children })
    }
  ) });
};

// components/ImageExportDialog.tsx
import { useEffect as useEffect31, useRef as useRef25, useState as useState28 } from "react";

// components/RadioGroup.tsx
import clsx34 from "clsx";
import { jsx as jsx80, jsxs as jsxs43 } from "react/jsx-runtime";
var RadioGroup = function({
  onChange,
  value,
  choices,
  name
}) {
  return /* @__PURE__ */ jsx80("div", { className: "RadioGroup", children: choices.map((choice) => /* @__PURE__ */ jsxs43(
    "div",
    {
      className: clsx34("RadioGroup__choice", {
        active: choice.value === value
      }),
      title: choice.ariaLabel,
      children: [
        /* @__PURE__ */ jsx80(
          "input",
          {
            name,
            type: "radio",
            checked: choice.value === value,
            onChange: () => onChange(choice.value),
            "aria-label": choice.ariaLabel
          }
        ),
        choice.label
      ]
    },
    String(choice.value)
  )) });
};

// components/Switch.tsx
import clsx35 from "clsx";
import { jsx as jsx81 } from "react/jsx-runtime";
var Switch = ({
  title,
  name,
  checked,
  onChange,
  disabled = false
}) => {
  return /* @__PURE__ */ jsx81("div", { className: clsx35("Switch", { toggled: checked, disabled }), children: /* @__PURE__ */ jsx81(
    "input",
    {
      name,
      id: name,
      title,
      type: "checkbox",
      checked,
      disabled,
      onChange: () => onChange(!checked),
      onKeyDown: (event) => {
        if (event.key === " ") {
          onChange(!checked);
        }
      }
    }
  ) });
};

// components/FilledButton.tsx
import { forwardRef as forwardRef4, useState as useState26 } from "react";
import clsx36 from "clsx";
import { jsx as jsx82, jsxs as jsxs44 } from "react/jsx-runtime";
var FilledButton = forwardRef4(
  ({
    children,
    icon,
    onClick,
    label,
    variant = "filled",
    color = "primary",
    size = "medium",
    fullWidth,
    className,
    status
  }, ref) => {
    const [isLoading, setIsLoading] = useState26(false);
    const _onClick = async (event) => {
      const ret = onClick?.(event);
      if (isPromiseLike(ret)) {
        const timer = window.setTimeout(() => {
          setIsLoading(true);
        }, 50);
        try {
          await ret;
        } catch (error) {
          if (!(error instanceof AbortError)) {
            throw error;
          } else {
            console.warn(error);
          }
        } finally {
          clearTimeout(timer);
          setIsLoading(false);
        }
      }
    };
    const _status = isLoading ? "loading" : status;
    color = _status === "success" ? "success" : color;
    return /* @__PURE__ */ jsx82(
      "button",
      {
        className: clsx36(
          "ExcButton",
          `ExcButton--color-${color}`,
          `ExcButton--variant-${variant}`,
          `ExcButton--size-${size}`,
          `ExcButton--status-${_status}`,
          { "ExcButton--fullWidth": fullWidth },
          className
        ),
        onClick: _onClick,
        type: "button",
        "aria-label": label,
        ref,
        disabled: _status === "loading" || _status === "success",
        children: /* @__PURE__ */ jsxs44("div", { className: "ExcButton__contents", children: [
          _status === "loading" ? /* @__PURE__ */ jsx82(Spinner_default, { className: "ExcButton__statusIcon" }) : _status === "success" && /* @__PURE__ */ jsx82("div", { className: "ExcButton__statusIcon", children: tablerCheckIcon }),
          icon && /* @__PURE__ */ jsx82("div", { className: "ExcButton__icon", "aria-hidden": true, children: icon }),
          variant !== "icon" && (children ?? label)
        ] })
      }
    );
  }
);

// hooks/useCopiedIndicator.ts
import { useCallback as useCallback11, useRef as useRef24, useState as useState27 } from "react";
var TIMEOUT = 2e3;
var useCopyStatus = () => {
  const [copyStatus, setCopyStatus] = useState27(null);
  const timeoutRef = useRef24(0);
  const onCopy = () => {
    clearTimeout(timeoutRef.current);
    setCopyStatus("success");
    timeoutRef.current = window.setTimeout(() => {
      setCopyStatus(null);
    }, TIMEOUT);
  };
  const resetCopyStatus = useCallback11(() => {
    setCopyStatus(null);
  }, []);
  return {
    copyStatus,
    resetCopyStatus,
    onCopy
  };
};

// components/ImageExportDialog.tsx
import { jsx as jsx83, jsxs as jsxs45 } from "react/jsx-runtime";
var supportsContextFilters = "filter" in document.createElement("canvas").getContext("2d");
var ErrorCanvasPreview = () => {
  return /* @__PURE__ */ jsxs45("div", { children: [
    /* @__PURE__ */ jsx83("h3", { children: t("canvasError.cannotShowPreview") }),
    /* @__PURE__ */ jsx83("p", { children: /* @__PURE__ */ jsx83("span", { children: t("canvasError.canvasTooBig") }) }),
    /* @__PURE__ */ jsxs45("em", { children: [
      "(",
      t("canvasError.canvasTooBigTip"),
      ")"
    ] })
  ] });
};
var ImageExportModal = ({
  appStateSnapshot,
  elementsSnapshot,
  files,
  actionManager,
  onExportImage,
  name
}) => {
  const hasSelection = isSomeElementSelected(
    elementsSnapshot,
    appStateSnapshot
  );
  const [projectName, setProjectName] = useState28(name);
  const [exportSelectionOnly, setExportSelectionOnly] = useState28(hasSelection);
  const [exportWithBackground, setExportWithBackground] = useState28(
    appStateSnapshot.exportBackground
  );
  const [exportDarkMode, setExportDarkMode] = useState28(
    appStateSnapshot.exportWithDarkMode
  );
  const [embedScene, setEmbedScene] = useState28(
    appStateSnapshot.exportEmbedScene
  );
  const [exportScale, setExportScale] = useState28(appStateSnapshot.exportScale);
  const previewRef = useRef25(null);
  const [renderError, setRenderError] = useState28(null);
  const { onCopy, copyStatus, resetCopyStatus } = useCopyStatus();
  useEffect31(() => {
    resetCopyStatus();
  }, [
    projectName,
    exportWithBackground,
    exportDarkMode,
    exportScale,
    embedScene,
    resetCopyStatus
  ]);
  const { exportedElements, exportingFrame } = prepareElementsForExport(
    elementsSnapshot,
    appStateSnapshot,
    exportSelectionOnly
  );
  useEffect31(() => {
    const previewNode = previewRef.current;
    if (!previewNode) {
      return;
    }
    const maxWidth = previewNode.offsetWidth;
    const maxHeight = previewNode.offsetHeight;
    if (!maxWidth) {
      return;
    }
    exportToCanvas2({
      elements: exportedElements,
      appState: {
        ...appStateSnapshot,
        name: projectName,
        exportBackground: exportWithBackground,
        exportWithDarkMode: exportDarkMode,
        exportScale,
        exportEmbedScene: embedScene
      },
      files,
      exportPadding: DEFAULT_EXPORT_PADDING,
      maxWidthOrHeight: Math.max(maxWidth, maxHeight),
      exportingFrame
    }).then((canvas) => {
      setRenderError(null);
      return canvasToBlob(canvas).then(() => {
        previewNode.replaceChildren(canvas);
      }).catch((e) => {
        if (e.name === "CANVAS_POSSIBLY_TOO_BIG") {
          throw new Error(t("canvasError.canvasTooBig"));
        }
        throw e;
      });
    }).catch((error) => {
      console.error(error);
      setRenderError(error);
    });
  }, [
    appStateSnapshot,
    files,
    exportedElements,
    exportingFrame,
    projectName,
    exportWithBackground,
    exportDarkMode,
    exportScale,
    embedScene
  ]);
  return /* @__PURE__ */ jsxs45("div", { className: "ImageExportModal", children: [
    /* @__PURE__ */ jsx83("h3", { children: t("imageExportDialog.header") }),
    /* @__PURE__ */ jsxs45("div", { className: "ImageExportModal__preview", children: [
      /* @__PURE__ */ jsx83("div", { className: "ImageExportModal__preview__canvas", ref: previewRef, children: renderError && /* @__PURE__ */ jsx83(ErrorCanvasPreview, {}) }),
      /* @__PURE__ */ jsx83("div", { className: "ImageExportModal__preview__filename", children: !nativeFileSystemSupported && /* @__PURE__ */ jsx83(
        "input",
        {
          type: "text",
          className: "TextInput",
          value: projectName,
          style: { width: "30ch" },
          onChange: (event) => {
            setProjectName(event.target.value);
            actionManager.executeAction(
              actionChangeProjectName,
              "ui",
              event.target.value
            );
          }
        }
      ) })
    ] }),
    /* @__PURE__ */ jsxs45("div", { className: "ImageExportModal__settings", children: [
      /* @__PURE__ */ jsx83("h3", { children: t("imageExportDialog.header") }),
      hasSelection && /* @__PURE__ */ jsx83(
        ExportSetting,
        {
          label: t("imageExportDialog.label.onlySelected"),
          name: "exportOnlySelected",
          children: /* @__PURE__ */ jsx83(
            Switch,
            {
              name: "exportOnlySelected",
              checked: exportSelectionOnly,
              onChange: (checked) => {
                setExportSelectionOnly(checked);
              }
            }
          )
        }
      ),
      /* @__PURE__ */ jsx83(
        ExportSetting,
        {
          label: t("imageExportDialog.label.withBackground"),
          name: "exportBackgroundSwitch",
          children: /* @__PURE__ */ jsx83(
            Switch,
            {
              name: "exportBackgroundSwitch",
              checked: exportWithBackground,
              onChange: (checked) => {
                setExportWithBackground(checked);
                actionManager.executeAction(
                  actionChangeExportBackground,
                  "ui",
                  checked
                );
              }
            }
          )
        }
      ),
      supportsContextFilters && /* @__PURE__ */ jsx83(
        ExportSetting,
        {
          label: t("imageExportDialog.label.darkMode"),
          name: "exportDarkModeSwitch",
          children: /* @__PURE__ */ jsx83(
            Switch,
            {
              name: "exportDarkModeSwitch",
              checked: exportDarkMode,
              onChange: (checked) => {
                setExportDarkMode(checked);
                actionManager.executeAction(
                  actionExportWithDarkMode,
                  "ui",
                  checked
                );
              }
            }
          )
        }
      ),
      /* @__PURE__ */ jsx83(
        ExportSetting,
        {
          label: t("imageExportDialog.label.embedScene"),
          tooltip: t("imageExportDialog.tooltip.embedScene"),
          name: "exportEmbedSwitch",
          children: /* @__PURE__ */ jsx83(
            Switch,
            {
              name: "exportEmbedSwitch",
              checked: embedScene,
              onChange: (checked) => {
                setEmbedScene(checked);
                actionManager.executeAction(
                  actionChangeExportEmbedScene,
                  "ui",
                  checked
                );
              }
            }
          )
        }
      ),
      /* @__PURE__ */ jsx83(
        ExportSetting,
        {
          label: t("imageExportDialog.label.scale"),
          name: "exportScale",
          children: /* @__PURE__ */ jsx83(
            RadioGroup,
            {
              name: "exportScale",
              value: exportScale,
              onChange: (scale) => {
                setExportScale(scale);
                actionManager.executeAction(actionChangeExportScale, "ui", scale);
              },
              choices: EXPORT_SCALES.map((scale) => ({
                value: scale,
                label: `${scale}\xD7`
              }))
            }
          )
        }
      ),
      /* @__PURE__ */ jsxs45("div", { className: "ImageExportModal__settings__buttons", children: [
        /* @__PURE__ */ jsx83(
          FilledButton,
          {
            className: "ImageExportModal__settings__buttons__button",
            label: t("imageExportDialog.title.exportToPng"),
            onClick: () => onExportImage(EXPORT_IMAGE_TYPES.png, exportedElements, {
              exportingFrame
            }),
            icon: downloadIcon,
            children: t("imageExportDialog.button.exportToPng")
          }
        ),
        /* @__PURE__ */ jsx83(
          FilledButton,
          {
            className: "ImageExportModal__settings__buttons__button",
            label: t("imageExportDialog.title.exportToSvg"),
            onClick: () => onExportImage(EXPORT_IMAGE_TYPES.svg, exportedElements, {
              exportingFrame
            }),
            icon: downloadIcon,
            children: t("imageExportDialog.button.exportToSvg")
          }
        ),
        (probablySupportsClipboardBlob || isFirefox) && /* @__PURE__ */ jsx83(
          FilledButton,
          {
            className: "ImageExportModal__settings__buttons__button",
            label: t("imageExportDialog.title.copyPngToClipboard"),
            status: copyStatus,
            onClick: async () => {
              await onExportImage(
                EXPORT_IMAGE_TYPES.clipboard,
                exportedElements,
                {
                  exportingFrame
                }
              );
              onCopy();
            },
            icon: copyIcon,
            children: t("imageExportDialog.button.copyPngToClipboard")
          }
        )
      ] })
    ] })
  ] });
};
var ExportSetting = ({
  label,
  children,
  tooltip,
  name
}) => {
  return /* @__PURE__ */ jsxs45("div", { className: "ImageExportModal__settings__setting", title: label, children: [
    /* @__PURE__ */ jsxs45(
      "label",
      {
        htmlFor: name,
        className: "ImageExportModal__settings__setting__label",
        children: [
          label,
          tooltip && /* @__PURE__ */ jsx83(Tooltip, { label: tooltip, long: true, children: helpIcon })
        ]
      }
    ),
    /* @__PURE__ */ jsx83("div", { className: "ImageExportModal__settings__setting__content", children })
  ] });
};
var ImageExportDialog = ({
  elements,
  appState,
  files,
  actionManager,
  onExportImage,
  onCloseRequest,
  name
}) => {
  const [{ appStateSnapshot, elementsSnapshot }] = useState28(() => {
    return {
      appStateSnapshot: cloneJSON(appState),
      elementsSnapshot: cloneJSON(elements)
    };
  });
  return /* @__PURE__ */ jsx83(Dialog, { onCloseRequest, size: "wide", title: false, children: /* @__PURE__ */ jsx83(
    ImageExportModal,
    {
      elementsSnapshot,
      appStateSnapshot,
      files,
      actionManager,
      onExportImage,
      name
    }
  ) });
};

// components/FixedSideContainer.tsx
import clsx37 from "clsx";
import { jsx as jsx84 } from "react/jsx-runtime";
var FixedSideContainer = ({
  children,
  side,
  className
}) => /* @__PURE__ */ jsx84(
  "div",
  {
    className: clsx37(
      "FixedSideContainer",
      `FixedSideContainer_side_${side}`,
      className
    ),
    children
  }
);

// element/flowchart.ts
var VERTICAL_OFFSET = 100;
var HORIZONTAL_OFFSET = 100;
var getLinkDirectionFromKey = (key) => {
  switch (key) {
    case KEYS.ARROW_UP:
      return "up";
    case KEYS.ARROW_DOWN:
      return "down";
    case KEYS.ARROW_RIGHT:
      return "right";
    case KEYS.ARROW_LEFT:
      return "left";
    default:
      return "right";
  }
};
var getNodeRelatives = (type, node, elementsMap, direction) => {
  const items = [...elementsMap.values()].reduce(
    (acc, el) => {
      let oppositeBinding;
      if (isElbowArrow(el) && // we want check existence of the opposite binding, in the direction
      // we're interested in
      (oppositeBinding = el[type === "predecessors" ? "startBinding" : "endBinding"]) && // similarly, we need to filter only arrows bound to target node
      el[type === "predecessors" ? "endBinding" : "startBinding"]?.elementId === node.id) {
        const relative = elementsMap.get(oppositeBinding.elementId);
        if (!relative) {
          return acc;
        }
        invariant(
          isBindableElement(relative),
          "not an ExcalidrawBindableElement"
        );
        const edgePoint = type === "predecessors" ? el.points[el.points.length - 1] : [0, 0];
        const heading = headingForPointFromElement(node, aabbForElement(node), [
          edgePoint[0] + el.x,
          edgePoint[1] + el.y
        ]);
        acc.push({
          relative,
          heading
        });
      }
      return acc;
    },
    []
  );
  switch (direction) {
    case "up":
      return items.filter((item) => compareHeading(item.heading, HEADING_UP)).map((item) => item.relative);
    case "down":
      return items.filter((item) => compareHeading(item.heading, HEADING_DOWN)).map((item) => item.relative);
    case "right":
      return items.filter((item) => compareHeading(item.heading, HEADING_RIGHT)).map((item) => item.relative);
    case "left":
      return items.filter((item) => compareHeading(item.heading, HEADING_LEFT)).map((item) => item.relative);
  }
};
var getSuccessors = (node, elementsMap, direction) => {
  return getNodeRelatives("successors", node, elementsMap, direction);
};
var getPredecessors = (node, elementsMap, direction) => {
  return getNodeRelatives("predecessors", node, elementsMap, direction);
};
var getOffsets = (element, linkedNodes, direction) => {
  const _HORIZONTAL_OFFSET = HORIZONTAL_OFFSET + element.width;
  if (direction === "up" || direction === "down") {
    const _VERTICAL_OFFSET2 = VERTICAL_OFFSET + element.height;
    const minX = element.x;
    const maxX = element.x + element.width;
    if (linkedNodes.every(
      (linkedNode) => linkedNode.x + linkedNode.width < minX || linkedNode.x > maxX
    )) {
      return {
        x: 0,
        y: _VERTICAL_OFFSET2 * (direction === "up" ? -1 : 1)
      };
    }
  } else if (direction === "right" || direction === "left") {
    const minY = element.y;
    const maxY = element.y + element.height;
    if (linkedNodes.every(
      (linkedNode) => linkedNode.y + linkedNode.height < minY || linkedNode.y > maxY
    )) {
      return {
        x: (HORIZONTAL_OFFSET + element.width) * (direction === "left" ? -1 : 1),
        y: 0
      };
    }
  }
  if (direction === "up" || direction === "down") {
    const _VERTICAL_OFFSET2 = VERTICAL_OFFSET + element.height;
    const y2 = linkedNodes.length === 0 ? _VERTICAL_OFFSET2 : _VERTICAL_OFFSET2;
    const x2 = linkedNodes.length === 0 ? 0 : (linkedNodes.length + 1) % 2 === 0 ? (linkedNodes.length + 1) / 2 * _HORIZONTAL_OFFSET : linkedNodes.length / 2 * _HORIZONTAL_OFFSET * -1;
    if (direction === "up") {
      return {
        x: x2,
        y: y2 * -1
      };
    }
    return {
      x: x2,
      y: y2
    };
  }
  const _VERTICAL_OFFSET = VERTICAL_OFFSET + element.height;
  const x = (linkedNodes.length === 0 ? HORIZONTAL_OFFSET : HORIZONTAL_OFFSET) + element.width;
  const y = linkedNodes.length === 0 ? 0 : (linkedNodes.length + 1) % 2 === 0 ? (linkedNodes.length + 1) / 2 * _VERTICAL_OFFSET : linkedNodes.length / 2 * _VERTICAL_OFFSET * -1;
  if (direction === "left") {
    return {
      x: x * -1,
      y
    };
  }
  return {
    x,
    y
  };
};
var addNewNode = (element, elementsMap, appState, direction) => {
  const successors = getSuccessors(element, elementsMap, direction);
  const predeccessors = getPredecessors(element, elementsMap, direction);
  const offsets = getOffsets(
    element,
    [...successors, ...predeccessors],
    direction
  );
  const nextNode = newElement({
    type: element.type,
    x: element.x + offsets.x,
    y: element.y + offsets.y,
    // TODO: extract this to a util
    width: element.width,
    height: element.height,
    roundness: element.roundness,
    roughness: element.roughness,
    backgroundColor: element.backgroundColor,
    strokeColor: element.strokeColor,
    strokeWidth: element.strokeWidth,
    opacity: element.opacity,
    fillStyle: element.fillStyle,
    strokeStyle: element.strokeStyle
  });
  invariant(
    isFlowchartNodeElement(nextNode),
    "not an ExcalidrawFlowchartNodeElement"
  );
  const bindingArrow = createBindingArrow(
    element,
    nextNode,
    elementsMap,
    direction,
    appState
  );
  return {
    nextNode,
    bindingArrow
  };
};
var addNewNodes = (startNode, elementsMap, appState, direction, numberOfNodes) => {
  const newNodes = [];
  for (let i = 0; i < numberOfNodes; i++) {
    let nextX;
    let nextY;
    if (direction === "left" || direction === "right") {
      const totalHeight = VERTICAL_OFFSET * (numberOfNodes - 1) + numberOfNodes * startNode.height;
      const startY = startNode.y + startNode.height / 2 - totalHeight / 2;
      let offsetX = HORIZONTAL_OFFSET + startNode.width;
      if (direction === "left") {
        offsetX *= -1;
      }
      nextX = startNode.x + offsetX;
      const offsetY = (VERTICAL_OFFSET + startNode.height) * i;
      nextY = startY + offsetY;
    } else {
      const totalWidth = HORIZONTAL_OFFSET * (numberOfNodes - 1) + numberOfNodes * startNode.width;
      const startX = startNode.x + startNode.width / 2 - totalWidth / 2;
      let offsetY = VERTICAL_OFFSET + startNode.height;
      if (direction === "up") {
        offsetY *= -1;
      }
      nextY = startNode.y + offsetY;
      const offsetX = (HORIZONTAL_OFFSET + startNode.width) * i;
      nextX = startX + offsetX;
    }
    const nextNode = newElement({
      type: startNode.type,
      x: nextX,
      y: nextY,
      // TODO: extract this to a util
      width: startNode.width,
      height: startNode.height,
      roundness: startNode.roundness,
      roughness: startNode.roughness,
      backgroundColor: startNode.backgroundColor,
      strokeColor: startNode.strokeColor,
      strokeWidth: startNode.strokeWidth,
      opacity: startNode.opacity,
      fillStyle: startNode.fillStyle,
      strokeStyle: startNode.strokeStyle
    });
    invariant(
      isFlowchartNodeElement(nextNode),
      "not an ExcalidrawFlowchartNodeElement"
    );
    const bindingArrow = createBindingArrow(
      startNode,
      nextNode,
      elementsMap,
      direction,
      appState
    );
    newNodes.push(nextNode);
    newNodes.push(bindingArrow);
  }
  return newNodes;
};
var createBindingArrow = (startBindingElement, endBindingElement, elementsMap, direction, appState) => {
  let startX;
  let startY;
  const PADDING = 6;
  switch (direction) {
    case "up": {
      startX = startBindingElement.x + startBindingElement.width / 2;
      startY = startBindingElement.y - PADDING;
      break;
    }
    case "down": {
      startX = startBindingElement.x + startBindingElement.width / 2;
      startY = startBindingElement.y + startBindingElement.height + PADDING;
      break;
    }
    case "right": {
      startX = startBindingElement.x + startBindingElement.width + PADDING;
      startY = startBindingElement.y + startBindingElement.height / 2;
      break;
    }
    case "left": {
      startX = startBindingElement.x - PADDING;
      startY = startBindingElement.y + startBindingElement.height / 2;
      break;
    }
  }
  let endX;
  let endY;
  switch (direction) {
    case "up": {
      endX = endBindingElement.x + endBindingElement.width / 2 - startX;
      endY = endBindingElement.y + endBindingElement.height - startY + PADDING;
      break;
    }
    case "down": {
      endX = endBindingElement.x + endBindingElement.width / 2 - startX;
      endY = endBindingElement.y - startY - PADDING;
      break;
    }
    case "right": {
      endX = endBindingElement.x - startX - PADDING;
      endY = endBindingElement.y - startY + endBindingElement.height / 2;
      break;
    }
    case "left": {
      endX = endBindingElement.x + endBindingElement.width - startX + PADDING;
      endY = endBindingElement.y - startY + endBindingElement.height / 2;
      break;
    }
  }
  const bindingArrow = newArrowElement({
    type: "arrow",
    x: startX,
    y: startY,
    startArrowhead: null,
    endArrowhead: appState.currentItemEndArrowhead,
    strokeColor: startBindingElement.strokeColor,
    strokeStyle: startBindingElement.strokeStyle,
    strokeWidth: startBindingElement.strokeWidth,
    opacity: startBindingElement.opacity,
    roughness: startBindingElement.roughness,
    points: [pointFrom(0, 0), pointFrom(endX, endY)],
    elbowed: true
  });
  bindLinearElement(
    bindingArrow,
    startBindingElement,
    "start",
    elementsMap
  );
  bindLinearElement(
    bindingArrow,
    endBindingElement,
    "end",
    elementsMap
  );
  const changedElements = /* @__PURE__ */ new Map();
  changedElements.set(
    startBindingElement.id,
    startBindingElement
  );
  changedElements.set(
    endBindingElement.id,
    endBindingElement
  );
  changedElements.set(
    bindingArrow.id,
    bindingArrow
  );
  LinearElementEditor.movePoints(bindingArrow, [
    {
      index: 1,
      point: bindingArrow.points[1]
    }
  ]);
  const update = updateElbowArrowPoints(
    bindingArrow,
    toBrandedType(
      new Map([
        ...elementsMap.entries(),
        [startBindingElement.id, startBindingElement],
        [endBindingElement.id, endBindingElement],
        [bindingArrow.id, bindingArrow]
      ])
    ),
    { points: bindingArrow.points }
  );
  return {
    ...bindingArrow,
    ...update
  };
};
var FlowChartNavigator = class {
  constructor() {
    __publicField(this, "isExploring", false);
    // nodes that are ONE link away (successor and predecessor both included)
    __publicField(this, "sameLevelNodes", []);
    __publicField(this, "sameLevelIndex", 0);
    // set it to the opposite of the defalut creation direction
    __publicField(this, "direction", null);
    // for speedier navigation
    __publicField(this, "visitedNodes", /* @__PURE__ */ new Set());
  }
  clear() {
    this.isExploring = false;
    this.sameLevelNodes = [];
    this.sameLevelIndex = 0;
    this.direction = null;
    this.visitedNodes.clear();
  }
  exploreByDirection(element, elementsMap, direction) {
    if (!isBindableElement(element)) {
      return null;
    }
    if (direction !== this.direction) {
      this.clear();
    }
    if (!this.visitedNodes.has(element.id)) {
      this.visitedNodes.add(element.id);
    }
    if (this.isExploring && direction === this.direction && this.sameLevelNodes.length > 1) {
      this.sameLevelIndex = (this.sameLevelIndex + 1) % this.sameLevelNodes.length;
      return this.sameLevelNodes[this.sameLevelIndex].id;
    }
    const nodes = [
      ...getSuccessors(element, elementsMap, direction),
      ...getPredecessors(element, elementsMap, direction)
    ];
    if (nodes.length > 0) {
      this.sameLevelIndex = 0;
      this.isExploring = true;
      this.sameLevelNodes = nodes;
      this.direction = direction;
      this.visitedNodes.add(nodes[0].id);
      return nodes[0].id;
    }
    if (direction === this.direction || !this.isExploring) {
      if (!this.isExploring) {
        this.visitedNodes.add(element.id);
      }
      const otherDirections = [
        "up",
        "right",
        "down",
        "left"
      ].filter((dir) => dir !== direction);
      const otherLinkedNodes = otherDirections.map((dir) => [
        ...getSuccessors(element, elementsMap, dir),
        ...getPredecessors(element, elementsMap, dir)
      ]).flat().filter((linkedNode) => !this.visitedNodes.has(linkedNode.id));
      for (const linkedNode of otherLinkedNodes) {
        if (!this.visitedNodes.has(linkedNode.id)) {
          this.visitedNodes.add(linkedNode.id);
          this.isExploring = true;
          this.direction = direction;
          return linkedNode.id;
        }
      }
    }
    return null;
  }
};
var FlowChartCreator = class {
  constructor() {
    __publicField(this, "isCreatingChart", false);
    __publicField(this, "numberOfNodes", 0);
    __publicField(this, "direction", "right");
    __publicField(this, "pendingNodes", null);
  }
  createNodes(startNode, elementsMap, appState, direction) {
    if (direction !== this.direction) {
      const { nextNode, bindingArrow } = addNewNode(
        startNode,
        elementsMap,
        appState,
        direction
      );
      this.numberOfNodes = 1;
      this.isCreatingChart = true;
      this.direction = direction;
      this.pendingNodes = [nextNode, bindingArrow];
    } else {
      this.numberOfNodes += 1;
      const newNodes = addNewNodes(
        startNode,
        elementsMap,
        appState,
        direction,
        this.numberOfNodes
      );
      this.isCreatingChart = true;
      this.direction = direction;
      this.pendingNodes = newNodes;
    }
    if (startNode.frameId) {
      const frame = elementsMap.get(startNode.frameId);
      invariant(
        frame && isFrameElement(frame),
        "not an ExcalidrawFrameElement"
      );
      if (frame && this.pendingNodes.every(
        (node) => elementsAreInFrameBounds([node], frame, elementsMap) || elementOverlapsWithFrame(node, frame, elementsMap)
      )) {
        this.pendingNodes = this.pendingNodes.map(
          (node) => mutateElement(
            node,
            {
              frameId: startNode.frameId
            },
            false
          )
        );
      }
    }
  }
  clear() {
    this.isCreatingChart = false;
    this.pendingNodes = null;
    this.direction = null;
    this.numberOfNodes = 0;
  }
};
var isNodeInFlowchart = (element, elementsMap) => {
  for (const [, el] of elementsMap) {
    if (el.type === "arrow" && (el.startBinding?.elementId === element.id || el.endBinding?.elementId === element.id)) {
      return true;
    }
  }
  return false;
};

// components/HintViewer.tsx
import { jsx as jsx85 } from "react/jsx-runtime";
var getHints = ({
  appState,
  isMobile,
  device,
  app
}) => {
  const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
  const multiMode = appState.multiElement !== null;
  if (appState.openSidebar?.name === DEFAULT_SIDEBAR.name && appState.openSidebar.tab === CANVAS_SEARCH_TAB && appState.searchMatches?.length) {
    return t("hints.dismissSearch");
  }
  if (appState.openSidebar && !device.editor.canFitSidebar) {
    return null;
  }
  if (isEraserActive(appState)) {
    return t("hints.eraserRevert");
  }
  if (activeTool.type === "arrow" || activeTool.type === "line") {
    if (multiMode) {
      return t("hints.linearElementMulti");
    }
    if (activeTool.type === "arrow") {
      return t("hints.arrowTool", { arrowShortcut: getShortcutKey("A") });
    }
    return t("hints.linearElement");
  }
  if (activeTool.type === "freedraw") {
    return t("hints.freeDraw");
  }
  if (activeTool.type === "text") {
    return t("hints.text");
  }
  if (activeTool.type === "embeddable") {
    return t("hints.embeddable");
  }
  if (appState.activeTool.type === "image" && appState.pendingImageElementId) {
    return t("hints.placeImage");
  }
  const selectedElements = app.scene.getSelectedElements(appState);
  if (isResizing && lastPointerDownWith === "mouse" && selectedElements.length === 1) {
    const targetElement = selectedElements[0];
    if (isLinearElement(targetElement) && targetElement.points.length === 2) {
      return t("hints.lockAngle");
    }
    return isImageElement(targetElement) ? t("hints.resizeImage") : t("hints.resize");
  }
  if (isRotating && lastPointerDownWith === "mouse") {
    return t("hints.rotate");
  }
  if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
    return t("hints.text_selected");
  }
  if (appState.editingTextElement) {
    return t("hints.text_editing");
  }
  if (appState.croppingElementId) {
    return t("hints.leaveCropEditor");
  }
  if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
    return t("hints.enterCropEditor");
  }
  if (activeTool.type === "selection") {
    if (appState.selectionElement && !selectedElements.length && !appState.editingTextElement && !appState.editingLinearElement) {
      return t("hints.deepBoxSelect");
    }
    if (isGridModeEnabled(app) && appState.selectedElementsAreBeingDragged) {
      return t("hints.disableSnapping");
    }
    if (!selectedElements.length && !isMobile) {
      return t("hints.canvasPanning");
    }
    if (selectedElements.length === 1) {
      if (isLinearElement(selectedElements[0])) {
        if (appState.editingLinearElement) {
          return appState.editingLinearElement.selectedPointsIndices ? t("hints.lineEditor_pointSelected") : t("hints.lineEditor_nothingSelected");
        }
        return t("hints.lineEditor_info");
      }
      if (!appState.newElement && !appState.selectedElementsAreBeingDragged && isTextBindableContainer(selectedElements[0])) {
        if (isFlowchartNodeElement(selectedElements[0])) {
          if (isNodeInFlowchart(
            selectedElements[0],
            app.scene.getNonDeletedElementsMap()
          )) {
            return [t("hints.bindTextToElement"), t("hints.createFlowchart")];
          }
          return [t("hints.bindTextToElement"), t("hints.createFlowchart")];
        }
        return t("hints.bindTextToElement");
      }
    }
  }
  return null;
};
var HintViewer = ({
  appState,
  isMobile,
  device,
  app
}) => {
  const hints = getHints({
    appState,
    isMobile,
    device,
    app
  });
  if (!hints) {
    return null;
  }
  const hint = Array.isArray(hints) ? hints.map((hint2) => {
    return getShortcutKey(hint2).replace(/\. ?$/, "");
  }).join(". ") : getShortcutKey(hints);
  return /* @__PURE__ */ jsx85("div", { className: "HintViewer", children: /* @__PURE__ */ jsx85("span", { children: hint }) });
};

// components/LockButton.tsx
import clsx38 from "clsx";
import { jsx as jsx86, jsxs as jsxs46 } from "react/jsx-runtime";
var DEFAULT_SIZE = "medium";
var ICONS2 = {
  CHECKED: LockedIcon,
  UNCHECKED: UnlockedIcon
};
var LockButton = (props) => {
  return /* @__PURE__ */ jsxs46(
    "label",
    {
      className: clsx38(
        "ToolIcon ToolIcon__lock",
        `ToolIcon_size_${DEFAULT_SIZE}`,
        {
          "is-mobile": props.isMobile
        }
      ),
      title: `${props.title} \u2014 Q`,
      children: [
        /* @__PURE__ */ jsx86(
          "input",
          {
            className: "ToolIcon_type_checkbox",
            type: "checkbox",
            name: props.name,
            onChange: props.onChange,
            checked: props.checked,
            "aria-label": props.title,
            "data-testid": "toolbar-lock"
          }
        ),
        /* @__PURE__ */ jsx86("div", { className: "ToolIcon__icon", children: props.checked ? ICONS2.CHECKED : ICONS2.UNCHECKED })
      ]
    }
  );
};

// components/Section.tsx
import { Fragment as Fragment11, jsx as jsx87, jsxs as jsxs47 } from "react/jsx-runtime";
var Section = ({ heading, children, ...props }) => {
  const { id } = useExcalidrawContainer();
  const header = /* @__PURE__ */ jsx87("h2", { className: "visually-hidden", id: `${id}-${heading}-title`, children: t(`headings.${heading}`) });
  return /* @__PURE__ */ jsx87("section", { ...props, "aria-labelledby": `${id}-${heading}-title`, children: typeof children === "function" ? children(header) : /* @__PURE__ */ jsxs47(Fragment11, { children: [
    header,
    children
  ] }) });
};

// scene/scrollbars.ts
var SCROLLBAR_MARGIN = 4;
var SCROLLBAR_WIDTH = 6;
var SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
var getScrollBars = (elements, viewportWidth, viewportHeight, appState) => {
  if (!elements.length) {
    return {
      horizontal: null,
      vertical: null
    };
  }
  const [elementsMinX, elementsMinY, elementsMaxX, elementsMaxY] = getCommonBounds(elements);
  const viewportWidthWithZoom = viewportWidth / appState.zoom.value;
  const viewportHeightWithZoom = viewportHeight / appState.zoom.value;
  const viewportWidthDiff = viewportWidth - viewportWidthWithZoom;
  const viewportHeightDiff = viewportHeight - viewportHeightWithZoom;
  const safeArea = {
    top: parseInt(getGlobalCSSVariable("sat")) || 0,
    bottom: parseInt(getGlobalCSSVariable("sab")) || 0,
    left: parseInt(getGlobalCSSVariable("sal")) || 0,
    right: parseInt(getGlobalCSSVariable("sar")) || 0
  };
  const isRTL = getLanguage().rtl;
  const viewportMinX = -appState.scrollX + viewportWidthDiff / 2 + safeArea.left;
  const viewportMinY = -appState.scrollY + viewportHeightDiff / 2 + safeArea.top;
  const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right;
  const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom;
  const sceneMinX = Math.min(elementsMinX, viewportMinX);
  const sceneMinY = Math.min(elementsMinY, viewportMinY);
  const sceneMaxX = Math.max(elementsMaxX, viewportMaxX);
  const sceneMaxY = Math.max(elementsMaxY, viewportMaxY);
  return {
    horizontal: viewportMinX === sceneMinX && viewportMaxX === sceneMaxX ? null : {
      x: Math.max(safeArea.left, SCROLLBAR_MARGIN) + (viewportMinX - sceneMinX) / (sceneMaxX - sceneMinX) * viewportWidth,
      y: viewportHeight - SCROLLBAR_WIDTH - Math.max(SCROLLBAR_MARGIN, safeArea.bottom),
      width: (viewportMaxX - viewportMinX) / (sceneMaxX - sceneMinX) * viewportWidth - Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right),
      height: SCROLLBAR_WIDTH
    },
    vertical: viewportMinY === sceneMinY && viewportMaxY === sceneMaxY ? null : {
      x: isRTL ? Math.max(safeArea.left, SCROLLBAR_MARGIN) : viewportWidth - SCROLLBAR_WIDTH - Math.max(safeArea.right, SCROLLBAR_MARGIN),
      y: (viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY) * viewportHeight + Math.max(safeArea.top, SCROLLBAR_MARGIN),
      width: SCROLLBAR_WIDTH,
      height: (viewportMaxY - viewportMinY) / (sceneMaxY - sceneMinY) * viewportHeight - Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom)
    }
  };
};
var isOverScrollBars = (scrollBars, x, y) => {
  const [isOverHorizontal, isOverVertical] = [
    scrollBars.horizontal,
    scrollBars.vertical
  ].map((scrollBar) => {
    return scrollBar != null && scrollBar.x <= x && x <= scrollBar.x + scrollBar.width && scrollBar.y <= y && y <= scrollBar.y + scrollBar.height;
  });
  const isOverEither = isOverHorizontal || isOverVertical;
  return { isOverEither, isOverHorizontal, isOverVertical };
};

// components/PenModeButton.tsx
import clsx39 from "clsx";
import { jsx as jsx88, jsxs as jsxs48 } from "react/jsx-runtime";
var DEFAULT_SIZE2 = "medium";
var PenModeButton = (props) => {
  if (!props.penDetected) {
    return null;
  }
  return /* @__PURE__ */ jsxs48(
    "label",
    {
      className: clsx39(
        "ToolIcon ToolIcon__penMode",
        `ToolIcon_size_${DEFAULT_SIZE2}`,
        {
          "is-mobile": props.isMobile
        }
      ),
      title: `${props.title}`,
      children: [
        /* @__PURE__ */ jsx88(
          "input",
          {
            className: "ToolIcon_type_checkbox",
            type: "checkbox",
            name: props.name,
            onChange: props.onChange,
            checked: props.checked,
            "aria-label": props.title
          }
        ),
        /* @__PURE__ */ jsx88("div", { className: "ToolIcon__icon", children: PenModeIcon })
      ]
    }
  );
};

// components/HandButton.tsx
import clsx40 from "clsx";
import { jsx as jsx89 } from "react/jsx-runtime";
var HandButton = (props) => {
  return /* @__PURE__ */ jsx89(
    ToolButton,
    {
      className: clsx40("Shape", { fillable: false }),
      type: "radio",
      icon: handIcon,
      name: "editor-current-shape",
      checked: props.checked,
      title: `${props.title} \u2014 H`,
      keyBindingLabel: !props.isMobile ? KEYS.H.toLocaleUpperCase() : void 0,
      "aria-label": `${props.title} \u2014 H`,
      "aria-keyshortcuts": KEYS.H,
      "data-testid": `toolbar-hand`,
      onChange: () => props.onChange?.()
    }
  );
};

// components/MobileMenu.tsx
import { Fragment as Fragment12, jsx as jsx90, jsxs as jsxs49 } from "react/jsx-runtime";
var MobileMenu = ({
  appState,
  elements,
  actionManager,
  setAppState,
  onLockToggle,
  onHandToolToggle,
  onPenModeToggle,
  renderTopRightUI,
  renderCustomStats,
  renderSidebars,
  device,
  renderWelcomeScreen,
  UIOptions,
  app
}) => {
  const {
    WelcomeScreenCenterTunnel,
    MainMenuTunnel,
    DefaultSidebarTriggerTunnel
  } = useTunnels();
  const renderToolbar = () => {
    return /* @__PURE__ */ jsxs49(FixedSideContainer, { side: "top", className: "App-top-bar", children: [
      renderWelcomeScreen && /* @__PURE__ */ jsx90(WelcomeScreenCenterTunnel.Out, {}),
      /* @__PURE__ */ jsx90(Section, { heading: "shapes", children: (heading) => /* @__PURE__ */ jsx90(Stack_default.Col, { gap: 4, align: "center", children: /* @__PURE__ */ jsxs49(Stack_default.Row, { gap: 1, className: "App-toolbar-container", children: [
        /* @__PURE__ */ jsxs49(Island, { padding: 1, className: "App-toolbar App-toolbar--mobile", children: [
          heading,
          /* @__PURE__ */ jsx90(Stack_default.Row, { gap: 1, children: /* @__PURE__ */ jsx90(
            ShapesSwitcher,
            {
              appState,
              activeTool: appState.activeTool,
              UIOptions,
              app
            }
          ) })
        ] }),
        renderTopRightUI && renderTopRightUI(true, appState),
        /* @__PURE__ */ jsxs49("div", { className: "mobile-misc-tools-container", children: [
          !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector" && /* @__PURE__ */ jsx90(DefaultSidebarTriggerTunnel.Out, {}),
          /* @__PURE__ */ jsx90(
            PenModeButton,
            {
              checked: appState.penMode,
              onChange: () => onPenModeToggle(null),
              title: t("toolBar.penMode"),
              isMobile: true,
              penDetected: appState.penDetected
            }
          ),
          /* @__PURE__ */ jsx90(
            LockButton,
            {
              checked: appState.activeTool.locked,
              onChange: onLockToggle,
              title: t("toolBar.lock"),
              isMobile: true
            }
          ),
          /* @__PURE__ */ jsx90(
            HandButton,
            {
              checked: isHandToolActive(appState),
              onChange: () => onHandToolToggle(),
              title: t("toolBar.hand"),
              isMobile: true
            }
          )
        ] })
      ] }) }) }),
      /* @__PURE__ */ jsx90(
        HintViewer,
        {
          appState,
          isMobile: true,
          device,
          app
        }
      )
    ] });
  };
  const renderAppToolbar = () => {
    if (appState.viewModeEnabled || appState.openDialog?.name === "elementLinkSelector") {
      return /* @__PURE__ */ jsx90("div", { className: "App-toolbar-content", children: /* @__PURE__ */ jsx90(MainMenuTunnel.Out, {}) });
    }
    return /* @__PURE__ */ jsxs49("div", { className: "App-toolbar-content", children: [
      /* @__PURE__ */ jsx90(MainMenuTunnel.Out, {}),
      actionManager.renderAction("toggleEditMenu"),
      actionManager.renderAction(
        appState.multiElement ? "finalize" : "duplicateSelection"
      ),
      actionManager.renderAction("deleteSelectedElements"),
      /* @__PURE__ */ jsxs49("div", { children: [
        actionManager.renderAction("undo"),
        actionManager.renderAction("redo")
      ] })
    ] });
  };
  return /* @__PURE__ */ jsxs49(Fragment12, { children: [
    renderSidebars(),
    !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector" && renderToolbar(),
    /* @__PURE__ */ jsx90(
      "div",
      {
        className: "App-bottom-bar",
        style: {
          marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
          marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
          marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2
        },
        children: /* @__PURE__ */ jsxs49(Island, { padding: 0, children: [
          appState.openMenu === "shape" && !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector" && showSelectedShapeActions(appState, elements) ? /* @__PURE__ */ jsx90(Section, { className: "App-mobile-menu", heading: "selectedShapeActions", children: /* @__PURE__ */ jsx90(
            SelectedShapeActions,
            {
              appState,
              elementsMap: app.scene.getNonDeletedElementsMap(),
              renderAction: actionManager.renderAction,
              app
            }
          ) }) : null,
          /* @__PURE__ */ jsxs49("footer", { className: "App-toolbar", children: [
            renderAppToolbar(),
            appState.scrolledOutside && !appState.openMenu && !appState.openSidebar && /* @__PURE__ */ jsx90(
              "button",
              {
                type: "button",
                className: "scroll-back-to-content",
                onClick: () => {
                  setAppState((appState2) => ({
                    ...calculateScrollCenter(elements, appState2)
                  }));
                },
                children: t("buttons.scrollBackToContent")
              }
            )
          ] })
        ] })
      }
    )
  ] });
};

// components/PasteChartDialog.tsx
import oc from "open-color";
import React33, { useLayoutEffect as useLayoutEffect5, useRef as useRef26, useState as useState29 } from "react";
import { jsx as jsx91, jsxs as jsxs50 } from "react/jsx-runtime";
var ChartPreviewBtn = (props) => {
  const previewRef = useRef26(null);
  const [chartElements, setChartElements] = useState29(
    null
  );
  useLayoutEffect5(() => {
    if (!props.spreadsheet) {
      return;
    }
    const elements = renderSpreadsheet(
      props.chartType,
      props.spreadsheet,
      0,
      0
    );
    setChartElements(elements);
    let svg;
    const previewNode = previewRef.current;
    (async () => {
      svg = await exportToSvg(
        elements,
        {
          exportBackground: false,
          viewBackgroundColor: oc.white
        },
        null,
        // files
        {
          skipInliningFonts: true
        }
      );
      svg.querySelector(".style-fonts")?.remove();
      previewNode.replaceChildren();
      previewNode.appendChild(svg);
      if (props.selected) {
        previewNode.parentNode.focus();
      }
    })();
    return () => {
      previewNode.replaceChildren();
    };
  }, [props.spreadsheet, props.chartType, props.selected]);
  return /* @__PURE__ */ jsx91(
    "button",
    {
      type: "button",
      className: "ChartPreview",
      onClick: () => {
        if (chartElements) {
          props.onClick(props.chartType, chartElements);
        }
      },
      children: /* @__PURE__ */ jsx91("div", { ref: previewRef })
    }
  );
};
var PasteChartDialog = ({
  setAppState,
  appState,
  onClose
}) => {
  const { onInsertElements } = useApp();
  const handleClose = React33.useCallback(() => {
    if (onClose) {
      onClose();
    }
  }, [onClose]);
  const handleChartClick = (chartType, elements) => {
    onInsertElements(elements);
    trackEvent("paste", "chart", chartType);
    setAppState({
      currentChartType: chartType,
      pasteDialog: {
        shown: false,
        data: null
      }
    });
  };
  return /* @__PURE__ */ jsx91(
    Dialog,
    {
      size: "small",
      onCloseRequest: handleClose,
      title: t("labels.pasteCharts"),
      className: "PasteChartDialog",
      autofocus: false,
      children: /* @__PURE__ */ jsxs50("div", { className: "container", children: [
        /* @__PURE__ */ jsx91(
          ChartPreviewBtn,
          {
            chartType: "bar",
            spreadsheet: appState.pasteDialog.data,
            selected: appState.currentChartType === "bar",
            onClick: handleChartClick
          }
        ),
        /* @__PURE__ */ jsx91(
          ChartPreviewBtn,
          {
            chartType: "line",
            spreadsheet: appState.pasteDialog.data,
            selected: appState.currentChartType === "line",
            onClick: handleChartClick
          }
        )
      ] })
    }
  );
};

// components/HelpDialog.tsx
import React34 from "react";
import { Fragment as Fragment13, jsx as jsx92, jsxs as jsxs51 } from "react/jsx-runtime";
var Header = () => /* @__PURE__ */ jsxs51("div", { className: "HelpDialog__header", children: [
  /* @__PURE__ */ jsxs51(
    "a",
    {
      className: "HelpDialog__btn",
      href: "https://docs.excalidraw.com",
      target: "_blank",
      rel: "noopener noreferrer",
      children: [
        /* @__PURE__ */ jsx92("div", { className: "HelpDialog__link-icon", children: ExternalLinkIcon }),
        t("helpDialog.documentation")
      ]
    }
  ),
  /* @__PURE__ */ jsxs51(
    "a",
    {
      className: "HelpDialog__btn",
      href: "https://plus.excalidraw.com/blog",
      target: "_blank",
      rel: "noopener noreferrer",
      children: [
        /* @__PURE__ */ jsx92("div", { className: "HelpDialog__link-icon", children: ExternalLinkIcon }),
        t("helpDialog.blog")
      ]
    }
  ),
  /* @__PURE__ */ jsxs51(
    "a",
    {
      className: "HelpDialog__btn",
      href: "https://github.com/excalidraw/excalidraw/issues",
      target: "_blank",
      rel: "noopener noreferrer",
      children: [
        /* @__PURE__ */ jsx92("div", { className: "HelpDialog__link-icon", children: GithubIcon }),
        t("helpDialog.github")
      ]
    }
  ),
  /* @__PURE__ */ jsxs51(
    "a",
    {
      className: "HelpDialog__btn",
      href: "https://youtube.com/@excalidraw",
      target: "_blank",
      rel: "noopener noreferrer",
      children: [
        /* @__PURE__ */ jsx92("div", { className: "HelpDialog__link-icon", children: youtubeIcon }),
        "YouTube"
      ]
    }
  )
] });
var Section2 = (props) => /* @__PURE__ */ jsxs51(Fragment13, { children: [
  /* @__PURE__ */ jsx92("h3", { children: props.title }),
  /* @__PURE__ */ jsx92("div", { className: "HelpDialog__islands-container", children: props.children })
] });
var ShortcutIsland = (props) => /* @__PURE__ */ jsxs51("div", { className: `HelpDialog__island ${props.className}`, children: [
  /* @__PURE__ */ jsx92("h4", { className: "HelpDialog__island-title", children: props.caption }),
  /* @__PURE__ */ jsx92("div", { className: "HelpDialog__island-content", children: props.children })
] });
function* intersperse(as, delim) {
  let first = true;
  for (const x of as) {
    if (!first) {
      yield delim;
    }
    first = false;
    yield x;
  }
}
var upperCaseSingleChars = (str) => {
  return str.replace(/\b[a-z]\b/, (c) => c.toUpperCase());
};
var Shortcut = ({
  label,
  shortcuts,
  isOr = true
}) => {
  const splitShortcutKeys = shortcuts.map((shortcut) => {
    const keys = shortcut.endsWith("++") ? [...shortcut.slice(0, -2).split("+"), "+"] : shortcut.split("+");
    return keys.map((key) => /* @__PURE__ */ jsx92(ShortcutKey, { children: upperCaseSingleChars(key) }, key));
  });
  return /* @__PURE__ */ jsxs51("div", { className: "HelpDialog__shortcut", children: [
    /* @__PURE__ */ jsx92("div", { children: label }),
    /* @__PURE__ */ jsx92("div", { className: "HelpDialog__key-container", children: [...intersperse(splitShortcutKeys, isOr ? t("helpDialog.or") : null)] })
  ] });
};
var ShortcutKey = (props) => /* @__PURE__ */ jsx92("kbd", { className: "HelpDialog__key", ...props });
var HelpDialog = ({ onClose }) => {
  const handleClose = React34.useCallback(() => {
    if (onClose) {
      onClose();
    }
  }, [onClose]);
  return /* @__PURE__ */ jsx92(Fragment13, { children: /* @__PURE__ */ jsxs51(
    Dialog,
    {
      onCloseRequest: handleClose,
      title: t("helpDialog.title"),
      className: "HelpDialog",
      children: [
        /* @__PURE__ */ jsx92(Header, {}),
        /* @__PURE__ */ jsxs51(Section2, { title: t("helpDialog.shortcuts"), children: [
          /* @__PURE__ */ jsxs51(
            ShortcutIsland,
            {
              className: "HelpDialog__island--tools",
              caption: t("helpDialog.tools"),
              children: [
                /* @__PURE__ */ jsx92(Shortcut, { label: t("toolBar.hand"), shortcuts: [KEYS.H] }),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.selection"),
                    shortcuts: [KEYS.V, KEYS["1"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.rectangle"),
                    shortcuts: [KEYS.R, KEYS["2"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.diamond"),
                    shortcuts: [KEYS.D, KEYS["3"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.ellipse"),
                    shortcuts: [KEYS.O, KEYS["4"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.arrow"),
                    shortcuts: [KEYS.A, KEYS["5"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.line"),
                    shortcuts: [KEYS.L, KEYS["6"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.freedraw"),
                    shortcuts: [KEYS.P, KEYS["7"]]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.text"),
                    shortcuts: [KEYS.T, KEYS["8"]]
                  }
                ),
                /* @__PURE__ */ jsx92(Shortcut, { label: t("toolBar.image"), shortcuts: [KEYS["9"]] }),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.eraser"),
                    shortcuts: [KEYS.E, KEYS["0"]]
                  }
                ),
                /* @__PURE__ */ jsx92(Shortcut, { label: t("toolBar.frame"), shortcuts: [KEYS.F] }),
                /* @__PURE__ */ jsx92(Shortcut, { label: t("toolBar.laser"), shortcuts: [KEYS.K] }),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.eyeDropper"),
                    shortcuts: [KEYS.I, "Shift+S", "Shift+G"]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.editLineArrowPoints"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Enter")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.editText"),
                    shortcuts: [getShortcutKey("Enter")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.textNewLine"),
                    shortcuts: [
                      getShortcutKey("Enter"),
                      getShortcutKey("Shift+Enter")
                    ]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.textFinish"),
                    shortcuts: [
                      getShortcutKey("Esc"),
                      getShortcutKey("CtrlOrCmd+Enter")
                    ]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.curvedArrow"),
                    shortcuts: [
                      "A",
                      t("helpDialog.click"),
                      t("helpDialog.click"),
                      t("helpDialog.click")
                    ],
                    isOr: false
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.curvedLine"),
                    shortcuts: [
                      "L",
                      t("helpDialog.click"),
                      t("helpDialog.click"),
                      t("helpDialog.click")
                    ],
                    isOr: false
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.cropStart"),
                    shortcuts: [t("helpDialog.doubleClick"), getShortcutKey("Enter")],
                    isOr: true
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.cropFinish"),
                    shortcuts: [getShortcutKey("Enter"), getShortcutKey("Escape")],
                    isOr: true
                  }
                ),
                /* @__PURE__ */ jsx92(Shortcut, { label: t("toolBar.lock"), shortcuts: [KEYS.Q] }),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.preventBinding"),
                    shortcuts: [getShortcutKey("CtrlOrCmd")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("toolBar.link"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+K")]
                  }
                )
              ]
            }
          ),
          /* @__PURE__ */ jsxs51(
            ShortcutIsland,
            {
              className: "HelpDialog__island--view",
              caption: t("helpDialog.view"),
              children: [
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.zoomIn"),
                    shortcuts: [getShortcutKey("CtrlOrCmd++")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.zoomOut"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+-")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.resetZoom"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+0")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.zoomToFit"),
                    shortcuts: ["Shift+1"]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.zoomToSelection"),
                    shortcuts: ["Shift+2"]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.movePageUpDown"),
                    shortcuts: ["PgUp/PgDn"]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.movePageLeftRight"),
                    shortcuts: ["Shift+PgUp/PgDn"]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.zenMode"),
                    shortcuts: [getShortcutKey("Alt+Z")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.objectsSnapMode"),
                    shortcuts: [getShortcutKey("Alt+S")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.toggleGrid"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+'")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.viewMode"),
                    shortcuts: [getShortcutKey("Alt+R")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.toggleTheme"),
                    shortcuts: [getShortcutKey("Alt+Shift+D")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("stats.fullTitle"),
                    shortcuts: [getShortcutKey("Alt+/")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("search.title"),
                    shortcuts: [getShortcutFromShortcutName("searchMenu")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("commandPalette.title"),
                    shortcuts: isFirefox ? [getShortcutFromShortcutName("commandPalette")] : [
                      getShortcutFromShortcutName("commandPalette"),
                      getShortcutFromShortcutName("commandPalette", 1)
                    ]
                  }
                )
              ]
            }
          ),
          /* @__PURE__ */ jsxs51(
            ShortcutIsland,
            {
              className: "HelpDialog__island--editor",
              caption: t("helpDialog.editor"),
              children: [
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.createFlowchart"),
                    shortcuts: [getShortcutKey(`CtrlOrCmd+Arrow Key`)],
                    isOr: true
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.navigateFlowchart"),
                    shortcuts: [getShortcutKey(`Alt+Arrow Key`)],
                    isOr: true
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.moveCanvas"),
                    shortcuts: [
                      getShortcutKey(`Space+${t("helpDialog.drag")}`),
                      getShortcutKey(`Wheel+${t("helpDialog.drag")}`)
                    ],
                    isOr: true
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.clearReset"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Delete")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.delete"),
                    shortcuts: [getShortcutKey("Delete")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.cut"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+X")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.copy"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+C")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.paste"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+V")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.pasteAsPlaintext"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+V")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.selectAll"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+A")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.multiSelect"),
                    shortcuts: [getShortcutKey(`Shift+${t("helpDialog.click")}`)]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.deepSelect"),
                    shortcuts: [getShortcutKey(`CtrlOrCmd+${t("helpDialog.click")}`)]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.deepBoxSelect"),
                    shortcuts: [getShortcutKey(`CtrlOrCmd+${t("helpDialog.drag")}`)]
                  }
                ),
                (probablySupportsClipboardBlob || isFirefox) && /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.copyAsPng"),
                    shortcuts: [getShortcutKey("Shift+Alt+C")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.copyStyles"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Alt+C")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.pasteStyles"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Alt+V")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.sendToBack"),
                    shortcuts: [
                      isDarwin ? getShortcutKey("CtrlOrCmd+Alt+[") : getShortcutKey("CtrlOrCmd+Shift+[")
                    ]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.bringToFront"),
                    shortcuts: [
                      isDarwin ? getShortcutKey("CtrlOrCmd+Alt+]") : getShortcutKey("CtrlOrCmd+Shift+]")
                    ]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.sendBackward"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+[")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.bringForward"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+]")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.alignTop"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+Up")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.alignBottom"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+Down")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.alignLeft"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+Left")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.alignRight"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+Right")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.duplicateSelection"),
                    shortcuts: [
                      getShortcutKey("CtrlOrCmd+D"),
                      getShortcutKey(`Alt+${t("helpDialog.drag")}`)
                    ]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("helpDialog.toggleElementLock"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+L")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.undo"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Z")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("buttons.redo"),
                    shortcuts: isWindows ? [
                      getShortcutKey("CtrlOrCmd+Y"),
                      getShortcutKey("CtrlOrCmd+Shift+Z")
                    ] : [getShortcutKey("CtrlOrCmd+Shift+Z")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.group"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+G")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.ungroup"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+G")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.flipHorizontal"),
                    shortcuts: [getShortcutKey("Shift+H")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.flipVertical"),
                    shortcuts: [getShortcutKey("Shift+V")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.showStroke"),
                    shortcuts: [getShortcutKey("S")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.showBackground"),
                    shortcuts: [getShortcutKey("G")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.showFonts"),
                    shortcuts: [getShortcutKey("Shift+F")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.decreaseFontSize"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+<")]
                  }
                ),
                /* @__PURE__ */ jsx92(
                  Shortcut,
                  {
                    label: t("labels.increaseFontSize"),
                    shortcuts: [getShortcutKey("CtrlOrCmd+Shift+>")]
                  }
                )
              ]
            }
          )
        ] })
      ]
    }
  ) });
};

// components/UserList.tsx
import React35, { useLayoutEffect as useLayoutEffect6 } from "react";
import clsx41 from "clsx";
import * as Popover7 from "@radix-ui/react-popover";
import { Fragment as Fragment14, jsx as jsx93, jsxs as jsxs52 } from "react/jsx-runtime";
var DEFAULT_MAX_AVATARS = 4;
var SHOW_COLLABORATORS_FILTER_AT = 8;
var ConditionalTooltipWrapper = ({
  shouldWrap,
  children,
  username
}) => shouldWrap ? /* @__PURE__ */ jsx93(Tooltip, { label: username || "Unknown user", children }) : /* @__PURE__ */ jsx93(Fragment14, { children });
var renderCollaborator = ({
  actionManager,
  collaborator,
  socketId,
  withName = false,
  shouldWrapWithTooltip = false,
  isBeingFollowed
}) => {
  const data = {
    socketId,
    collaborator,
    withName,
    isBeingFollowed
  };
  const avatarJSX = actionManager.renderAction("goToCollaborator", data);
  return /* @__PURE__ */ jsx93(
    ConditionalTooltipWrapper,
    {
      username: collaborator.username,
      shouldWrap: shouldWrapWithTooltip,
      children: avatarJSX
    },
    socketId
  );
};
var collaboratorComparatorKeys = [
  "avatarUrl",
  "id",
  "socketId",
  "username",
  "isInCall",
  "isSpeaking",
  "isMuted"
];
var UserList = React35.memo(
  ({ className, mobile, collaborators, userToFollow }) => {
    const actionManager = useExcalidrawActionManager();
    const uniqueCollaboratorsMap = /* @__PURE__ */ new Map();
    collaborators.forEach((collaborator, socketId) => {
      const userId = collaborator.id || socketId;
      uniqueCollaboratorsMap.set(
        // filter on user id, else fall back on unique socketId
        userId,
        { ...collaborator, socketId }
      );
    });
    const uniqueCollaboratorsArray = Array.from(
      uniqueCollaboratorsMap.values()
    ).filter((collaborator) => collaborator.username?.trim());
    const [searchTerm, setSearchTerm] = React35.useState("");
    const filteredCollaborators = uniqueCollaboratorsArray.filter(
      (collaborator) => collaborator.username?.toLowerCase().includes(searchTerm)
    );
    const userListWrapper = React35.useRef(null);
    useLayoutEffect6(() => {
      if (userListWrapper.current) {
        const updateMaxAvatars = (width) => {
          const maxAvatars2 = Math.max(1, Math.min(8, Math.floor(width / 38)));
          setMaxAvatars(maxAvatars2);
        };
        updateMaxAvatars(userListWrapper.current.clientWidth);
        if (!supportsResizeObserver) {
          return;
        }
        const resizeObserver = new ResizeObserver((entries) => {
          for (const entry of entries) {
            const { width } = entry.contentRect;
            updateMaxAvatars(width);
          }
        });
        resizeObserver.observe(userListWrapper.current);
        return () => {
          resizeObserver.disconnect();
        };
      }
    }, []);
    const [maxAvatars, setMaxAvatars] = React35.useState(DEFAULT_MAX_AVATARS);
    const firstNCollaborators = uniqueCollaboratorsArray.slice(
      0,
      maxAvatars - 1
    );
    const firstNAvatarsJSX = firstNCollaborators.map(
      (collaborator) => renderCollaborator({
        actionManager,
        collaborator,
        socketId: collaborator.socketId,
        shouldWrapWithTooltip: true,
        isBeingFollowed: collaborator.socketId === userToFollow
      })
    );
    return mobile ? /* @__PURE__ */ jsx93("div", { className: clsx41("UserList UserList_mobile", className), children: uniqueCollaboratorsArray.map(
      (collaborator) => renderCollaborator({
        actionManager,
        collaborator,
        socketId: collaborator.socketId,
        shouldWrapWithTooltip: true,
        isBeingFollowed: collaborator.socketId === userToFollow
      })
    ) }) : /* @__PURE__ */ jsx93("div", { className: "UserList__wrapper", ref: userListWrapper, children: /* @__PURE__ */ jsxs52(
      "div",
      {
        className: clsx41("UserList", className),
        style: { [`--max-avatars`]: maxAvatars },
        children: [
          firstNAvatarsJSX,
          uniqueCollaboratorsArray.length > maxAvatars - 1 && /* @__PURE__ */ jsxs52(Popover7.Root, { children: [
            /* @__PURE__ */ jsxs52(Popover7.Trigger, { className: "UserList__more", children: [
              "+",
              uniqueCollaboratorsArray.length - maxAvatars + 1
            ] }),
            /* @__PURE__ */ jsx93(
              Popover7.Content,
              {
                style: {
                  zIndex: 2,
                  width: "15rem",
                  textAlign: "left"
                },
                align: "end",
                sideOffset: 10,
                children: /* @__PURE__ */ jsxs52(Island, { padding: 2, children: [
                  uniqueCollaboratorsArray.length >= SHOW_COLLABORATORS_FILTER_AT && /* @__PURE__ */ jsx93(
                    QuickSearch,
                    {
                      placeholder: t("quickSearch.placeholder"),
                      onChange: setSearchTerm
                    }
                  ),
                  /* @__PURE__ */ jsx93(
                    ScrollableList,
                    {
                      className: "dropdown-menu UserList__collaborators",
                      placeholder: t("userList.empty"),
                      children: filteredCollaborators.length > 0 ? [
                        /* @__PURE__ */ jsx93("div", { className: "hint", children: t("userList.hint.text") }),
                        filteredCollaborators.map(
                          (collaborator) => renderCollaborator({
                            actionManager,
                            collaborator,
                            socketId: collaborator.socketId,
                            withName: true,
                            isBeingFollowed: collaborator.socketId === userToFollow
                          })
                        )
                      ] : []
                    }
                  ),
                  /* @__PURE__ */ jsx93(
                    Popover7.Arrow,
                    {
                      width: 20,
                      height: 10,
                      style: {
                        fill: "var(--popup-bg-color)",
                        filter: "drop-shadow(rgba(0, 0, 0, 0.05) 0px 3px 2px)"
                      }
                    }
                  )
                ] })
              }
            )
          ] })
        ]
      }
    ) });
  },
  (prev, next) => {
    if (prev.collaborators.size !== next.collaborators.size || prev.mobile !== next.mobile || prev.className !== next.className || prev.userToFollow !== next.userToFollow) {
      return false;
    }
    const nextCollaboratorSocketIds = next.collaborators.keys();
    for (const [socketId, collaborator] of prev.collaborators) {
      const nextCollaborator = next.collaborators.get(socketId);
      if (!nextCollaborator || // this checks order of collaborators in the map is the same
      // as previous render
      socketId !== nextCollaboratorSocketIds.next().value || !isShallowEqual(
        collaborator,
        nextCollaborator,
        collaboratorComparatorKeys
      )) {
        return false;
      }
    }
    return true;
  }
);

// components/JSONExportDialog.tsx
import React36 from "react";

// components/Card.tsx
import OpenColor2 from "open-color";
import { jsx as jsx94 } from "react/jsx-runtime";
var Card = ({ children, color }) => {
  return /* @__PURE__ */ jsx94(
    "div",
    {
      className: "Card",
      style: {
        ["--card-color"]: color === "primary" ? "var(--color-primary)" : OpenColor2[color][7],
        ["--card-color-darker"]: color === "primary" ? "var(--color-primary-darker)" : OpenColor2[color][8],
        ["--card-color-darkest"]: color === "primary" ? "var(--color-primary-darkest)" : OpenColor2[color][9]
      },
      children
    }
  );
};

// components/JSONExportDialog.tsx
import { Fragment as Fragment15, jsx as jsx95, jsxs as jsxs53 } from "react/jsx-runtime";
var JSONExportModal = ({
  elements,
  appState,
  setAppState,
  files,
  actionManager,
  exportOpts,
  canvas,
  onCloseRequest
}) => {
  const { onExportToBackend } = exportOpts;
  return /* @__PURE__ */ jsx95("div", { className: "ExportDialog ExportDialog--json", children: /* @__PURE__ */ jsxs53("div", { className: "ExportDialog-cards", children: [
    exportOpts.saveFileToDisk && /* @__PURE__ */ jsxs53(Card, { color: "lime", children: [
      /* @__PURE__ */ jsx95("div", { className: "Card-icon", children: exportToFileIcon }),
      /* @__PURE__ */ jsx95("h2", { children: t("exportDialog.disk_title") }),
      /* @__PURE__ */ jsxs53("div", { className: "Card-details", children: [
        t("exportDialog.disk_details"),
        !nativeFileSystemSupported && actionManager.renderAction("changeProjectName")
      ] }),
      /* @__PURE__ */ jsx95(
        ToolButton,
        {
          className: "Card-button",
          type: "button",
          title: t("exportDialog.disk_button"),
          "aria-label": t("exportDialog.disk_button"),
          showAriaLabel: true,
          onClick: () => {
            actionManager.executeAction(actionSaveFileToDisk, "ui");
          }
        }
      )
    ] }),
    onExportToBackend && /* @__PURE__ */ jsxs53(Card, { color: "pink", children: [
      /* @__PURE__ */ jsx95("div", { className: "Card-icon", children: LinkIcon }),
      /* @__PURE__ */ jsx95("h2", { children: t("exportDialog.link_title") }),
      /* @__PURE__ */ jsx95("div", { className: "Card-details", children: t("exportDialog.link_details") }),
      /* @__PURE__ */ jsx95(
        ToolButton,
        {
          className: "Card-button",
          type: "button",
          title: t("exportDialog.link_button"),
          "aria-label": t("exportDialog.link_button"),
          showAriaLabel: true,
          onClick: async () => {
            try {
              trackEvent("export", "link", `ui (${getFrame()})`);
              await onExportToBackend(elements, appState, files);
              onCloseRequest();
            } catch (error) {
              setAppState({ errorMessage: error.message });
            }
          }
        }
      )
    ] }),
    exportOpts.renderCustomUI && exportOpts.renderCustomUI(elements, appState, files, canvas)
  ] }) });
};
var JSONExportDialog = ({
  elements,
  appState,
  files,
  actionManager,
  exportOpts,
  canvas,
  setAppState
}) => {
  const handleClose = React36.useCallback(() => {
    setAppState({ openDialog: null });
  }, [setAppState]);
  return /* @__PURE__ */ jsx95(Fragment15, { children: appState.openDialog?.name === "jsonExport" && /* @__PURE__ */ jsx95(Dialog, { onCloseRequest: handleClose, title: t("buttons.export"), children: /* @__PURE__ */ jsx95(
    JSONExportModal,
    {
      elements,
      appState,
      setAppState,
      files,
      actionManager,
      onCloseRequest: handleClose,
      exportOpts,
      canvas
    }
  ) }) });
};

// components/footer/Footer.tsx
import clsx42 from "clsx";

// components/HelpButton.tsx
import { jsx as jsx96 } from "react/jsx-runtime";
var HelpButton = (props) => /* @__PURE__ */ jsx96(
  "button",
  {
    className: "help-icon",
    onClick: props.onClick,
    type: "button",
    title: `${t("helpDialog.title")} \u2014 ?`,
    "aria-label": t("helpDialog.title"),
    children: HelpIcon
  }
);

// components/footer/Footer.tsx
import { jsx as jsx97, jsxs as jsxs54 } from "react/jsx-runtime";
var Footer = ({
  appState,
  actionManager,
  showExitZenModeBtn,
  renderWelcomeScreen
}) => {
  const { FooterCenterTunnel, WelcomeScreenHelpHintTunnel } = useTunnels();
  const device = useDevice();
  const showFinalize = !appState.viewModeEnabled && appState.multiElement && device.isTouchScreen;
  return /* @__PURE__ */ jsxs54(
    "footer",
    {
      role: "contentinfo",
      className: "layer-ui__wrapper__footer App-menu App-menu_bottom",
      children: [
        /* @__PURE__ */ jsx97(
          "div",
          {
            className: clsx42("layer-ui__wrapper__footer-left zen-mode-transition", {
              "layer-ui__wrapper__footer-left--transition-left": appState.zenModeEnabled
            }),
            children: /* @__PURE__ */ jsx97(Stack_default.Col, { gap: 2, children: /* @__PURE__ */ jsxs54(Section, { heading: "canvasActions", children: [
              /* @__PURE__ */ jsx97(
                ZoomActions,
                {
                  renderAction: actionManager.renderAction,
                  zoom: appState.zoom
                }
              ),
              !appState.viewModeEnabled && /* @__PURE__ */ jsx97(
                UndoRedoActions,
                {
                  renderAction: actionManager.renderAction,
                  className: clsx42("zen-mode-transition", {
                    "layer-ui__wrapper__footer-left--transition-bottom": appState.zenModeEnabled
                  })
                }
              ),
              showFinalize && /* @__PURE__ */ jsx97(
                FinalizeAction,
                {
                  renderAction: actionManager.renderAction,
                  className: clsx42("zen-mode-transition", {
                    "layer-ui__wrapper__footer-left--transition-left": appState.zenModeEnabled
                  })
                }
              )
            ] }) })
          }
        ),
        /* @__PURE__ */ jsx97(FooterCenterTunnel.Out, {}),
        /* @__PURE__ */ jsx97(
          "div",
          {
            className: clsx42("layer-ui__wrapper__footer-right zen-mode-transition", {
              "transition-right": appState.zenModeEnabled
            }),
            children: /* @__PURE__ */ jsxs54("div", { style: { position: "relative" }, children: [
              renderWelcomeScreen && /* @__PURE__ */ jsx97(WelcomeScreenHelpHintTunnel.Out, {}),
              /* @__PURE__ */ jsx97(
                HelpButton,
                {
                  onClick: () => actionManager.executeAction(actionShortcuts)
                }
              )
            ] })
          }
        ),
        /* @__PURE__ */ jsx97(
          ExitZenModeAction,
          {
            actionManager,
            showExitZenModeBtn
          }
        )
      ]
    }
  );
};
var Footer_default = Footer;
Footer.displayName = "Footer";

// components/Sidebar/Sidebar.tsx
import {
  useEffect as useEffect32,
  useLayoutEffect as useLayoutEffect7,
  useRef as useRef27,
  useState as useState30,
  forwardRef as forwardRef5,
  useImperativeHandle as useImperativeHandle2,
  useCallback as useCallback12
} from "react";

// components/Sidebar/common.ts
import React37 from "react";
var SidebarPropsContext = React37.createContext({});

// components/Sidebar/SidebarHeader.tsx
import clsx43 from "clsx";
import { useContext as useContext2 } from "react";
import { jsx as jsx98, jsxs as jsxs55 } from "react/jsx-runtime";
var SidebarHeader = ({
  children,
  className
}) => {
  const device = useDevice();
  const props = useContext2(SidebarPropsContext);
  const renderDockButton = !!(device.editor.canFitSidebar && props.shouldRenderDockButton);
  return /* @__PURE__ */ jsxs55(
    "div",
    {
      className: clsx43("sidebar__header", className),
      "data-testid": "sidebar-header",
      children: [
        children,
        /* @__PURE__ */ jsxs55("div", { className: "sidebar__header__buttons", children: [
          renderDockButton && /* @__PURE__ */ jsx98(Tooltip, { label: t("labels.sidebarLock"), children: /* @__PURE__ */ jsx98(
            Button,
            {
              onSelect: () => props.onDock?.(!props.docked),
              selected: !!props.docked,
              className: "sidebar__dock",
              "data-testid": "sidebar-dock",
              "aria-label": t("labels.sidebarLock"),
              children: PinIcon
            }
          ) }),
          /* @__PURE__ */ jsx98(
            Button,
            {
              "data-testid": "sidebar-close",
              className: "sidebar__close",
              onSelect: props.onCloseRequest,
              "aria-label": t("buttons.close"),
              children: CloseIcon
            }
          )
        ] })
      ]
    }
  );
};
SidebarHeader.displayName = "SidebarHeader";

// components/Sidebar/Sidebar.tsx
import clsx45 from "clsx";

// components/Sidebar/SidebarTrigger.tsx
import clsx44 from "clsx";
import { jsx as jsx99, jsxs as jsxs56 } from "react/jsx-runtime";
var SidebarTrigger = ({
  name,
  tab,
  icon,
  title,
  children,
  onToggle,
  className,
  style
}) => {
  const setAppState = useExcalidrawSetAppState();
  const appState = useUIAppState();
  return /* @__PURE__ */ jsxs56("label", { title, className: "sidebar-trigger__label-element", children: [
    /* @__PURE__ */ jsx99(
      "input",
      {
        className: "ToolIcon_type_checkbox",
        type: "checkbox",
        onChange: (event) => {
          document.querySelector(".layer-ui__wrapper")?.classList.remove("animate");
          const isOpen = event.target.checked;
          setAppState({ openSidebar: isOpen ? { name, tab } : null });
          onToggle?.(isOpen);
        },
        checked: appState.openSidebar?.name === name,
        "aria-label": title,
        "aria-keyshortcuts": "0"
      }
    ),
    /* @__PURE__ */ jsxs56("div", { className: clsx44("sidebar-trigger", className), style, children: [
      icon && /* @__PURE__ */ jsx99("div", { children: icon }),
      children && /* @__PURE__ */ jsx99("div", { className: "sidebar-trigger__label", children })
    ] })
  ] });
};
SidebarTrigger.displayName = "SidebarTrigger";

// components/Sidebar/SidebarTabTriggers.tsx
import * as RadixTabs from "@radix-ui/react-tabs";
import { jsx as jsx100 } from "react/jsx-runtime";
var SidebarTabTriggers = ({
  children,
  ...rest
}) => {
  return /* @__PURE__ */ jsx100(RadixTabs.List, { className: "sidebar-triggers", ...rest, children });
};
SidebarTabTriggers.displayName = "SidebarTabTriggers";

// components/Sidebar/SidebarTabTrigger.tsx
import * as RadixTabs2 from "@radix-ui/react-tabs";
import { jsx as jsx101 } from "react/jsx-runtime";
var SidebarTabTrigger = ({
  children,
  tab,
  onSelect,
  ...rest
}) => {
  return /* @__PURE__ */ jsx101(RadixTabs2.Trigger, { value: tab, asChild: true, onSelect, children: /* @__PURE__ */ jsx101(
    "button",
    {
      type: "button",
      className: `excalidraw-button sidebar-tab-trigger`,
      ...rest,
      children
    }
  ) });
};
SidebarTabTrigger.displayName = "SidebarTabTrigger";

// components/Sidebar/SidebarTabs.tsx
import * as RadixTabs3 from "@radix-ui/react-tabs";
import { jsx as jsx102 } from "react/jsx-runtime";
var SidebarTabs = ({
  children,
  ...rest
}) => {
  const appState = useUIAppState();
  const setAppState = useExcalidrawSetAppState();
  if (!appState.openSidebar) {
    return null;
  }
  const { name } = appState.openSidebar;
  return /* @__PURE__ */ jsx102(
    RadixTabs3.Root,
    {
      className: "sidebar-tabs-root",
      value: appState.openSidebar.tab,
      onValueChange: (tab) => setAppState((state) => ({
        ...state,
        openSidebar: { ...state.openSidebar, name, tab }
      })),
      ...rest,
      children
    }
  );
};
SidebarTabs.displayName = "SidebarTabs";

// components/Sidebar/SidebarTab.tsx
import * as RadixTabs4 from "@radix-ui/react-tabs";
import { jsx as jsx103 } from "react/jsx-runtime";
var SidebarTab = ({
  tab,
  children,
  ...rest
}) => {
  return /* @__PURE__ */ jsx103(RadixTabs4.Content, { ...rest, value: tab, "data-testid": tab, children });
};
SidebarTab.displayName = "SidebarTab";

// components/Sidebar/Sidebar.tsx
import { jsx as jsx104 } from "react/jsx-runtime";
import { createElement } from "react";
var isSidebarDockedAtom = atom(false);
var SidebarInner = forwardRef5(
  ({
    name,
    children,
    onDock,
    docked,
    className,
    ...rest
  }, ref) => {
    if (define_import_meta_env_default.DEV && onDock && docked == null) {
      console.warn(
        "Sidebar: `docked` must be set when `onDock` is supplied for the sidebar to be user-dockable. To hide this message, either pass `docked` or remove `onDock`"
      );
    }
    const setAppState = useExcalidrawSetAppState();
    const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom);
    useLayoutEffect7(() => {
      setIsSidebarDockedAtom(!!docked);
      return () => {
        setIsSidebarDockedAtom(false);
      };
    }, [setIsSidebarDockedAtom, docked]);
    const headerPropsRef = useRef27(
      {}
    );
    headerPropsRef.current.onCloseRequest = () => {
      setAppState({ openSidebar: null });
    };
    headerPropsRef.current.onDock = (isDocked) => onDock?.(isDocked);
    headerPropsRef.current = updateObject(headerPropsRef.current, {
      docked,
      // explicit prop to rerender on update
      shouldRenderDockButton: !!onDock && docked != null
    });
    const islandRef = useRef27(null);
    useImperativeHandle2(ref, () => {
      return islandRef.current;
    });
    const device = useDevice();
    const closeLibrary = useCallback12(() => {
      const isDialogOpen = !!document.querySelector(".Dialog");
      if (isDialogOpen) {
        return;
      }
      setAppState({ openSidebar: null });
    }, [setAppState]);
    useOutsideClick(
      islandRef,
      useCallback12(
        (event) => {
          if (event.target.closest(".sidebar-trigger")) {
            return;
          }
          if (!docked || !device.editor.canFitSidebar) {
            closeLibrary();
          }
        },
        [closeLibrary, docked, device.editor.canFitSidebar]
      )
    );
    useEffect32(() => {
      const handleKeyDown = (event) => {
        if (event.key === KEYS.ESCAPE && (!docked || !device.editor.canFitSidebar)) {
          closeLibrary();
        }
      };
      document.addEventListener("keydown" /* KEYDOWN */, handleKeyDown);
      return () => {
        document.removeEventListener("keydown" /* KEYDOWN */, handleKeyDown);
      };
    }, [closeLibrary, docked, device.editor.canFitSidebar]);
    return /* @__PURE__ */ jsx104(
      Island,
      {
        ...rest,
        className: clsx45("sidebar", { "sidebar--docked": docked }, className),
        ref: islandRef,
        children: /* @__PURE__ */ jsx104(SidebarPropsContext.Provider, { value: headerPropsRef.current, children })
      }
    );
  }
);
SidebarInner.displayName = "SidebarInner";
var Sidebar = Object.assign(
  forwardRef5((props, ref) => {
    const appState = useUIAppState();
    const { onStateChange } = props;
    const refPrevOpenSidebar = useRef27(appState.openSidebar);
    useEffect32(() => {
      if (
        // closing sidebar
        (!appState.openSidebar && refPrevOpenSidebar?.current?.name === props.name || // opening current sidebar
        appState.openSidebar?.name === props.name && refPrevOpenSidebar?.current?.name !== props.name || // switching tabs or switching to a different sidebar
        refPrevOpenSidebar.current?.name === props.name) && appState.openSidebar !== refPrevOpenSidebar.current
      ) {
        onStateChange?.(
          appState.openSidebar?.name !== props.name ? null : appState.openSidebar
        );
      }
      refPrevOpenSidebar.current = appState.openSidebar;
    }, [appState.openSidebar, onStateChange, props.name]);
    const [mounted, setMounted] = useState30(false);
    useLayoutEffect7(() => {
      setMounted(true);
      return () => setMounted(false);
    }, []);
    const shouldRender = mounted && appState.openSidebar?.name === props.name;
    if (!shouldRender) {
      return null;
    }
    return /* @__PURE__ */ createElement(SidebarInner, { ...props, ref, key: props.name });
  }),
  {
    Header: SidebarHeader,
    TabTriggers: SidebarTabTriggers,
    TabTrigger: SidebarTabTrigger,
    Tabs: SidebarTabs,
    Tab: SidebarTab,
    Trigger: SidebarTrigger
  }
);
Sidebar.displayName = "Sidebar";

// components/main-menu/DefaultItems.tsx
var DefaultItems_exports = {};
__export(DefaultItems_exports, {
  ChangeCanvasBackground: () => ChangeCanvasBackground,
  ClearCanvas: () => ClearCanvas,
  CommandPalette: () => CommandPalette2,
  Export: () => Export,
  Help: () => Help,
  LiveCollaborationTrigger: () => LiveCollaborationTrigger,
  LoadScene: () => LoadScene,
  SaveAsImage: () => SaveAsImage,
  SaveToActiveFile: () => SaveToActiveFile,
  SearchMenu: () => SearchMenu,
  Socials: () => Socials,
  ToggleTheme: () => ToggleTheme
});
import clsx46 from "clsx";

// components/OverwriteConfirm/OverwriteConfirmState.ts
var overwriteConfirmStateAtom = atom({
  active: false
});
async function openConfirmModal({
  title,
  description,
  actionLabel,
  color
}) {
  return new Promise((resolve) => {
    editorJotaiStore.set(overwriteConfirmStateAtom, {
      active: true,
      onConfirm: () => resolve(true),
      onClose: () => resolve(false),
      onReject: () => resolve(false),
      title,
      description,
      actionLabel,
      color
    });
  });
}

// components/dropdownMenu/DropdownMenuItemContentRadio.tsx
import { Fragment as Fragment16, jsx as jsx105, jsxs as jsxs57 } from "react/jsx-runtime";
var DropdownMenuItemContentRadio = ({
  value,
  shortcut,
  onChange,
  choices,
  children,
  name
}) => {
  const device = useDevice();
  return /* @__PURE__ */ jsxs57(Fragment16, { children: [
    /* @__PURE__ */ jsxs57("div", { className: "dropdown-menu-item-base dropdown-menu-item-bare", children: [
      /* @__PURE__ */ jsx105("label", { className: "dropdown-menu-item__text", htmlFor: name, children }),
      /* @__PURE__ */ jsx105(
        RadioGroup,
        {
          name,
          value,
          onChange,
          choices
        }
      )
    ] }),
    shortcut && !device.editor.isMobile && /* @__PURE__ */ jsx105("div", { className: "dropdown-menu-item__shortcut dropdown-menu-item__shortcut--orphaned", children: shortcut })
  ] });
};
DropdownMenuItemContentRadio.displayName = "DropdownMenuItemContentRadio";
var DropdownMenuItemContentRadio_default = DropdownMenuItemContentRadio;

// components/main-menu/DefaultItems.tsx
import { Fragment as Fragment17, jsx as jsx106, jsxs as jsxs58 } from "react/jsx-runtime";
var LoadScene = () => {
  const { t: t2 } = useI18n();
  const actionManager = useExcalidrawActionManager();
  const elements = useExcalidrawElements();
  if (!actionManager.isActionEnabled(actionLoadScene)) {
    return null;
  }
  const handleSelect = async () => {
    if (!elements.length || await openConfirmModal({
      title: t2("overwriteConfirm.modal.loadFromFile.title"),
      actionLabel: t2("overwriteConfirm.modal.loadFromFile.button"),
      color: "warning",
      description: /* @__PURE__ */ jsx106(
        Trans_default,
        {
          i18nKey: "overwriteConfirm.modal.loadFromFile.description",
          bold: (text) => /* @__PURE__ */ jsx106("strong", { children: text }),
          br: () => /* @__PURE__ */ jsx106("br", {})
        }
      )
    })) {
      actionManager.executeAction(actionLoadScene);
    }
  };
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      icon: LoadIcon,
      onSelect: handleSelect,
      "data-testid": "load-button",
      shortcut: getShortcutFromShortcutName("loadScene"),
      "aria-label": t2("buttons.load"),
      children: t2("buttons.load")
    }
  );
};
LoadScene.displayName = "LoadScene";
var SaveToActiveFile = () => {
  const { t: t2 } = useI18n();
  const actionManager = useExcalidrawActionManager();
  if (!actionManager.isActionEnabled(actionSaveToActiveFile)) {
    return null;
  }
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      shortcut: getShortcutFromShortcutName("saveScene"),
      "data-testid": "save-button",
      onSelect: () => actionManager.executeAction(actionSaveToActiveFile),
      icon: save,
      "aria-label": `${t2("buttons.save")}`,
      children: `${t2("buttons.save")}`
    }
  );
};
SaveToActiveFile.displayName = "SaveToActiveFile";
var SaveAsImage = () => {
  const setAppState = useExcalidrawSetAppState();
  const { t: t2 } = useI18n();
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      icon: ExportImageIcon,
      "data-testid": "image-export-button",
      onSelect: () => setAppState({ openDialog: { name: "imageExport" } }),
      shortcut: getShortcutFromShortcutName("imageExport"),
      "aria-label": t2("buttons.exportImage"),
      children: t2("buttons.exportImage")
    }
  );
};
SaveAsImage.displayName = "SaveAsImage";
var CommandPalette2 = (opts) => {
  const setAppState = useExcalidrawSetAppState();
  const { t: t2 } = useI18n();
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      icon: boltIcon,
      "data-testid": "command-palette-button",
      onSelect: () => {
        trackEvent("command_palette", "open", "menu");
        setAppState({ openDialog: { name: "commandPalette" } });
      },
      shortcut: getShortcutFromShortcutName("commandPalette"),
      "aria-label": t2("commandPalette.title"),
      className: opts?.className,
      children: t2("commandPalette.title")
    }
  );
};
CommandPalette2.displayName = "CommandPalette";
var SearchMenu = (opts) => {
  const { t: t2 } = useI18n();
  const actionManager = useExcalidrawActionManager();
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      icon: searchIcon,
      "data-testid": "search-menu-button",
      onSelect: () => {
        actionManager.executeAction(actionToggleSearchMenu);
      },
      shortcut: getShortcutFromShortcutName("searchMenu"),
      "aria-label": t2("search.title"),
      className: opts?.className,
      children: t2("search.title")
    }
  );
};
SearchMenu.displayName = "SearchMenu";
var Help = () => {
  const { t: t2 } = useI18n();
  const actionManager = useExcalidrawActionManager();
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      "data-testid": "help-menu-item",
      icon: HelpIcon,
      onSelect: () => actionManager.executeAction(actionShortcuts),
      shortcut: "?",
      "aria-label": t2("helpDialog.title"),
      children: t2("helpDialog.title")
    }
  );
};
Help.displayName = "Help";
var ClearCanvas = () => {
  const { t: t2 } = useI18n();
  const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom);
  const actionManager = useExcalidrawActionManager();
  if (!actionManager.isActionEnabled(actionClearCanvas)) {
    return null;
  }
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      icon: TrashIcon,
      onSelect: () => setActiveConfirmDialog("clearCanvas"),
      "data-testid": "clear-canvas-button",
      "aria-label": t2("buttons.clearReset"),
      children: t2("buttons.clearReset")
    }
  );
};
ClearCanvas.displayName = "ClearCanvas";
var ToggleTheme = (props) => {
  const { t: t2 } = useI18n();
  const appState = useUIAppState();
  const actionManager = useExcalidrawActionManager();
  const shortcut = getShortcutFromShortcutName("toggleTheme");
  if (!actionManager.isActionEnabled(actionToggleTheme)) {
    return null;
  }
  if (props?.allowSystemTheme) {
    return /* @__PURE__ */ jsx106(
      DropdownMenuItemContentRadio_default,
      {
        name: "theme",
        value: props.theme,
        onChange: (value) => props.onSelect(value),
        choices: [
          {
            value: THEME.LIGHT,
            label: SunIcon,
            ariaLabel: `${t2("buttons.lightMode")} - ${shortcut}`
          },
          {
            value: THEME.DARK,
            label: MoonIcon,
            ariaLabel: `${t2("buttons.darkMode")} - ${shortcut}`
          },
          {
            value: "system",
            label: DeviceDesktopIcon,
            ariaLabel: t2("buttons.systemMode")
          }
        ],
        children: t2("labels.theme")
      }
    );
  }
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      onSelect: (event) => {
        event.preventDefault();
        if (props?.onSelect) {
          props.onSelect(
            appState.theme === THEME.DARK ? THEME.LIGHT : THEME.DARK
          );
        } else {
          return actionManager.executeAction(actionToggleTheme);
        }
      },
      icon: appState.theme === THEME.DARK ? SunIcon : MoonIcon,
      "data-testid": "toggle-dark-mode",
      shortcut,
      "aria-label": appState.theme === THEME.DARK ? t2("buttons.lightMode") : t2("buttons.darkMode"),
      children: appState.theme === THEME.DARK ? t2("buttons.lightMode") : t2("buttons.darkMode")
    }
  );
};
ToggleTheme.displayName = "ToggleTheme";
var ChangeCanvasBackground = () => {
  const { t: t2 } = useI18n();
  const appState = useUIAppState();
  const actionManager = useExcalidrawActionManager();
  const appProps = useAppProps();
  if (appState.viewModeEnabled || !appProps.UIOptions.canvasActions.changeViewBackgroundColor) {
    return null;
  }
  return /* @__PURE__ */ jsxs58("div", { style: { marginTop: "0.5rem" }, children: [
    /* @__PURE__ */ jsx106(
      "div",
      {
        "data-testid": "canvas-background-label",
        style: { fontSize: ".75rem", marginBottom: ".5rem" },
        children: t2("labels.canvasBackground")
      }
    ),
    /* @__PURE__ */ jsx106("div", { style: { padding: "0 0.625rem" }, children: actionManager.renderAction("changeViewBackgroundColor") })
  ] });
};
ChangeCanvasBackground.displayName = "ChangeCanvasBackground";
var Export = () => {
  const { t: t2 } = useI18n();
  const setAppState = useExcalidrawSetAppState();
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      icon: ExportIcon,
      onSelect: () => {
        setAppState({ openDialog: { name: "jsonExport" } });
      },
      "data-testid": "json-export-button",
      "aria-label": t2("buttons.export"),
      children: t2("buttons.export")
    }
  );
};
Export.displayName = "Export";
var Socials = () => {
  const { t: t2 } = useI18n();
  return /* @__PURE__ */ jsxs58(Fragment17, { children: [
    /* @__PURE__ */ jsx106(
      DropdownMenuItemLink_default,
      {
        icon: GithubIcon,
        href: "https://github.com/excalidraw/excalidraw",
        "aria-label": "GitHub",
        children: "GitHub"
      }
    ),
    /* @__PURE__ */ jsx106(
      DropdownMenuItemLink_default,
      {
        icon: XBrandIcon,
        href: "https://x.com/excalidraw",
        "aria-label": "X",
        children: t2("labels.followUs")
      }
    ),
    /* @__PURE__ */ jsx106(
      DropdownMenuItemLink_default,
      {
        icon: DiscordIcon,
        href: "https://discord.gg/UexuTaE",
        "aria-label": "Discord",
        children: t2("labels.discordChat")
      }
    )
  ] });
};
Socials.displayName = "Socials";
var LiveCollaborationTrigger = ({
  onSelect,
  isCollaborating
}) => {
  const { t: t2 } = useI18n();
  return /* @__PURE__ */ jsx106(
    DropdownMenuItem_default,
    {
      "data-testid": "collab-button",
      icon: usersIcon,
      className: clsx46({
        "active-collab": isCollaborating
      }),
      onSelect,
      children: t2("labels.liveCollaboration")
    }
  );
};
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";

// components/hoc/withInternalFallback.tsx
import { useLayoutEffect as useLayoutEffect8, useRef as useRef28 } from "react";
import { jsx as jsx107 } from "react/jsx-runtime";
var withInternalFallback = (componentName, Component) => {
  const renderAtom = atom(0);
  const WrapperComponent = (props) => {
    const {
      tunnelsJotai: { useAtom: useAtom2 }
    } = useTunnels();
    const [, setCounter] = useAtom2(renderAtom);
    const metaRef = useRef28({
      // flag set on initial render to tell the fallback component to skip the
      // render until mount counter are initialized. This is because the counter
      // is initialized in an effect, and thus we could end rendering both
      // components at the same time until counter is initialized.
      preferHost: false,
      counter: 0
    });
    useLayoutEffect8(() => {
      const meta = metaRef.current;
      setCounter((c) => {
        const next = c + 1;
        meta.counter = next;
        return next;
      });
      return () => {
        setCounter((c) => {
          const next = c - 1;
          meta.counter = next;
          if (!next) {
            meta.preferHost = false;
          }
          return next;
        });
      };
    }, [setCounter]);
    if (!props.__fallback) {
      metaRef.current.preferHost = true;
    }
    if (
      // either before the counters are initialized
      !metaRef.current.counter && props.__fallback && metaRef.current.preferHost || // or after the counters are initialized, and both are rendered
      // (this is the default when host renders as well)
      metaRef.current.counter > 1 && props.__fallback
    ) {
      return null;
    }
    return /* @__PURE__ */ jsx107(Component, { ...props });
  };
  WrapperComponent.displayName = componentName;
  return WrapperComponent;
};

// components/main-menu/MainMenu.tsx
import { jsx as jsx108, jsxs as jsxs59 } from "react/jsx-runtime";
var MainMenu = Object.assign(
  withInternalFallback(
    "MainMenu",
    ({
      children,
      onSelect
    }) => {
      const { MainMenuTunnel } = useTunnels();
      const device = useDevice();
      const appState = useUIAppState();
      const setAppState = useExcalidrawSetAppState();
      const onClickOutside = device.editor.isMobile ? void 0 : () => setAppState({ openMenu: null });
      return /* @__PURE__ */ jsx108(MainMenuTunnel.In, { children: /* @__PURE__ */ jsxs59(DropdownMenu_default, { open: appState.openMenu === "canvas", children: [
        /* @__PURE__ */ jsx108(
          DropdownMenu_default.Trigger,
          {
            onToggle: () => {
              setAppState({
                openMenu: appState.openMenu === "canvas" ? null : "canvas"
              });
            },
            "data-testid": "main-menu-trigger",
            className: "main-menu-trigger",
            children: HamburgerMenuIcon
          }
        ),
        /* @__PURE__ */ jsxs59(
          DropdownMenu_default.Content,
          {
            onClickOutside,
            onSelect: composeEventHandlers(onSelect, () => {
              setAppState({ openMenu: null });
            }),
            children: [
              children,
              device.editor.isMobile && appState.collaborators.size > 0 && /* @__PURE__ */ jsxs59("fieldset", { className: "UserList-Wrapper", children: [
                /* @__PURE__ */ jsx108("legend", { children: t("labels.collaborators") }),
                /* @__PURE__ */ jsx108(
                  UserList,
                  {
                    mobile: true,
                    collaborators: appState.collaborators,
                    userToFollow: appState.userToFollow?.socketId || null
                  }
                )
              ] })
            ]
          }
        )
      ] }) });
    }
  ),
  {
    Trigger: DropdownMenu_default.Trigger,
    Item: DropdownMenu_default.Item,
    ItemLink: DropdownMenu_default.ItemLink,
    ItemCustom: DropdownMenu_default.ItemCustom,
    Group: DropdownMenu_default.Group,
    Separator: DropdownMenu_default.Separator,
    DefaultItems: DefaultItems_exports
  }
);
var MainMenu_default = MainMenu;

// components/OverwriteConfirm/OverwriteConfirmActions.tsx
import { jsx as jsx109, jsxs as jsxs60 } from "react/jsx-runtime";
var Action = ({
  title,
  children,
  actionLabel,
  onClick
}) => {
  return /* @__PURE__ */ jsxs60("div", { className: "OverwriteConfirm__Actions__Action", children: [
    /* @__PURE__ */ jsx109("h4", { children: title }),
    /* @__PURE__ */ jsx109("div", { className: "OverwriteConfirm__Actions__Action__content", children }),
    /* @__PURE__ */ jsx109(
      FilledButton,
      {
        variant: "outlined",
        color: "muted",
        label: actionLabel,
        size: "large",
        fullWidth: true,
        onClick
      }
    )
  ] });
};
var ExportToImage = () => {
  const { t: t2 } = useI18n();
  const actionManager = useExcalidrawActionManager();
  const setAppState = useExcalidrawSetAppState();
  return /* @__PURE__ */ jsx109(
    Action,
    {
      title: t2("overwriteConfirm.action.exportToImage.title"),
      actionLabel: t2("overwriteConfirm.action.exportToImage.button"),
      onClick: () => {
        actionManager.executeAction(actionChangeExportEmbedScene, "ui", true);
        setAppState({ openDialog: { name: "imageExport" } });
      },
      children: t2("overwriteConfirm.action.exportToImage.description")
    }
  );
};
var SaveToDisk = () => {
  const { t: t2 } = useI18n();
  const actionManager = useExcalidrawActionManager();
  return /* @__PURE__ */ jsx109(
    Action,
    {
      title: t2("overwriteConfirm.action.saveToDisk.title"),
      actionLabel: t2("overwriteConfirm.action.saveToDisk.button"),
      onClick: () => {
        actionManager.executeAction(actionSaveFileToDisk, "ui");
      },
      children: t2("overwriteConfirm.action.saveToDisk.description")
    }
  );
};
var Actions = Object.assign(
  ({ children }) => {
    return /* @__PURE__ */ jsx109("div", { className: "OverwriteConfirm__Actions", children });
  },
  {
    ExportToImage,
    SaveToDisk
  }
);

// components/OverwriteConfirm/OverwriteConfirm.tsx
import { jsx as jsx110, jsxs as jsxs61 } from "react/jsx-runtime";
var OverwriteConfirmDialog = Object.assign(
  withInternalFallback(
    "OverwriteConfirmDialog",
    ({ children }) => {
      const { OverwriteConfirmDialogTunnel } = useTunnels();
      const [overwriteConfirmState, setState] = useAtom(
        overwriteConfirmStateAtom
      );
      if (!overwriteConfirmState.active) {
        return null;
      }
      const handleClose = () => {
        overwriteConfirmState.onClose();
        setState((state) => ({ ...state, active: false }));
      };
      const handleConfirm = () => {
        overwriteConfirmState.onConfirm();
        setState((state) => ({ ...state, active: false }));
      };
      return /* @__PURE__ */ jsx110(OverwriteConfirmDialogTunnel.In, { children: /* @__PURE__ */ jsx110(Dialog, { onCloseRequest: handleClose, title: false, size: 916, children: /* @__PURE__ */ jsxs61("div", { className: "OverwriteConfirm", children: [
        /* @__PURE__ */ jsx110("h3", { children: overwriteConfirmState.title }),
        /* @__PURE__ */ jsxs61(
          "div",
          {
            className: `OverwriteConfirm__Description OverwriteConfirm__Description--color-${overwriteConfirmState.color}`,
            children: [
              /* @__PURE__ */ jsx110("div", { className: "OverwriteConfirm__Description__icon", children: alertTriangleIcon }),
              /* @__PURE__ */ jsx110("div", { children: overwriteConfirmState.description }),
              /* @__PURE__ */ jsx110("div", { className: "OverwriteConfirm__Description__spacer" }),
              /* @__PURE__ */ jsx110(
                FilledButton,
                {
                  color: overwriteConfirmState.color,
                  size: "large",
                  label: overwriteConfirmState.actionLabel,
                  onClick: handleConfirm
                }
              )
            ]
          }
        ),
        /* @__PURE__ */ jsx110(Actions, { children })
      ] }) }) });
    }
  ),
  {
    Actions,
    Action
  }
);

// components/DefaultSidebar.tsx
import clsx48 from "clsx";

// components/SearchMenu.tsx
import { Fragment as Fragment18, memo as memo4, useEffect as useEffect33, useRef as useRef29, useState as useState31 } from "react";
import debounce2 from "lodash.debounce";
import clsx47 from "clsx";
import { Fragment as Fragment19, jsx as jsx111, jsxs as jsxs62 } from "react/jsx-runtime";
var searchQueryAtom = atom("");
var searchItemInFocusAtom = atom(null);
var SEARCH_DEBOUNCE = 350;
var SearchMenu2 = () => {
  const app = useApp();
  const setAppState = useExcalidrawSetAppState();
  const searchInputRef = useRef29(null);
  const [inputValue, setInputValue] = useAtom(searchQueryAtom);
  const searchQuery = inputValue.trim();
  const [isSearching, setIsSearching] = useState31(false);
  const [searchMatches, setSearchMatches] = useState31({
    nonce: null,
    items: []
  });
  const searchedQueryRef = useRef29(null);
  const lastSceneNonceRef = useRef29(void 0);
  const [focusIndex, setFocusIndex] = useAtom(searchItemInFocusAtom);
  const elementsMap = app.scene.getNonDeletedElementsMap();
  useEffect33(() => {
    if (isSearching) {
      return;
    }
    if (searchQuery !== searchedQueryRef.current || app.scene.getSceneNonce() !== lastSceneNonceRef.current) {
      searchedQueryRef.current = null;
      handleSearch(searchQuery, app, (matchItems, index) => {
        setSearchMatches({
          nonce: randomInteger(),
          items: matchItems
        });
        searchedQueryRef.current = searchQuery;
        lastSceneNonceRef.current = app.scene.getSceneNonce();
        setAppState({
          searchMatches: matchItems.map((searchMatch) => ({
            id: searchMatch.textElement.id,
            focus: false,
            matchedLines: searchMatch.matchedLines
          }))
        });
      });
    }
  }, [
    isSearching,
    searchQuery,
    elementsMap,
    app,
    setAppState,
    setFocusIndex,
    lastSceneNonceRef
  ]);
  const goToNextItem = () => {
    if (searchMatches.items.length > 0) {
      setFocusIndex((focusIndex2) => {
        if (focusIndex2 === null) {
          return 0;
        }
        return (focusIndex2 + 1) % searchMatches.items.length;
      });
    }
  };
  const goToPreviousItem = () => {
    if (searchMatches.items.length > 0) {
      setFocusIndex((focusIndex2) => {
        if (focusIndex2 === null) {
          return 0;
        }
        return focusIndex2 - 1 < 0 ? searchMatches.items.length - 1 : focusIndex2 - 1;
      });
    }
  };
  useEffect33(() => {
    setAppState((state) => {
      return {
        searchMatches: state.searchMatches.map((match, index) => {
          if (index === focusIndex) {
            return { ...match, focus: true };
          }
          return { ...match, focus: false };
        })
      };
    });
  }, [focusIndex, setAppState]);
  useEffect33(() => {
    if (searchMatches.items.length > 0 && focusIndex !== null) {
      const match = searchMatches.items[focusIndex];
      if (match) {
        const zoomValue = app.state.zoom.value;
        const matchAsElement = newTextElement({
          text: match.searchQuery,
          x: match.textElement.x + (match.matchedLines[0]?.offsetX ?? 0),
          y: match.textElement.y + (match.matchedLines[0]?.offsetY ?? 0),
          width: match.matchedLines[0]?.width,
          height: match.matchedLines[0]?.height,
          fontSize: match.textElement.fontSize,
          fontFamily: match.textElement.fontFamily
        });
        const FONT_SIZE_LEGIBILITY_THRESHOLD = 14;
        const fontSize = match.textElement.fontSize;
        const isTextTiny = fontSize * zoomValue < FONT_SIZE_LEGIBILITY_THRESHOLD;
        if (!isElementCompletelyInViewport(
          [matchAsElement],
          app.canvas.width / window.devicePixelRatio,
          app.canvas.height / window.devicePixelRatio,
          {
            offsetLeft: app.state.offsetLeft,
            offsetTop: app.state.offsetTop,
            scrollX: app.state.scrollX,
            scrollY: app.state.scrollY,
            zoom: app.state.zoom
          },
          app.scene.getNonDeletedElementsMap(),
          app.getEditorUIOffsets()
        ) || isTextTiny) {
          let zoomOptions;
          if (isTextTiny) {
            if (fontSize >= FONT_SIZE_LEGIBILITY_THRESHOLD) {
              zoomOptions = { fitToContent: true };
            } else {
              zoomOptions = {
                fitToViewport: true,
                // calculate zoom level to make the fontSize ~equal to FONT_SIZE_THRESHOLD, rounded to nearest 10%
                maxZoom: round(FONT_SIZE_LEGIBILITY_THRESHOLD / fontSize, 1)
              };
            }
          } else {
            zoomOptions = { fitToContent: true };
          }
          app.scrollToContent(matchAsElement, {
            animate: true,
            duration: 300,
            ...zoomOptions,
            canvasOffsets: app.getEditorUIOffsets()
          });
        }
      }
    }
  }, [focusIndex, searchMatches, app]);
  useEffect33(() => {
    return () => {
      setFocusIndex(null);
      searchedQueryRef.current = null;
      lastSceneNonceRef.current = void 0;
      setAppState({
        searchMatches: []
      });
      setIsSearching(false);
    };
  }, [setAppState, setFocusIndex]);
  const stableState = useStable({
    goToNextItem,
    goToPreviousItem,
    searchMatches
  });
  useEffect33(() => {
    const eventHandler = (event) => {
      if (event.key === KEYS.ESCAPE && !app.state.openDialog && !app.state.openPopup) {
        event.preventDefault();
        event.stopPropagation();
        setAppState({
          openSidebar: null
        });
        return;
      }
      if (event[KEYS.CTRL_OR_CMD] && event.key === KEYS.F) {
        event.preventDefault();
        event.stopPropagation();
        if (!searchInputRef.current?.matches(":focus")) {
          if (app.state.openDialog) {
            setAppState({
              openDialog: null
            });
          }
          searchInputRef.current?.focus();
          searchInputRef.current?.select();
        } else {
          setAppState({
            openSidebar: null
          });
        }
      }
      if (event.target instanceof HTMLElement && event.target.closest(".layer-ui__search")) {
        if (stableState.searchMatches.items.length) {
          if (event.key === KEYS.ENTER) {
            event.stopPropagation();
            stableState.goToNextItem();
          }
          if (event.key === KEYS.ARROW_UP) {
            event.stopPropagation();
            stableState.goToPreviousItem();
          } else if (event.key === KEYS.ARROW_DOWN) {
            event.stopPropagation();
            stableState.goToNextItem();
          }
        }
      }
    };
    return addEventListener(window, "keydown" /* KEYDOWN */, eventHandler, {
      capture: true,
      passive: false
    });
  }, [setAppState, stableState, app]);
  const matchCount = `${searchMatches.items.length} ${searchMatches.items.length === 1 ? t("search.singleResult") : t("search.multipleResults")}`;
  return /* @__PURE__ */ jsxs62("div", { className: "layer-ui__search", children: [
    /* @__PURE__ */ jsx111("div", { className: "layer-ui__search-header", children: /* @__PURE__ */ jsx111(
      TextField,
      {
        className: CLASSES.SEARCH_MENU_INPUT_WRAPPER,
        value: inputValue,
        ref: searchInputRef,
        placeholder: t("search.placeholder"),
        icon: searchIcon,
        onChange: (value) => {
          setInputValue(value);
          setIsSearching(true);
          const searchQuery2 = value.trim();
          handleSearch(searchQuery2, app, (matchItems, index) => {
            setSearchMatches({
              nonce: randomInteger(),
              items: matchItems
            });
            setFocusIndex(index);
            searchedQueryRef.current = searchQuery2;
            lastSceneNonceRef.current = app.scene.getSceneNonce();
            setAppState({
              searchMatches: matchItems.map((searchMatch) => ({
                id: searchMatch.textElement.id,
                focus: false,
                matchedLines: searchMatch.matchedLines
              }))
            });
            setIsSearching(false);
          });
        },
        selectOnRender: true
      }
    ) }),
    /* @__PURE__ */ jsxs62("div", { className: "layer-ui__search-count", children: [
      searchMatches.items.length > 0 && /* @__PURE__ */ jsxs62(Fragment19, { children: [
        focusIndex !== null && focusIndex > -1 ? /* @__PURE__ */ jsxs62("div", { children: [
          focusIndex + 1,
          " / ",
          matchCount
        ] }) : /* @__PURE__ */ jsx111("div", { children: matchCount }),
        /* @__PURE__ */ jsxs62("div", { className: "result-nav", children: [
          /* @__PURE__ */ jsx111(
            Button,
            {
              onSelect: () => {
                goToNextItem();
              },
              className: "result-nav-btn",
              children: collapseDownIcon
            }
          ),
          /* @__PURE__ */ jsx111(
            Button,
            {
              onSelect: () => {
                goToPreviousItem();
              },
              className: "result-nav-btn",
              children: upIcon
            }
          )
        ] })
      ] }),
      searchMatches.items.length === 0 && searchQuery && searchedQueryRef.current && /* @__PURE__ */ jsx111("div", { style: { margin: "1rem auto" }, children: t("search.noMatch") })
    ] }),
    /* @__PURE__ */ jsx111(
      MatchList,
      {
        matches: searchMatches,
        onItemClick: setFocusIndex,
        focusIndex,
        searchQuery
      }
    )
  ] });
};
var ListItem = (props) => {
  const preview = [
    props.preview.moreBefore ? "..." : "",
    props.preview.previewText.slice(0, props.preview.indexInSearchQuery),
    props.preview.previewText.slice(
      props.preview.indexInSearchQuery,
      props.preview.indexInSearchQuery + props.searchQuery.length
    ),
    props.preview.previewText.slice(
      props.preview.indexInSearchQuery + props.searchQuery.length
    ),
    props.preview.moreAfter ? "..." : ""
  ];
  return /* @__PURE__ */ jsx111(
    "div",
    {
      tabIndex: -1,
      className: clsx47("layer-ui__result-item", {
        active: props.highlighted
      }),
      onClick: props.onClick,
      ref: (ref) => {
        if (props.highlighted) {
          ref?.scrollIntoView({ behavior: "auto", block: "nearest" });
        }
      },
      children: /* @__PURE__ */ jsx111("div", { className: "preview-text", children: preview.flatMap((text, idx) => /* @__PURE__ */ jsx111(Fragment18, { children: idx === 2 ? /* @__PURE__ */ jsx111("b", { children: text }) : text }, idx)) })
    }
  );
};
var MatchListBase = (props) => {
  return /* @__PURE__ */ jsx111("div", { className: "layer-ui__search-result-container", children: props.matches.items.map((searchMatch, index) => /* @__PURE__ */ jsx111(
    ListItem,
    {
      searchQuery: props.searchQuery,
      preview: searchMatch.preview,
      highlighted: index === props.focusIndex,
      onClick: () => props.onItemClick(index)
    },
    searchMatch.textElement.id + searchMatch.index
  )) });
};
var areEqual = (prevProps, nextProps) => {
  return prevProps.matches.nonce === nextProps.matches.nonce && prevProps.focusIndex === nextProps.focusIndex;
};
var MatchList = memo4(MatchListBase, areEqual);
var getMatchPreview = (text, index, searchQuery) => {
  const WORDS_BEFORE = 2;
  const WORDS_AFTER = 5;
  const substrBeforeQuery = text.slice(0, index);
  const wordsBeforeQuery = substrBeforeQuery.split(/\s+/);
  const isQueryCompleteBefore = substrBeforeQuery.endsWith(" ");
  const startWordIndex = wordsBeforeQuery.length - WORDS_BEFORE - 1 - (isQueryCompleteBefore ? 0 : 1);
  let wordsBeforeAsString = wordsBeforeQuery.slice(startWordIndex <= 0 ? 0 : startWordIndex).join(" ") + (isQueryCompleteBefore ? " " : "");
  const MAX_ALLOWED_CHARS = 20;
  wordsBeforeAsString = wordsBeforeAsString.length > MAX_ALLOWED_CHARS ? wordsBeforeAsString.slice(-MAX_ALLOWED_CHARS) : wordsBeforeAsString;
  const substrAfterQuery = text.slice(index + searchQuery.length);
  const wordsAfter = substrAfterQuery.split(/\s+/);
  const isQueryCompleteAfter = !substrAfterQuery.startsWith(" ");
  const numberOfWordsToTake = isQueryCompleteAfter ? WORDS_AFTER + 1 : WORDS_AFTER;
  const wordsAfterAsString = (isQueryCompleteAfter ? "" : " ") + wordsAfter.slice(0, numberOfWordsToTake).join(" ");
  return {
    indexInSearchQuery: wordsBeforeAsString.length,
    previewText: wordsBeforeAsString + searchQuery + wordsAfterAsString,
    moreBefore: startWordIndex > 0,
    moreAfter: wordsAfter.length > numberOfWordsToTake
  };
};
var normalizeWrappedText = (wrappedText, originalText) => {
  const wrappedLines = wrappedText.split("\n");
  const normalizedLines = [];
  let originalIndex = 0;
  for (let i = 0; i < wrappedLines.length; i++) {
    let currentLine = wrappedLines[i];
    const nextLine = wrappedLines[i + 1];
    if (nextLine) {
      const nextLineIndexInOriginal = originalText.indexOf(
        nextLine,
        originalIndex
      );
      if (nextLineIndexInOriginal > currentLine.length + originalIndex) {
        let j = nextLineIndexInOriginal - (currentLine.length + originalIndex);
        while (j > 0) {
          currentLine += " ";
          j--;
        }
      }
    }
    normalizedLines.push(currentLine);
    originalIndex = originalIndex + currentLine.length;
  }
  return normalizedLines.join("\n");
};
var getMatchedLines = (textElement, searchQuery, index) => {
  const normalizedText = normalizeWrappedText(
    textElement.text,
    textElement.originalText
  );
  const lines = normalizedText.split("\n");
  const lineIndexRanges = [];
  let currentIndex = 0;
  let lineNumber = 0;
  for (const line of lines) {
    const startIndex2 = currentIndex;
    const endIndex = startIndex2 + line.length - 1;
    lineIndexRanges.push({
      line,
      startIndex: startIndex2,
      endIndex,
      lineNumber
    });
    currentIndex = endIndex + 1;
    lineNumber++;
  }
  let startIndex = index;
  let remainingQuery = textElement.originalText.slice(
    index,
    index + searchQuery.length
  );
  const matchedLines = [];
  for (const lineIndexRange of lineIndexRanges) {
    if (remainingQuery === "") {
      break;
    }
    if (startIndex >= lineIndexRange.startIndex && startIndex <= lineIndexRange.endIndex) {
      const matchCapacity = lineIndexRange.endIndex + 1 - startIndex;
      const textToStart = lineIndexRange.line.slice(
        0,
        startIndex - lineIndexRange.startIndex
      );
      const matchedWord = remainingQuery.slice(0, matchCapacity);
      remainingQuery = remainingQuery.slice(matchCapacity);
      const offset = measureText(
        textToStart,
        getFontString(textElement),
        textElement.lineHeight
      );
      if (textToStart === "") {
        offset.width = 0;
      }
      if (textElement.textAlign !== "left" && lineIndexRange.line.length > 0) {
        const lineLength = measureText(
          lineIndexRange.line,
          getFontString(textElement),
          textElement.lineHeight
        );
        const spaceToStart = textElement.textAlign === "center" ? (textElement.width - lineLength.width) / 2 : textElement.width - lineLength.width;
        offset.width += spaceToStart;
      }
      const { width, height } = measureText(
        matchedWord,
        getFontString(textElement),
        textElement.lineHeight
      );
      const offsetX = offset.width;
      const offsetY = lineIndexRange.lineNumber * offset.height;
      matchedLines.push({
        offsetX,
        offsetY,
        width,
        height
      });
      startIndex += matchCapacity;
    }
  }
  return matchedLines;
};
var escapeSpecialCharacters = (string) => {
  return string.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
};
var handleSearch = debounce2(
  (searchQuery, app, cb) => {
    if (!searchQuery || searchQuery === "") {
      cb([], null);
      return;
    }
    const elements = app.scene.getNonDeletedElements();
    const texts = elements.filter(
      (el) => isTextElement(el)
    );
    texts.sort((a, b) => a.y - b.y);
    const matchItems = [];
    const regex = new RegExp(escapeSpecialCharacters(searchQuery), "gi");
    for (const textEl of texts) {
      let match = null;
      const text = textEl.originalText;
      while ((match = regex.exec(text)) !== null) {
        const preview = getMatchPreview(text, match.index, searchQuery);
        const matchedLines = getMatchedLines(textEl, searchQuery, match.index);
        if (matchedLines.length > 0) {
          matchItems.push({
            textElement: textEl,
            searchQuery,
            preview,
            index: match.index,
            matchedLines
          });
        }
      }
    }
    const visibleIds = new Set(
      app.visibleElements.map((visibleElement) => visibleElement.id)
    );
    const focusIndex = matchItems.findIndex(
      (matchItem) => visibleIds.has(matchItem.textElement.id)
    ) ?? null;
    cb(matchItems, focusIndex);
  },
  SEARCH_DEBOUNCE
);

// components/DefaultSidebar.tsx
import { jsx as jsx112, jsxs as jsxs63 } from "react/jsx-runtime";
import { createElement as createElement2 } from "react";
var DefaultSidebarTrigger = withInternalFallback(
  "DefaultSidebarTrigger",
  (props) => {
    const { DefaultSidebarTriggerTunnel } = useTunnels();
    return /* @__PURE__ */ jsx112(DefaultSidebarTriggerTunnel.In, { children: /* @__PURE__ */ jsx112(
      Sidebar.Trigger,
      {
        ...props,
        className: "default-sidebar-trigger",
        name: DEFAULT_SIDEBAR.name
      }
    ) });
  }
);
DefaultSidebarTrigger.displayName = "DefaultSidebarTrigger";
var DefaultTabTriggers = ({ children }) => {
  const { DefaultSidebarTabTriggersTunnel } = useTunnels();
  return /* @__PURE__ */ jsx112(DefaultSidebarTabTriggersTunnel.In, { children });
};
DefaultTabTriggers.displayName = "DefaultTabTriggers";
var DefaultSidebar = Object.assign(
  withInternalFallback(
    "DefaultSidebar",
    ({
      children,
      className,
      onDock,
      docked,
      ...rest
    }) => {
      const appState = useUIAppState();
      const setAppState = useExcalidrawSetAppState();
      const { DefaultSidebarTabTriggersTunnel } = useTunnels();
      const isForceDocked = appState.openSidebar?.tab === CANVAS_SEARCH_TAB;
      return /* @__PURE__ */ createElement2(
        Sidebar,
        {
          ...rest,
          name: "default",
          key: "default",
          className: clsx48("default-sidebar", className),
          docked: isForceDocked || (docked ?? appState.defaultSidebarDockedPreference),
          onDock: (
            // `onDock=false` disables docking.
            // if `docked` passed, but no onDock passed, disable manual docking.
            isForceDocked || onDock === false || !onDock && docked != null ? void 0 : (
              // compose to allow the host app to listen on default behavior
              composeEventHandlers(onDock, (docked2) => {
                setAppState({ defaultSidebarDockedPreference: docked2 });
              })
            )
          )
        },
        /* @__PURE__ */ jsxs63(Sidebar.Tabs, { children: [
          /* @__PURE__ */ jsx112(Sidebar.Header, { children: /* @__PURE__ */ jsxs63(Sidebar.TabTriggers, { children: [
            /* @__PURE__ */ jsx112(Sidebar.TabTrigger, { tab: CANVAS_SEARCH_TAB, children: searchIcon }),
            /* @__PURE__ */ jsx112(Sidebar.TabTrigger, { tab: LIBRARY_SIDEBAR_TAB, children: LibraryIcon }),
            /* @__PURE__ */ jsx112(DefaultSidebarTabTriggersTunnel.Out, {})
          ] }) }),
          /* @__PURE__ */ jsx112(Sidebar.Tab, { tab: LIBRARY_SIDEBAR_TAB, children: /* @__PURE__ */ jsx112(LibraryMenu, {}) }),
          /* @__PURE__ */ jsx112(Sidebar.Tab, { tab: CANVAS_SEARCH_TAB, children: /* @__PURE__ */ jsx112(SearchMenu2, {}) }),
          children
        ] })
      );
    }
  ),
  {
    Trigger: DefaultSidebarTrigger,
    TabTriggers: DefaultTabTriggers
  }
);

// components/LaserPointerButton.tsx
import clsx49 from "clsx";
import { jsx as jsx113, jsxs as jsxs64 } from "react/jsx-runtime";
var DEFAULT_SIZE3 = "small";
var LaserPointerButton = (props) => {
  return /* @__PURE__ */ jsxs64(
    "label",
    {
      className: clsx49(
        "ToolIcon ToolIcon__LaserPointer",
        `ToolIcon_size_${DEFAULT_SIZE3}`,
        {
          "is-mobile": props.isMobile
        }
      ),
      title: `${props.title}`,
      children: [
        /* @__PURE__ */ jsx113(
          "input",
          {
            className: "ToolIcon_type_checkbox",
            type: "checkbox",
            name: props.name,
            onChange: props.onChange,
            checked: props.checked,
            "aria-label": props.title,
            "data-testid": "toolbar-LaserPointer"
          }
        ),
        /* @__PURE__ */ jsx113("div", { className: "ToolIcon__icon", children: laserPointerToolIcon })
      ]
    }
  );
};

// components/TTDDialog/MermaidToExcalidraw.tsx
import { useState as useState32, useRef as useRef31, useEffect as useEffect35, useDeferredValue } from "react";

// components/TTDDialog/common.ts
var resetPreview = ({
  canvasRef,
  setError
}) => {
  const canvasNode = canvasRef.current;
  if (!canvasNode) {
    return;
  }
  const parent = canvasNode.parentElement;
  if (!parent) {
    return;
  }
  parent.style.background = "";
  setError(null);
  canvasNode.replaceChildren();
};
var convertMermaidToExcalidraw = async ({
  canvasRef,
  mermaidToExcalidrawLib,
  mermaidDefinition,
  setError,
  data
}) => {
  const canvasNode = canvasRef.current;
  const parent = canvasNode?.parentElement;
  if (!canvasNode || !parent) {
    return;
  }
  if (!mermaidDefinition) {
    resetPreview({ canvasRef, setError });
    return;
  }
  try {
    const api = await mermaidToExcalidrawLib.api;
    let ret;
    try {
      ret = await api.parseMermaidToExcalidraw(mermaidDefinition);
    } catch (err) {
      ret = await api.parseMermaidToExcalidraw(
        mermaidDefinition.replace(/"/g, "'")
      );
    }
    const { elements, files } = ret;
    setError(null);
    data.current = {
      elements: convertToExcalidrawElements(elements, {
        regenerateIds: true
      }),
      files
    };
    const canvas = await exportToCanvas2({
      elements: data.current.elements,
      files: data.current.files,
      exportPadding: DEFAULT_EXPORT_PADDING,
      maxWidthOrHeight: Math.max(parent.offsetWidth, parent.offsetHeight) * window.devicePixelRatio
    });
    try {
      await canvasToBlob(canvas);
    } catch (e) {
      if (e.name === "CANVAS_POSSIBLY_TOO_BIG") {
        throw new Error(t("canvasError.canvasTooBig"));
      }
      throw e;
    }
    parent.style.background = "var(--default-bg-color)";
    canvasNode.replaceChildren(canvas);
  } catch (err) {
    parent.style.background = "var(--default-bg-color)";
    if (mermaidDefinition) {
      setError(err);
    }
    throw err;
  }
};
var saveMermaidDataToStorage = (mermaidDefinition) => {
  EditorLocalStorage.set(
    EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW,
    mermaidDefinition
  );
};
var insertToEditor = ({
  app,
  data,
  text,
  shouldSaveMermaidDataToStorage
}) => {
  const { elements: newElements, files } = data.current;
  if (!newElements.length) {
    return;
  }
  app.addElementsFromPasteOrLibrary({
    elements: newElements,
    files,
    position: "center",
    fitToContent: true
  });
  app.setOpenDialog(null);
  if (shouldSaveMermaidDataToStorage && text) {
    saveMermaidDataToStorage(text);
  }
};

// components/TTDDialog/TTDDialogPanels.tsx
import { jsx as jsx114 } from "react/jsx-runtime";
var TTDDialogPanels = ({ children }) => {
  return /* @__PURE__ */ jsx114("div", { className: "ttd-dialog-panels", children });
};

// components/TTDDialog/TTDDialogPanel.tsx
import clsx50 from "clsx";
import { jsx as jsx115, jsxs as jsxs65 } from "react/jsx-runtime";
var TTDDialogPanel = ({
  label,
  children,
  panelAction,
  panelActionDisabled = false,
  onTextSubmitInProgess,
  renderTopRight,
  renderSubmitShortcut,
  renderBottomRight
}) => {
  return /* @__PURE__ */ jsxs65("div", { className: "ttd-dialog-panel", children: [
    /* @__PURE__ */ jsxs65("div", { className: "ttd-dialog-panel__header", children: [
      /* @__PURE__ */ jsx115("label", { children: label }),
      renderTopRight?.()
    ] }),
    children,
    /* @__PURE__ */ jsxs65(
      "div",
      {
        className: clsx50("ttd-dialog-panel-button-container", {
          invisible: !panelAction
        }),
        style: { display: "flex", alignItems: "center" },
        children: [
          /* @__PURE__ */ jsxs65(
            Button,
            {
              className: "ttd-dialog-panel-button",
              onSelect: panelAction ? panelAction.action : () => {
              },
              disabled: panelActionDisabled || onTextSubmitInProgess,
              children: [
                /* @__PURE__ */ jsxs65("div", { className: clsx50({ invisible: onTextSubmitInProgess }), children: [
                  panelAction?.label,
                  panelAction?.icon && /* @__PURE__ */ jsx115("span", { children: panelAction.icon })
                ] }),
                onTextSubmitInProgess && /* @__PURE__ */ jsx115(Spinner_default, {})
              ]
            }
          ),
          !panelActionDisabled && !onTextSubmitInProgess && renderSubmitShortcut?.(),
          renderBottomRight?.()
        ]
      }
    )
  ] });
};

// components/TTDDialog/TTDDialogInput.tsx
import { useEffect as useEffect34, useRef as useRef30 } from "react";
import { jsx as jsx116 } from "react/jsx-runtime";
var TTDDialogInput = ({
  input,
  placeholder,
  onChange,
  onKeyboardSubmit
}) => {
  const ref = useRef30(null);
  const callbackRef = useRef30(onKeyboardSubmit);
  callbackRef.current = onKeyboardSubmit;
  useEffect34(() => {
    if (!callbackRef.current) {
      return;
    }
    const textarea = ref.current;
    if (textarea) {
      const handleKeyDown = (event) => {
        if (event[KEYS.CTRL_OR_CMD] && event.key === KEYS.ENTER) {
          event.preventDefault();
          callbackRef.current?.();
        }
      };
      textarea.addEventListener("keydown" /* KEYDOWN */, handleKeyDown);
      return () => {
        textarea.removeEventListener("keydown" /* KEYDOWN */, handleKeyDown);
      };
    }
  }, []);
  return /* @__PURE__ */ jsx116(
    "textarea",
    {
      className: "ttd-dialog-input",
      onChange,
      value: input,
      placeholder,
      autoFocus: true,
      ref
    }
  );
};

// components/TTDDialog/TTDDialogOutput.tsx
import { jsx as jsx117, jsxs as jsxs66 } from "react/jsx-runtime";
var ErrorComp = ({ error }) => {
  return /* @__PURE__ */ jsxs66(
    "div",
    {
      "data-testid": "ttd-dialog-output-error",
      className: "ttd-dialog-output-error",
      children: [
        "Error! ",
        /* @__PURE__ */ jsx117("p", { children: error })
      ]
    }
  );
};
var TTDDialogOutput = ({
  error,
  canvasRef,
  loaded
}) => {
  return /* @__PURE__ */ jsxs66("div", { className: "ttd-dialog-output-wrapper", children: [
    error && /* @__PURE__ */ jsx117(ErrorComp, { error: error.message }),
    loaded ? /* @__PURE__ */ jsx117(
      "div",
      {
        ref: canvasRef,
        style: { opacity: error ? "0.15" : 1 },
        className: "ttd-dialog-output-canvas-container"
      }
    ) : /* @__PURE__ */ jsx117(Spinner_default, { size: "2rem" })
  ] });
};

// components/TTDDialog/TTDDialogSubmitShortcut.tsx
import { jsx as jsx118, jsxs as jsxs67 } from "react/jsx-runtime";
var TTDDialogSubmitShortcut = () => {
  return /* @__PURE__ */ jsxs67("div", { className: "ttd-dialog-submit-shortcut", children: [
    /* @__PURE__ */ jsx118("div", { className: "ttd-dialog-submit-shortcut__key", children: getShortcutKey("CtrlOrCmd") }),
    /* @__PURE__ */ jsx118("div", { className: "ttd-dialog-submit-shortcut__key", children: getShortcutKey("Enter") })
  ] });
};

// components/TTDDialog/MermaidToExcalidraw.tsx
import { Fragment as Fragment20, jsx as jsx119, jsxs as jsxs68 } from "react/jsx-runtime";
var MERMAID_EXAMPLE = "flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]";
var debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300);
var MermaidToExcalidraw = ({
  mermaidToExcalidrawLib
}) => {
  const [text, setText] = useState32(
    () => EditorLocalStorage.get(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) || MERMAID_EXAMPLE
  );
  const deferredText = useDeferredValue(text.trim());
  const [error, setError] = useState32(null);
  const canvasRef = useRef31(null);
  const data = useRef31({ elements: [], files: null });
  const app = useApp();
  useEffect35(() => {
    convertMermaidToExcalidraw({
      canvasRef,
      data,
      mermaidToExcalidrawLib,
      setError,
      mermaidDefinition: deferredText
    }).catch((err) => {
      if (isDevEnv()) {
        console.error("Failed to parse mermaid definition", err);
      }
    });
    debouncedSaveMermaidDefinition(deferredText);
  }, [deferredText, mermaidToExcalidrawLib]);
  useEffect35(
    () => () => {
      debouncedSaveMermaidDefinition.flush();
    },
    []
  );
  const onInsertToEditor = () => {
    insertToEditor({
      app,
      data,
      text,
      shouldSaveMermaidDataToStorage: true
    });
  };
  return /* @__PURE__ */ jsxs68(Fragment20, { children: [
    /* @__PURE__ */ jsx119("div", { className: "ttd-dialog-desc", children: /* @__PURE__ */ jsx119(
      Trans_default,
      {
        i18nKey: "mermaid.description",
        flowchartLink: (el) => /* @__PURE__ */ jsx119("a", { href: "https://mermaid.js.org/syntax/flowchart.html", children: el }),
        sequenceLink: (el) => /* @__PURE__ */ jsx119("a", { href: "https://mermaid.js.org/syntax/sequenceDiagram.html", children: el }),
        classLink: (el) => /* @__PURE__ */ jsx119("a", { href: "https://mermaid.js.org/syntax/classDiagram.html", children: el })
      }
    ) }),
    /* @__PURE__ */ jsxs68(TTDDialogPanels, { children: [
      /* @__PURE__ */ jsx119(TTDDialogPanel, { label: t("mermaid.syntax"), children: /* @__PURE__ */ jsx119(
        TTDDialogInput,
        {
          input: text,
          placeholder: "Write Mermaid diagram defintion here...",
          onChange: (event) => setText(event.target.value),
          onKeyboardSubmit: () => {
            onInsertToEditor();
          }
        }
      ) }),
      /* @__PURE__ */ jsx119(
        TTDDialogPanel,
        {
          label: t("mermaid.preview"),
          panelAction: {
            action: () => {
              onInsertToEditor();
            },
            label: t("mermaid.button"),
            icon: ArrowRightIcon
          },
          renderSubmitShortcut: () => /* @__PURE__ */ jsx119(TTDDialogSubmitShortcut, {}),
          children: /* @__PURE__ */ jsx119(
            TTDDialogOutput,
            {
              canvasRef,
              loaded: mermaidToExcalidrawLib.loaded,
              error
            }
          )
        }
      )
    ] })
  ] });
};
var MermaidToExcalidraw_default = MermaidToExcalidraw;

// components/TTDDialog/TTDDialogTabs.tsx
import * as RadixTabs5 from "@radix-ui/react-tabs";
import { useRef as useRef32 } from "react";
import { jsx as jsx120 } from "react/jsx-runtime";
var TTDDialogTabs = (props) => {
  const setAppState = useExcalidrawSetAppState();
  const rootRef = useRef32(null);
  const minHeightRef = useRef32(0);
  return /* @__PURE__ */ jsx120(
    RadixTabs5.Root,
    {
      ref: rootRef,
      className: "ttd-dialog-tabs-root",
      value: props.tab,
      onValueChange: (tab) => {
        if (!tab) {
          return;
        }
        const modalContentNode = rootRef.current?.closest(".Modal__content");
        if (modalContentNode) {
          const currHeight = modalContentNode.offsetHeight || 0;
          if (currHeight > minHeightRef.current) {
            minHeightRef.current = currHeight;
            modalContentNode.style.minHeight = `min(${minHeightRef.current}px, 100%)`;
          }
        }
        if (props.dialog === "ttd" && isMemberOf(["text-to-diagram", "mermaid"], tab)) {
          setAppState({
            openDialog: { name: props.dialog, tab }
          });
        }
      },
      children: props.children
    }
  );
};
TTDDialogTabs.displayName = "TTDDialogTabs";
var TTDDialogTabs_default = TTDDialogTabs;

// components/TTDDialog/TTDDialog.tsx
import { useEffect as useEffect36, useRef as useRef33, useState as useState33 } from "react";

// components/TTDDialog/TTDDialogTabTriggers.tsx
import * as RadixTabs6 from "@radix-ui/react-tabs";
import { jsx as jsx121 } from "react/jsx-runtime";
var TTDDialogTabTriggers = ({
  children,
  ...rest
}) => {
  return /* @__PURE__ */ jsx121(RadixTabs6.List, { className: "ttd-dialog-triggers", ...rest, children });
};
TTDDialogTabTriggers.displayName = "TTDDialogTabTriggers";

// components/TTDDialog/TTDDialogTabTrigger.tsx
import * as RadixTabs7 from "@radix-ui/react-tabs";
import { jsx as jsx122 } from "react/jsx-runtime";
var TTDDialogTabTrigger = ({
  children,
  tab,
  onSelect,
  ...rest
}) => {
  return /* @__PURE__ */ jsx122(RadixTabs7.Trigger, { value: tab, asChild: true, onSelect, children: /* @__PURE__ */ jsx122("button", { type: "button", className: "ttd-dialog-tab-trigger", ...rest, children }) });
};
TTDDialogTabTrigger.displayName = "TTDDialogTabTrigger";

// components/TTDDialog/TTDDialogTab.tsx
import * as RadixTabs8 from "@radix-ui/react-tabs";
import { jsx as jsx123 } from "react/jsx-runtime";
var TTDDialogTab = ({
  tab,
  children,
  ...rest
}) => {
  return /* @__PURE__ */ jsx123(RadixTabs8.Content, { ...rest, value: tab, children });
};
TTDDialogTab.displayName = "TTDDialogTab";

// components/TTDDialog/TTDDialog.tsx
import { jsx as jsx124, jsxs as jsxs69 } from "react/jsx-runtime";
var MIN_PROMPT_LENGTH = 3;
var MAX_PROMPT_LENGTH = 1e3;
var rateLimitsAtom = atom(null);
var ttdGenerationAtom = atom(null);
var TTDDialog = (props) => {
  const appState = useUIAppState();
  if (appState.openDialog?.name !== "ttd") {
    return null;
  }
  return /* @__PURE__ */ jsx124(TTDDialogBase, { ...props, tab: appState.openDialog.tab });
};
var TTDDialogBase = withInternalFallback(
  "TTDDialogBase",
  ({
    tab,
    ...rest
  }) => {
    const app = useApp();
    const setAppState = useExcalidrawSetAppState();
    const someRandomDivRef = useRef33(null);
    const [ttdGeneration, setTtdGeneration] = useAtom(ttdGenerationAtom);
    const [text, setText] = useState33(ttdGeneration?.prompt ?? "");
    const prompt = text.trim();
    const handleTextChange = (event) => {
      setText(event.target.value);
      setTtdGeneration((s) => ({
        generatedResponse: s?.generatedResponse ?? null,
        prompt: event.target.value
      }));
    };
    const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState33(false);
    const [rateLimits, setRateLimits] = useAtom(rateLimitsAtom);
    const onGenerate = async () => {
      if (prompt.length > MAX_PROMPT_LENGTH || prompt.length < MIN_PROMPT_LENGTH || onTextSubmitInProgess || rateLimits?.rateLimitRemaining === 0 || // means this is not a text-to-diagram dialog (needed for TS only)
      "__fallback" in rest) {
        if (prompt.length < MIN_PROMPT_LENGTH) {
          setError(
            new Error(
              `Prompt is too short (min ${MIN_PROMPT_LENGTH} characters)`
            )
          );
        }
        if (prompt.length > MAX_PROMPT_LENGTH) {
          setError(
            new Error(
              `Prompt is too long (max ${MAX_PROMPT_LENGTH} characters)`
            )
          );
        }
        return;
      }
      try {
        setOnTextSubmitInProgess(true);
        trackEvent("ai", "generate", "ttd");
        const { generatedResponse, error: error2, rateLimit, rateLimitRemaining } = await rest.onTextSubmit(prompt);
        if (typeof generatedResponse === "string") {
          setTtdGeneration((s) => ({
            generatedResponse,
            prompt: s?.prompt ?? null
          }));
        }
        if (isFiniteNumber(rateLimit) && isFiniteNumber(rateLimitRemaining)) {
          setRateLimits({ rateLimit, rateLimitRemaining });
        }
        if (error2) {
          setError(error2);
          return;
        }
        if (!generatedResponse) {
          setError(new Error("Generation failed"));
          return;
        }
        try {
          await convertMermaidToExcalidraw({
            canvasRef: someRandomDivRef,
            data,
            mermaidToExcalidrawLib,
            setError,
            mermaidDefinition: generatedResponse
          });
          trackEvent("ai", "mermaid parse success", "ttd");
        } catch (error3) {
          console.info(
            `%cTTD mermaid render errror: ${error3.message}`,
            "color: red"
          );
          console.info(
            `>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
TTD mermaid definition render errror: ${error3.message}`,
            "color: yellow"
          );
          trackEvent("ai", "mermaid parse failed", "ttd");
          setError(
            new Error(
              "Generated an invalid diagram :(. You may also try a different prompt."
            )
          );
        }
      } catch (error2) {
        let message = error2.message;
        if (!message || message === "Failed to fetch") {
          message = "Request failed";
        }
        setError(new Error(message));
      } finally {
        setOnTextSubmitInProgess(false);
      }
    };
    const refOnGenerate = useRef33(onGenerate);
    refOnGenerate.current = onGenerate;
    const [mermaidToExcalidrawLib, setMermaidToExcalidrawLib] = useState33({
      loaded: false,
      api: import("@excalidraw/mermaid-to-excalidraw")
    });
    useEffect36(() => {
      const fn = async () => {
        await mermaidToExcalidrawLib.api;
        setMermaidToExcalidrawLib((prev) => ({ ...prev, loaded: true }));
      };
      fn();
    }, [mermaidToExcalidrawLib.api]);
    const data = useRef33({ elements: [], files: null });
    const [error, setError] = useState33(null);
    return /* @__PURE__ */ jsx124(
      Dialog,
      {
        className: "ttd-dialog",
        onCloseRequest: () => {
          app.setOpenDialog(null);
        },
        size: 1200,
        title: false,
        ...rest,
        autofocus: false,
        children: /* @__PURE__ */ jsxs69(TTDDialogTabs_default, { dialog: "ttd", tab, children: [
          "__fallback" in rest && rest.__fallback ? /* @__PURE__ */ jsx124("p", { className: "dialog-mermaid-title", children: t("mermaid.title") }) : /* @__PURE__ */ jsxs69(TTDDialogTabTriggers, { children: [
            /* @__PURE__ */ jsx124(TTDDialogTabTrigger, { tab: "text-to-diagram", children: /* @__PURE__ */ jsxs69("div", { style: { display: "flex", alignItems: "center" }, children: [
              t("labels.textToDiagram"),
              /* @__PURE__ */ jsx124(
                "div",
                {
                  style: {
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    padding: "1px 6px",
                    marginLeft: "10px",
                    fontSize: 10,
                    borderRadius: "12px",
                    background: "var(--color-promo)",
                    color: "var(--color-surface-lowest)"
                  },
                  children: "AI Beta"
                }
              )
            ] }) }),
            /* @__PURE__ */ jsx124(TTDDialogTabTrigger, { tab: "mermaid", children: "Mermaid" })
          ] }),
          /* @__PURE__ */ jsx124(TTDDialogTab, { className: "ttd-dialog-content", tab: "mermaid", children: /* @__PURE__ */ jsx124(
            MermaidToExcalidraw_default,
            {
              mermaidToExcalidrawLib
            }
          ) }),
          !("__fallback" in rest) && /* @__PURE__ */ jsxs69(TTDDialogTab, { className: "ttd-dialog-content", tab: "text-to-diagram", children: [
            /* @__PURE__ */ jsx124("div", { className: "ttd-dialog-desc", children: "Currently we use Mermaid as a middle step, so you'll get best results if you describe a diagram, workflow, flow chart, and similar." }),
            /* @__PURE__ */ jsxs69(TTDDialogPanels, { children: [
              /* @__PURE__ */ jsx124(
                TTDDialogPanel,
                {
                  label: t("labels.prompt"),
                  panelAction: {
                    action: onGenerate,
                    label: "Generate",
                    icon: ArrowRightIcon
                  },
                  onTextSubmitInProgess,
                  panelActionDisabled: prompt.length > MAX_PROMPT_LENGTH || rateLimits?.rateLimitRemaining === 0,
                  renderTopRight: () => {
                    if (!rateLimits) {
                      return null;
                    }
                    return /* @__PURE__ */ jsxs69(
                      "div",
                      {
                        className: "ttd-dialog-rate-limit",
                        style: {
                          fontSize: 12,
                          marginLeft: "auto",
                          color: rateLimits.rateLimitRemaining === 0 ? "var(--color-danger)" : void 0
                        },
                        children: [
                          rateLimits.rateLimitRemaining,
                          " requests left today"
                        ]
                      }
                    );
                  },
                  renderSubmitShortcut: () => /* @__PURE__ */ jsx124(TTDDialogSubmitShortcut, {}),
                  renderBottomRight: () => {
                    if (typeof ttdGeneration?.generatedResponse === "string") {
                      return /* @__PURE__ */ jsxs69(
                        "div",
                        {
                          className: "excalidraw-link",
                          style: { marginLeft: "auto", fontSize: 14 },
                          onClick: () => {
                            if (typeof ttdGeneration?.generatedResponse === "string") {
                              saveMermaidDataToStorage(
                                ttdGeneration.generatedResponse
                              );
                              setAppState({
                                openDialog: { name: "ttd", tab: "mermaid" }
                              });
                            }
                          },
                          children: [
                            "View as Mermaid",
                            /* @__PURE__ */ jsx124(InlineIcon, { icon: ArrowRightIcon })
                          ]
                        }
                      );
                    }
                    const ratio = prompt.length / MAX_PROMPT_LENGTH;
                    if (ratio > 0.8) {
                      return /* @__PURE__ */ jsxs69(
                        "div",
                        {
                          style: {
                            marginLeft: "auto",
                            fontSize: 12,
                            fontFamily: "monospace",
                            color: ratio > 1 ? "var(--color-danger)" : void 0
                          },
                          children: [
                            "Length: ",
                            prompt.length,
                            "/",
                            MAX_PROMPT_LENGTH
                          ]
                        }
                      );
                    }
                    return null;
                  },
                  children: /* @__PURE__ */ jsx124(
                    TTDDialogInput,
                    {
                      onChange: handleTextChange,
                      input: text,
                      placeholder: "Describe what you want to see...",
                      onKeyboardSubmit: () => {
                        refOnGenerate.current();
                      }
                    }
                  )
                }
              ),
              /* @__PURE__ */ jsx124(
                TTDDialogPanel,
                {
                  label: "Preview",
                  panelAction: {
                    action: () => {
                      console.info("Panel action clicked");
                      insertToEditor({ app, data });
                    },
                    label: "Insert",
                    icon: ArrowRightIcon
                  },
                  children: /* @__PURE__ */ jsx124(
                    TTDDialogOutput,
                    {
                      canvasRef: someRandomDivRef,
                      error,
                      loaded: mermaidToExcalidrawLib.loaded
                    }
                  )
                }
              )
            ] })
          ] })
        ] })
      }
    );
  }
);

// components/Stats/index.tsx
import { useEffect as useEffect38, useMemo as useMemo9, useState as useState35, memo as memo5 } from "react";
import throttle2 from "lodash.throttle";

// components/Stats/DragInput.tsx
import { useEffect as useEffect37, useRef as useRef34, useState as useState34 } from "react";
import clsx51 from "clsx";

// components/Stats/utils.ts
var SMALLEST_DELTA = 0.01;
var isPropertyEditable = (element, property) => {
  if (property === "height" && isTextElement(element)) {
    return false;
  }
  if (property === "width" && isTextElement(element)) {
    return false;
  }
  if (property === "angle" && isFrameLikeElement(element)) {
    return false;
  }
  return true;
};
var getStepSizedValue = (value, stepSize) => {
  const v = value + stepSize / 2;
  return v - v % stepSize;
};
var getElementsInAtomicUnit = (atomicUnit, elementsMap, originalElementsMap) => {
  return Object.keys(atomicUnit).map((id) => ({
    original: (originalElementsMap ?? elementsMap).get(id),
    latest: elementsMap.get(id)
  })).filter((el) => el.original !== void 0 && el.latest !== void 0);
};
var moveElement = (newTopLeftX, newTopLeftY, originalElement, elementsMap, elements, scene, originalElementsMap, shouldInformMutation = true) => {
  const latestElement = elementsMap.get(originalElement.id);
  if (!latestElement) {
    return;
  }
  const [cx, cy] = [
    originalElement.x + originalElement.width / 2,
    originalElement.y + originalElement.height / 2
  ];
  const [topLeftX, topLeftY] = pointRotateRads(
    pointFrom(originalElement.x, originalElement.y),
    pointFrom(cx, cy),
    originalElement.angle
  );
  const changeInX = newTopLeftX - topLeftX;
  const changeInY = newTopLeftY - topLeftY;
  const [x, y] = pointRotateRads(
    pointFrom(newTopLeftX, newTopLeftY),
    pointFrom(cx + changeInX, cy + changeInY),
    -originalElement.angle
  );
  mutateElement(
    latestElement,
    {
      x,
      y
    },
    shouldInformMutation
  );
  updateBindings(latestElement, elementsMap, elements, scene);
  const boundTextElement = getBoundTextElement(
    originalElement,
    originalElementsMap
  );
  if (boundTextElement) {
    const latestBoundTextElement = elementsMap.get(boundTextElement.id);
    latestBoundTextElement && mutateElement(
      latestBoundTextElement,
      {
        x: boundTextElement.x + changeInX,
        y: boundTextElement.y + changeInY
      },
      shouldInformMutation
    );
  }
};
var getAtomicUnits = (targetElements, appState) => {
  const selectedGroupIds = getSelectedGroupIds(appState);
  const _atomicUnits = selectedGroupIds.map((gid) => {
    return getElementsInGroup(targetElements, gid).reduce((acc, el) => {
      acc[el.id] = true;
      return acc;
    }, {});
  });
  targetElements.filter((el) => !isInGroup(el)).forEach((el) => {
    _atomicUnits.push({
      [el.id]: true
    });
  });
  return _atomicUnits;
};
var updateBindings = (latestElement, elementsMap, elements, scene, options) => {
  if (isLinearElement(latestElement)) {
    bindOrUnbindLinearElements(
      [latestElement],
      elementsMap,
      elements,
      scene,
      true,
      [],
      options?.zoom
    );
  } else {
    updateBoundElements(latestElement, elementsMap, options);
  }
};

// components/Stats/DragInput.tsx
import { jsx as jsx125, jsxs as jsxs70 } from "react/jsx-runtime";
var StatsDragInput = ({
  label,
  icon,
  dragInputCallback,
  value,
  elements,
  editable = true,
  shouldKeepAspectRatio,
  property,
  scene,
  appState,
  sensitivity = 1
}) => {
  const app = useApp();
  const inputRef = useRef34(null);
  const labelRef = useRef34(null);
  const [inputValue, setInputValue] = useState34(value.toString());
  const stateRef = useRef34(null);
  if (!stateRef.current) {
    stateRef.current = {
      originalAppState: cloneJSON(appState),
      originalElements: elements,
      lastUpdatedValue: inputValue,
      updatePending: false
    };
  }
  useEffect37(() => {
    const inputValue2 = value.toString();
    setInputValue(inputValue2);
    stateRef.current.lastUpdatedValue = inputValue2;
  }, [value]);
  const handleInputValue = (updatedValue, elements2, appState2) => {
    if (!stateRef.current.updatePending) {
      return false;
    }
    stateRef.current.updatePending = false;
    const parsed = Number(updatedValue);
    if (isNaN(parsed)) {
      setInputValue(value.toString());
      return;
    }
    const rounded = Number(parsed.toFixed(2));
    const original = Number(value);
    if (isNaN(original) || Math.abs(rounded - original) >= SMALLEST_DELTA) {
      stateRef.current.lastUpdatedValue = updatedValue;
      dragInputCallback({
        accumulatedChange: 0,
        instantChange: 0,
        originalElements: elements2,
        originalElementsMap: app.scene.getNonDeletedElementsMap(),
        shouldKeepAspectRatio,
        shouldChangeByStepSize: false,
        scene,
        nextValue: rounded,
        property,
        originalAppState: appState2,
        setInputValue: (value2) => setInputValue(String(value2))
      });
      app.syncActionResult({
        captureUpdate: CaptureUpdateAction.IMMEDIATELY
      });
    }
  };
  const callbacksRef = useRef34({});
  callbacksRef.current.handleInputValue = handleInputValue;
  useEffect37(() => {
    const input = inputRef.current;
    const callbacks = callbacksRef.current;
    return () => {
      const nextValue = input?.value;
      if (nextValue) {
        callbacks.handleInputValue?.(
          nextValue,
          stateRef.current.originalElements,
          stateRef.current.originalAppState
        );
      }
      window.removeEventListener(
        "pointermove" /* POINTER_MOVE */,
        callbacks.onPointerMove,
        false
      );
      window.removeEventListener(
        "pointerup" /* POINTER_UP */,
        callbacks.onPointerUp,
        false
      );
    };
  }, [
    // we need to track change of `editable` state as mount/unmount
    // because react doesn't trigger `blur` when a an input is blurred due
    // to being disabled (https://github.com/facebook/react/issues/9142).
    // As such, if we keep rendering disabled inputs, then change in selection
    // to an element that has a given property as non-editable would not trigger
    // blur/unmount and wouldn't update the value.
    editable
  ]);
  if (!editable) {
    return null;
  }
  return /* @__PURE__ */ jsxs70(
    "div",
    {
      className: clsx51("drag-input-container", !editable && "disabled"),
      "data-testid": label,
      children: [
        /* @__PURE__ */ jsx125(
          "div",
          {
            className: "drag-input-label",
            ref: labelRef,
            onPointerDown: (event) => {
              if (inputRef.current && editable) {
                document.body.classList.add("excalidraw-cursor-resize");
                let startValue = Number(inputRef.current.value);
                if (isNaN(startValue)) {
                  startValue = 0;
                }
                let lastPointer = null;
                let originalElementsMap = app.scene.getNonDeletedElements().reduce((acc, element) => {
                  acc.set(element.id, deepCopyElement(element));
                  return acc;
                }, /* @__PURE__ */ new Map());
                let originalElements = elements.map(
                  (element) => originalElementsMap.get(element.id)
                );
                const originalAppState = cloneJSON(appState);
                let accumulatedChange = 0;
                let stepChange = 0;
                const onPointerMove = (event2) => {
                  if (lastPointer && originalElementsMap !== null && originalElements !== null) {
                    const instantChange = event2.clientX - lastPointer.x;
                    if (instantChange !== 0) {
                      stepChange += instantChange;
                      if (Math.abs(stepChange) >= sensitivity) {
                        stepChange = Math.sign(stepChange) * Math.floor(Math.abs(stepChange) / sensitivity);
                        accumulatedChange += stepChange;
                        dragInputCallback({
                          accumulatedChange,
                          instantChange: stepChange,
                          originalElements,
                          originalElementsMap,
                          shouldKeepAspectRatio,
                          shouldChangeByStepSize: event2.shiftKey,
                          property,
                          scene,
                          originalAppState,
                          setInputValue: (value2) => setInputValue(String(value2))
                        });
                        stepChange = 0;
                      }
                    }
                  }
                  lastPointer = {
                    x: event2.clientX,
                    y: event2.clientY
                  };
                };
                const onPointerUp = () => {
                  window.removeEventListener(
                    "pointermove" /* POINTER_MOVE */,
                    onPointerMove,
                    false
                  );
                  app.syncActionResult({
                    captureUpdate: CaptureUpdateAction.IMMEDIATELY
                  });
                  lastPointer = null;
                  accumulatedChange = 0;
                  stepChange = 0;
                  originalElements = null;
                  originalElementsMap = null;
                  document.body.classList.remove("excalidraw-cursor-resize");
                  window.removeEventListener("pointerup" /* POINTER_UP */, onPointerUp, false);
                };
                callbacksRef.current.onPointerMove = onPointerMove;
                callbacksRef.current.onPointerUp = onPointerUp;
                window.addEventListener("pointermove" /* POINTER_MOVE */, onPointerMove, false);
                window.addEventListener("pointerup" /* POINTER_UP */, onPointerUp, false);
              }
            },
            onPointerEnter: () => {
              if (labelRef.current) {
                labelRef.current.style.cursor = "ew-resize";
              }
            },
            children: icon ? /* @__PURE__ */ jsx125(InlineIcon, { icon }) : label
          }
        ),
        /* @__PURE__ */ jsx125(
          "input",
          {
            className: "drag-input",
            autoComplete: "off",
            spellCheck: "false",
            onKeyDown: (event) => {
              if (editable) {
                const eventTarget = event.target;
                if (eventTarget instanceof HTMLInputElement && event.key === KEYS.ENTER) {
                  handleInputValue(eventTarget.value, elements, appState);
                  app.focusContainer();
                }
              }
            },
            ref: inputRef,
            value: inputValue,
            onChange: (event) => {
              stateRef.current.updatePending = true;
              setInputValue(event.target.value);
            },
            onFocus: (event) => {
              event.target.select();
              stateRef.current.originalElements = elements;
              stateRef.current.originalAppState = cloneJSON(appState);
            },
            onBlur: (event) => {
              if (!inputValue) {
                setInputValue(value.toString());
              } else if (editable) {
                handleInputValue(
                  event.target.value,
                  stateRef.current.originalElements,
                  stateRef.current.originalAppState
                );
              }
            },
            disabled: !editable
          }
        )
      ]
    }
  );
};
var DragInput_default = StatsDragInput;

// components/Stats/Dimension.tsx
import { jsx as jsx126 } from "react/jsx-runtime";
var STEP_SIZE = 10;
var _shouldKeepAspectRatio = (element) => {
  return element.type === "image";
};
var handleDimensionChange = ({
  accumulatedChange,
  originalElements,
  originalElementsMap,
  shouldKeepAspectRatio,
  shouldChangeByStepSize,
  nextValue,
  property,
  originalAppState,
  instantChange,
  scene
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const origElement = originalElements[0];
  const latestElement = elementsMap.get(origElement.id);
  if (origElement && latestElement) {
    const keepAspectRatio = shouldKeepAspectRatio || _shouldKeepAspectRatio(origElement);
    const aspectRatio = origElement.width / origElement.height;
    if (originalAppState.croppingElementId === origElement.id) {
      const element = elementsMap.get(origElement.id);
      if (!element || !isImageElement(element) || !element.crop) {
        return;
      }
      const crop = element.crop;
      let nextCrop = { ...crop };
      const isFlippedByX = element.scale[0] === -1;
      const isFlippedByY = element.scale[1] === -1;
      const { width: uncroppedWidth, height: uncroppedHeight } = getUncroppedWidthAndHeight(element);
      const naturalToUncroppedWidthRatio = crop.naturalWidth / uncroppedWidth;
      const naturalToUncroppedHeightRatio = crop.naturalHeight / uncroppedHeight;
      const MAX_POSSIBLE_WIDTH = isFlippedByX ? crop.width + crop.x : crop.naturalWidth - crop.x;
      const MAX_POSSIBLE_HEIGHT = isFlippedByY ? crop.height + crop.y : crop.naturalHeight - crop.y;
      const MIN_WIDTH = MINIMAL_CROP_SIZE * naturalToUncroppedWidthRatio;
      const MIN_HEIGHT = MINIMAL_CROP_SIZE * naturalToUncroppedHeightRatio;
      if (nextValue !== void 0) {
        if (property === "width") {
          const nextValueInNatural = nextValue * naturalToUncroppedWidthRatio;
          const nextCropWidth2 = clamp(
            nextValueInNatural,
            MIN_WIDTH,
            MAX_POSSIBLE_WIDTH
          );
          nextCrop = {
            ...nextCrop,
            width: nextCropWidth2,
            x: isFlippedByX ? crop.x + crop.width - nextCropWidth2 : crop.x
          };
        } else if (property === "height") {
          const nextValueInNatural = nextValue * naturalToUncroppedHeightRatio;
          const nextCropHeight2 = clamp(
            nextValueInNatural,
            MIN_HEIGHT,
            MAX_POSSIBLE_HEIGHT
          );
          nextCrop = {
            ...nextCrop,
            height: nextCropHeight2,
            y: isFlippedByY ? crop.y + crop.height - nextCropHeight2 : crop.y
          };
        }
        mutateElement(element, {
          crop: nextCrop,
          width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
          height: nextCrop.height / (crop.naturalHeight / uncroppedHeight)
        });
        return;
      }
      const changeInWidth2 = property === "width" ? instantChange : 0;
      const changeInHeight2 = property === "height" ? instantChange : 0;
      const nextCropWidth = clamp(
        crop.width + changeInWidth2,
        MIN_WIDTH,
        MAX_POSSIBLE_WIDTH
      );
      const nextCropHeight = clamp(
        crop.height + changeInHeight2,
        MIN_WIDTH,
        MAX_POSSIBLE_HEIGHT
      );
      nextCrop = {
        ...crop,
        x: isFlippedByX ? crop.x + crop.width - nextCropWidth : crop.x,
        y: isFlippedByY ? crop.y + crop.height - nextCropHeight : crop.y,
        width: nextCropWidth,
        height: nextCropHeight
      };
      mutateElement(element, {
        crop: nextCrop,
        width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
        height: nextCrop.height / (crop.naturalHeight / uncroppedHeight)
      });
      return;
    }
    if (nextValue !== void 0) {
      const nextWidth2 = Math.max(
        property === "width" ? nextValue : keepAspectRatio ? nextValue * aspectRatio : origElement.width,
        MIN_WIDTH_OR_HEIGHT
      );
      const nextHeight2 = Math.max(
        property === "height" ? nextValue : keepAspectRatio ? nextValue / aspectRatio : origElement.height,
        MIN_WIDTH_OR_HEIGHT
      );
      resizeSingleElement(
        nextWidth2,
        nextHeight2,
        latestElement,
        origElement,
        elementsMap,
        originalElementsMap,
        property === "width" ? "e" : "s",
        {
          shouldMaintainAspectRatio: keepAspectRatio
        }
      );
      return;
    }
    const changeInWidth = property === "width" ? accumulatedChange : 0;
    const changeInHeight = property === "height" ? accumulatedChange : 0;
    let nextWidth = Math.max(0, origElement.width + changeInWidth);
    if (property === "width") {
      if (shouldChangeByStepSize) {
        nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
      } else {
        nextWidth = Math.round(nextWidth);
      }
    }
    let nextHeight = Math.max(0, origElement.height + changeInHeight);
    if (property === "height") {
      if (shouldChangeByStepSize) {
        nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
      } else {
        nextHeight = Math.round(nextHeight);
      }
    }
    if (keepAspectRatio) {
      if (property === "width") {
        nextHeight = Math.round(nextWidth / aspectRatio * 100) / 100;
      } else {
        nextWidth = Math.round(nextHeight * aspectRatio * 100) / 100;
      }
    }
    nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
    nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
    resizeSingleElement(
      nextWidth,
      nextHeight,
      latestElement,
      origElement,
      elementsMap,
      originalElementsMap,
      property === "width" ? "e" : "s",
      {
        shouldMaintainAspectRatio: keepAspectRatio
      }
    );
  }
};
var DimensionDragInput = ({
  property,
  element,
  scene,
  appState
}) => {
  let value = round(property === "width" ? element.width : element.height, 2);
  if (appState.croppingElementId && appState.croppingElementId === element.id && isImageElement(element) && element.crop) {
    const { width: uncroppedWidth, height: uncroppedHeight } = getUncroppedWidthAndHeight(element);
    if (property === "width") {
      const ratio = uncroppedWidth / element.crop.naturalWidth;
      value = round(element.crop.width * ratio, 2);
    }
    if (property === "height") {
      const ratio = uncroppedHeight / element.crop.naturalHeight;
      value = round(element.crop.height * ratio, 2);
    }
  }
  return /* @__PURE__ */ jsx126(
    DragInput_default,
    {
      label: property === "width" ? "W" : "H",
      elements: [element],
      dragInputCallback: handleDimensionChange,
      value,
      editable: isPropertyEditable(element, property),
      scene,
      appState,
      property
    }
  );
};
var Dimension_default = DimensionDragInput;

// components/Stats/Angle.tsx
import { jsx as jsx127 } from "react/jsx-runtime";
var STEP_SIZE2 = 15;
var handleDegreeChange = ({
  accumulatedChange,
  originalElements,
  shouldChangeByStepSize,
  nextValue,
  scene
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const elements = scene.getNonDeletedElements();
  const origElement = originalElements[0];
  if (origElement && !isElbowArrow(origElement)) {
    const latestElement = elementsMap.get(origElement.id);
    if (!latestElement) {
      return;
    }
    if (nextValue !== void 0) {
      const nextAngle2 = degreesToRadians(nextValue);
      mutateElement(latestElement, {
        angle: nextAngle2
      });
      updateBindings(latestElement, elementsMap, elements, scene);
      const boundTextElement2 = getBoundTextElement(latestElement, elementsMap);
      if (boundTextElement2 && !isArrowElement(latestElement)) {
        mutateElement(boundTextElement2, { angle: nextAngle2 });
      }
      return;
    }
    const originalAngleInDegrees = Math.round(radiansToDegrees(origElement.angle) * 100) / 100;
    const changeInDegrees = Math.round(accumulatedChange);
    let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
    if (shouldChangeByStepSize) {
      nextAngleInDegrees = getStepSizedValue(nextAngleInDegrees, STEP_SIZE2);
    }
    nextAngleInDegrees = nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees;
    const nextAngle = degreesToRadians(nextAngleInDegrees);
    mutateElement(latestElement, {
      angle: nextAngle
    });
    updateBindings(latestElement, elementsMap, elements, scene);
    const boundTextElement = getBoundTextElement(latestElement, elementsMap);
    if (boundTextElement && !isArrowElement(latestElement)) {
      mutateElement(boundTextElement, { angle: nextAngle });
    }
  }
};
var Angle = ({ element, scene, appState, property }) => {
  return /* @__PURE__ */ jsx127(
    DragInput_default,
    {
      label: "A",
      icon: angleIcon,
      value: Math.round(radiansToDegrees(element.angle) % 360 * 100) / 100,
      elements: [element],
      dragInputCallback: handleDegreeChange,
      editable: isPropertyEditable(element, "angle"),
      scene,
      appState,
      property
    }
  );
};
var Angle_default = Angle;

// components/Stats/FontSize.tsx
import { jsx as jsx128 } from "react/jsx-runtime";
var MIN_FONT_SIZE = 4;
var STEP_SIZE3 = 4;
var handleFontSizeChange = ({
  accumulatedChange,
  originalElements,
  shouldChangeByStepSize,
  nextValue,
  scene
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const origElement = originalElements[0];
  if (origElement) {
    const latestElement = elementsMap.get(origElement.id);
    if (!latestElement || !isTextElement(latestElement)) {
      return;
    }
    let nextFontSize;
    if (nextValue !== void 0) {
      nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
    } else if (origElement.type === "text") {
      const originalFontSize = Math.round(origElement.fontSize);
      const changeInFontSize = Math.round(accumulatedChange);
      nextFontSize = Math.max(
        originalFontSize + changeInFontSize,
        MIN_FONT_SIZE
      );
      if (shouldChangeByStepSize) {
        nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE3);
      }
    }
    if (nextFontSize) {
      mutateElement(latestElement, {
        fontSize: nextFontSize
      });
      redrawTextBoundingBox(
        latestElement,
        scene.getContainerElement(latestElement),
        scene.getNonDeletedElementsMap()
      );
    }
  }
};
var FontSize = ({ element, scene, appState, property }) => {
  const _element = isTextElement(element) ? element : hasBoundTextElement(element) ? getBoundTextElement(element, scene.getNonDeletedElementsMap()) : null;
  if (!_element) {
    return null;
  }
  return /* @__PURE__ */ jsx128(
    DragInput_default,
    {
      label: "F",
      value: Math.round(_element.fontSize * 10) / 10,
      elements: [_element],
      dragInputCallback: handleFontSizeChange,
      icon: fontSizeIcon,
      appState,
      scene,
      property
    }
  );
};
var FontSize_default = FontSize;

// components/Stats/MultiDimension.tsx
import { useMemo as useMemo7 } from "react";
import { jsx as jsx129 } from "react/jsx-runtime";
var STEP_SIZE4 = 10;
var getResizedUpdates = (anchorX, anchorY, scale, origElement) => {
  const offsetX = origElement.x - anchorX;
  const offsetY = origElement.y - anchorY;
  const nextWidth = origElement.width * scale;
  const nextHeight = origElement.height * scale;
  const x = anchorX + offsetX * scale;
  const y = anchorY + offsetY * scale;
  return {
    width: nextWidth,
    height: nextHeight,
    x,
    y,
    ...rescalePointsInElement(origElement, nextWidth, nextHeight, false),
    ...isTextElement(origElement) ? { fontSize: origElement.fontSize * scale } : {}
  };
};
var resizeElementInGroup = (anchorX, anchorY, property, scale, latestElement, origElement, elementsMap, originalElementsMap) => {
  const updates = getResizedUpdates(anchorX, anchorY, scale, origElement);
  mutateElement(latestElement, updates, false);
  const boundTextElement = getBoundTextElement(
    origElement,
    originalElementsMap
  );
  if (boundTextElement) {
    const newFontSize = boundTextElement.fontSize * scale;
    updateBoundElements(latestElement, elementsMap, {
      newSize: { width: updates.width, height: updates.height }
    });
    const latestBoundTextElement = elementsMap.get(boundTextElement.id);
    if (latestBoundTextElement && isTextElement(latestBoundTextElement)) {
      mutateElement(
        latestBoundTextElement,
        {
          fontSize: newFontSize
        },
        false
      );
      handleBindTextResize(
        latestElement,
        elementsMap,
        property === "width" ? "e" : "s",
        true
      );
    }
  }
};
var resizeGroup = (nextWidth, nextHeight, initialHeight, aspectRatio, anchor, property, latestElements, originalElements, elementsMap, originalElementsMap) => {
  if (property === "width") {
    nextHeight = Math.round(nextWidth / aspectRatio * 100) / 100;
  } else {
    nextWidth = Math.round(nextHeight * aspectRatio * 100) / 100;
  }
  const scale = nextHeight / initialHeight;
  for (let i = 0; i < originalElements.length; i++) {
    const origElement = originalElements[i];
    const latestElement = latestElements[i];
    resizeElementInGroup(
      anchor[0],
      anchor[1],
      property,
      scale,
      latestElement,
      origElement,
      elementsMap,
      originalElementsMap
    );
  }
};
var handleDimensionChange2 = ({
  accumulatedChange,
  originalElements,
  originalElementsMap,
  originalAppState,
  shouldChangeByStepSize,
  nextValue,
  scene,
  property
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const atomicUnits = getAtomicUnits(originalElements, originalAppState);
  if (nextValue !== void 0) {
    for (const atomicUnit of atomicUnits) {
      const elementsInUnit = getElementsInAtomicUnit(
        atomicUnit,
        elementsMap,
        originalElementsMap
      );
      if (elementsInUnit.length > 1) {
        const latestElements = elementsInUnit.map((el) => el.latest);
        const originalElements2 = elementsInUnit.map((el) => el.original);
        const [x1, y1, x2, y2] = getCommonBounds(originalElements2);
        const initialWidth = x2 - x1;
        const initialHeight = y2 - y1;
        const aspectRatio = initialWidth / initialHeight;
        const nextWidth = Math.max(
          MIN_WIDTH_OR_HEIGHT,
          property === "width" ? Math.max(0, nextValue) : initialWidth
        );
        const nextHeight = Math.max(
          MIN_WIDTH_OR_HEIGHT,
          property === "height" ? Math.max(0, nextValue) : initialHeight
        );
        resizeGroup(
          nextWidth,
          nextHeight,
          initialHeight,
          aspectRatio,
          pointFrom(x1, y1),
          property,
          latestElements,
          originalElements2,
          elementsMap,
          originalElementsMap
        );
      } else {
        const [el] = elementsInUnit;
        const latestElement = el?.latest;
        const origElement = el?.original;
        if (latestElement && origElement && isPropertyEditable(latestElement, property)) {
          let nextWidth = property === "width" ? Math.max(0, nextValue) : latestElement.width;
          if (property === "width") {
            if (shouldChangeByStepSize) {
              nextWidth = getStepSizedValue(nextWidth, STEP_SIZE4);
            } else {
              nextWidth = Math.round(nextWidth);
            }
          }
          let nextHeight = property === "height" ? Math.max(0, nextValue) : latestElement.height;
          if (property === "height") {
            if (shouldChangeByStepSize) {
              nextHeight = getStepSizedValue(nextHeight, STEP_SIZE4);
            } else {
              nextHeight = Math.round(nextHeight);
            }
          }
          nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
          nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
          resizeSingleElement(
            nextWidth,
            nextHeight,
            latestElement,
            origElement,
            elementsMap,
            originalElementsMap,
            property === "width" ? "e" : "s",
            {
              shouldInformMutation: false
            }
          );
        }
      }
    }
    scene.triggerUpdate();
    return;
  }
  const changeInWidth = property === "width" ? accumulatedChange : 0;
  const changeInHeight = property === "height" ? accumulatedChange : 0;
  for (const atomicUnit of atomicUnits) {
    const elementsInUnit = getElementsInAtomicUnit(
      atomicUnit,
      elementsMap,
      originalElementsMap
    );
    if (elementsInUnit.length > 1) {
      const latestElements = elementsInUnit.map((el) => el.latest);
      const originalElements2 = elementsInUnit.map((el) => el.original);
      const [x1, y1, x2, y2] = getCommonBounds(originalElements2);
      const initialWidth = x2 - x1;
      const initialHeight = y2 - y1;
      const aspectRatio = initialWidth / initialHeight;
      let nextWidth = Math.max(0, initialWidth + changeInWidth);
      if (property === "width") {
        if (shouldChangeByStepSize) {
          nextWidth = getStepSizedValue(nextWidth, STEP_SIZE4);
        } else {
          nextWidth = Math.round(nextWidth);
        }
      }
      let nextHeight = Math.max(0, initialHeight + changeInHeight);
      if (property === "height") {
        if (shouldChangeByStepSize) {
          nextHeight = getStepSizedValue(nextHeight, STEP_SIZE4);
        } else {
          nextHeight = Math.round(nextHeight);
        }
      }
      nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
      nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
      resizeGroup(
        nextWidth,
        nextHeight,
        initialHeight,
        aspectRatio,
        pointFrom(x1, y1),
        property,
        latestElements,
        originalElements2,
        elementsMap,
        originalElementsMap
      );
    } else {
      const [el] = elementsInUnit;
      const latestElement = el?.latest;
      const origElement = el?.original;
      if (latestElement && origElement && isPropertyEditable(latestElement, property)) {
        let nextWidth = Math.max(0, origElement.width + changeInWidth);
        if (property === "width") {
          if (shouldChangeByStepSize) {
            nextWidth = getStepSizedValue(nextWidth, STEP_SIZE4);
          } else {
            nextWidth = Math.round(nextWidth);
          }
        }
        let nextHeight = Math.max(0, origElement.height + changeInHeight);
        if (property === "height") {
          if (shouldChangeByStepSize) {
            nextHeight = getStepSizedValue(nextHeight, STEP_SIZE4);
          } else {
            nextHeight = Math.round(nextHeight);
          }
        }
        nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
        nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
        resizeSingleElement(
          nextWidth,
          nextHeight,
          latestElement,
          origElement,
          elementsMap,
          originalElementsMap,
          property === "width" ? "e" : "s",
          {
            shouldInformMutation: false
          }
        );
      }
    }
  }
  scene.triggerUpdate();
};
var MultiDimension = ({
  property,
  elements,
  elementsMap,
  atomicUnits,
  scene,
  appState
}) => {
  const sizes = useMemo7(
    () => atomicUnits.map((atomicUnit) => {
      const elementsInUnit = getElementsInAtomicUnit(atomicUnit, elementsMap);
      if (elementsInUnit.length > 1) {
        const [x1, y1, x2, y2] = getCommonBounds(
          elementsInUnit.map((el2) => el2.latest)
        );
        return Math.round((property === "width" ? x2 - x1 : y2 - y1) * 100) / 100;
      }
      const [el] = elementsInUnit;
      return Math.round(
        (property === "width" ? el.latest.width : el.latest.height) * 100
      ) / 100;
    }),
    [elementsMap, atomicUnits, property]
  );
  const value = new Set(sizes).size === 1 ? Math.round(sizes[0] * 100) / 100 : "Mixed";
  const editable = sizes.length > 0;
  return /* @__PURE__ */ jsx129(
    DragInput_default,
    {
      label: property === "width" ? "W" : "H",
      elements,
      dragInputCallback: handleDimensionChange2,
      value,
      editable,
      appState,
      property,
      scene
    }
  );
};
var MultiDimension_default = MultiDimension;

// components/Stats/MultiAngle.tsx
import { jsx as jsx130 } from "react/jsx-runtime";
var STEP_SIZE5 = 15;
var handleDegreeChange2 = ({
  accumulatedChange,
  originalElements,
  shouldChangeByStepSize,
  nextValue,
  property,
  scene
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const editableLatestIndividualElements = originalElements.map((el) => elementsMap.get(el.id)).filter((el) => el && !isInGroup(el) && isPropertyEditable(el, property));
  const editableOriginalIndividualElements = originalElements.filter(
    (el) => !isInGroup(el) && isPropertyEditable(el, property)
  );
  if (nextValue !== void 0) {
    const nextAngle = degreesToRadians(nextValue);
    for (const element of editableLatestIndividualElements) {
      if (!element) {
        continue;
      }
      mutateElement(
        element,
        {
          angle: nextAngle
        },
        false
      );
      const boundTextElement = getBoundTextElement(element, elementsMap);
      if (boundTextElement && !isArrowElement(element)) {
        mutateElement(boundTextElement, { angle: nextAngle }, false);
      }
    }
    scene.triggerUpdate();
    return;
  }
  for (let i = 0; i < editableLatestIndividualElements.length; i++) {
    const latestElement = editableLatestIndividualElements[i];
    if (!latestElement) {
      continue;
    }
    const originalElement = editableOriginalIndividualElements[i];
    const originalAngleInDegrees = Math.round(radiansToDegrees(originalElement.angle) * 100) / 100;
    const changeInDegrees = Math.round(accumulatedChange);
    let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
    if (shouldChangeByStepSize) {
      nextAngleInDegrees = getStepSizedValue(nextAngleInDegrees, STEP_SIZE5);
    }
    nextAngleInDegrees = nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees;
    const nextAngle = degreesToRadians(nextAngleInDegrees);
    mutateElement(
      latestElement,
      {
        angle: nextAngle
      },
      false
    );
    const boundTextElement = getBoundTextElement(latestElement, elementsMap);
    if (boundTextElement && !isArrowElement(latestElement)) {
      mutateElement(boundTextElement, { angle: nextAngle }, false);
    }
  }
  scene.triggerUpdate();
};
var MultiAngle = ({
  elements,
  scene,
  appState,
  property
}) => {
  const editableLatestIndividualElements = elements.filter(
    (el) => !isInGroup(el) && isPropertyEditable(el, "angle")
  );
  const angles = editableLatestIndividualElements.map(
    (el) => Math.round(radiansToDegrees(el.angle) % 360 * 100) / 100
  );
  const value = new Set(angles).size === 1 ? angles[0] : "Mixed";
  const editable = editableLatestIndividualElements.some(
    (el) => isPropertyEditable(el, "angle")
  );
  return /* @__PURE__ */ jsx130(
    DragInput_default,
    {
      label: "A",
      icon: angleIcon,
      value,
      elements,
      dragInputCallback: handleDegreeChange2,
      editable,
      appState,
      scene,
      property
    }
  );
};
var MultiAngle_default = MultiAngle;

// components/Stats/MultiFontSize.tsx
import { jsx as jsx131 } from "react/jsx-runtime";
var MIN_FONT_SIZE2 = 4;
var STEP_SIZE6 = 4;
var getApplicableTextElements = (elements, elementsMap) => elements.reduce(
  (acc, el) => {
    if (!el || isInGroup(el)) {
      return acc;
    }
    if (isTextElement(el)) {
      acc.push(el);
      return acc;
    }
    if (hasBoundTextElement(el)) {
      const boundTextElement = getBoundTextElement(el, elementsMap);
      if (boundTextElement) {
        acc.push(boundTextElement);
        return acc;
      }
    }
    return acc;
  },
  []
);
var handleFontSizeChange2 = ({
  accumulatedChange,
  originalElements,
  shouldChangeByStepSize,
  nextValue,
  scene
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const latestTextElements = originalElements.map(
    (el) => elementsMap.get(el.id)
  );
  let nextFontSize;
  if (nextValue) {
    nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE2);
    for (const textElement of latestTextElements) {
      mutateElement(
        textElement,
        {
          fontSize: nextFontSize
        },
        false
      );
      redrawTextBoundingBox(
        textElement,
        scene.getContainerElement(textElement),
        elementsMap,
        false
      );
    }
    scene.triggerUpdate();
  } else {
    const originalTextElements = originalElements;
    for (let i = 0; i < latestTextElements.length; i++) {
      const latestElement = latestTextElements[i];
      const originalElement = originalTextElements[i];
      const originalFontSize = Math.round(originalElement.fontSize);
      const changeInFontSize = Math.round(accumulatedChange);
      let nextFontSize2 = Math.max(
        originalFontSize + changeInFontSize,
        MIN_FONT_SIZE2
      );
      if (shouldChangeByStepSize) {
        nextFontSize2 = getStepSizedValue(nextFontSize2, STEP_SIZE6);
      }
      mutateElement(
        latestElement,
        {
          fontSize: nextFontSize2
        },
        false
      );
      redrawTextBoundingBox(
        latestElement,
        scene.getContainerElement(latestElement),
        elementsMap,
        false
      );
    }
    scene.triggerUpdate();
  }
};
var MultiFontSize = ({
  elements,
  scene,
  appState,
  property,
  elementsMap
}) => {
  const latestTextElements = getApplicableTextElements(elements, elementsMap);
  if (!latestTextElements.length) {
    return null;
  }
  const fontSizes = latestTextElements.map(
    (textEl) => Math.round(textEl.fontSize * 10) / 10
  );
  const value = new Set(fontSizes).size === 1 ? fontSizes[0] : "Mixed";
  const editable = fontSizes.length > 0;
  return /* @__PURE__ */ jsx131(
    DragInput_default,
    {
      label: "F",
      icon: fontSizeIcon,
      elements: latestTextElements,
      dragInputCallback: handleFontSizeChange2,
      value,
      editable,
      scene,
      property,
      appState
    }
  );
};
var MultiFontSize_default = MultiFontSize;

// components/Stats/Position.tsx
import { jsx as jsx132 } from "react/jsx-runtime";
var STEP_SIZE7 = 10;
var handlePositionChange = ({
  accumulatedChange,
  instantChange,
  originalElements,
  originalElementsMap,
  shouldChangeByStepSize,
  nextValue,
  property,
  scene,
  originalAppState
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const elements = scene.getNonDeletedElements();
  const origElement = originalElements[0];
  const [cx, cy] = [
    origElement.x + origElement.width / 2,
    origElement.y + origElement.height / 2
  ];
  const [topLeftX, topLeftY] = pointRotateRads(
    pointFrom(origElement.x, origElement.y),
    pointFrom(cx, cy),
    origElement.angle
  );
  if (originalAppState.croppingElementId === origElement.id) {
    const element = elementsMap.get(origElement.id);
    if (!element || !isImageElement(element) || !element.crop) {
      return;
    }
    const crop = element.crop;
    let nextCrop = crop;
    const isFlippedByX = element.scale[0] === -1;
    const isFlippedByY = element.scale[1] === -1;
    const { width: uncroppedWidth, height: uncroppedHeight } = getUncroppedWidthAndHeight(element);
    if (nextValue !== void 0) {
      if (property === "x") {
        const nextValueInNatural = nextValue * (crop.naturalWidth / uncroppedWidth);
        if (isFlippedByX) {
          nextCrop = {
            ...crop,
            x: clamp(
              crop.naturalWidth - nextValueInNatural - crop.width,
              0,
              crop.naturalWidth - crop.width
            )
          };
        } else {
          nextCrop = {
            ...crop,
            x: clamp(
              nextValue * (crop.naturalWidth / uncroppedWidth),
              0,
              crop.naturalWidth - crop.width
            )
          };
        }
      }
      if (property === "y") {
        nextCrop = {
          ...crop,
          y: clamp(
            nextValue * (crop.naturalHeight / uncroppedHeight),
            0,
            crop.naturalHeight - crop.height
          )
        };
      }
      mutateElement(element, {
        crop: nextCrop
      });
      return;
    }
    const changeInX = (property === "x" ? instantChange : 0) * (isFlippedByX ? -1 : 1);
    const changeInY = (property === "y" ? instantChange : 0) * (isFlippedByY ? -1 : 1);
    nextCrop = {
      ...crop,
      x: clamp(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
      y: clamp(crop.y + changeInY, 0, crop.naturalHeight - crop.height)
    };
    mutateElement(element, {
      crop: nextCrop
    });
    return;
  }
  if (nextValue !== void 0) {
    const newTopLeftX2 = property === "x" ? nextValue : topLeftX;
    const newTopLeftY2 = property === "y" ? nextValue : topLeftY;
    moveElement(
      newTopLeftX2,
      newTopLeftY2,
      origElement,
      elementsMap,
      elements,
      scene,
      originalElementsMap
    );
    return;
  }
  const changeInTopX = property === "x" ? accumulatedChange : 0;
  const changeInTopY = property === "y" ? accumulatedChange : 0;
  const newTopLeftX = property === "x" ? Math.round(
    shouldChangeByStepSize ? getStepSizedValue(origElement.x + changeInTopX, STEP_SIZE7) : topLeftX + changeInTopX
  ) : topLeftX;
  const newTopLeftY = property === "y" ? Math.round(
    shouldChangeByStepSize ? getStepSizedValue(origElement.y + changeInTopY, STEP_SIZE7) : topLeftY + changeInTopY
  ) : topLeftY;
  moveElement(
    newTopLeftX,
    newTopLeftY,
    origElement,
    elementsMap,
    elements,
    scene,
    originalElementsMap
  );
};
var Position = ({
  property,
  element,
  elementsMap,
  scene,
  appState
}) => {
  const [topLeftX, topLeftY] = pointRotateRads(
    pointFrom(element.x, element.y),
    pointFrom(element.x + element.width / 2, element.y + element.height / 2),
    element.angle
  );
  let value = round(property === "x" ? topLeftX : topLeftY, 2);
  if (appState.croppingElementId === element.id && isImageElement(element) && element.crop) {
    const flipAdjustedPosition = getFlipAdjustedCropPosition(element);
    if (flipAdjustedPosition) {
      value = round(
        property === "x" ? flipAdjustedPosition.x : flipAdjustedPosition.y,
        2
      );
    }
  }
  return /* @__PURE__ */ jsx132(
    DragInput_default,
    {
      label: property === "x" ? "X" : "Y",
      elements: [element],
      dragInputCallback: handlePositionChange,
      scene,
      value,
      property,
      appState
    }
  );
};
var Position_default = Position;

// components/Stats/MultiPosition.tsx
import { useMemo as useMemo8 } from "react";
import { jsx as jsx133 } from "react/jsx-runtime";
var STEP_SIZE8 = 10;
var moveElements = (property, changeInTopX, changeInTopY, elements, originalElements, elementsMap, originalElementsMap, scene) => {
  for (let i = 0; i < elements.length; i++) {
    const origElement = originalElements[i];
    const [cx, cy] = [
      origElement.x + origElement.width / 2,
      origElement.y + origElement.height / 2
    ];
    const [topLeftX, topLeftY] = pointRotateRads(
      pointFrom(origElement.x, origElement.y),
      pointFrom(cx, cy),
      origElement.angle
    );
    const newTopLeftX = property === "x" ? Math.round(topLeftX + changeInTopX) : topLeftX;
    const newTopLeftY = property === "y" ? Math.round(topLeftY + changeInTopY) : topLeftY;
    moveElement(
      newTopLeftX,
      newTopLeftY,
      origElement,
      elementsMap,
      elements,
      scene,
      originalElementsMap,
      false
    );
  }
};
var moveGroupTo = (nextX, nextY, originalElements, elementsMap, elements, originalElementsMap, scene) => {
  const [x1, y1, ,] = getCommonBounds(originalElements);
  const offsetX = nextX - x1;
  const offsetY = nextY - y1;
  for (let i = 0; i < originalElements.length; i++) {
    const origElement = originalElements[i];
    const latestElement = elementsMap.get(origElement.id);
    if (!latestElement) {
      continue;
    }
    if (!isTextElement(latestElement) || !latestElement.containerId) {
      const [cx, cy] = [
        latestElement.x + latestElement.width / 2,
        latestElement.y + latestElement.height / 2
      ];
      const [topLeftX, topLeftY] = pointRotateRads(
        pointFrom(latestElement.x, latestElement.y),
        pointFrom(cx, cy),
        latestElement.angle
      );
      moveElement(
        topLeftX + offsetX,
        topLeftY + offsetY,
        origElement,
        elementsMap,
        elements,
        scene,
        originalElementsMap,
        false
      );
    }
  }
};
var handlePositionChange2 = ({
  accumulatedChange,
  originalElements,
  originalElementsMap,
  shouldChangeByStepSize,
  nextValue,
  property,
  scene,
  originalAppState
}) => {
  const elementsMap = scene.getNonDeletedElementsMap();
  const elements = scene.getNonDeletedElements();
  if (nextValue !== void 0) {
    for (const atomicUnit of getAtomicUnits(
      originalElements,
      originalAppState
    )) {
      const elementsInUnit = getElementsInAtomicUnit(
        atomicUnit,
        elementsMap,
        originalElementsMap
      );
      if (elementsInUnit.length > 1) {
        const [x1, y1, ,] = getCommonBounds(
          elementsInUnit.map((el) => el.latest)
        );
        const newTopLeftX = property === "x" ? nextValue : x1;
        const newTopLeftY = property === "y" ? nextValue : y1;
        moveGroupTo(
          newTopLeftX,
          newTopLeftY,
          elementsInUnit.map((el) => el.original),
          elementsMap,
          elements,
          originalElementsMap,
          scene
        );
      } else {
        const origElement = elementsInUnit[0]?.original;
        const latestElement = elementsInUnit[0]?.latest;
        if (origElement && latestElement && isPropertyEditable(latestElement, property)) {
          const [cx, cy] = [
            origElement.x + origElement.width / 2,
            origElement.y + origElement.height / 2
          ];
          const [topLeftX, topLeftY] = pointRotateRads(
            pointFrom(origElement.x, origElement.y),
            pointFrom(cx, cy),
            origElement.angle
          );
          const newTopLeftX = property === "x" ? nextValue : topLeftX;
          const newTopLeftY = property === "y" ? nextValue : topLeftY;
          moveElement(
            newTopLeftX,
            newTopLeftY,
            origElement,
            elementsMap,
            elements,
            scene,
            originalElementsMap,
            false
          );
        }
      }
    }
    scene.triggerUpdate();
    return;
  }
  const change = shouldChangeByStepSize ? getStepSizedValue(accumulatedChange, STEP_SIZE8) : accumulatedChange;
  const changeInTopX = property === "x" ? change : 0;
  const changeInTopY = property === "y" ? change : 0;
  moveElements(
    property,
    changeInTopX,
    changeInTopY,
    originalElements,
    originalElements,
    elementsMap,
    originalElementsMap,
    scene
  );
  scene.triggerUpdate();
};
var MultiPosition = ({
  property,
  elements,
  elementsMap,
  atomicUnits,
  scene,
  appState
}) => {
  const positions = useMemo8(
    () => atomicUnits.map((atomicUnit) => {
      const elementsInUnit = Object.keys(atomicUnit).map((id) => elementsMap.get(id)).filter((el2) => el2 !== void 0);
      if (elementsInUnit.length > 1) {
        const [x1, y1] = getCommonBounds(elementsInUnit);
        return Math.round((property === "x" ? x1 : y1) * 100) / 100;
      }
      const [el] = elementsInUnit;
      const [cx, cy] = [el.x + el.width / 2, el.y + el.height / 2];
      const [topLeftX, topLeftY] = pointRotateRads(
        pointFrom(el.x, el.y),
        pointFrom(cx, cy),
        el.angle
      );
      return Math.round((property === "x" ? topLeftX : topLeftY) * 100) / 100;
    }),
    [atomicUnits, elementsMap, property]
  );
  const value = new Set(positions).size === 1 ? positions[0] : "Mixed";
  return /* @__PURE__ */ jsx133(
    DragInput_default,
    {
      label: property === "x" ? "X" : "Y",
      elements,
      dragInputCallback: handlePositionChange2,
      value,
      property,
      scene,
      appState
    }
  );
};
var MultiPosition_default = MultiPosition;

// components/Stats/CanvasGrid.tsx
import { jsx as jsx134 } from "react/jsx-runtime";
var STEP_SIZE9 = 5;
var CanvasGrid = ({
  property,
  scene,
  appState,
  setAppState
}) => {
  return /* @__PURE__ */ jsx134(
    DragInput_default,
    {
      label: "Grid step",
      sensitivity: 8,
      elements: [],
      dragInputCallback: ({
        nextValue,
        instantChange,
        shouldChangeByStepSize,
        setInputValue
      }) => {
        setAppState((state) => {
          let nextGridStep;
          if (nextValue) {
            nextGridStep = nextValue;
          } else if (instantChange) {
            nextGridStep = shouldChangeByStepSize ? getStepSizedValue(
              state.gridStep + STEP_SIZE9 * Math.sign(instantChange),
              STEP_SIZE9
            ) : state.gridStep + instantChange;
          }
          if (!nextGridStep) {
            setInputValue(state.gridStep);
            return null;
          }
          nextGridStep = getNormalizedGridStep(nextGridStep);
          setInputValue(nextGridStep);
          return {
            gridStep: nextGridStep
          };
        });
      },
      scene,
      value: appState.gridStep,
      property,
      appState
    }
  );
};
var CanvasGrid_default = CanvasGrid;

// components/Stats/index.tsx
import clsx52 from "clsx";
import { Fragment as Fragment21, jsx as jsx135, jsxs as jsxs71 } from "react/jsx-runtime";
var STATS_TIMEOUT = 50;
var Stats = (props) => {
  const appState = useExcalidrawAppState();
  const sceneNonce = props.app.scene.getSceneNonce() || 1;
  const selectedElements = props.app.scene.getSelectedElements({
    selectedElementIds: appState.selectedElementIds,
    includeBoundTextElement: false
  });
  const gridModeEnabled = isGridModeEnabled(props.app);
  return /* @__PURE__ */ jsx135(
    StatsInner,
    {
      ...props,
      appState,
      sceneNonce,
      selectedElements,
      gridModeEnabled
    }
  );
};
var StatsRow = ({
  children,
  columns = 1,
  heading,
  style,
  ...rest
}) => /* @__PURE__ */ jsx135(
  "div",
  {
    className: clsx52("exc-stats__row", { "exc-stats__row--heading": heading }),
    style: {
      gridTemplateColumns: `repeat(${columns}, 1fr)`,
      ...style
    },
    ...rest,
    children
  }
);
StatsRow.displayName = "StatsRow";
var StatsRows = ({
  children,
  order,
  style,
  ...rest
}) => /* @__PURE__ */ jsx135("div", { className: "exc-stats__rows", style: { order, ...style }, ...rest, children });
StatsRows.displayName = "StatsRows";
Stats.StatsRow = StatsRow;
Stats.StatsRows = StatsRows;
var StatsInner = memo5(
  ({
    app,
    onClose,
    renderCustomStats,
    selectedElements,
    appState,
    sceneNonce,
    gridModeEnabled
  }) => {
    const scene = app.scene;
    const elements = scene.getNonDeletedElements();
    const elementsMap = scene.getNonDeletedElementsMap();
    const setAppState = useExcalidrawSetAppState();
    const singleElement = selectedElements.length === 1 ? selectedElements[0] : null;
    const multipleElements = selectedElements.length > 1 ? selectedElements : null;
    const cropMode = appState.croppingElementId && isImageElement(singleElement);
    const unCroppedDimension = cropMode ? getUncroppedWidthAndHeight(singleElement) : null;
    const [sceneDimension, setSceneDimension] = useState35({
      width: 0,
      height: 0
    });
    const throttledSetSceneDimension = useMemo9(
      () => throttle2((elements2) => {
        const boundingBox = getCommonBounds(elements2);
        setSceneDimension({
          width: Math.round(boundingBox[2]) - Math.round(boundingBox[0]),
          height: Math.round(boundingBox[3]) - Math.round(boundingBox[1])
        });
      }, STATS_TIMEOUT),
      []
    );
    useEffect38(() => {
      throttledSetSceneDimension(elements);
    }, [sceneNonce, elements, throttledSetSceneDimension]);
    useEffect38(
      () => () => throttledSetSceneDimension.cancel(),
      [throttledSetSceneDimension]
    );
    const atomicUnits = useMemo9(() => {
      return getAtomicUnits(selectedElements, appState);
    }, [selectedElements, appState]);
    const _frameAndChildrenSelectedTogether = useMemo9(() => {
      return frameAndChildrenSelectedTogether(selectedElements);
    }, [selectedElements]);
    return /* @__PURE__ */ jsx135("div", { className: "exc-stats", children: /* @__PURE__ */ jsxs71(Island, { padding: 3, children: [
      /* @__PURE__ */ jsxs71("div", { className: "title", children: [
        /* @__PURE__ */ jsx135("h2", { children: t("stats.title") }),
        /* @__PURE__ */ jsx135("div", { className: "close", onClick: onClose, children: CloseIcon })
      ] }),
      /* @__PURE__ */ jsxs71(
        Collapsible_default,
        {
          label: /* @__PURE__ */ jsx135("h3", { children: t("stats.generalStats") }),
          open: !!(appState.stats.panels & STATS_PANELS.generalStats),
          openTrigger: () => setAppState((state) => {
            return {
              stats: {
                open: true,
                panels: state.stats.panels ^ STATS_PANELS.generalStats
              }
            };
          }),
          children: [
            /* @__PURE__ */ jsxs71(StatsRows, { children: [
              /* @__PURE__ */ jsx135(StatsRow, { heading: true, children: t("stats.scene") }),
              /* @__PURE__ */ jsxs71(StatsRow, { columns: 2, children: [
                /* @__PURE__ */ jsx135("div", { children: t("stats.shapes") }),
                /* @__PURE__ */ jsx135("div", { children: elements.length })
              ] }),
              /* @__PURE__ */ jsxs71(StatsRow, { columns: 2, children: [
                /* @__PURE__ */ jsx135("div", { children: t("stats.width") }),
                /* @__PURE__ */ jsx135("div", { children: sceneDimension.width })
              ] }),
              /* @__PURE__ */ jsxs71(StatsRow, { columns: 2, children: [
                /* @__PURE__ */ jsx135("div", { children: t("stats.height") }),
                /* @__PURE__ */ jsx135("div", { children: sceneDimension.height })
              ] }),
              gridModeEnabled && /* @__PURE__ */ jsxs71(Fragment21, { children: [
                /* @__PURE__ */ jsx135(StatsRow, { heading: true, children: "Canvas" }),
                /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                  CanvasGrid_default,
                  {
                    property: "gridStep",
                    scene,
                    appState,
                    setAppState
                  }
                ) })
              ] })
            ] }),
            renderCustomStats?.(elements, appState)
          ]
        }
      ),
      !_frameAndChildrenSelectedTogether && selectedElements.length > 0 && /* @__PURE__ */ jsx135(
        "div",
        {
          id: "elementStats",
          style: {
            marginTop: 12
          },
          children: /* @__PURE__ */ jsx135(
            Collapsible_default,
            {
              label: /* @__PURE__ */ jsx135("h3", { children: t("stats.elementProperties") }),
              open: !!(appState.stats.panels & STATS_PANELS.elementProperties),
              openTrigger: () => setAppState((state) => {
                return {
                  stats: {
                    open: true,
                    panels: state.stats.panels ^ STATS_PANELS.elementProperties
                  }
                };
              }),
              children: /* @__PURE__ */ jsxs71(StatsRows, { children: [
                singleElement && /* @__PURE__ */ jsxs71(Fragment21, { children: [
                  cropMode && /* @__PURE__ */ jsx135(StatsRow, { heading: true, children: t("labels.unCroppedDimension") }),
                  appState.croppingElementId && isImageElement(singleElement) && unCroppedDimension && /* @__PURE__ */ jsxs71(StatsRow, { columns: 2, children: [
                    /* @__PURE__ */ jsx135("div", { children: t("stats.width") }),
                    /* @__PURE__ */ jsx135("div", { children: round(unCroppedDimension.width, 2) })
                  ] }),
                  appState.croppingElementId && isImageElement(singleElement) && unCroppedDimension && /* @__PURE__ */ jsxs71(StatsRow, { columns: 2, children: [
                    /* @__PURE__ */ jsx135("div", { children: t("stats.height") }),
                    /* @__PURE__ */ jsx135("div", { children: round(unCroppedDimension.height, 2) })
                  ] }),
                  /* @__PURE__ */ jsx135(StatsRow, { heading: true, "data-testid": "stats-element-type", children: appState.croppingElementId ? t("labels.imageCropping") : t(`element.${singleElement.type}`) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    Position_default,
                    {
                      element: singleElement,
                      property: "x",
                      elementsMap,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    Position_default,
                    {
                      element: singleElement,
                      property: "y",
                      elementsMap,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    Dimension_default,
                    {
                      property: "width",
                      element: singleElement,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    Dimension_default,
                    {
                      property: "height",
                      element: singleElement,
                      scene,
                      appState
                    }
                  ) }),
                  !isElbowArrow(singleElement) && /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    Angle_default,
                    {
                      property: "angle",
                      element: singleElement,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    FontSize_default,
                    {
                      property: "fontSize",
                      element: singleElement,
                      scene,
                      appState
                    }
                  ) })
                ] }),
                multipleElements && /* @__PURE__ */ jsxs71(Fragment21, { children: [
                  elementsAreInSameGroup(multipleElements) && /* @__PURE__ */ jsx135(StatsRow, { heading: true, children: t("element.group") }),
                  /* @__PURE__ */ jsxs71(StatsRow, { columns: 2, style: { margin: "0.3125rem 0" }, children: [
                    /* @__PURE__ */ jsx135("div", { children: t("stats.shapes") }),
                    /* @__PURE__ */ jsx135("div", { children: selectedElements.length })
                  ] }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    MultiPosition_default,
                    {
                      property: "x",
                      elements: multipleElements,
                      elementsMap,
                      atomicUnits,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    MultiPosition_default,
                    {
                      property: "y",
                      elements: multipleElements,
                      elementsMap,
                      atomicUnits,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    MultiDimension_default,
                    {
                      property: "width",
                      elements: multipleElements,
                      elementsMap,
                      atomicUnits,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    MultiDimension_default,
                    {
                      property: "height",
                      elements: multipleElements,
                      elementsMap,
                      atomicUnits,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    MultiAngle_default,
                    {
                      property: "angle",
                      elements: multipleElements,
                      scene,
                      appState
                    }
                  ) }),
                  /* @__PURE__ */ jsx135(StatsRow, { children: /* @__PURE__ */ jsx135(
                    MultiFontSize_default,
                    {
                      property: "fontSize",
                      elements: multipleElements,
                      scene,
                      appState,
                      elementsMap
                    }
                  ) })
                ] })
              ] })
            }
          )
        }
      )
    ] }) });
  },
  (prev, next) => {
    return prev.sceneNonce === next.sceneNonce && prev.selectedElements === next.selectedElements && prev.appState.stats.panels === next.appState.stats.panels && prev.gridModeEnabled === next.gridModeEnabled && prev.appState.gridStep === next.appState.gridStep && prev.appState.croppingElementId === next.appState.croppingElementId;
  }
);

// components/ElementLinkDialog.tsx
import { useCallback as useCallback13, useEffect as useEffect39, useState as useState36 } from "react";
import { jsx as jsx136, jsxs as jsxs72 } from "react/jsx-runtime";
var ElementLinkDialog = ({
  sourceElementId,
  onClose,
  elementsMap,
  appState,
  generateLinkForSelection = defaultGetElementLinkFromSelection
}) => {
  const originalLink = elementsMap.get(sourceElementId)?.link ?? null;
  const [nextLink, setNextLink] = useState36(originalLink);
  const [linkEdited, setLinkEdited] = useState36(false);
  useEffect39(() => {
    const selectedElements = getSelectedElements(elementsMap, appState);
    let nextLink2 = originalLink;
    if (selectedElements.length > 0 && generateLinkForSelection) {
      const idAndType = getLinkIdAndTypeFromSelection(
        selectedElements,
        appState
      );
      if (idAndType) {
        nextLink2 = normalizeLink(
          generateLinkForSelection(idAndType.id, idAndType.type)
        );
      }
    }
    setNextLink(nextLink2);
  }, [
    elementsMap,
    appState,
    appState.selectedElementIds,
    originalLink,
    generateLinkForSelection
  ]);
  const handleConfirm = useCallback13(() => {
    if (nextLink && nextLink !== elementsMap.get(sourceElementId)?.link) {
      const elementToLink = elementsMap.get(sourceElementId);
      elementToLink && mutateElement(elementToLink, {
        link: nextLink
      });
    }
    if (!nextLink && linkEdited && sourceElementId) {
      const elementToLink = elementsMap.get(sourceElementId);
      elementToLink && mutateElement(elementToLink, {
        link: null
      });
    }
    onClose?.();
  }, [sourceElementId, nextLink, elementsMap, linkEdited, onClose]);
  useEffect39(() => {
    const handleKeyDown = (event) => {
      if (appState.openDialog?.name === "elementLinkSelector" && event.key === KEYS.ENTER) {
        handleConfirm();
      }
      if (appState.openDialog?.name === "elementLinkSelector" && event.key === KEYS.ESCAPE) {
        onClose?.();
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [appState, onClose, handleConfirm]);
  return /* @__PURE__ */ jsxs72("div", { className: "ElementLinkDialog", children: [
    /* @__PURE__ */ jsxs72("div", { className: "ElementLinkDialog__header", children: [
      /* @__PURE__ */ jsx136("h2", { children: t("elementLink.title") }),
      /* @__PURE__ */ jsx136("p", { children: t("elementLink.desc") })
    ] }),
    /* @__PURE__ */ jsxs72("div", { className: "ElementLinkDialog__input", children: [
      /* @__PURE__ */ jsx136(
        TextField,
        {
          value: nextLink ?? "",
          onChange: (value) => {
            if (!linkEdited) {
              setLinkEdited(true);
            }
            setNextLink(value);
          },
          onKeyDown: (event) => {
            if (event.key === KEYS.ENTER) {
              handleConfirm();
            }
          },
          className: "ElementLinkDialog__input-field",
          selectOnRender: true
        }
      ),
      originalLink && nextLink && /* @__PURE__ */ jsx136(
        ToolButton,
        {
          type: "button",
          title: t("buttons.remove"),
          "aria-label": t("buttons.remove"),
          label: t("buttons.remove"),
          onClick: () => {
            setNextLink(null);
            setLinkEdited(true);
          },
          className: "ElementLinkDialog__remove",
          icon: TrashIcon
        }
      )
    ] }),
    /* @__PURE__ */ jsxs72("div", { className: "ElementLinkDialog__actions", children: [
      /* @__PURE__ */ jsx136(
        DialogActionButton_default,
        {
          label: t("buttons.cancel"),
          onClick: () => {
            onClose?.();
          },
          style: {
            marginRight: 10
          }
        }
      ),
      /* @__PURE__ */ jsx136(
        DialogActionButton_default,
        {
          label: t("buttons.confirm"),
          onClick: handleConfirm,
          actionType: "primary"
        }
      )
    ] })
  ] });
};
var ElementLinkDialog_default = ElementLinkDialog;

// components/LayerUI.tsx
import { Fragment as Fragment22, jsx as jsx137, jsxs as jsxs73 } from "react/jsx-runtime";
var DefaultMainMenu = ({ UIOptions }) => {
  return /* @__PURE__ */ jsxs73(MainMenu_default, { __fallback: true, children: [
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.LoadScene, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.SaveToActiveFile, {}),
    UIOptions.canvasActions.export && /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.Export, {}),
    UIOptions.canvasActions.saveAsImage && /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.SaveAsImage, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.SearchMenu, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.Help, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.ClearCanvas, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.Separator, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.Group, { title: "Excalidraw links", children: /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.Socials, {}) }),
    /* @__PURE__ */ jsx137(MainMenu_default.Separator, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.ToggleTheme, {}),
    /* @__PURE__ */ jsx137(MainMenu_default.DefaultItems.ChangeCanvasBackground, {})
  ] });
};
var DefaultOverwriteConfirmDialog = () => {
  return /* @__PURE__ */ jsxs73(OverwriteConfirmDialog, { __fallback: true, children: [
    /* @__PURE__ */ jsx137(OverwriteConfirmDialog.Actions.SaveToDisk, {}),
    /* @__PURE__ */ jsx137(OverwriteConfirmDialog.Actions.ExportToImage, {})
  ] });
};
var LayerUI = ({
  actionManager,
  appState,
  files,
  setAppState,
  elements,
  canvas,
  onLockToggle,
  onHandToolToggle,
  onPenModeToggle,
  showExitZenModeBtn,
  renderTopRightUI,
  renderCustomStats,
  UIOptions,
  onExportImage,
  renderWelcomeScreen,
  children,
  app,
  isCollaborating,
  generateLinkForSelection
}) => {
  const device = useDevice();
  const tunnels = useInitializeTunnels();
  const TunnelsJotaiProvider = tunnels.tunnelsJotai.Provider;
  const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
  const renderJSONExportDialog = () => {
    if (!UIOptions.canvasActions.export) {
      return null;
    }
    return /* @__PURE__ */ jsx137(
      JSONExportDialog,
      {
        elements,
        appState,
        files,
        actionManager,
        exportOpts: UIOptions.canvasActions.export,
        canvas,
        setAppState
      }
    );
  };
  const renderImageExportDialog = () => {
    if (!UIOptions.canvasActions.saveAsImage || appState.openDialog?.name !== "imageExport") {
      return null;
    }
    return /* @__PURE__ */ jsx137(
      ImageExportDialog,
      {
        elements,
        appState,
        files,
        actionManager,
        onExportImage,
        onCloseRequest: () => setAppState({ openDialog: null }),
        name: app.getName()
      }
    );
  };
  const renderCanvasActions = () => /* @__PURE__ */ jsxs73("div", { style: { position: "relative" }, children: [
    /* @__PURE__ */ jsx137(tunnels.MainMenuTunnel.Out, {}),
    renderWelcomeScreen && /* @__PURE__ */ jsx137(tunnels.WelcomeScreenMenuHintTunnel.Out, {})
  ] });
  const renderSelectedShapeActions = () => /* @__PURE__ */ jsx137(
    Section,
    {
      heading: "selectedShapeActions",
      className: clsx53("selected-shape-actions zen-mode-transition", {
        "transition-left": appState.zenModeEnabled
      }),
      children: /* @__PURE__ */ jsx137(
        Island,
        {
          className: CLASSES.SHAPE_ACTIONS_MENU,
          padding: 2,
          style: {
            // we want to make sure this doesn't overflow so subtracting the
            // approximate height of hamburgerMenu + footer
            maxHeight: `${appState.height - 166}px`
          },
          children: /* @__PURE__ */ jsx137(
            SelectedShapeActions,
            {
              appState,
              elementsMap: app.scene.getNonDeletedElementsMap(),
              renderAction: actionManager.renderAction,
              app
            }
          )
        }
      )
    }
  );
  const renderFixedSideContainer = () => {
    const shouldRenderSelectedShapeActions = showSelectedShapeActions(
      appState,
      elements
    );
    const shouldShowStats = appState.stats.open && !appState.zenModeEnabled && !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector";
    return /* @__PURE__ */ jsx137(FixedSideContainer, { side: "top", children: /* @__PURE__ */ jsxs73("div", { className: "App-menu App-menu_top", children: [
      /* @__PURE__ */ jsxs73(Stack_default.Col, { gap: 6, className: clsx53("App-menu_top__left"), children: [
        renderCanvasActions(),
        shouldRenderSelectedShapeActions && renderSelectedShapeActions()
      ] }),
      !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector" && /* @__PURE__ */ jsx137(Section, { heading: "shapes", className: "shapes-section", children: (heading) => /* @__PURE__ */ jsxs73("div", { style: { position: "relative" }, children: [
        renderWelcomeScreen && /* @__PURE__ */ jsx137(tunnels.WelcomeScreenToolbarHintTunnel.Out, {}),
        /* @__PURE__ */ jsx137(Stack_default.Col, { gap: 4, align: "start", children: /* @__PURE__ */ jsxs73(
          Stack_default.Row,
          {
            gap: 1,
            className: clsx53("App-toolbar-container", {
              "zen-mode": appState.zenModeEnabled
            }),
            children: [
              /* @__PURE__ */ jsxs73(
                Island,
                {
                  padding: 1,
                  className: clsx53("App-toolbar", {
                    "zen-mode": appState.zenModeEnabled
                  }),
                  children: [
                    /* @__PURE__ */ jsx137(
                      HintViewer,
                      {
                        appState,
                        isMobile: device.editor.isMobile,
                        device,
                        app
                      }
                    ),
                    heading,
                    /* @__PURE__ */ jsxs73(Stack_default.Row, { gap: 1, children: [
                      /* @__PURE__ */ jsx137(
                        PenModeButton,
                        {
                          zenModeEnabled: appState.zenModeEnabled,
                          checked: appState.penMode,
                          onChange: () => onPenModeToggle(null),
                          title: t("toolBar.penMode"),
                          penDetected: appState.penDetected
                        }
                      ),
                      /* @__PURE__ */ jsx137(
                        LockButton,
                        {
                          checked: appState.activeTool.locked,
                          onChange: onLockToggle,
                          title: t("toolBar.lock")
                        }
                      ),
                      /* @__PURE__ */ jsx137("div", { className: "App-toolbar__divider" }),
                      /* @__PURE__ */ jsx137(
                        HandButton,
                        {
                          checked: isHandToolActive(appState),
                          onChange: () => onHandToolToggle(),
                          title: t("toolBar.hand"),
                          isMobile: true
                        }
                      ),
                      /* @__PURE__ */ jsx137(
                        ShapesSwitcher,
                        {
                          appState,
                          activeTool: appState.activeTool,
                          UIOptions,
                          app
                        }
                      )
                    ] })
                  ]
                }
              ),
              isCollaborating && /* @__PURE__ */ jsx137(
                Island,
                {
                  style: {
                    marginLeft: 8,
                    alignSelf: "center",
                    height: "fit-content"
                  },
                  children: /* @__PURE__ */ jsx137(
                    LaserPointerButton,
                    {
                      title: t("toolBar.laser"),
                      checked: appState.activeTool.type === TOOL_TYPE.laser,
                      onChange: () => app.setActiveTool({ type: TOOL_TYPE.laser }),
                      isMobile: true
                    }
                  )
                }
              )
            ]
          }
        ) })
      ] }) }),
      /* @__PURE__ */ jsxs73(
        "div",
        {
          className: clsx53(
            "layer-ui__wrapper__top-right zen-mode-transition",
            {
              "transition-right": appState.zenModeEnabled
            }
          ),
          children: [
            appState.collaborators.size > 0 && /* @__PURE__ */ jsx137(
              UserList,
              {
                collaborators: appState.collaborators,
                userToFollow: appState.userToFollow?.socketId || null
              }
            ),
            renderTopRightUI?.(device.editor.isMobile, appState),
            !appState.viewModeEnabled && appState.openDialog?.name !== "elementLinkSelector" && // hide button when sidebar docked
            (!isSidebarDocked || appState.openSidebar?.name !== DEFAULT_SIDEBAR.name) && /* @__PURE__ */ jsx137(tunnels.DefaultSidebarTriggerTunnel.Out, {}),
            shouldShowStats && /* @__PURE__ */ jsx137(
              Stats,
              {
                app,
                onClose: () => {
                  actionManager.executeAction(actionToggleStats);
                },
                renderCustomStats
              }
            )
          ]
        }
      )
    ] }) });
  };
  const renderSidebars = () => {
    return /* @__PURE__ */ jsx137(
      DefaultSidebar,
      {
        __fallback: true,
        onDock: (docked) => {
          trackEvent(
            "sidebar",
            `toggleDock (${docked ? "dock" : "undock"})`,
            `(${device.editor.isMobile ? "mobile" : "desktop"})`
          );
        }
      }
    );
  };
  const isSidebarDocked = useAtomValue(isSidebarDockedAtom);
  const layerUIJSX = /* @__PURE__ */ jsxs73(Fragment22, { children: [
    children,
    /* @__PURE__ */ jsx137(DefaultMainMenu, { UIOptions }),
    /* @__PURE__ */ jsx137(
      DefaultSidebar.Trigger,
      {
        __fallback: true,
        icon: LibraryIcon,
        title: capitalizeString(t("toolBar.library")),
        onToggle: (open) => {
          if (open) {
            trackEvent(
              "sidebar",
              `${DEFAULT_SIDEBAR.name} (open)`,
              `button (${device.editor.isMobile ? "mobile" : "desktop"})`
            );
          }
        },
        tab: DEFAULT_SIDEBAR.defaultTab,
        children: t("toolBar.library")
      }
    ),
    /* @__PURE__ */ jsx137(DefaultOverwriteConfirmDialog, {}),
    appState.openDialog?.name === "ttd" && /* @__PURE__ */ jsx137(TTDDialog, { __fallback: true }),
    appState.isLoading && /* @__PURE__ */ jsx137(LoadingMessage, { delay: 250 }),
    appState.errorMessage && /* @__PURE__ */ jsx137(ErrorDialog, { onClose: () => setAppState({ errorMessage: null }), children: appState.errorMessage }),
    eyeDropperState && !device.editor.isMobile && /* @__PURE__ */ jsx137(
      EyeDropper,
      {
        colorPickerType: eyeDropperState.colorPickerType,
        onCancel: () => {
          setEyeDropperState(null);
        },
        onChange: (colorPickerType, color, selectedElements, { altKey }) => {
          if (colorPickerType !== "elementBackground" && colorPickerType !== "elementStroke") {
            return;
          }
          if (selectedElements.length) {
            for (const element of selectedElements) {
              mutateElement(
                element,
                {
                  [altKey && eyeDropperState.swapPreviewOnAlt ? colorPickerType === "elementBackground" ? "strokeColor" : "backgroundColor" : colorPickerType === "elementBackground" ? "backgroundColor" : "strokeColor"]: color
                },
                false
              );
              ShapeCache.delete(element);
            }
            Scene_default.getScene(selectedElements[0])?.triggerUpdate();
          } else if (colorPickerType === "elementBackground") {
            setAppState({
              currentItemBackgroundColor: color
            });
          } else {
            setAppState({ currentItemStrokeColor: color });
          }
        },
        onSelect: (color, event) => {
          setEyeDropperState((state) => {
            return state?.keepOpenOnAlt && event.altKey ? state : null;
          });
          eyeDropperState?.onSelect?.(color, event);
        }
      }
    ),
    appState.openDialog?.name === "help" && /* @__PURE__ */ jsx137(
      HelpDialog,
      {
        onClose: () => {
          setAppState({ openDialog: null });
        }
      }
    ),
    /* @__PURE__ */ jsx137(ActiveConfirmDialog, {}),
    appState.openDialog?.name === "elementLinkSelector" && /* @__PURE__ */ jsx137(
      ElementLinkDialog_default,
      {
        sourceElementId: appState.openDialog.sourceElementId,
        onClose: () => {
          setAppState({
            openDialog: null
          });
        },
        elementsMap: app.scene.getNonDeletedElementsMap(),
        appState,
        generateLinkForSelection
      }
    ),
    /* @__PURE__ */ jsx137(tunnels.OverwriteConfirmDialogTunnel.Out, {}),
    renderImageExportDialog(),
    renderJSONExportDialog(),
    appState.pasteDialog.shown && /* @__PURE__ */ jsx137(
      PasteChartDialog,
      {
        setAppState,
        appState,
        onClose: () => setAppState({
          pasteDialog: { shown: false, data: null }
        })
      }
    ),
    device.editor.isMobile && /* @__PURE__ */ jsx137(
      MobileMenu,
      {
        app,
        appState,
        elements,
        actionManager,
        renderJSONExportDialog,
        renderImageExportDialog,
        setAppState,
        onLockToggle,
        onHandToolToggle,
        onPenModeToggle,
        renderTopRightUI,
        renderCustomStats,
        renderSidebars,
        device,
        renderWelcomeScreen,
        UIOptions
      }
    ),
    !device.editor.isMobile && /* @__PURE__ */ jsxs73(Fragment22, { children: [
      /* @__PURE__ */ jsxs73(
        "div",
        {
          className: "layer-ui__wrapper",
          style: appState.openSidebar && isSidebarDocked && device.editor.canFitSidebar ? { width: `calc(100% - var(--right-sidebar-width))` } : {},
          children: [
            renderWelcomeScreen && /* @__PURE__ */ jsx137(tunnels.WelcomeScreenCenterTunnel.Out, {}),
            renderFixedSideContainer(),
            /* @__PURE__ */ jsx137(
              Footer_default,
              {
                appState,
                actionManager,
                showExitZenModeBtn,
                renderWelcomeScreen
              }
            ),
            appState.scrolledOutside && /* @__PURE__ */ jsx137(
              "button",
              {
                type: "button",
                className: "scroll-back-to-content",
                onClick: () => {
                  setAppState((appState2) => ({
                    ...calculateScrollCenter(elements, appState2)
                  }));
                },
                children: t("buttons.scrollBackToContent")
              }
            )
          ]
        }
      ),
      renderSidebars()
    ] })
  ] });
  return /* @__PURE__ */ jsx137(UIAppStateContext.Provider, { value: appState, children: /* @__PURE__ */ jsx137(TunnelsJotaiProvider, { children: /* @__PURE__ */ jsx137(TunnelsContext.Provider, { value: tunnels, children: layerUIJSX }) }) });
};
var stripIrrelevantAppStateProps = (appState) => {
  const {
    suggestedBindings,
    startBoundElement,
    cursorButton,
    scrollX,
    scrollY,
    ...ret
  } = appState;
  return ret;
};
var areEqual2 = (prevProps, nextProps) => {
  if (prevProps.children !== nextProps.children) {
    return false;
  }
  const { canvas: _pC, appState: prevAppState, ...prev } = prevProps;
  const { canvas: _nC, appState: nextAppState, ...next } = nextProps;
  return isShallowEqual(
    // asserting AppState because we're being passed the whole AppState
    // but resolve to only the UI-relevant props
    stripIrrelevantAppStateProps(prevAppState),
    stripIrrelevantAppStateProps(nextAppState),
    {
      selectedElementIds: isShallowEqual,
      selectedGroupIds: isShallowEqual
    }
  ) && isShallowEqual(prev, next);
};
var LayerUI_default = React40.memo(LayerUI, areEqual2);

// components/Toast.tsx
import { useCallback as useCallback14, useEffect as useEffect40, useRef as useRef35 } from "react";
import { jsx as jsx138, jsxs as jsxs74 } from "react/jsx-runtime";
var DEFAULT_TOAST_TIMEOUT = 5e3;
var Toast = ({
  message,
  onClose,
  closable = false,
  // To prevent autoclose, pass duration as Infinity
  duration = DEFAULT_TOAST_TIMEOUT,
  style
}) => {
  const timerRef = useRef35(0);
  const shouldAutoClose = duration !== Infinity;
  const scheduleTimeout = useCallback14(() => {
    if (!shouldAutoClose) {
      return;
    }
    timerRef.current = window.setTimeout(() => onClose(), duration);
  }, [onClose, duration, shouldAutoClose]);
  useEffect40(() => {
    if (!shouldAutoClose) {
      return;
    }
    scheduleTimeout();
    return () => clearTimeout(timerRef.current);
  }, [scheduleTimeout, message, duration, shouldAutoClose]);
  const onMouseEnter = shouldAutoClose ? () => clearTimeout(timerRef?.current) : void 0;
  const onMouseLeave = shouldAutoClose ? scheduleTimeout : void 0;
  return /* @__PURE__ */ jsxs74(
    "div",
    {
      className: "Toast",
      onMouseEnter,
      onMouseLeave,
      style,
      children: [
        /* @__PURE__ */ jsx138("p", { className: "Toast__message", children: message }),
        closable && /* @__PURE__ */ jsx138(
          ToolButton,
          {
            icon: CloseIcon,
            "aria-label": "close",
            type: "icon",
            onClick: onClose,
            className: "close"
          }
        )
      ]
    }
  );
};

// actions/actionToggleViewMode.tsx
var actionToggleViewMode = register({
  name: "viewMode",
  label: "labels.viewMode",
  paletteName: "Toggle view mode",
  icon: eyeIcon,
  viewMode: true,
  trackEvent: {
    category: "canvas",
    predicate: (appState) => !appState.viewModeEnabled
  },
  perform(elements, appState) {
    return {
      appState: {
        ...appState,
        viewModeEnabled: !this.checked(appState)
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.viewModeEnabled,
  predicate: (elements, appState, appProps) => {
    return typeof appProps.viewModeEnabled === "undefined";
  },
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.R
});

// components/App.tsx
import throttle3 from "lodash.throttle";

// actions/actionFrame.ts
var isSingleFrameSelected = (appState, app) => {
  const selectedElements = app.scene.getSelectedElements(appState);
  return selectedElements.length === 1 && isFrameLikeElement(selectedElements[0]);
};
var actionSelectAllElementsInFrame = register({
  name: "selectAllElementsInFrame",
  label: "labels.selectAllElementsInFrame",
  trackEvent: { category: "canvas" },
  perform: (elements, appState, _, app) => {
    const selectedElement = app.scene.getSelectedElements(appState).at(0) || null;
    if (isFrameLikeElement(selectedElement)) {
      const elementsInFrame = getFrameChildren(
        getNonDeletedElements(elements),
        selectedElement.id
      ).filter((element) => !(element.type === "text" && element.containerId));
      return {
        elements,
        appState: {
          ...appState,
          selectedElementIds: elementsInFrame.reduce((acc, element) => {
            acc[element.id] = true;
            return acc;
          }, {})
        },
        captureUpdate: CaptureUpdateAction.IMMEDIATELY
      };
    }
    return {
      elements,
      appState,
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  predicate: (elements, appState, _, app) => isSingleFrameSelected(appState, app)
});
var actionRemoveAllElementsFromFrame = register({
  name: "removeAllElementsFromFrame",
  label: "labels.removeAllElementsFromFrame",
  trackEvent: { category: "history" },
  perform: (elements, appState, _, app) => {
    const selectedElement = app.scene.getSelectedElements(appState).at(0) || null;
    if (isFrameLikeElement(selectedElement)) {
      return {
        elements: removeAllElementsFromFrame(elements, selectedElement),
        appState: {
          ...appState,
          selectedElementIds: {
            [selectedElement.id]: true
          }
        },
        captureUpdate: CaptureUpdateAction.IMMEDIATELY
      };
    }
    return {
      elements,
      appState,
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  predicate: (elements, appState, _, app) => isSingleFrameSelected(appState, app)
});
var actionupdateFrameRendering = register({
  name: "updateFrameRendering",
  label: "labels.updateFrameRendering",
  viewMode: true,
  trackEvent: { category: "canvas" },
  perform: (elements, appState) => {
    return {
      elements,
      appState: {
        ...appState,
        frameRendering: {
          ...appState.frameRendering,
          enabled: !appState.frameRendering.enabled
        }
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  checked: (appState) => appState.frameRendering.enabled
});
var actionSetFrameAsActiveTool = register({
  name: "setFrameAsActiveTool",
  label: "toolBar.frame",
  trackEvent: { category: "toolbar" },
  icon: frameToolIcon,
  viewMode: false,
  perform: (elements, appState, _, app) => {
    const nextActiveTool = updateActiveTool(appState, {
      type: "frame"
    });
    setCursorForShape(app.interactiveCanvas, {
      ...appState,
      activeTool: nextActiveTool
    });
    return {
      elements,
      appState: {
        ...appState,
        activeTool: updateActiveTool(appState, {
          type: "frame"
        })
      },
      captureUpdate: CaptureUpdateAction.EVENTUALLY
    };
  },
  keyTest: (event) => !event[KEYS.CTRL_OR_CMD] && !event.shiftKey && !event.altKey && event.key.toLocaleLowerCase() === KEYS.F
});
var actionWrapSelectionInFrame = register({
  name: "wrapSelectionInFrame",
  label: "labels.wrapSelectionInFrame",
  trackEvent: { category: "element" },
  predicate: (elements, appState, _, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    return selectedElements.length > 0 && !selectedElements.some((element) => isFrameLikeElement(element));
  },
  perform: (elements, appState, _, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    const [x1, y1, x2, y2] = getCommonBounds(
      selectedElements,
      app.scene.getNonDeletedElementsMap()
    );
    const PADDING = 16;
    const frame = newFrameElement({
      x: x1 - PADDING,
      y: y1 - PADDING,
      width: x2 - x1 + PADDING * 2,
      height: y2 - y1 + PADDING * 2
    });
    if (appState.editingGroupId) {
      const elementsInGroup = getElementsInGroup(
        selectedElements,
        appState.editingGroupId
      );
      for (const elementInGroup of elementsInGroup) {
        const index = elementInGroup.groupIds.indexOf(appState.editingGroupId);
        mutateElement(
          elementInGroup,
          {
            groupIds: elementInGroup.groupIds.slice(0, index)
          },
          false
        );
      }
    }
    const nextElements = addElementsToFrame(
      [...app.scene.getElementsIncludingDeleted(), frame],
      selectedElements,
      frame,
      appState
    );
    return {
      elements: nextElements,
      appState: {
        selectedElementIds: { [frame.id]: true }
      },
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  }
});

// components/BraveMeasureTextError.tsx
import { jsx as jsx139, jsxs as jsxs75 } from "react/jsx-runtime";
var BraveMeasureTextError = () => {
  return /* @__PURE__ */ jsxs75("div", { "data-testid": "brave-measure-text-error", children: [
    /* @__PURE__ */ jsx139("p", { children: /* @__PURE__ */ jsx139(
      Trans_default,
      {
        i18nKey: "errors.brave_measure_text_error.line1",
        bold: (el) => /* @__PURE__ */ jsx139("span", { style: { fontWeight: 600 }, children: el })
      }
    ) }),
    /* @__PURE__ */ jsx139("p", { children: /* @__PURE__ */ jsx139(
      Trans_default,
      {
        i18nKey: "errors.brave_measure_text_error.line2",
        bold: (el) => /* @__PURE__ */ jsx139("span", { style: { fontWeight: 600 }, children: el })
      }
    ) }),
    /* @__PURE__ */ jsx139("p", { children: /* @__PURE__ */ jsx139(
      Trans_default,
      {
        i18nKey: "errors.brave_measure_text_error.line3",
        link: (el) => /* @__PURE__ */ jsx139("a", { href: "http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser", children: el })
      }
    ) }),
    /* @__PURE__ */ jsx139("p", { children: /* @__PURE__ */ jsx139(
      Trans_default,
      {
        i18nKey: "errors.brave_measure_text_error.line4",
        issueLink: (el) => /* @__PURE__ */ jsx139("a", { href: "https://github.com/excalidraw/excalidraw/issues/new", children: el }),
        discordLink: (el) => /* @__PURE__ */ jsxs75("a", { href: "https://discord.gg/UexuTaE", children: [
          el,
          "."
        ] })
      }
    ) })
  ] });
};
var BraveMeasureTextError_default = BraveMeasureTextError;

// data/transform.ts
var DEFAULT_LINEAR_ELEMENT_PROPS = {
  width: 100,
  height: 0
};
var DEFAULT_DIMENSION = 100;
var bindTextToContainer = (container, textProps, elementsMap) => {
  const textElement = newTextElement({
    x: 0,
    y: 0,
    textAlign: TEXT_ALIGN.CENTER,
    verticalAlign: VERTICAL_ALIGN.MIDDLE,
    ...textProps,
    containerId: container.id,
    strokeColor: textProps.strokeColor || container.strokeColor
  });
  Object.assign(container, {
    boundElements: (container.boundElements || []).concat({
      type: "text",
      id: textElement.id
    })
  });
  redrawTextBoundingBox(textElement, container, elementsMap);
  return [container, textElement];
};
var bindLinearElementToElement = (linearElement, start, end, elementStore, elementsMap) => {
  let startBoundElement;
  let endBoundElement;
  Object.assign(linearElement, {
    startBinding: linearElement?.startBinding || null,
    endBinding: linearElement.endBinding || null
  });
  if (start) {
    const width = start?.width ?? DEFAULT_DIMENSION;
    const height = start?.height ?? DEFAULT_DIMENSION;
    let existingElement;
    if (start.id) {
      existingElement = elementStore.getElement(start.id);
      if (!existingElement) {
        console.error(`No element for start binding with id ${start.id} found`);
      }
    }
    const startX = start.x || linearElement.x - width;
    const startY = start.y || linearElement.y - height / 2;
    const startType = existingElement ? existingElement.type : start.type;
    if (startType) {
      if (startType === "text") {
        let text = "";
        if (existingElement && existingElement.type === "text") {
          text = existingElement.text;
        } else if (start.type === "text") {
          text = start.text;
        }
        if (!text) {
          console.error(
            `No text found for start binding text element for ${linearElement.id}`
          );
        }
        startBoundElement = newTextElement({
          x: startX,
          y: startY,
          type: "text",
          ...existingElement,
          ...start,
          text
        });
        Object.assign(startBoundElement, {
          x: start.x || linearElement.x - startBoundElement.width,
          y: start.y || linearElement.y - startBoundElement.height / 2
        });
      } else {
        switch (startType) {
          case "rectangle":
          case "ellipse":
          case "diamond": {
            startBoundElement = newElement({
              x: startX,
              y: startY,
              width,
              height,
              ...existingElement,
              ...start,
              type: startType
            });
            break;
          }
          default: {
            assertNever(
              linearElement,
              `Unhandled element start type "${start.type}"`,
              true
            );
          }
        }
      }
      bindLinearElement(
        linearElement,
        startBoundElement,
        "start",
        elementsMap
      );
    }
  }
  if (end) {
    const height = end?.height ?? DEFAULT_DIMENSION;
    const width = end?.width ?? DEFAULT_DIMENSION;
    let existingElement;
    if (end.id) {
      existingElement = elementStore.getElement(end.id);
      if (!existingElement) {
        console.error(`No element for end binding with id ${end.id} found`);
      }
    }
    const endX = end.x || linearElement.x + linearElement.width;
    const endY = end.y || linearElement.y - height / 2;
    const endType = existingElement ? existingElement.type : end.type;
    if (endType) {
      if (endType === "text") {
        let text = "";
        if (existingElement && existingElement.type === "text") {
          text = existingElement.text;
        } else if (end.type === "text") {
          text = end.text;
        }
        if (!text) {
          console.error(
            `No text found for end binding text element for ${linearElement.id}`
          );
        }
        endBoundElement = newTextElement({
          x: endX,
          y: endY,
          type: "text",
          ...existingElement,
          ...end,
          text
        });
        Object.assign(endBoundElement, {
          y: end.y || linearElement.y - endBoundElement.height / 2
        });
      } else {
        switch (endType) {
          case "rectangle":
          case "ellipse":
          case "diamond": {
            endBoundElement = newElement({
              x: endX,
              y: endY,
              width,
              height,
              ...existingElement,
              ...end,
              type: endType
            });
            break;
          }
          default: {
            assertNever(
              linearElement,
              `Unhandled element end type "${endType}"`,
              true
            );
          }
        }
      }
      bindLinearElement(
        linearElement,
        endBoundElement,
        "end",
        elementsMap
      );
    }
  }
  if (linearElement.points.length < 2) {
    return {
      linearElement,
      startBoundElement,
      endBoundElement
    };
  }
  const endPointIndex = linearElement.points.length - 1;
  const delta = 0.5;
  const newPoints = cloneJSON(linearElement.points);
  if (linearElement.points[endPointIndex][0] > linearElement.points[endPointIndex - 1][0]) {
    newPoints[0][0] = delta;
    newPoints[endPointIndex][0] -= delta;
  }
  if (linearElement.points[endPointIndex][0] < linearElement.points[endPointIndex - 1][0]) {
    newPoints[0][0] = -delta;
    newPoints[endPointIndex][0] += delta;
  }
  if (linearElement.points[endPointIndex][1] > linearElement.points[endPointIndex - 1][1]) {
    newPoints[0][1] = delta;
    newPoints[endPointIndex][1] -= delta;
  }
  if (linearElement.points[endPointIndex][1] < linearElement.points[endPointIndex - 1][1]) {
    newPoints[0][1] = -delta;
    newPoints[endPointIndex][1] += delta;
  }
  Object.assign(linearElement, { points: newPoints });
  return {
    linearElement,
    startBoundElement,
    endBoundElement
  };
};
var ElementStore = class {
  constructor() {
    __publicField(this, "excalidrawElements", /* @__PURE__ */ new Map());
    __publicField(this, "add", (ele) => {
      if (!ele) {
        return;
      }
      this.excalidrawElements.set(ele.id, ele);
    });
    __publicField(this, "getElements", () => {
      return syncInvalidIndices(Array.from(this.excalidrawElements.values()));
    });
    __publicField(this, "getElementsMap", () => {
      return toBrandedType(
        arrayToMap(this.getElements())
      );
    });
    __publicField(this, "getElement", (id) => {
      return this.excalidrawElements.get(id);
    });
  }
};
var convertToExcalidrawElements = (elementsSkeleton, opts) => {
  if (!elementsSkeleton) {
    return [];
  }
  const elements = cloneJSON(elementsSkeleton);
  const elementStore = new ElementStore();
  const elementsWithIds = /* @__PURE__ */ new Map();
  const oldToNewElementIdMap = /* @__PURE__ */ new Map();
  for (const element of elements) {
    let excalidrawElement;
    const originalId = element.id;
    if (opts?.regenerateIds !== false) {
      Object.assign(element, { id: randomId() });
    }
    switch (element.type) {
      case "rectangle":
      case "ellipse":
      case "diamond": {
        const width = element?.label?.text && element.width === void 0 ? 0 : element?.width || DEFAULT_DIMENSION;
        const height = element?.label?.text && element.height === void 0 ? 0 : element?.height || DEFAULT_DIMENSION;
        excalidrawElement = newElement({
          ...element,
          width,
          height
        });
        break;
      }
      case "line": {
        const width = element.width || DEFAULT_LINEAR_ELEMENT_PROPS.width;
        const height = element.height || DEFAULT_LINEAR_ELEMENT_PROPS.height;
        excalidrawElement = newLinearElement({
          width,
          height,
          points: [pointFrom(0, 0), pointFrom(width, height)],
          ...element
        });
        break;
      }
      case "arrow": {
        const width = element.width || DEFAULT_LINEAR_ELEMENT_PROPS.width;
        const height = element.height || DEFAULT_LINEAR_ELEMENT_PROPS.height;
        excalidrawElement = newArrowElement({
          width,
          height,
          endArrowhead: "arrow",
          points: [pointFrom(0, 0), pointFrom(width, height)],
          ...element,
          type: "arrow"
        });
        Object.assign(
          excalidrawElement,
          getSizeFromPoints(excalidrawElement.points)
        );
        break;
      }
      case "text": {
        const fontFamily = element?.fontFamily || DEFAULT_FONT_FAMILY;
        const fontSize = element?.fontSize || DEFAULT_FONT_SIZE;
        const lineHeight = element?.lineHeight || getLineHeight(fontFamily);
        const text = element.text ?? "";
        const normalizedText = normalizeText(text);
        const metrics = measureText(
          normalizedText,
          getFontString({ fontFamily, fontSize }),
          lineHeight
        );
        excalidrawElement = newTextElement({
          width: metrics.width,
          height: metrics.height,
          fontFamily,
          fontSize,
          ...element
        });
        break;
      }
      case "image": {
        excalidrawElement = newImageElement({
          width: element?.width || DEFAULT_DIMENSION,
          height: element?.height || DEFAULT_DIMENSION,
          ...element
        });
        break;
      }
      case "frame": {
        excalidrawElement = newFrameElement({
          x: 0,
          y: 0,
          ...element
        });
        break;
      }
      case "magicframe": {
        excalidrawElement = newMagicFrameElement({
          x: 0,
          y: 0,
          ...element
        });
        break;
      }
      case "freedraw":
      case "iframe":
      case "embeddable": {
        excalidrawElement = element;
        break;
      }
      default: {
        excalidrawElement = element;
        assertNever(
          element,
          `Unhandled element type "${element.type}"`,
          true
        );
      }
    }
    const existingElement = elementStore.getElement(excalidrawElement.id);
    if (existingElement) {
      console.error(`Duplicate id found for ${excalidrawElement.id}`);
    } else {
      elementStore.add(excalidrawElement);
      elementsWithIds.set(excalidrawElement.id, element);
      if (originalId) {
        oldToNewElementIdMap.set(originalId, excalidrawElement.id);
      }
    }
  }
  const elementsMap = elementStore.getElementsMap();
  for (const [id, element] of elementsWithIds) {
    const excalidrawElement = elementStore.getElement(id);
    switch (element.type) {
      case "rectangle":
      case "ellipse":
      case "diamond":
      case "arrow": {
        if (element.label?.text) {
          let [container, text] = bindTextToContainer(
            excalidrawElement,
            element?.label,
            elementsMap
          );
          elementStore.add(container);
          elementStore.add(text);
          if (isArrowElement(container)) {
            const originalStart = element.type === "arrow" ? element?.start : void 0;
            const originalEnd = element.type === "arrow" ? element?.end : void 0;
            if (originalStart && originalStart.id) {
              const newStartId = oldToNewElementIdMap.get(originalStart.id);
              if (newStartId) {
                Object.assign(originalStart, { id: newStartId });
              }
            }
            if (originalEnd && originalEnd.id) {
              const newEndId = oldToNewElementIdMap.get(originalEnd.id);
              if (newEndId) {
                Object.assign(originalEnd, { id: newEndId });
              }
            }
            const { linearElement, startBoundElement, endBoundElement } = bindLinearElementToElement(
              container,
              originalStart,
              originalEnd,
              elementStore,
              elementsMap
            );
            container = linearElement;
            elementStore.add(linearElement);
            elementStore.add(startBoundElement);
            elementStore.add(endBoundElement);
          }
        } else {
          switch (element.type) {
            case "arrow": {
              const { start, end } = element;
              if (start && start.id) {
                const newStartId = oldToNewElementIdMap.get(start.id);
                Object.assign(start, { id: newStartId });
              }
              if (end && end.id) {
                const newEndId = oldToNewElementIdMap.get(end.id);
                Object.assign(end, { id: newEndId });
              }
              const { linearElement, startBoundElement, endBoundElement } = bindLinearElementToElement(
                excalidrawElement,
                start,
                end,
                elementStore,
                elementsMap
              );
              elementStore.add(linearElement);
              elementStore.add(startBoundElement);
              elementStore.add(endBoundElement);
              break;
            }
          }
        }
        break;
      }
    }
  }
  for (const [id, element] of elementsWithIds) {
    if (element.type !== "frame" && element.type !== "magicframe") {
      continue;
    }
    const frame = elementStore.getElement(id);
    if (!frame) {
      throw new Error(`Excalidraw element with id ${id} doesn't exist`);
    }
    const childrenElements = [];
    element.children.forEach((id2) => {
      const newElementId = oldToNewElementIdMap.get(id2);
      if (!newElementId) {
        throw new Error(`Element with ${id2} wasn't mapped correctly`);
      }
      const elementInFrame = elementStore.getElement(newElementId);
      if (!elementInFrame) {
        throw new Error(`Frame element with id ${newElementId} doesn't exist`);
      }
      Object.assign(elementInFrame, { frameId: frame.id });
      elementInFrame?.boundElements?.forEach((boundElement) => {
        const ele = elementStore.getElement(boundElement.id);
        if (!ele) {
          throw new Error(
            `Bound element with id ${boundElement.id} doesn't exist`
          );
        }
        Object.assign(ele, { frameId: frame.id });
        childrenElements.push(ele);
      });
      childrenElements.push(elementInFrame);
    });
    let [minX, minY, maxX, maxY] = getCommonBounds(childrenElements);
    const PADDING = 10;
    minX = minX - PADDING;
    minY = minY - PADDING;
    maxX = maxX + PADDING;
    maxY = maxY + PADDING;
    const frameX = frame?.x || minX;
    const frameY = frame?.y || minY;
    const frameWidth = frame?.width || maxX - minX;
    const frameHeight = frame?.height || maxY - minY;
    Object.assign(frame, {
      x: frameX,
      y: frameY,
      width: frameWidth,
      height: frameHeight
    });
    if (isDevEnv() && element.children.length && (frame?.x || frame?.y || frame?.width || frame?.height)) {
      console.info(
        "User provided frame attributes are being considered, if you find this inaccurate, please remove any of the attributes - x, y, width and height so frame coordinates and dimensions are calculated automatically"
      );
    }
  }
  return elementStore.getElements();
};

// components/canvases/InteractiveCanvas.tsx
import React41, { useEffect as useEffect41, useRef as useRef36 } from "react";

// reactUtils.ts
import { unstable_batchedUpdates as unstable_batchedUpdates2 } from "react-dom";
import { version as ReactVersion } from "react";
var withBatchedUpdates = (func) => (event) => {
  unstable_batchedUpdates2(func, event);
};
var withBatchedUpdatesThrottled = (func) => {
  return throttleRAF((event) => {
    unstable_batchedUpdates2(func, event);
  });
};
var isRenderThrottlingEnabled = (() => {
  let IS_REACT_18_AND_UP;
  try {
    const version = ReactVersion.split(".");
    IS_REACT_18_AND_UP = Number(version[0]) > 17;
  } catch {
    IS_REACT_18_AND_UP = false;
  }
  let hasWarned = false;
  return () => {
    if (window.EXCALIDRAW_THROTTLE_RENDER === true) {
      if (!IS_REACT_18_AND_UP) {
        if (!hasWarned) {
          hasWarned = true;
          console.warn(
            "Excalidraw: render throttling is disabled on React versions < 18."
          );
        }
        return false;
      }
      return true;
    }
    return false;
  };
})();

// renderer/renderSnaps.ts
var SNAP_COLOR_LIGHT = "#ff6b6b";
var SNAP_COLOR_DARK = "#ff0000";
var SNAP_WIDTH = 1;
var SNAP_CROSS_SIZE = 2;
var renderSnaps = (context, appState) => {
  if (!appState.snapLines.length) {
    return;
  }
  const snapColor = appState.theme === THEME.LIGHT || appState.zenModeEnabled ? SNAP_COLOR_LIGHT : SNAP_COLOR_DARK;
  const snapWidth = (appState.zenModeEnabled ? SNAP_WIDTH * 1.5 : SNAP_WIDTH) / appState.zoom.value;
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  for (const snapLine of appState.snapLines) {
    if (snapLine.type === "pointer") {
      context.lineWidth = snapWidth;
      context.strokeStyle = snapColor;
      drawPointerSnapLine(snapLine, context, appState);
    } else if (snapLine.type === "gap") {
      context.lineWidth = snapWidth;
      context.strokeStyle = snapColor;
      drawGapLine(
        snapLine.points[0],
        snapLine.points[1],
        snapLine.direction,
        appState,
        context
      );
    } else if (snapLine.type === "points") {
      context.lineWidth = snapWidth;
      context.strokeStyle = snapColor;
      drawPointsSnapLine(snapLine, context, appState);
    }
  }
  context.restore();
};
var drawPointsSnapLine = (pointSnapLine, context, appState) => {
  if (!appState.zenModeEnabled) {
    const firstPoint = pointSnapLine.points[0];
    const lastPoint = pointSnapLine.points[pointSnapLine.points.length - 1];
    drawLine(firstPoint, lastPoint, context);
  }
  for (const point of pointSnapLine.points) {
    drawCross(point, appState, context);
  }
};
var drawPointerSnapLine = (pointerSnapLine, context, appState) => {
  drawCross(pointerSnapLine.points[0], appState, context);
  if (!appState.zenModeEnabled) {
    drawLine(pointerSnapLine.points[0], pointerSnapLine.points[1], context);
  }
};
var drawCross = ([x, y], appState, context) => {
  context.save();
  const size = (appState.zenModeEnabled ? SNAP_CROSS_SIZE * 1.5 : SNAP_CROSS_SIZE) / appState.zoom.value;
  context.beginPath();
  context.moveTo(x - size, y - size);
  context.lineTo(x + size, y + size);
  context.moveTo(x + size, y - size);
  context.lineTo(x - size, y + size);
  context.stroke();
  context.restore();
};
var drawLine = (from, to, context) => {
  context.beginPath();
  context.lineTo(from[0], from[1]);
  context.lineTo(to[0], to[1]);
  context.stroke();
};
var drawGapLine = (from, to, direction, appState, context) => {
  const FULL = 8 / appState.zoom.value;
  const HALF = FULL / 2;
  const QUARTER = FULL / 4;
  if (direction === "horizontal") {
    const halfPoint = [(from[0] + to[0]) / 2, from[1]];
    if (!appState.zenModeEnabled) {
      drawLine(
        pointFrom(from[0], from[1] - FULL),
        pointFrom(from[0], from[1] + FULL),
        context
      );
    }
    drawLine(
      pointFrom(halfPoint[0] - QUARTER, halfPoint[1] - HALF),
      pointFrom(halfPoint[0] - QUARTER, halfPoint[1] + HALF),
      context
    );
    drawLine(
      pointFrom(halfPoint[0] + QUARTER, halfPoint[1] - HALF),
      pointFrom(halfPoint[0] + QUARTER, halfPoint[1] + HALF),
      context
    );
    if (!appState.zenModeEnabled) {
      drawLine(
        pointFrom(to[0], to[1] - FULL),
        pointFrom(to[0], to[1] + FULL),
        context
      );
      drawLine(from, to, context);
    }
  } else {
    const halfPoint = [from[0], (from[1] + to[1]) / 2];
    if (!appState.zenModeEnabled) {
      drawLine(
        pointFrom(from[0] - FULL, from[1]),
        pointFrom(from[0] + FULL, from[1]),
        context
      );
    }
    drawLine(
      pointFrom(halfPoint[0] - HALF, halfPoint[1] - QUARTER),
      pointFrom(halfPoint[0] + HALF, halfPoint[1] - QUARTER),
      context
    );
    drawLine(
      pointFrom(halfPoint[0] - HALF, halfPoint[1] + QUARTER),
      pointFrom(halfPoint[0] + HALF, halfPoint[1] + QUARTER),
      context
    );
    if (!appState.zenModeEnabled) {
      drawLine(
        pointFrom(to[0] - FULL, to[1]),
        pointFrom(to[0] + FULL, to[1]),
        context
      );
      drawLine(from, to, context);
    }
  }
};

// renderer/interactiveScene.ts
import oc2 from "open-color";
var renderElbowArrowMidPointHighlight = (context, appState) => {
  invariant(appState.selectedLinearElement, "selectedLinearElement is null");
  const { segmentMidPointHoveredCoords } = appState.selectedLinearElement;
  invariant(segmentMidPointHoveredCoords, "midPointCoords is null");
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  highlightPoint(segmentMidPointHoveredCoords, context, appState);
  context.restore();
};
var renderLinearElementPointHighlight = (context, appState, elementsMap) => {
  const { elementId, hoverPointIndex } = appState.selectedLinearElement;
  if (appState.editingLinearElement?.selectedPointsIndices?.includes(
    hoverPointIndex
  )) {
    return;
  }
  const element = LinearElementEditor.getElement(elementId, elementsMap);
  if (!element) {
    return;
  }
  const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(
    element,
    hoverPointIndex,
    elementsMap
  );
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  highlightPoint(point, context, appState);
  context.restore();
};
var highlightPoint = (point, context, appState) => {
  context.fillStyle = "rgba(105, 101, 219, 0.4)";
  fillCircle(
    context,
    point[0],
    point[1],
    LinearElementEditor.POINT_HANDLE_SIZE / appState.zoom.value,
    false
  );
};
var strokeRectWithRotation = (context, x, y, width, height, cx, cy, angle, fill = false, radius = 0) => {
  context.save();
  context.translate(cx, cy);
  context.rotate(angle);
  if (fill) {
    context.fillRect(x - cx, y - cy, width, height);
  }
  if (radius && context.roundRect) {
    context.beginPath();
    context.roundRect(x - cx, y - cy, width, height, radius);
    context.stroke();
    context.closePath();
  } else {
    context.strokeRect(x - cx, y - cy, width, height);
  }
  context.restore();
};
var strokeDiamondWithRotation = (context, width, height, cx, cy, angle) => {
  context.save();
  context.translate(cx, cy);
  context.rotate(angle);
  context.beginPath();
  context.moveTo(0, height / 2);
  context.lineTo(width / 2, 0);
  context.lineTo(0, -height / 2);
  context.lineTo(-width / 2, 0);
  context.closePath();
  context.stroke();
  context.restore();
};
var renderSingleLinearPoint = (context, appState, point, radius, isSelected, isPhantomPoint = false) => {
  context.strokeStyle = "#5e5ad8";
  context.setLineDash([]);
  context.fillStyle = "rgba(255, 255, 255, 0.9)";
  if (isSelected) {
    context.fillStyle = "rgba(134, 131, 226, 0.9)";
  } else if (isPhantomPoint) {
    context.fillStyle = "rgba(177, 151, 252, 0.7)";
  }
  fillCircle(
    context,
    point[0],
    point[1],
    radius / appState.zoom.value,
    !isPhantomPoint
  );
};
var strokeEllipseWithRotation = (context, width, height, cx, cy, angle) => {
  context.beginPath();
  context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2);
  context.stroke();
};
var renderBindingHighlightForBindableElement = (context, element, elementsMap, zoom) => {
  const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
  const width = x2 - x1;
  const height = y2 - y1;
  context.strokeStyle = "rgba(0,0,0,.05)";
  const zoomValue = zoom.value < 1 ? zoom.value : 1;
  context.lineWidth = BINDING_HIGHLIGHT_THICKNESS / zoomValue;
  const padding = context.lineWidth / 2 + BINDING_HIGHLIGHT_OFFSET;
  const radius = getCornerRadius(
    Math.min(element.width, element.height),
    element
  );
  switch (element.type) {
    case "rectangle":
    case "text":
    case "image":
    case "iframe":
    case "embeddable":
    case "frame":
    case "magicframe":
      strokeRectWithRotation(
        context,
        x1 - padding,
        y1 - padding,
        width + padding * 2,
        height + padding * 2,
        x1 + width / 2,
        y1 + height / 2,
        element.angle,
        void 0,
        radius
      );
      break;
    case "diamond":
      const side = Math.hypot(width, height);
      const wPadding = padding * side / height;
      const hPadding = padding * side / width;
      strokeDiamondWithRotation(
        context,
        width + wPadding * 2,
        height + hPadding * 2,
        x1 + width / 2,
        y1 + height / 2,
        element.angle
      );
      break;
    case "ellipse":
      strokeEllipseWithRotation(
        context,
        width + padding * 2,
        height + padding * 2,
        x1 + width / 2,
        y1 + height / 2,
        element.angle
      );
      break;
  }
};
var renderBindingHighlightForSuggestedPointBinding = (context, suggestedBinding, elementsMap, zoom) => {
  const [element, startOrEnd, bindableElement] = suggestedBinding;
  const threshold = maxBindingGap(
    bindableElement,
    bindableElement.width,
    bindableElement.height,
    zoom
  );
  context.strokeStyle = "rgba(0,0,0,0)";
  context.fillStyle = "rgba(0,0,0,.05)";
  const pointIndices = startOrEnd === "both" ? [0, -1] : startOrEnd === "start" ? [0] : [-1];
  pointIndices.forEach((index) => {
    const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
      element,
      index,
      elementsMap
    );
    fillCircle(context, x, y, threshold);
  });
};
var renderSelectionBorder = (context, appState, elementProperties) => {
  const {
    angle,
    x1,
    y1,
    x2,
    y2,
    selectionColors,
    cx,
    cy,
    dashed,
    activeEmbeddable
  } = elementProperties;
  const elementWidth = x2 - x1;
  const elementHeight = y2 - y1;
  const padding = elementProperties.padding ?? DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
  const linePadding = padding / appState.zoom.value;
  const lineWidth = 8 / appState.zoom.value;
  const spaceWidth = 4 / appState.zoom.value;
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  context.lineWidth = (activeEmbeddable ? 4 : 1) / appState.zoom.value;
  const count = selectionColors.length;
  for (let index = 0; index < count; ++index) {
    context.strokeStyle = selectionColors[index];
    if (dashed) {
      context.setLineDash([
        lineWidth,
        spaceWidth + (lineWidth + spaceWidth) * (count - 1)
      ]);
    }
    context.lineDashOffset = (lineWidth + spaceWidth) * index;
    strokeRectWithRotation(
      context,
      x1 - linePadding,
      y1 - linePadding,
      elementWidth + linePadding * 2,
      elementHeight + linePadding * 2,
      cx,
      cy,
      angle
    );
  }
  context.restore();
};
var renderBindingHighlight = (context, appState, suggestedBinding, elementsMap) => {
  const renderHighlight = Array.isArray(suggestedBinding) ? renderBindingHighlightForSuggestedPointBinding : renderBindingHighlightForBindableElement;
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  renderHighlight(context, suggestedBinding, elementsMap, appState.zoom);
  context.restore();
};
var renderFrameHighlight = (context, appState, frame, elementsMap) => {
  const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
  const width = x2 - x1;
  const height = y2 - y1;
  context.strokeStyle = "rgb(0,118,255)";
  context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  strokeRectWithRotation(
    context,
    x1,
    y1,
    width,
    height,
    x1 + width / 2,
    y1 + height / 2,
    frame.angle,
    false,
    FRAME_STYLE.radius / appState.zoom.value
  );
  context.restore();
};
var renderElementsBoxHighlight = (context, appState, elements) => {
  const individualElements = elements.filter(
    (element) => element.groupIds.length === 0
  );
  const elementsInGroups = elements.filter(
    (element) => element.groupIds.length > 0
  );
  const getSelectionFromElements = (elements2) => {
    const [x1, y1, x2, y2] = getCommonBounds(elements2);
    return {
      angle: 0,
      x1,
      x2,
      y1,
      y2,
      selectionColors: ["rgb(0,118,255)"],
      dashed: false,
      cx: x1 + (x2 - x1) / 2,
      cy: y1 + (y2 - y1) / 2,
      activeEmbeddable: false
    };
  };
  const getSelectionForGroupId = (groupId) => {
    const groupElements = getElementsInGroup(elements, groupId);
    return getSelectionFromElements(groupElements);
  };
  Object.entries(selectGroupsFromGivenElements(elementsInGroups, appState)).filter(([id, isSelected]) => isSelected).map(([id, isSelected]) => id).map((groupId) => getSelectionForGroupId(groupId)).concat(
    individualElements.map((element) => getSelectionFromElements([element]))
  ).forEach(
    (selection) => renderSelectionBorder(context, appState, selection)
  );
};
var renderLinearPointHandles = (context, appState, element, elementsMap) => {
  if (!appState.selectedLinearElement) {
    return;
  }
  context.save();
  context.translate(appState.scrollX, appState.scrollY);
  context.lineWidth = 1 / appState.zoom.value;
  const points = LinearElementEditor.getPointsGlobalCoordinates(
    element,
    elementsMap
  );
  const { POINT_HANDLE_SIZE } = LinearElementEditor;
  const radius = appState.editingLinearElement ? POINT_HANDLE_SIZE : POINT_HANDLE_SIZE / 2;
  points.forEach((point, idx) => {
    if (isElbowArrow(element) && idx !== 0 && idx !== points.length - 1) {
      return;
    }
    const isSelected = !!appState.editingLinearElement?.selectedPointsIndices?.includes(idx);
    renderSingleLinearPoint(context, appState, point, radius, isSelected);
  });
  if (isElbowArrow(element)) {
    const fixedSegments = element.fixedSegments?.map((segment) => segment.index) || [];
    points.slice(0, -1).forEach((p, idx) => {
      if (!LinearElementEditor.isSegmentTooShort(
        element,
        points[idx + 1],
        points[idx],
        idx,
        appState.zoom
      )) {
        renderSingleLinearPoint(
          context,
          appState,
          pointFrom(
            (p[0] + points[idx + 1][0]) / 2,
            (p[1] + points[idx + 1][1]) / 2
          ),
          POINT_HANDLE_SIZE / 2,
          false,
          !fixedSegments.includes(idx + 1)
        );
      }
    });
  } else {
    const midPoints = LinearElementEditor.getEditorMidPoints(
      element,
      elementsMap,
      appState
    ).filter(
      (midPoint, idx, midPoints2) => midPoint !== null && !(isElbowArrow(element) && (idx === 0 || idx === midPoints2.length - 1))
    );
    midPoints.forEach((segmentMidPoint) => {
      if (appState.editingLinearElement || points.length === 2) {
        renderSingleLinearPoint(
          context,
          appState,
          segmentMidPoint,
          POINT_HANDLE_SIZE / 2,
          false,
          true
        );
      }
    });
  }
  context.restore();
};
var renderTransformHandles = (context, renderConfig, appState, transformHandles, angle) => {
  Object.keys(transformHandles).forEach((key) => {
    const transformHandle = transformHandles[key];
    if (transformHandle !== void 0) {
      const [x, y, width, height] = transformHandle;
      context.save();
      context.lineWidth = 1 / appState.zoom.value;
      if (renderConfig.selectionColor) {
        context.strokeStyle = renderConfig.selectionColor;
      }
      if (key === "rotation") {
        fillCircle(context, x + width / 2, y + height / 2, width / 2);
      } else if (context.roundRect) {
        context.beginPath();
        context.roundRect(x, y, width, height, 2 / appState.zoom.value);
        context.fill();
        context.stroke();
      } else {
        strokeRectWithRotation(
          context,
          x,
          y,
          width,
          height,
          x + width / 2,
          y + height / 2,
          angle,
          true
          // fill before stroke
        );
      }
      context.restore();
    }
  });
};
var renderCropHandles = (context, renderConfig, appState, croppingElement, elementsMap) => {
  const [x1, y1, , , cx, cy] = getElementAbsoluteCoords(
    croppingElement,
    elementsMap
  );
  const LINE_WIDTH = 3;
  const LINE_LENGTH = 20;
  const ZOOMED_LINE_WIDTH = LINE_WIDTH / appState.zoom.value;
  const ZOOMED_HALF_LINE_WIDTH = ZOOMED_LINE_WIDTH / 2;
  const HALF_WIDTH = cx - x1 + ZOOMED_LINE_WIDTH;
  const HALF_HEIGHT = cy - y1 + ZOOMED_LINE_WIDTH;
  const HORIZONTAL_LINE_LENGTH = Math.min(
    LINE_LENGTH / appState.zoom.value,
    HALF_WIDTH
  );
  const VERTICAL_LINE_LENGTH = Math.min(
    LINE_LENGTH / appState.zoom.value,
    HALF_HEIGHT
  );
  context.save();
  context.fillStyle = renderConfig.selectionColor;
  context.strokeStyle = renderConfig.selectionColor;
  context.lineWidth = ZOOMED_LINE_WIDTH;
  const handles = [
    [
      // x, y
      [-HALF_WIDTH, -HALF_HEIGHT],
      // horizontal line: first start and to
      [0, ZOOMED_HALF_LINE_WIDTH],
      [HORIZONTAL_LINE_LENGTH, ZOOMED_HALF_LINE_WIDTH],
      // vertical line: second  start and to
      [ZOOMED_HALF_LINE_WIDTH, 0],
      [ZOOMED_HALF_LINE_WIDTH, VERTICAL_LINE_LENGTH]
    ],
    [
      [HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, -HALF_HEIGHT],
      [ZOOMED_HALF_LINE_WIDTH, ZOOMED_HALF_LINE_WIDTH],
      [
        -HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
        ZOOMED_HALF_LINE_WIDTH
      ],
      [0, 0],
      [0, VERTICAL_LINE_LENGTH]
    ],
    [
      [-HALF_WIDTH, HALF_HEIGHT],
      [0, -ZOOMED_HALF_LINE_WIDTH],
      [HORIZONTAL_LINE_LENGTH, -ZOOMED_HALF_LINE_WIDTH],
      [ZOOMED_HALF_LINE_WIDTH, 0],
      [ZOOMED_HALF_LINE_WIDTH, -VERTICAL_LINE_LENGTH]
    ],
    [
      [HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, HALF_HEIGHT],
      [ZOOMED_HALF_LINE_WIDTH, -ZOOMED_HALF_LINE_WIDTH],
      [
        -HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
        -ZOOMED_HALF_LINE_WIDTH
      ],
      [0, 0],
      [0, -VERTICAL_LINE_LENGTH]
    ]
  ];
  handles.forEach((handle) => {
    const [[x, y], [x1s, y1s], [x1t, y1t], [x2s, y2s], [x2t, y2t]] = handle;
    context.save();
    context.translate(cx, cy);
    context.rotate(croppingElement.angle);
    context.beginPath();
    context.moveTo(x + x1s, y + y1s);
    context.lineTo(x + x1t, y + y1t);
    context.stroke();
    context.beginPath();
    context.moveTo(x + x2s, y + y2s);
    context.lineTo(x + x2t, y + y2t);
    context.stroke();
    context.restore();
  });
  context.restore();
};
var renderTextBox = (text, context, appState, selectionColor) => {
  context.save();
  const padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2 / appState.zoom.value;
  const width = text.width + padding * 2;
  const height = text.height + padding * 2;
  const cx = text.x + width / 2;
  const cy = text.y + height / 2;
  const shiftX = -(width / 2 + padding);
  const shiftY = -(height / 2 + padding);
  context.translate(cx + appState.scrollX, cy + appState.scrollY);
  context.rotate(text.angle);
  context.lineWidth = 1 / appState.zoom.value;
  context.strokeStyle = selectionColor;
  context.strokeRect(shiftX, shiftY, width, height);
  context.restore();
};
var _renderInteractiveScene = ({
  canvas,
  elementsMap,
  visibleElements,
  selectedElements,
  allElementsMap,
  scale,
  appState,
  renderConfig,
  device
}) => {
  if (canvas === null) {
    return { atLeastOneVisibleElement: false, elementsMap };
  }
  const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
    canvas,
    scale
  );
  const context = bootstrapCanvas({
    canvas,
    scale,
    normalizedWidth,
    normalizedHeight
  });
  context.save();
  context.scale(appState.zoom.value, appState.zoom.value);
  let editingLinearElement = void 0;
  visibleElements.forEach((element) => {
    if (appState.editingLinearElement?.elementId === element.id) {
      if (element) {
        editingLinearElement = element;
      }
    }
  });
  if (editingLinearElement) {
    renderLinearPointHandles(
      context,
      appState,
      editingLinearElement,
      elementsMap
    );
  }
  if (appState.selectionElement && !appState.isCropping) {
    try {
      renderSelectionElement(
        appState.selectionElement,
        context,
        appState,
        renderConfig.selectionColor
      );
    } catch (error) {
      console.error(error);
    }
  }
  if (appState.editingTextElement && isTextElement(appState.editingTextElement)) {
    const textElement = allElementsMap.get(appState.editingTextElement.id);
    if (textElement && !textElement.autoResize) {
      renderTextBox(
        textElement,
        context,
        appState,
        renderConfig.selectionColor
      );
    }
  }
  if (appState.isBindingEnabled) {
    appState.suggestedBindings.filter((binding) => binding != null).forEach((suggestedBinding) => {
      renderBindingHighlight(
        context,
        appState,
        suggestedBinding,
        elementsMap
      );
    });
  }
  if (appState.frameToHighlight) {
    renderFrameHighlight(
      context,
      appState,
      appState.frameToHighlight,
      elementsMap
    );
  }
  if (appState.elementsToHighlight) {
    renderElementsBoxHighlight(context, appState, appState.elementsToHighlight);
  }
  const isFrameSelected = selectedElements.some(
    (element) => isFrameLikeElement(element)
  );
  if (selectedElements.length === 1 && appState.editingLinearElement?.elementId === selectedElements[0].id) {
    renderLinearPointHandles(
      context,
      appState,
      selectedElements[0],
      elementsMap
    );
  }
  if (isElbowArrow(selectedElements[0]) && appState.selectedLinearElement && appState.selectedLinearElement.segmentMidPointHoveredCoords) {
    renderElbowArrowMidPointHighlight(context, appState);
  } else if (appState.selectedLinearElement && appState.selectedLinearElement.hoverPointIndex >= 0 && !(isElbowArrow(selectedElements[0]) && appState.selectedLinearElement.hoverPointIndex > 0 && appState.selectedLinearElement.hoverPointIndex < selectedElements[0].points.length - 1)) {
    renderLinearElementPointHighlight(context, appState, elementsMap);
  }
  if (!appState.multiElement && !appState.editingLinearElement) {
    const showBoundingBox = shouldShowBoundingBox(selectedElements, appState);
    const isSingleLinearElementSelected = selectedElements.length === 1 && isLinearElement(selectedElements[0]);
    if (isSingleLinearElementSelected && appState.selectedLinearElement?.elementId === selectedElements[0].id && !selectedElements[0].locked) {
      renderLinearPointHandles(
        context,
        appState,
        selectedElements[0],
        elementsMap
      );
    }
    const selectionColor = renderConfig.selectionColor || oc2.black;
    if (showBoundingBox) {
      const locallySelectedIds = arrayToMap(selectedElements);
      const selections = [];
      for (const element of elementsMap.values()) {
        const selectionColors = [];
        const remoteClients = renderConfig.remoteSelectedElementIds.get(
          element.id
        );
        if (!// Elbow arrow elements cannot be selected when bound on either end
        (isSingleLinearElementSelected && isElbowArrow(element) && (element.startBinding || element.endBinding))) {
          if (locallySelectedIds.has(element.id) && !isSelectedViaGroup(appState, element)) {
            selectionColors.push(selectionColor);
          }
          if (remoteClients) {
            selectionColors.push(
              ...remoteClients.map((socketId) => {
                const background = getClientColor(
                  socketId,
                  appState.collaborators.get(socketId)
                );
                return background;
              })
            );
          }
        }
        if (selectionColors.length) {
          const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
            element,
            elementsMap,
            true
          );
          selections.push({
            angle: element.angle,
            x1,
            y1,
            x2,
            y2,
            selectionColors,
            dashed: !!remoteClients,
            cx,
            cy,
            activeEmbeddable: appState.activeEmbeddable?.element === element && appState.activeEmbeddable.state === "active",
            padding: element.id === appState.croppingElementId || isImageElement(element) ? 0 : void 0
          });
        }
      }
      const addSelectionForGroupId = (groupId) => {
        const groupElements = getElementsInGroup(elementsMap, groupId);
        const [x1, y1, x2, y2] = getCommonBounds(groupElements);
        selections.push({
          angle: 0,
          x1,
          x2,
          y1,
          y2,
          selectionColors: [oc2.black],
          dashed: true,
          cx: x1 + (x2 - x1) / 2,
          cy: y1 + (y2 - y1) / 2,
          activeEmbeddable: false
        });
      };
      for (const groupId of getSelectedGroupIds(appState)) {
        addSelectionForGroupId(groupId);
      }
      if (appState.editingGroupId) {
        addSelectionForGroupId(appState.editingGroupId);
      }
      selections.forEach(
        (selection) => renderSelectionBorder(context, appState, selection)
      );
    }
    context.save();
    context.translate(appState.scrollX, appState.scrollY);
    if (selectedElements.length === 1) {
      context.fillStyle = oc2.white;
      const transformHandles = getTransformHandles(
        selectedElements[0],
        appState.zoom,
        elementsMap,
        "mouse",
        // when we render we don't know which pointer type so use mouse,
        getOmitSidesForDevice(device)
      );
      if (!appState.viewModeEnabled && showBoundingBox && // do not show transform handles when text is being edited
      !isTextElement(appState.editingTextElement) && // do not show transform handles when image is being cropped
      !appState.croppingElementId) {
        renderTransformHandles(
          context,
          renderConfig,
          appState,
          transformHandles,
          selectedElements[0].angle
        );
      }
      if (appState.croppingElementId && !appState.isCropping) {
        const croppingElement = elementsMap.get(appState.croppingElementId);
        if (croppingElement && isImageElement(croppingElement)) {
          renderCropHandles(
            context,
            renderConfig,
            appState,
            croppingElement,
            elementsMap
          );
        }
      }
    } else if (selectedElements.length > 1 && !appState.isRotating) {
      const dashedLinePadding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2 / appState.zoom.value;
      context.fillStyle = oc2.white;
      const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
      const initialLineDash = context.getLineDash();
      context.setLineDash([2 / appState.zoom.value]);
      const lineWidth = context.lineWidth;
      context.lineWidth = 1 / appState.zoom.value;
      context.strokeStyle = selectionColor;
      strokeRectWithRotation(
        context,
        x1 - dashedLinePadding,
        y1 - dashedLinePadding,
        x2 - x1 + dashedLinePadding * 2,
        y2 - y1 + dashedLinePadding * 2,
        (x1 + x2) / 2,
        (y1 + y2) / 2,
        0
      );
      context.lineWidth = lineWidth;
      context.setLineDash(initialLineDash);
      const transformHandles = getTransformHandlesFromCoords(
        [x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
        0,
        appState.zoom,
        "mouse",
        isFrameSelected ? { ...getOmitSidesForDevice(device), rotation: true } : getOmitSidesForDevice(device)
      );
      if (selectedElements.some((element) => !element.locked)) {
        renderTransformHandles(
          context,
          renderConfig,
          appState,
          transformHandles,
          0
        );
      }
    }
    context.restore();
  }
  appState.searchMatches.forEach(({ id, focus, matchedLines }) => {
    const element = elementsMap.get(id);
    if (element && isTextElement(element)) {
      const [elementX1, elementY1, , , cx, cy] = getElementAbsoluteCoords(
        element,
        elementsMap,
        true
      );
      context.save();
      if (appState.theme === THEME.LIGHT) {
        if (focus) {
          context.fillStyle = "rgba(255, 124, 0, 0.4)";
        } else {
          context.fillStyle = "rgba(255, 226, 0, 0.4)";
        }
      } else if (focus) {
        context.fillStyle = "rgba(229, 82, 0, 0.4)";
      } else {
        context.fillStyle = "rgba(99, 52, 0, 0.4)";
      }
      context.translate(appState.scrollX, appState.scrollY);
      context.translate(cx, cy);
      context.rotate(element.angle);
      matchedLines.forEach((matchedLine) => {
        context.fillRect(
          elementX1 + matchedLine.offsetX - cx,
          elementY1 + matchedLine.offsetY - cy,
          matchedLine.width,
          matchedLine.height
        );
      });
      context.restore();
    }
  });
  renderSnaps(context, appState);
  context.restore();
  renderRemoteCursors({
    context,
    renderConfig,
    appState,
    normalizedWidth,
    normalizedHeight
  });
  let scrollBars;
  if (renderConfig.renderScrollbars) {
    scrollBars = getScrollBars(
      visibleElements,
      normalizedWidth,
      normalizedHeight,
      appState
    );
    context.save();
    context.fillStyle = SCROLLBAR_COLOR;
    context.strokeStyle = "rgba(255,255,255,0.8)";
    [scrollBars.horizontal, scrollBars.vertical].forEach((scrollBar) => {
      if (scrollBar) {
        roundRect(
          context,
          scrollBar.x,
          scrollBar.y,
          scrollBar.width,
          scrollBar.height,
          SCROLLBAR_WIDTH / 2
        );
      }
    });
    context.restore();
  }
  return {
    scrollBars,
    atLeastOneVisibleElement: visibleElements.length > 0,
    elementsMap
  };
};
var renderInteractiveSceneThrottled = throttleRAF(
  (config) => {
    const ret = _renderInteractiveScene(config);
    config.callback?.(ret);
  },
  { trailing: true }
);
var renderInteractiveScene = (renderConfig, throttle5) => {
  if (throttle5) {
    renderInteractiveSceneThrottled(renderConfig);
    return void 0;
  }
  const ret = _renderInteractiveScene(renderConfig);
  renderConfig.callback(ret);
  return ret;
};

// components/canvases/InteractiveCanvas.tsx
import { jsx as jsx140 } from "react/jsx-runtime";
var InteractiveCanvas = (props) => {
  const isComponentMounted = useRef36(false);
  useEffect41(() => {
    if (!isComponentMounted.current) {
      isComponentMounted.current = true;
      return;
    }
    const remotePointerButton = /* @__PURE__ */ new Map();
    const remotePointerViewportCoords = /* @__PURE__ */ new Map();
    const remoteSelectedElementIds = /* @__PURE__ */ new Map();
    const remotePointerUsernames = /* @__PURE__ */ new Map();
    const remotePointerUserStates = /* @__PURE__ */ new Map();
    props.appState.collaborators.forEach((user, socketId) => {
      if (user.selectedElementIds) {
        for (const id of Object.keys(user.selectedElementIds)) {
          if (!remoteSelectedElementIds.has(id)) {
            remoteSelectedElementIds.set(id, []);
          }
          remoteSelectedElementIds.get(id).push(socketId);
        }
      }
      if (!user.pointer || user.pointer.renderCursor === false) {
        return;
      }
      if (user.username) {
        remotePointerUsernames.set(socketId, user.username);
      }
      if (user.userState) {
        remotePointerUserStates.set(socketId, user.userState);
      }
      remotePointerViewportCoords.set(
        socketId,
        sceneCoordsToViewportCoords(
          {
            sceneX: user.pointer.x,
            sceneY: user.pointer.y
          },
          props.appState
        )
      );
      remotePointerButton.set(socketId, user.button);
    });
    const selectionColor = props.containerRef?.current && getComputedStyle(props.containerRef.current).getPropertyValue(
      "--color-selection"
    ) || "#6965db";
    renderInteractiveScene(
      {
        canvas: props.canvas,
        elementsMap: props.elementsMap,
        visibleElements: props.visibleElements,
        selectedElements: props.selectedElements,
        allElementsMap: props.allElementsMap,
        scale: window.devicePixelRatio,
        appState: props.appState,
        renderConfig: {
          remotePointerViewportCoords,
          remotePointerButton,
          remoteSelectedElementIds,
          remotePointerUsernames,
          remotePointerUserStates,
          selectionColor,
          renderScrollbars: false
        },
        device: props.device,
        callback: props.renderInteractiveSceneCallback
      },
      isRenderThrottlingEnabled()
    );
  });
  return /* @__PURE__ */ jsx140(
    "canvas",
    {
      className: "excalidraw__canvas interactive",
      style: {
        width: props.appState.width,
        height: props.appState.height,
        cursor: props.appState.viewModeEnabled ? CURSOR_TYPE.GRAB : CURSOR_TYPE.AUTO
      },
      width: props.appState.width * props.scale,
      height: props.appState.height * props.scale,
      ref: props.handleCanvasRef,
      onContextMenu: props.onContextMenu,
      onPointerMove: props.onPointerMove,
      onPointerUp: props.onPointerUp,
      onPointerCancel: props.onPointerCancel,
      onTouchMove: props.onTouchMove,
      onPointerDown: props.onPointerDown,
      onDoubleClick: props.appState.viewModeEnabled ? void 0 : props.onDoubleClick,
      children: t("labels.drawingCanvas")
    }
  );
};
var getRelevantAppStateProps = (appState) => ({
  zoom: appState.zoom,
  scrollX: appState.scrollX,
  scrollY: appState.scrollY,
  width: appState.width,
  height: appState.height,
  viewModeEnabled: appState.viewModeEnabled,
  openDialog: appState.openDialog,
  editingGroupId: appState.editingGroupId,
  editingLinearElement: appState.editingLinearElement,
  selectedElementIds: appState.selectedElementIds,
  frameToHighlight: appState.frameToHighlight,
  offsetLeft: appState.offsetLeft,
  offsetTop: appState.offsetTop,
  theme: appState.theme,
  pendingImageElementId: appState.pendingImageElementId,
  selectionElement: appState.selectionElement,
  selectedGroupIds: appState.selectedGroupIds,
  selectedLinearElement: appState.selectedLinearElement,
  multiElement: appState.multiElement,
  isBindingEnabled: appState.isBindingEnabled,
  suggestedBindings: appState.suggestedBindings,
  isRotating: appState.isRotating,
  elementsToHighlight: appState.elementsToHighlight,
  collaborators: appState.collaborators,
  // Necessary for collab. sessions
  activeEmbeddable: appState.activeEmbeddable,
  snapLines: appState.snapLines,
  zenModeEnabled: appState.zenModeEnabled,
  editingTextElement: appState.editingTextElement,
  isCropping: appState.isCropping,
  croppingElementId: appState.croppingElementId,
  searchMatches: appState.searchMatches
});
var areEqual3 = (prevProps, nextProps) => {
  if (prevProps.selectionNonce !== nextProps.selectionNonce || prevProps.sceneNonce !== nextProps.sceneNonce || prevProps.scale !== nextProps.scale || // we need to memoize on elementsMap because they may have renewed
  // even if sceneNonce didn't change (e.g. we filter elements out based
  // on appState)
  prevProps.elementsMap !== nextProps.elementsMap || prevProps.visibleElements !== nextProps.visibleElements || prevProps.selectedElements !== nextProps.selectedElements) {
    return false;
  }
  return isShallowEqual(
    // asserting AppState because we're being passed the whole AppState
    // but resolve to only the InteractiveCanvas-relevant props
    getRelevantAppStateProps(prevProps.appState),
    getRelevantAppStateProps(nextProps.appState)
  );
};
var InteractiveCanvas_default = React41.memo(InteractiveCanvas, areEqual3);

// components/canvases/StaticCanvas.tsx
import React42, { useEffect as useEffect42, useRef as useRef37 } from "react";
import { jsx as jsx141 } from "react/jsx-runtime";
var StaticCanvas = (props) => {
  const wrapperRef = useRef37(null);
  const isComponentMounted = useRef37(false);
  useEffect42(() => {
    const wrapper = wrapperRef.current;
    if (!wrapper) {
      return;
    }
    const canvas = props.canvas;
    if (!isComponentMounted.current) {
      isComponentMounted.current = true;
      wrapper.replaceChildren(canvas);
      canvas.classList.add("excalidraw__canvas", "static");
    }
    const widthString = `${props.appState.width}px`;
    const heightString = `${props.appState.height}px`;
    if (canvas.style.width !== widthString) {
      canvas.style.width = widthString;
    }
    if (canvas.style.height !== heightString) {
      canvas.style.height = heightString;
    }
    const scaledWidth = props.appState.width * props.scale;
    const scaledHeight = props.appState.height * props.scale;
    if (canvas.width !== scaledWidth) {
      canvas.width = scaledWidth;
    }
    if (canvas.height !== scaledHeight) {
      canvas.height = scaledHeight;
    }
    renderStaticScene(
      {
        canvas,
        rc: props.rc,
        scale: props.scale,
        elementsMap: props.elementsMap,
        allElementsMap: props.allElementsMap,
        visibleElements: props.visibleElements,
        appState: props.appState,
        renderConfig: props.renderConfig
      },
      isRenderThrottlingEnabled()
    );
  });
  return /* @__PURE__ */ jsx141("div", { className: "excalidraw__canvas-wrapper", ref: wrapperRef });
};
var getRelevantAppStateProps2 = (appState) => ({
  zoom: appState.zoom,
  scrollX: appState.scrollX,
  scrollY: appState.scrollY,
  width: appState.width,
  height: appState.height,
  viewModeEnabled: appState.viewModeEnabled,
  openDialog: appState.openDialog,
  hoveredElementIds: appState.hoveredElementIds,
  offsetLeft: appState.offsetLeft,
  offsetTop: appState.offsetTop,
  theme: appState.theme,
  pendingImageElementId: appState.pendingImageElementId,
  shouldCacheIgnoreZoom: appState.shouldCacheIgnoreZoom,
  viewBackgroundColor: appState.viewBackgroundColor,
  exportScale: appState.exportScale,
  selectedElementsAreBeingDragged: appState.selectedElementsAreBeingDragged,
  gridSize: appState.gridSize,
  gridStep: appState.gridStep,
  frameRendering: appState.frameRendering,
  selectedElementIds: appState.selectedElementIds,
  frameToHighlight: appState.frameToHighlight,
  editingGroupId: appState.editingGroupId,
  currentHoveredFontFamily: appState.currentHoveredFontFamily,
  croppingElementId: appState.croppingElementId
});
var areEqual4 = (prevProps, nextProps) => {
  if (prevProps.sceneNonce !== nextProps.sceneNonce || prevProps.scale !== nextProps.scale || // we need to memoize on elementsMap because they may have renewed
  // even if sceneNonce didn't change (e.g. we filter elements out based
  // on appState)
  prevProps.elementsMap !== nextProps.elementsMap || prevProps.visibleElements !== nextProps.visibleElements) {
    return false;
  }
  return isShallowEqual(
    // asserting AppState because we're being passed the whole AppState
    // but resolve to only the StaticCanvas-relevant props
    getRelevantAppStateProps2(prevProps.appState),
    getRelevantAppStateProps2(nextProps.appState)
  ) && isShallowEqual(prevProps.renderConfig, nextProps.renderConfig);
};
var StaticCanvas_default = React42.memo(StaticCanvas, areEqual4);

// scene/Renderer.ts
var Renderer = class {
  constructor(scene) {
    __publicField(this, "scene");
    __publicField(this, "getRenderableElements", (() => {
      const getVisibleCanvasElements = ({
        elementsMap,
        zoom,
        offsetLeft,
        offsetTop,
        scrollX,
        scrollY,
        height,
        width
      }) => {
        const visibleElements = [];
        for (const element of elementsMap.values()) {
          if (isElementInViewport(
            element,
            width,
            height,
            {
              zoom,
              offsetLeft,
              offsetTop,
              scrollX,
              scrollY
            },
            elementsMap
          )) {
            visibleElements.push(element);
          }
        }
        return visibleElements;
      };
      const getRenderableElements = ({
        elements,
        editingTextElement,
        newElementId,
        pendingImageElementId
      }) => {
        const elementsMap = toBrandedType(/* @__PURE__ */ new Map());
        for (const element of elements) {
          if (isImageElement(element)) {
            if (
              // => not placed on canvas yet (but in elements array)
              pendingImageElementId === element.id
            ) {
              continue;
            }
          }
          if (newElementId === element.id) {
            continue;
          }
          if (!editingTextElement || editingTextElement.type !== "text" || element.id !== editingTextElement.id) {
            elementsMap.set(element.id, element);
          }
        }
        return elementsMap;
      };
      return memoize(
        ({
          zoom,
          offsetLeft,
          offsetTop,
          scrollX,
          scrollY,
          height,
          width,
          editingTextElement,
          newElementId,
          pendingImageElementId,
          // cache-invalidation nonce
          sceneNonce: _sceneNonce
        }) => {
          const elements = this.scene.getNonDeletedElements();
          const elementsMap = getRenderableElements({
            elements,
            editingTextElement,
            newElementId,
            pendingImageElementId
          });
          const visibleElements = getVisibleCanvasElements({
            elementsMap,
            zoom,
            offsetLeft,
            offsetTop,
            scrollX,
            scrollY,
            height,
            width
          });
          return { elementsMap, visibleElements };
        }
      );
    })());
    this.scene = scene;
  }
  // NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
  // safe to break TS contract here (for upstream cases)
  destroy() {
    renderInteractiveSceneThrottled.cancel();
    renderStaticSceneThrottled.cancel();
    this.getRenderableElements.clear();
  }
};

// components/SVGLayer.tsx
import { useEffect as useEffect43, useRef as useRef38 } from "react";
import { jsx as jsx142 } from "react/jsx-runtime";
var SVGLayer = ({ trails }) => {
  const svgRef = useRef38(null);
  useEffect43(() => {
    if (svgRef.current) {
      for (const trail of trails) {
        trail.start(svgRef.current);
      }
    }
    return () => {
      for (const trail of trails) {
        trail.stop();
      }
    };
  }, trails);
  return /* @__PURE__ */ jsx142("div", { className: "SVGLayer", children: /* @__PURE__ */ jsx142("svg", { ref: svgRef }) });
};

// element/ElementCanvasButtons.tsx
import { jsx as jsx143 } from "react/jsx-runtime";
var CONTAINER_PADDING = 5;
var getContainerCoords = (element, appState, elementsMap) => {
  const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
  const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
    { sceneX: x1 + element.width, sceneY: y1 },
    appState
  );
  const x = viewportX - appState.offsetLeft + 10;
  const y = viewportY - appState.offsetTop;
  return { x, y };
};
var ElementCanvasButtons = ({
  children,
  element,
  elementsMap
}) => {
  const appState = useExcalidrawAppState();
  if (appState.contextMenu || appState.newElement || appState.resizingElement || appState.isRotating || appState.openMenu || appState.viewModeEnabled) {
    return null;
  }
  const { x, y } = getContainerCoords(element, appState, elementsMap);
  return /* @__PURE__ */ jsx143(
    "div",
    {
      className: "excalidraw-canvas-buttons",
      style: {
        top: `${y}px`,
        left: `${x}px`,
        // width: CONTAINER_WIDTH,
        padding: CONTAINER_PADDING
      },
      children
    }
  );
};

// components/MagicButton.tsx
import clsx54 from "clsx";
import { jsx as jsx144, jsxs as jsxs76 } from "react/jsx-runtime";
var DEFAULT_SIZE4 = "small";
var ElementCanvasButton = (props) => {
  return /* @__PURE__ */ jsxs76(
    "label",
    {
      className: clsx54(
        "ToolIcon ToolIcon__MagicButton",
        `ToolIcon_size_${DEFAULT_SIZE4}`,
        {
          "is-mobile": props.isMobile
        }
      ),
      title: `${props.title}`,
      children: [
        /* @__PURE__ */ jsx144(
          "input",
          {
            className: "ToolIcon_type_checkbox",
            type: "checkbox",
            name: props.name,
            onChange: props.onChange,
            checked: props.checked,
            "aria-label": props.title
          }
        ),
        /* @__PURE__ */ jsx144("div", { className: "ToolIcon__icon", children: props.icon })
      ]
    }
  );
};

// components/FollowMode/FollowMode.tsx
import { jsx as jsx145, jsxs as jsxs77 } from "react/jsx-runtime";
var FollowMode = ({
  height,
  width,
  userToFollow,
  onDisconnect
}) => {
  return /* @__PURE__ */ jsx145("div", { className: "follow-mode", style: { width, height }, children: /* @__PURE__ */ jsxs77("div", { className: "follow-mode__badge", children: [
    /* @__PURE__ */ jsxs77("div", { className: "follow-mode__badge__label", children: [
      "Following",
      " ",
      /* @__PURE__ */ jsx145(
        "span",
        {
          className: "follow-mode__badge__username",
          title: userToFollow.username,
          children: userToFollow.username
        }
      )
    ] }),
    /* @__PURE__ */ jsx145(
      "button",
      {
        type: "button",
        onClick: onDisconnect,
        className: "follow-mode__disconnect-btn",
        children: CloseIcon
      }
    )
  ] }) });
};
var FollowMode_default = FollowMode;

// animation-frame-handler.ts
var AnimationFrameHandler = class {
  constructor() {
    __publicField(this, "targets", /* @__PURE__ */ new WeakMap());
    __publicField(this, "rafIds", /* @__PURE__ */ new WeakMap());
  }
  register(key, callback) {
    this.targets.set(key, { callback, stopped: true });
  }
  start(key) {
    const target = this.targets.get(key);
    if (!target) {
      return;
    }
    if (this.rafIds.has(key)) {
      return;
    }
    this.targets.set(key, { ...target, stopped: false });
    this.scheduleFrame(key);
  }
  stop(key) {
    const target = this.targets.get(key);
    if (target && !target.stopped) {
      this.targets.set(key, { ...target, stopped: true });
    }
    this.cancelFrame(key);
  }
  constructFrame(key) {
    return (timestamp) => {
      const target = this.targets.get(key);
      if (!target) {
        return;
      }
      const shouldAbort = this.onFrame(target, timestamp);
      if (!target.stopped && !shouldAbort) {
        this.scheduleFrame(key);
      } else {
        this.cancelFrame(key);
      }
    };
  }
  scheduleFrame(key) {
    const rafId = requestAnimationFrame(this.constructFrame(key));
    this.rafIds.set(key, rafId);
  }
  cancelFrame(key) {
    if (this.rafIds.has(key)) {
      const rafId = this.rafIds.get(key);
      cancelAnimationFrame(rafId);
    }
    this.rafIds.delete(key);
  }
  onFrame(target, timestamp) {
    const shouldAbort = target.callback(timestamp);
    return shouldAbort ?? false;
  }
};

// animated-trail.ts
import { LaserPointer } from "@excalidraw/laser-pointer";
var AnimatedTrail = class {
  constructor(animationFrameHandler, app, options) {
    this.animationFrameHandler = animationFrameHandler;
    this.app = app;
    this.options = options;
    __publicField(this, "currentTrail");
    __publicField(this, "pastTrails", []);
    __publicField(this, "container");
    __publicField(this, "trailElement");
    this.animationFrameHandler.register(this, this.onFrame.bind(this));
    this.trailElement = document.createElementNS(SVG_NS, "path");
  }
  get hasCurrentTrail() {
    return !!this.currentTrail;
  }
  hasLastPoint(x, y) {
    if (this.currentTrail) {
      const len = this.currentTrail.originalPoints.length;
      return this.currentTrail.originalPoints[len - 1][0] === x && this.currentTrail.originalPoints[len - 1][1] === y;
    }
    return false;
  }
  start(container) {
    if (container) {
      this.container = container;
    }
    if (this.trailElement.parentNode !== this.container && this.container) {
      this.container.appendChild(this.trailElement);
    }
    this.animationFrameHandler.start(this);
  }
  stop() {
    this.animationFrameHandler.stop(this);
    if (this.trailElement.parentNode === this.container) {
      this.container?.removeChild(this.trailElement);
    }
  }
  startPath(x, y) {
    this.currentTrail = new LaserPointer(this.options);
    this.currentTrail.addPoint([x, y, performance.now()]);
    this.update();
  }
  addPointToPath(x, y) {
    if (this.currentTrail) {
      this.currentTrail.addPoint([x, y, performance.now()]);
      this.update();
    }
  }
  endPath() {
    if (this.currentTrail) {
      this.currentTrail.close();
      this.currentTrail.options.keepHead = false;
      this.pastTrails.push(this.currentTrail);
      this.currentTrail = void 0;
      this.update();
    }
  }
  update() {
    this.start();
  }
  onFrame() {
    const paths = [];
    for (const trail of this.pastTrails) {
      paths.push(this.drawTrail(trail, this.app.state));
    }
    if (this.currentTrail) {
      const currentPath = this.drawTrail(this.currentTrail, this.app.state);
      paths.push(currentPath);
    }
    this.pastTrails = this.pastTrails.filter((trail) => {
      return trail.getStrokeOutline().length !== 0;
    });
    if (paths.length === 0) {
      this.stop();
    }
    const svgPaths = paths.join(" ").trim();
    this.trailElement.setAttribute("d", svgPaths);
    this.trailElement.setAttribute(
      "fill",
      (this.options.fill ?? (() => "black"))(this)
    );
  }
  drawTrail(trail, state) {
    const stroke = trail.getStrokeOutline(trail.options.size / state.zoom.value).map(([x, y]) => {
      const result = sceneCoordsToViewportCoords(
        { sceneX: x, sceneY: y },
        state
      );
      return [result.x, result.y];
    });
    return getSvgPathFromStroke(stroke, true);
  }
};

// laser-trails.ts
var LaserTrails = class {
  constructor(animationFrameHandler, app) {
    this.animationFrameHandler = animationFrameHandler;
    this.app = app;
    __publicField(this, "localTrail");
    __publicField(this, "collabTrails", /* @__PURE__ */ new Map());
    __publicField(this, "container");
    this.animationFrameHandler.register(this, this.onFrame.bind(this));
    this.localTrail = new AnimatedTrail(animationFrameHandler, app, {
      ...this.getTrailOptions(),
      fill: () => DEFAULT_LASER_COLOR
    });
  }
  getTrailOptions() {
    return {
      simplify: 0,
      streamline: 0.4,
      sizeMapping: (c) => {
        const DECAY_TIME = 1e3;
        const DECAY_LENGTH = 50;
        const t2 = Math.max(
          0,
          1 - (performance.now() - c.pressure) / DECAY_TIME
        );
        const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
        return Math.min(easeOut(l), easeOut(t2));
      }
    };
  }
  startPath(x, y) {
    this.localTrail.startPath(x, y);
  }
  addPointToPath(x, y) {
    this.localTrail.addPointToPath(x, y);
  }
  endPath() {
    this.localTrail.endPath();
  }
  start(container) {
    this.container = container;
    this.animationFrameHandler.start(this);
    this.localTrail.start(container);
  }
  stop() {
    this.animationFrameHandler.stop(this);
    this.localTrail.stop();
  }
  onFrame() {
    this.updateCollabTrails();
  }
  updateCollabTrails() {
    if (!this.container || this.app.state.collaborators.size === 0) {
      return;
    }
    for (const [key, collaborator] of this.app.state.collaborators.entries()) {
      let trail;
      if (!this.collabTrails.has(key)) {
        trail = new AnimatedTrail(this.animationFrameHandler, this.app, {
          ...this.getTrailOptions(),
          fill: () => collaborator.pointer?.laserColor || getClientColor(key, collaborator)
        });
        trail.start(this.container);
        this.collabTrails.set(key, trail);
      } else {
        trail = this.collabTrails.get(key);
      }
      if (collaborator.pointer && collaborator.pointer.tool === "laser") {
        if (collaborator.button === "down" && !trail.hasCurrentTrail) {
          trail.startPath(collaborator.pointer.x, collaborator.pointer.y);
        }
        if (collaborator.button === "down" && trail.hasCurrentTrail && !trail.hasLastPoint(collaborator.pointer.x, collaborator.pointer.y)) {
          trail.addPointToPath(collaborator.pointer.x, collaborator.pointer.y);
        }
        if (collaborator.button === "up" && trail.hasCurrentTrail) {
          trail.addPointToPath(collaborator.pointer.x, collaborator.pointer.y);
          trail.endPath();
        }
      }
    }
    for (const key of this.collabTrails.keys()) {
      if (!this.app.state.collaborators.has(key)) {
        const trail = this.collabTrails.get(key);
        trail.stop();
        this.collabTrails.delete(key);
      }
    }
  }
};

// element/textWysiwyg.tsx
var getTransform = (width, height, angle, appState, maxWidth, maxHeight) => {
  const { zoom } = appState;
  const degree = 180 * angle / Math.PI;
  let translateX = width * (zoom.value - 1) / 2;
  let translateY = height * (zoom.value - 1) / 2;
  if (width > maxWidth && zoom.value !== 1) {
    translateX = maxWidth * (zoom.value - 1) / 2;
  }
  if (height > maxHeight && zoom.value !== 1) {
    translateY = maxHeight * (zoom.value - 1) / 2;
  }
  return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`;
};
var textWysiwyg = ({
  id,
  onChange,
  onSubmit,
  getViewportCoords,
  element,
  canvas,
  excalidrawContainer,
  app,
  autoSelect = true
}) => {
  const textPropertiesUpdated = (updatedTextElement, editable2) => {
    if (!editable2.style.fontFamily || !editable2.style.fontSize) {
      return false;
    }
    const currentFont = editable2.style.fontFamily.replace(/"/g, "");
    if (getFontFamilyString({ fontFamily: updatedTextElement.fontFamily }) !== currentFont) {
      return true;
    }
    if (`${updatedTextElement.fontSize}px` !== editable2.style.fontSize) {
      return true;
    }
    return false;
  };
  const updateWysiwygStyle = () => {
    const appState = app.state;
    const updatedTextElement = Scene_default.getScene(element)?.getElement(id);
    if (!updatedTextElement) {
      return;
    }
    const { textAlign, verticalAlign } = updatedTextElement;
    const elementsMap = app.scene.getNonDeletedElementsMap();
    if (updatedTextElement && isTextElement(updatedTextElement)) {
      let coordX = updatedTextElement.x;
      let coordY = updatedTextElement.y;
      const container = getContainerElement(
        updatedTextElement,
        app.scene.getNonDeletedElementsMap()
      );
      let width = updatedTextElement.width;
      let height = updatedTextElement.height;
      let maxWidth = updatedTextElement.width;
      let maxHeight = updatedTextElement.height;
      if (container && updatedTextElement.containerId) {
        if (isArrowElement(container)) {
          const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
            container,
            updatedTextElement,
            elementsMap
          );
          coordX = boundTextCoords.x;
          coordY = boundTextCoords.y;
        }
        const propertiesUpdated = textPropertiesUpdated(
          updatedTextElement,
          editable
        );
        let originalContainerData;
        if (propertiesUpdated) {
          originalContainerData = updateOriginalContainerCache(
            container.id,
            container.height
          );
        } else {
          originalContainerData = originalContainerCache[container.id];
          if (!originalContainerData) {
            originalContainerData = updateOriginalContainerCache(
              container.id,
              container.height
            );
          }
        }
        maxWidth = getBoundTextMaxWidth(container, updatedTextElement);
        maxHeight = getBoundTextMaxHeight(
          container,
          updatedTextElement
        );
        if (!isArrowElement(container) && height > maxHeight) {
          const targetContainerHeight = computeContainerDimensionForBoundText(
            height,
            container.type
          );
          mutateElement(container, { height: targetContainerHeight });
          return;
        } else if (
          // autoshrink container height until original container height
          // is reached when text is removed
          !isArrowElement(container) && container.height > originalContainerData.height && height < maxHeight
        ) {
          const targetContainerHeight = computeContainerDimensionForBoundText(
            height,
            container.type
          );
          mutateElement(container, { height: targetContainerHeight });
        } else {
          const { y } = computeBoundTextPosition(
            container,
            updatedTextElement,
            elementsMap
          );
          coordY = y;
        }
      }
      const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
      const initialSelectionStart = editable.selectionStart;
      const initialSelectionEnd = editable.selectionEnd;
      const initialLength = editable.value.length;
      if (initialSelectionStart === initialSelectionEnd && initialSelectionEnd !== initialLength) {
        const diff = initialLength - initialSelectionEnd;
        editable.selectionStart = editable.value.length - diff;
        editable.selectionEnd = editable.value.length - diff;
      }
      if (!container) {
        maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
        width = Math.min(width, maxWidth);
      } else {
        width += 0.5;
      }
      height *= 1.05;
      const font = getFontString(updatedTextElement);
      const editorMaxHeight = (appState.height - viewportY) / appState.zoom.value;
      Object.assign(editable.style, {
        font,
        // must be defined *after* font ¯\_(ツ)_/¯
        lineHeight: updatedTextElement.lineHeight,
        width: `${width}px`,
        height: `${height}px`,
        left: `${viewportX}px`,
        top: `${viewportY}px`,
        transform: getTransform(
          width,
          height,
          getTextElementAngle(updatedTextElement, container),
          appState,
          maxWidth,
          editorMaxHeight
        ),
        textAlign,
        verticalAlign,
        color: updatedTextElement.strokeColor,
        opacity: updatedTextElement.opacity / 100,
        filter: "var(--theme-filter)",
        maxHeight: `${editorMaxHeight}px`
      });
      editable.scrollTop = 0;
      if (isTestEnv()) {
        editable.style.fontFamily = getFontFamilyString(updatedTextElement);
      }
      mutateElement(updatedTextElement, { x: coordX, y: coordY });
    }
  };
  const editable = document.createElement("textarea");
  editable.dir = "auto";
  editable.tabIndex = 0;
  editable.dataset.type = "wysiwyg";
  editable.wrap = "off";
  editable.classList.add("excalidraw-wysiwyg");
  let whiteSpace = "pre";
  let wordBreak = "normal";
  if (isBoundToContainer(element) || !element.autoResize) {
    whiteSpace = "pre-wrap";
    wordBreak = "break-word";
  }
  Object.assign(editable.style, {
    position: "absolute",
    display: "inline-block",
    minHeight: "1em",
    backfaceVisibility: "hidden",
    margin: 0,
    padding: 0,
    border: 0,
    outline: 0,
    resize: "none",
    background: "transparent",
    overflow: "hidden",
    // must be specified because in dark mode canvas creates a stacking context
    zIndex: "var(--zIndex-wysiwyg)",
    wordBreak,
    // prevent line wrapping (`whitespace: nowrap` doesn't work on FF)
    whiteSpace,
    overflowWrap: "break-word",
    boxSizing: "content-box"
  });
  editable.value = element.originalText;
  updateWysiwygStyle();
  if (onChange) {
    editable.onpaste = async (event) => {
      const clipboardData = await parseClipboard(event, true);
      if (!clipboardData.text) {
        return;
      }
      const data = normalizeText(clipboardData.text);
      if (!data) {
        return;
      }
      const container = getContainerElement(
        element,
        app.scene.getNonDeletedElementsMap()
      );
      const font = getFontString({
        fontSize: app.state.currentItemFontSize,
        fontFamily: app.state.currentItemFontFamily
      });
      if (container) {
        const boundTextElement = getBoundTextElement(
          container,
          app.scene.getNonDeletedElementsMap()
        );
        const wrappedText = wrapText(
          `${editable.value}${data}`,
          font,
          getBoundTextMaxWidth(container, boundTextElement)
        );
        const width = getTextWidth(wrappedText, font);
        editable.style.width = `${width}px`;
      }
    };
    editable.oninput = () => {
      const normalized = normalizeText(editable.value);
      if (editable.value !== normalized) {
        const selectionStart = editable.selectionStart;
        editable.value = normalized;
        editable.selectionStart = selectionStart;
        editable.selectionEnd = selectionStart;
      }
      onChange(editable.value);
    };
  }
  editable.onkeydown = (event) => {
    if (!event.shiftKey && actionZoomIn.keyTest(event)) {
      event.preventDefault();
      app.actionManager.executeAction(actionZoomIn);
      updateWysiwygStyle();
    } else if (!event.shiftKey && actionZoomOut.keyTest(event)) {
      event.preventDefault();
      app.actionManager.executeAction(actionZoomOut);
      updateWysiwygStyle();
    } else if (!event.shiftKey && actionResetZoom.keyTest(event)) {
      event.preventDefault();
      app.actionManager.executeAction(actionResetZoom);
      updateWysiwygStyle();
    } else if (actionDecreaseFontSize.keyTest(event)) {
      app.actionManager.executeAction(actionDecreaseFontSize);
    } else if (actionIncreaseFontSize.keyTest(event)) {
      app.actionManager.executeAction(actionIncreaseFontSize);
    } else if (event.key === KEYS.ESCAPE) {
      event.preventDefault();
      submittedViaKeyboard = true;
      handleSubmit();
    } else if (event.key === KEYS.ENTER && event[KEYS.CTRL_OR_CMD]) {
      event.preventDefault();
      if (event.isComposing || event.keyCode === 229) {
        return;
      }
      submittedViaKeyboard = true;
      handleSubmit();
    } else if (event.key === KEYS.TAB || event[KEYS.CTRL_OR_CMD] && (event.code === CODES.BRACKET_LEFT || event.code === CODES.BRACKET_RIGHT)) {
      event.preventDefault();
      if (event.isComposing) {
        return;
      } else if (event.shiftKey || event.code === CODES.BRACKET_LEFT) {
        outdent();
      } else {
        indent();
      }
      editable.dispatchEvent(new Event("input"));
    }
  };
  const TAB_SIZE = 4;
  const TAB = " ".repeat(TAB_SIZE);
  const RE_LEADING_TAB = new RegExp(`^ {1,${TAB_SIZE}}`);
  const indent = () => {
    const { selectionStart, selectionEnd } = editable;
    const linesStartIndices = getSelectedLinesStartIndices();
    let value = editable.value;
    linesStartIndices.forEach((startIndex) => {
      const startValue = value.slice(0, startIndex);
      const endValue = value.slice(startIndex);
      value = `${startValue}${TAB}${endValue}`;
    });
    editable.value = value;
    editable.selectionStart = selectionStart + TAB_SIZE;
    editable.selectionEnd = selectionEnd + TAB_SIZE * linesStartIndices.length;
  };
  const outdent = () => {
    const { selectionStart, selectionEnd } = editable;
    const linesStartIndices = getSelectedLinesStartIndices();
    const removedTabs = [];
    let value = editable.value;
    linesStartIndices.forEach((startIndex) => {
      const tabMatch = value.slice(startIndex, startIndex + TAB_SIZE).match(RE_LEADING_TAB);
      if (tabMatch) {
        const startValue = value.slice(0, startIndex);
        const endValue = value.slice(startIndex + tabMatch[0].length);
        value = `${startValue}${endValue}`;
        removedTabs.push(startIndex);
      }
    });
    editable.value = value;
    if (removedTabs.length) {
      if (selectionStart > removedTabs[removedTabs.length - 1]) {
        editable.selectionStart = Math.max(
          selectionStart - TAB_SIZE,
          removedTabs[removedTabs.length - 1]
        );
      } else {
        editable.selectionStart = selectionStart;
      }
      editable.selectionEnd = Math.max(
        editable.selectionStart,
        selectionEnd - TAB_SIZE * removedTabs.length
      );
    }
  };
  const getSelectedLinesStartIndices = () => {
    let { selectionStart, selectionEnd, value } = editable;
    const startOffset = value.slice(0, selectionStart).match(/[^\n]*$/)[0].length;
    selectionStart = selectionStart - startOffset;
    const selected = value.slice(selectionStart, selectionEnd);
    return selected.split("\n").reduce(
      (startIndices, line, idx, lines) => startIndices.concat(
        idx ? (
          // curr line index is prev line's start + prev line's length + \n
          startIndices[idx - 1] + lines[idx - 1].length + 1
        ) : (
          // first selected line
          selectionStart
        )
      ),
      []
    ).reverse();
  };
  const stopEvent = (event) => {
    if (event.target instanceof HTMLCanvasElement) {
      event.preventDefault();
      event.stopPropagation();
    }
  };
  let submittedViaKeyboard = false;
  const handleSubmit = () => {
    if (isDestroyed) {
      return;
    }
    isDestroyed = true;
    cleanup();
    const updateElement = Scene_default.getScene(element)?.getElement(
      element.id
    );
    if (!updateElement) {
      return;
    }
    const container = getContainerElement(
      updateElement,
      app.scene.getNonDeletedElementsMap()
    );
    if (container) {
      if (editable.value.trim()) {
        const boundTextElementId = getBoundTextElementId(container);
        if (!boundTextElementId || boundTextElementId !== element.id) {
          mutateElement(container, {
            boundElements: (container.boundElements || []).concat({
              type: "text",
              id: element.id
            })
          });
        } else if (isArrowElement(container)) {
          bumpVersion(container);
        }
      } else {
        mutateElement(container, {
          boundElements: container.boundElements?.filter(
            (ele) => !isTextElement(
              ele
            )
          )
        });
      }
      redrawTextBoundingBox(
        updateElement,
        container,
        app.scene.getNonDeletedElementsMap()
      );
    }
    onSubmit({
      viaKeyboard: submittedViaKeyboard,
      nextOriginalText: editable.value
    });
  };
  const cleanup = () => {
    editable.onblur = null;
    editable.oninput = null;
    editable.onkeydown = null;
    if (observer) {
      observer.disconnect();
    }
    window.removeEventListener("resize", updateWysiwygStyle);
    window.removeEventListener("wheel", stopEvent, true);
    window.removeEventListener("pointerdown", onPointerDown);
    window.removeEventListener("pointerup", bindBlurEvent);
    window.removeEventListener("blur", handleSubmit);
    window.removeEventListener("beforeunload", handleSubmit);
    unbindUpdate();
    unbindOnScroll();
    editable.remove();
  };
  const bindBlurEvent = (event) => {
    window.removeEventListener("pointerup", bindBlurEvent);
    const target = event?.target;
    const isPropertiesTrigger = target instanceof HTMLElement && target.classList.contains("properties-trigger");
    setTimeout(() => {
      editable.onblur = handleSubmit;
      if (!isPropertiesTrigger) {
        editable.focus();
      }
    });
  };
  const temporarilyDisableSubmit = () => {
    editable.onblur = null;
    window.addEventListener("pointerup", bindBlurEvent);
    window.addEventListener("blur", handleSubmit);
  };
  const onPointerDown = (event) => {
    const target = event?.target;
    if (event.button === POINTER_BUTTON.WHEEL) {
      if (target instanceof HTMLTextAreaElement) {
        event.preventDefault();
        app.handleCanvasPanUsingWheelOrSpaceDrag(event);
      }
      temporarilyDisableSubmit();
      return;
    }
    const isPropertiesTrigger = target instanceof HTMLElement && target.classList.contains("properties-trigger");
    if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) && event.target.closest(
      `.${CLASSES.SHAPE_ACTIONS_MENU}, .${CLASSES.ZOOM_ACTIONS}`
    ) && !isWritableElement(event.target) || isPropertiesTrigger) {
      temporarilyDisableSubmit();
    } else if (event.target instanceof HTMLCanvasElement && // Vitest simply ignores stopPropagation, capture-mode, or rAF
    // so without introducing crazier hacks, nothing we can do
    !isTestEnv()) {
      requestAnimationFrame(() => {
        handleSubmit();
      });
    }
  };
  const unbindUpdate = app.scene.onUpdate(() => {
    updateWysiwygStyle();
    const isPopupOpened = !!document.activeElement?.closest(
      ".properties-content"
    );
    if (!isPopupOpened) {
      editable.focus();
    }
  });
  const unbindOnScroll = app.onScrollChangeEmitter.on(() => {
    updateWysiwygStyle();
  });
  let isDestroyed = false;
  if (autoSelect) {
    editable.select();
  }
  bindBlurEvent();
  let observer = null;
  if (canvas && "ResizeObserver" in window) {
    observer = new window.ResizeObserver(() => {
      updateWysiwygStyle();
    });
    observer.observe(canvas);
  } else {
    window.addEventListener("resize", updateWysiwygStyle);
  }
  editable.onpointerdown = (event) => event.stopPropagation();
  requestAnimationFrame(() => {
    window.addEventListener("pointerdown", onPointerDown, { capture: true });
  });
  window.addEventListener("beforeunload", handleSubmit);
  excalidrawContainer?.querySelector(".excalidraw-textEditorContainer").appendChild(editable);
};

// actions/actionTextAutoResize.ts
var actionTextAutoResize = register({
  name: "autoResize",
  label: "labels.autoResize",
  icon: null,
  trackEvent: { category: "element" },
  predicate: (elements, appState, _, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    return selectedElements.length === 1 && isTextElement(selectedElements[0]) && !selectedElements[0].autoResize;
  },
  perform: (elements, appState, _, app) => {
    const selectedElements = getSelectedElements(elements, appState);
    return {
      appState,
      elements: elements.map((element) => {
        if (element.id === selectedElements[0].id && isTextElement(element)) {
          const metrics = measureText(
            element.originalText,
            getFontString(element),
            element.lineHeight
          );
          return newElementWith(element, {
            autoResize: true,
            width: metrics.width,
            height: metrics.height,
            text: element.originalText
          });
        }
        return element;
      }),
      captureUpdate: CaptureUpdateAction.IMMEDIATELY
    };
  }
});

// mermaid.ts
var isMaybeMermaidDefinition = (text) => {
  const chartTypes = [
    "flowchart",
    "graph",
    "sequenceDiagram",
    "classDiagram",
    "stateDiagram",
    "stateDiagram-v2",
    "erDiagram",
    "journey",
    "gantt",
    "pie",
    "quadrantChart",
    "requirementDiagram",
    "gitGraph",
    "C4Context",
    "mindmap",
    "timeline",
    "zenuml",
    "sankey",
    "xychart",
    "block"
  ];
  const re = new RegExp(
    `^(?:%%{.*?}%%[\\s\\n]*)?\\b(?:${chartTypes.map((x) => `\\s*${x}(-beta)?`).join("|")})\\b`
  );
  return re.test(text.trim());
};

// components/canvases/NewElementCanvas.tsx
import { useEffect as useEffect44, useRef as useRef39 } from "react";

// renderer/renderNewElementScene.ts
var _renderNewElementScene = ({
  canvas,
  rc,
  newElement: newElement2,
  elementsMap,
  allElementsMap,
  scale,
  appState,
  renderConfig
}) => {
  if (canvas) {
    const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
      canvas,
      scale
    );
    const context = bootstrapCanvas({
      canvas,
      scale,
      normalizedWidth,
      normalizedHeight
    });
    context.save();
    context.scale(appState.zoom.value, appState.zoom.value);
    if (newElement2 && newElement2.type !== "selection") {
      renderElement(
        newElement2,
        elementsMap,
        allElementsMap,
        rc,
        context,
        renderConfig,
        appState
      );
    } else {
      context.clearRect(0, 0, normalizedWidth, normalizedHeight);
    }
  }
};
var renderNewElementSceneThrottled = throttleRAF(
  (config) => {
    _renderNewElementScene(config);
  },
  { trailing: true }
);
var renderNewElementScene = (renderConfig, throttle5) => {
  if (throttle5) {
    renderNewElementSceneThrottled(renderConfig);
    return;
  }
  _renderNewElementScene(renderConfig);
};

// components/canvases/NewElementCanvas.tsx
import { jsx as jsx146 } from "react/jsx-runtime";
var NewElementCanvas = (props) => {
  const canvasRef = useRef39(null);
  useEffect44(() => {
    if (!canvasRef.current) {
      return;
    }
    renderNewElementScene(
      {
        canvas: canvasRef.current,
        scale: props.scale,
        newElement: props.appState.newElement,
        elementsMap: props.elementsMap,
        allElementsMap: props.allElementsMap,
        rc: props.rc,
        renderConfig: props.renderConfig,
        appState: props.appState
      },
      isRenderThrottlingEnabled()
    );
  });
  return /* @__PURE__ */ jsx146(
    "canvas",
    {
      className: "excalidraw__canvas",
      style: {
        width: props.appState.width,
        height: props.appState.height
      },
      width: props.appState.width * props.scale,
      height: props.appState.height * props.scale,
      ref: canvasRef
    }
  );
};
var NewElementCanvas_default = NewElementCanvas;

// components/App.tsx
import { Fragment as Fragment23, jsx as jsx147, jsxs as jsxs78 } from "react/jsx-runtime";
var AppContext = React43.createContext(null);
var AppPropsContext = React43.createContext(null);
var deviceContextInitialValue = {
  viewport: {
    isMobile: false,
    isLandscape: false
  },
  editor: {
    isMobile: false,
    canFitSidebar: false
  },
  isTouchScreen: false
};
var DeviceContext = React43.createContext(deviceContextInitialValue);
DeviceContext.displayName = "DeviceContext";
var ExcalidrawContainerContext = React43.createContext({ container: null, id: null });
ExcalidrawContainerContext.displayName = "ExcalidrawContainerContext";
var ExcalidrawElementsContext = React43.createContext([]);
ExcalidrawElementsContext.displayName = "ExcalidrawElementsContext";
var ExcalidrawAppStateContext = React43.createContext({
  ...getDefaultAppState(),
  width: 0,
  height: 0,
  offsetLeft: 0,
  offsetTop: 0
});
ExcalidrawAppStateContext.displayName = "ExcalidrawAppStateContext";
var ExcalidrawSetAppStateContext = React43.createContext(() => {
  console.warn("Uninitialized ExcalidrawSetAppStateContext context!");
});
ExcalidrawSetAppStateContext.displayName = "ExcalidrawSetAppStateContext";
var ExcalidrawActionManagerContext = React43.createContext(
  null
);
ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
var useApp = () => useContext3(AppContext);
var useAppProps = () => useContext3(AppPropsContext);
var useDevice = () => useContext3(DeviceContext);
var useExcalidrawContainer = () => useContext3(ExcalidrawContainerContext);
var useExcalidrawElements = () => useContext3(ExcalidrawElementsContext);
var useExcalidrawAppState = () => useContext3(ExcalidrawAppStateContext);
var useExcalidrawSetAppState = () => useContext3(ExcalidrawSetAppStateContext);
var useExcalidrawActionManager = () => useContext3(ExcalidrawActionManagerContext);
var didTapTwice = false;
var tappedTwiceTimer = 0;
var isHoldingSpace = false;
var isPanning = false;
var isDraggingScrollBar = false;
var currentScrollBars = { horizontal: null, vertical: null };
var touchTimeout = 0;
var invalidateContextMenu = false;
var YOUTUBE_VIDEO_STATES = /* @__PURE__ */ new Map();
var IS_PLAIN_PASTE = false;
var IS_PLAIN_PASTE_TIMER = 0;
var PLAIN_PASTE_TOAST_SHOWN = false;
var lastPointerUp = null;
var gesture = {
  pointers: /* @__PURE__ */ new Map(),
  lastCenter: null,
  initialDistance: null,
  initialScale: null
};
var App = class _App extends React43.Component {
  constructor(props) {
    super(props);
    __publicField(this, "canvas");
    __publicField(this, "interactiveCanvas", null);
    __publicField(this, "rc");
    __publicField(this, "unmounted", false);
    __publicField(this, "actionManager");
    __publicField(this, "device", deviceContextInitialValue);
    __publicField(this, "excalidrawContainerRef", React43.createRef());
    __publicField(this, "scene");
    __publicField(this, "fonts");
    __publicField(this, "renderer");
    __publicField(this, "visibleElements");
    __publicField(this, "resizeObserver");
    __publicField(this, "nearestScrollableContainer");
    __publicField(this, "library");
    __publicField(this, "libraryItemsFromStorage");
    __publicField(this, "id");
    __publicField(this, "store");
    __publicField(this, "history");
    __publicField(this, "excalidrawContainerValue");
    __publicField(this, "files", {});
    __publicField(this, "imageCache", /* @__PURE__ */ new Map());
    __publicField(this, "iFrameRefs", /* @__PURE__ */ new Map());
    /**
     * Indicates whether the embeddable's url has been validated for rendering.
     * If value not set, indicates that the validation is pending.
     * Initially or on url change the flag is not reset so that we can guarantee
     * the validation came from a trusted source (the editor).
     **/
    __publicField(this, "embedsValidationStatus", /* @__PURE__ */ new Map());
    /** embeds that have been inserted to DOM (as a perf optim, we don't want to
     * insert to DOM before user initially scrolls to them) */
    __publicField(this, "initializedEmbeds", /* @__PURE__ */ new Set());
    __publicField(this, "elementsPendingErasure", /* @__PURE__ */ new Set());
    __publicField(this, "flowChartCreator", new FlowChartCreator());
    __publicField(this, "flowChartNavigator", new FlowChartNavigator());
    __publicField(this, "hitLinkElement");
    __publicField(this, "lastPointerDownEvent", null);
    __publicField(this, "lastPointerUpEvent", null);
    __publicField(this, "lastPointerMoveEvent", null);
    __publicField(this, "lastPointerMoveCoords", null);
    __publicField(this, "lastViewportPosition", { x: 0, y: 0 });
    __publicField(this, "animationFrameHandler", new AnimationFrameHandler());
    __publicField(this, "laserTrails", new LaserTrails(this.animationFrameHandler, this));
    __publicField(this, "eraserTrail", new AnimatedTrail(this.animationFrameHandler, this, {
      streamline: 0.2,
      size: 5,
      keepHead: true,
      sizeMapping: (c) => {
        const DECAY_TIME = 200;
        const DECAY_LENGTH = 10;
        const t2 = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME);
        const l = (DECAY_LENGTH - Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) / DECAY_LENGTH;
        return Math.min(easeOut(l), easeOut(t2));
      },
      fill: () => this.state.theme === THEME.LIGHT ? "rgba(0, 0, 0, 0.2)" : "rgba(255, 255, 255, 0.2)"
    }));
    __publicField(this, "onChangeEmitter", new Emitter());
    __publicField(this, "onPointerDownEmitter", new Emitter());
    __publicField(this, "onPointerUpEmitter", new Emitter());
    __publicField(this, "onUserFollowEmitter", new Emitter());
    __publicField(this, "onScrollChangeEmitter", new Emitter());
    __publicField(this, "missingPointerEventCleanupEmitter", new Emitter());
    __publicField(this, "onRemoveEventListenersEmitter", new Emitter());
    /**
     * Returns gridSize taking into account `gridModeEnabled`.
     * If disabled, returns null.
     */
    __publicField(this, "getEffectiveGridSize", () => {
      return isGridModeEnabled(this) ? this.state.gridSize : null;
    });
    __publicField(this, "updateEmbedValidationStatus", (element, status) => {
      this.embedsValidationStatus.set(element.id, status);
      ShapeCache.delete(element);
    });
    __publicField(this, "updateEmbeddables", () => {
      const iframeLikes = /* @__PURE__ */ new Set();
      let updated = false;
      this.scene.getNonDeletedElements().filter((element) => {
        if (isEmbeddableElement(element)) {
          iframeLikes.add(element.id);
          if (!this.embedsValidationStatus.has(element.id)) {
            updated = true;
            const validated = embeddableURLValidator(
              element.link,
              this.props.validateEmbeddable
            );
            this.updateEmbedValidationStatus(element, validated);
          }
        } else if (isIframeElement(element)) {
          iframeLikes.add(element.id);
        }
        return false;
      });
      if (updated) {
        this.scene.triggerUpdate();
      }
      this.iFrameRefs.forEach((ref, id) => {
        if (!iframeLikes.has(id)) {
          this.iFrameRefs.delete(id);
        }
      });
    });
    __publicField(this, "getFrameNameDOMId", (frameElement) => {
      return `${this.id}-frame-name-${frameElement.id}`;
    });
    __publicField(this, "frameNameBoundsCache", {
      get: (frameElement) => {
        let bounds = this.frameNameBoundsCache._cache.get(frameElement.id);
        if (!bounds || bounds.zoom !== this.state.zoom.value || bounds.versionNonce !== frameElement.versionNonce) {
          const frameNameDiv = document.getElementById(
            this.getFrameNameDOMId(frameElement)
          );
          if (frameNameDiv) {
            const box = frameNameDiv.getBoundingClientRect();
            const boxSceneTopLeft = viewportCoordsToSceneCoords(
              { clientX: box.x, clientY: box.y },
              this.state
            );
            const boxSceneBottomRight = viewportCoordsToSceneCoords(
              { clientX: box.right, clientY: box.bottom },
              this.state
            );
            bounds = {
              x: boxSceneTopLeft.x,
              y: boxSceneTopLeft.y,
              width: boxSceneBottomRight.x - boxSceneTopLeft.x,
              height: boxSceneBottomRight.y - boxSceneTopLeft.y,
              angle: 0,
              zoom: this.state.zoom.value,
              versionNonce: frameElement.versionNonce
            };
            this.frameNameBoundsCache._cache.set(frameElement.id, bounds);
            return bounds;
          }
          return null;
        }
        return bounds;
      },
      /**
       * @private
       */
      _cache: /* @__PURE__ */ new Map()
    });
    __publicField(this, "resetEditingFrame", (frame) => {
      if (frame) {
        mutateElement(frame, { name: frame.name?.trim() || null });
      }
      this.setState({ editingFrame: null });
    });
    __publicField(this, "renderFrameNames", () => {
      if (!this.state.frameRendering.enabled || !this.state.frameRendering.name) {
        if (this.state.editingFrame) {
          this.resetEditingFrame(null);
        }
        return null;
      }
      const isDarkTheme = this.state.theme === THEME.DARK;
      return this.scene.getNonDeletedFramesLikes().map((f) => {
        if (!isElementInViewport(
          f,
          this.canvas.width / window.devicePixelRatio,
          this.canvas.height / window.devicePixelRatio,
          {
            offsetLeft: this.state.offsetLeft,
            offsetTop: this.state.offsetTop,
            scrollX: this.state.scrollX,
            scrollY: this.state.scrollY,
            zoom: this.state.zoom
          },
          this.scene.getNonDeletedElementsMap()
        )) {
          if (this.state.editingFrame === f.id) {
            this.resetEditingFrame(f);
          }
          return null;
        }
        const { x: x1, y: y1 } = sceneCoordsToViewportCoords(
          { sceneX: f.x, sceneY: f.y },
          this.state
        );
        const FRAME_NAME_EDIT_PADDING = 6;
        let frameNameJSX;
        const frameName = getFrameLikeTitle(f);
        if (f.id === this.state.editingFrame) {
          const frameNameInEdit = frameName;
          frameNameJSX = /* @__PURE__ */ jsx147(
            "input",
            {
              autoFocus: true,
              value: frameNameInEdit,
              onChange: (e) => {
                mutateElement(f, {
                  name: e.target.value
                });
              },
              onFocus: (e) => e.target.select(),
              onBlur: () => this.resetEditingFrame(f),
              onKeyDown: (event) => {
                if (event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) {
                  this.resetEditingFrame(f);
                }
              },
              style: {
                background: this.state.viewBackgroundColor,
                filter: isDarkTheme ? THEME_FILTER : "none",
                zIndex: 2,
                border: "none",
                display: "block",
                padding: `${FRAME_NAME_EDIT_PADDING}px`,
                borderRadius: 4,
                boxShadow: "inset 0 0 0 1px var(--color-primary)",
                fontFamily: "Assistant",
                fontSize: "14px",
                transform: `translate(-${FRAME_NAME_EDIT_PADDING}px, ${FRAME_NAME_EDIT_PADDING}px)`,
                color: "var(--color-gray-80)",
                overflow: "hidden",
                maxWidth: `${document.body.clientWidth - x1 - FRAME_NAME_EDIT_PADDING}px`
              },
              size: frameNameInEdit.length + 1 || 1,
              dir: "auto",
              autoComplete: "off",
              autoCapitalize: "off",
              autoCorrect: "off"
            }
          );
        } else {
          frameNameJSX = frameName;
        }
        return /* @__PURE__ */ jsx147(
          "div",
          {
            id: this.getFrameNameDOMId(f),
            style: {
              position: "absolute",
              // Positioning from bottom so that we don't to either
              // calculate text height or adjust using transform (which)
              // messes up input position when editing the frame name.
              // This makes the positioning deterministic and we can calculate
              // the same position when rendering to canvas / svg.
              bottom: `${this.state.height + FRAME_STYLE.nameOffsetY - y1 + this.state.offsetTop}px`,
              left: `${x1 - this.state.offsetLeft}px`,
              zIndex: 2,
              fontSize: FRAME_STYLE.nameFontSize,
              color: isDarkTheme ? FRAME_STYLE.nameColorDarkTheme : FRAME_STYLE.nameColorLightTheme,
              lineHeight: FRAME_STYLE.nameLineHeight,
              width: "max-content",
              maxWidth: `${f.width}px`,
              overflow: f.id === this.state.editingFrame ? "visible" : "hidden",
              whiteSpace: "nowrap",
              textOverflow: "ellipsis",
              cursor: CURSOR_TYPE.MOVE,
              pointerEvents: this.state.viewModeEnabled ? POINTER_EVENTS.disabled : POINTER_EVENTS.enabled
            },
            onPointerDown: (event) => this.handleCanvasPointerDown(event),
            onWheel: (event) => this.handleWheel(event),
            onContextMenu: this.handleCanvasContextMenu,
            onDoubleClick: () => {
              this.setState({
                editingFrame: f.id
              });
            },
            children: frameNameJSX
          },
          f.id
        );
      });
    });
    __publicField(this, "focusContainer", () => {
      this.excalidrawContainerRef.current?.focus();
    });
    __publicField(this, "getSceneElementsIncludingDeleted", () => {
      return this.scene.getElementsIncludingDeleted();
    });
    __publicField(this, "getSceneElements", () => {
      return this.scene.getNonDeletedElements();
    });
    __publicField(this, "onInsertElements", (elements) => {
      this.addElementsFromPasteOrLibrary({
        elements,
        position: "center",
        files: null
      });
    });
    __publicField(this, "onExportImage", async (type, elements, opts) => {
      trackEvent("export", type, "ui");
      const fileHandle = await exportCanvas(
        type,
        elements,
        this.state,
        this.files,
        {
          exportBackground: this.state.exportBackground,
          name: this.getName(),
          viewBackgroundColor: this.state.viewBackgroundColor,
          exportingFrame: opts.exportingFrame
        }
      ).catch(muteFSAbortError).catch((error) => {
        console.error(error);
        this.setState({ errorMessage: error.message });
      });
      if (this.state.exportEmbedScene && fileHandle && isImageFileHandle(fileHandle)) {
        this.setState({ fileHandle });
      }
    });
    __publicField(this, "magicGenerations", /* @__PURE__ */ new Map());
    __publicField(this, "updateMagicGeneration", ({
      frameElement,
      data
    }) => {
      if (data.status === "pending") {
        mutateElement(
          frameElement,
          { customData: { generationData: void 0 } },
          false
        );
      } else {
        mutateElement(
          frameElement,
          { customData: { generationData: data } },
          false
        );
      }
      this.magicGenerations.set(frameElement.id, data);
      this.triggerRender();
    });
    __publicField(this, "plugins", {});
    __publicField(this, "onMagicframeToolSelect", () => {
      const selectedElements = this.scene.getSelectedElements({
        selectedElementIds: this.state.selectedElementIds
      });
      if (selectedElements.length === 0) {
        this.setActiveTool({ type: TOOL_TYPE.magicframe });
        trackEvent("ai", "tool-select (empty-selection)", "d2c");
      } else {
        const selectedMagicFrame = selectedElements.length === 1 && isMagicFrameElement(selectedElements[0]) && selectedElements[0];
        if (!selectedMagicFrame && selectedElements.some((el) => isFrameLikeElement(el) || el.frameId)) {
          this.setActiveTool({ type: TOOL_TYPE.magicframe });
          return;
        }
        trackEvent("ai", "tool-select (existing selection)", "d2c");
        let frame;
        if (selectedMagicFrame) {
          frame = selectedMagicFrame;
        } else {
          const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
          const padding = 50;
          frame = newMagicFrameElement({
            ...FRAME_STYLE,
            x: minX - padding,
            y: minY - padding,
            width: maxX - minX + padding * 2,
            height: maxY - minY + padding * 2,
            opacity: 100,
            locked: false
          });
          this.scene.insertElement(frame);
          for (const child of selectedElements) {
            mutateElement(child, { frameId: frame.id });
          }
          this.setState({
            selectedElementIds: { [frame.id]: true }
          });
        }
        this.onMagicFrameGenerate(frame, "upstream");
      }
    });
    __publicField(this, "openEyeDropper", ({ type }) => {
      editorJotaiStore.set(activeEyeDropperAtom, {
        swapPreviewOnAlt: true,
        colorPickerType: type === "stroke" ? "elementStroke" : "elementBackground",
        onSelect: (color, event) => {
          const shouldUpdateStrokeColor = type === "background" && event.altKey || type === "stroke" && !event.altKey;
          const selectedElements = this.scene.getSelectedElements(this.state);
          if (!selectedElements.length || this.state.activeTool.type !== "selection") {
            if (shouldUpdateStrokeColor) {
              this.syncActionResult({
                appState: { ...this.state, currentItemStrokeColor: color },
                captureUpdate: CaptureUpdateAction.IMMEDIATELY
              });
            } else {
              this.syncActionResult({
                appState: { ...this.state, currentItemBackgroundColor: color },
                captureUpdate: CaptureUpdateAction.IMMEDIATELY
              });
            }
          } else {
            this.updateScene({
              elements: this.scene.getElementsIncludingDeleted().map((el) => {
                if (this.state.selectedElementIds[el.id]) {
                  return newElementWith(el, {
                    [shouldUpdateStrokeColor ? "strokeColor" : "backgroundColor"]: color
                  });
                }
                return el;
              }),
              captureUpdate: CaptureUpdateAction.IMMEDIATELY
            });
          }
        },
        keepOpenOnAlt: false
      });
    });
    __publicField(this, "dismissLinearEditor", () => {
      setTimeout(() => {
        this.setState({
          editingLinearElement: null
        });
      });
    });
    __publicField(this, "syncActionResult", withBatchedUpdates((actionResult) => {
      if (this.unmounted || actionResult === false) {
        return;
      }
      if (actionResult.captureUpdate === CaptureUpdateAction.NEVER) {
        this.store.shouldUpdateSnapshot();
      } else if (actionResult.captureUpdate === CaptureUpdateAction.IMMEDIATELY) {
        this.store.shouldCaptureIncrement();
      }
      let didUpdate = false;
      let editingTextElement = null;
      if (actionResult.elements) {
        this.scene.replaceAllElements(actionResult.elements);
        didUpdate = true;
      }
      if (actionResult.files) {
        this.addMissingFiles(actionResult.files, actionResult.replaceFiles);
        this.addNewImagesToImageCache();
      }
      if (actionResult.appState || editingTextElement || this.state.contextMenu) {
        let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
        let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
        const theme = actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
        const name = actionResult?.appState?.name ?? this.state.name;
        const errorMessage = actionResult?.appState?.errorMessage ?? this.state.errorMessage;
        if (typeof this.props.viewModeEnabled !== "undefined") {
          viewModeEnabled = this.props.viewModeEnabled;
        }
        if (typeof this.props.zenModeEnabled !== "undefined") {
          zenModeEnabled = this.props.zenModeEnabled;
        }
        editingTextElement = actionResult.appState?.editingTextElement || null;
        if (actionResult.elements && editingTextElement) {
          actionResult.elements.forEach((element) => {
            if (editingTextElement?.id === element.id && editingTextElement !== element && isNonDeletedElement(element) && isTextElement(element)) {
              editingTextElement = element;
            }
          });
        }
        if (editingTextElement?.isDeleted) {
          editingTextElement = null;
        }
        this.setState((prevAppState) => {
          const actionAppState = actionResult.appState || {};
          return {
            ...prevAppState,
            ...actionAppState,
            // NOTE this will prevent opening context menu using an action
            // or programmatically from the host, so it will need to be
            // rewritten later
            contextMenu: null,
            editingTextElement,
            viewModeEnabled,
            zenModeEnabled,
            theme,
            name,
            errorMessage
          };
        });
        didUpdate = true;
      }
      if (!didUpdate && actionResult.captureUpdate !== CaptureUpdateAction.EVENTUALLY) {
        this.scene.triggerUpdate();
      }
    }));
    // Lifecycle
    __publicField(this, "onBlur", withBatchedUpdates(() => {
      isHoldingSpace = false;
      this.setState({ isBindingEnabled: true });
    }));
    __publicField(this, "onUnload", () => {
      this.onBlur();
    });
    __publicField(this, "disableEvent", (event) => {
      event.preventDefault();
    });
    __publicField(this, "resetHistory", () => {
      this.history.clear();
    });
    __publicField(this, "resetStore", () => {
      this.store.clear();
    });
    /**
     * Resets scene & history.
     * ! Do not use to clear scene user action !
     */
    __publicField(this, "resetScene", withBatchedUpdates(
      (opts) => {
        this.scene.replaceAllElements([]);
        this.setState((state) => ({
          ...getDefaultAppState(),
          isLoading: opts?.resetLoadingState ? false : state.isLoading,
          theme: this.state.theme
        }));
        this.resetStore();
        this.resetHistory();
      }
    ));
    __publicField(this, "initializeScene", async () => {
      if ("launchQueue" in window && "LaunchParams" in window) {
        window.launchQueue.setConsumer(
          async (launchParams) => {
            if (!launchParams.files.length) {
              return;
            }
            const fileHandle = launchParams.files[0];
            const blob = await fileHandle.getFile();
            this.loadFileToCanvas(
              new File([blob], blob.name || "", { type: blob.type }),
              fileHandle
            );
          }
        );
      }
      if (this.props.theme) {
        this.setState({ theme: this.props.theme });
      }
      if (!this.state.isLoading) {
        this.setState({ isLoading: true });
      }
      let initialData = null;
      try {
        if (typeof this.props.initialData === "function") {
          initialData = await this.props.initialData() || null;
        } else {
          initialData = await this.props.initialData || null;
        }
        if (initialData?.libraryItems) {
          this.library.updateLibrary({
            libraryItems: initialData.libraryItems,
            merge: true
          }).catch((error) => {
            console.error(error);
          });
        }
      } catch (error) {
        console.error(error);
        initialData = {
          appState: {
            errorMessage: error.message || "Encountered an error during importing or restoring scene data"
          }
        };
      }
      const scene = restore(initialData, null, null, { repairBindings: true });
      scene.appState = {
        ...scene.appState,
        theme: this.props.theme || scene.appState.theme,
        // we're falling back to current (pre-init) state when deciding
        // whether to open the library, to handle a case where we
        // update the state outside of initialData (e.g. when loading the app
        // with a library install link, which should auto-open the library)
        openSidebar: scene.appState?.openSidebar || this.state.openSidebar,
        activeTool: scene.appState.activeTool.type === "image" ? { ...scene.appState.activeTool, type: "selection" } : scene.appState.activeTool,
        isLoading: false,
        toast: this.state.toast
      };
      if (initialData?.scrollToContent) {
        scene.appState = {
          ...scene.appState,
          ...calculateScrollCenter(scene.elements, {
            ...scene.appState,
            width: this.state.width,
            height: this.state.height,
            offsetTop: this.state.offsetTop,
            offsetLeft: this.state.offsetLeft
          })
        };
      }
      this.resetStore();
      this.resetHistory();
      this.syncActionResult({
        ...scene,
        captureUpdate: CaptureUpdateAction.NEVER
      });
      this.clearImageShapeCache();
      this.fonts.loadSceneFonts().then((fontFaces) => {
        this.fonts.onLoaded(fontFaces);
      });
      if (isElementLink(window.location.href)) {
        this.scrollToContent(window.location.href, { animate: false });
      }
    });
    __publicField(this, "isMobileBreakpoint", (width, height) => {
      return width < MQ_MAX_WIDTH_PORTRAIT || height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE;
    });
    __publicField(this, "refreshViewportBreakpoints", () => {
      const container = this.excalidrawContainerRef.current;
      if (!container) {
        return;
      }
      const { clientWidth: viewportWidth, clientHeight: viewportHeight } = document.body;
      const prevViewportState = this.device.viewport;
      const nextViewportState = updateObject(prevViewportState, {
        isLandscape: viewportWidth > viewportHeight,
        isMobile: this.isMobileBreakpoint(viewportWidth, viewportHeight)
      });
      if (prevViewportState !== nextViewportState) {
        this.device = { ...this.device, viewport: nextViewportState };
        return true;
      }
      return false;
    });
    __publicField(this, "refreshEditorBreakpoints", () => {
      const container = this.excalidrawContainerRef.current;
      if (!container) {
        return;
      }
      const { width: editorWidth, height: editorHeight } = container.getBoundingClientRect();
      const sidebarBreakpoint = this.props.UIOptions.dockedSidebarBreakpoint != null ? this.props.UIOptions.dockedSidebarBreakpoint : MQ_RIGHT_SIDEBAR_MIN_WIDTH;
      const prevEditorState = this.device.editor;
      const nextEditorState = updateObject(prevEditorState, {
        isMobile: this.isMobileBreakpoint(editorWidth, editorHeight),
        canFitSidebar: editorWidth > sidebarBreakpoint
      });
      if (prevEditorState !== nextEditorState) {
        this.device = { ...this.device, editor: nextEditorState };
        return true;
      }
      return false;
    });
    __publicField(this, "onResize", withBatchedUpdates(() => {
      this.scene.getElementsIncludingDeleted().forEach((element) => ShapeCache.delete(element));
      this.refreshViewportBreakpoints();
      this.updateDOMRect();
      if (!supportsResizeObserver) {
        this.refreshEditorBreakpoints();
      }
      this.setState({});
    }));
    /** generally invoked only if fullscreen was invoked programmatically */
    __publicField(this, "onFullscreenChange", () => {
      if (
        // points to the iframe element we fullscreened
        !document.fullscreenElement && this.state.activeEmbeddable?.state === "active"
      ) {
        this.setState({
          activeEmbeddable: null
        });
      }
    });
    __publicField(this, "renderInteractiveSceneCallback", ({
      atLeastOneVisibleElement,
      scrollBars,
      elementsMap
    }) => {
      if (scrollBars) {
        currentScrollBars = scrollBars;
      }
      const scrolledOutside = (
        // hide when editing text
        this.state.editingTextElement ? false : !atLeastOneVisibleElement && elementsMap.size > 0
      );
      if (this.state.scrolledOutside !== scrolledOutside) {
        this.setState({ scrolledOutside });
      }
      this.scheduleImageRefresh();
    });
    __publicField(this, "onScroll", debounce(() => {
      const { offsetTop, offsetLeft } = this.getCanvasOffsets();
      this.setState((state) => {
        if (state.offsetLeft === offsetLeft && state.offsetTop === offsetTop) {
          return null;
        }
        return { offsetTop, offsetLeft };
      });
    }, SCROLL_TIMEOUT));
    // Copy/paste
    __publicField(this, "onCut", withBatchedUpdates((event) => {
      const isExcalidrawActive = this.excalidrawContainerRef.current?.contains(
        document.activeElement
      );
      if (!isExcalidrawActive || isWritableElement(event.target)) {
        return;
      }
      this.actionManager.executeAction(actionCut, "keyboard", event);
      event.preventDefault();
      event.stopPropagation();
    }));
    __publicField(this, "onCopy", withBatchedUpdates((event) => {
      const isExcalidrawActive = this.excalidrawContainerRef.current?.contains(
        document.activeElement
      );
      if (!isExcalidrawActive || isWritableElement(event.target)) {
        return;
      }
      this.actionManager.executeAction(actionCopy, "keyboard", event);
      event.preventDefault();
      event.stopPropagation();
    }));
    __publicField(this, "onTouchStart", (event) => {
      if (isIOS) {
        event.preventDefault();
      }
      if (!didTapTwice) {
        didTapTwice = true;
        clearTimeout(tappedTwiceTimer);
        tappedTwiceTimer = window.setTimeout(
          _App.resetTapTwice,
          TAP_TWICE_TIMEOUT
        );
        return;
      }
      if (didTapTwice && event.touches.length === 1) {
        const touch = event.touches[0];
        this.handleCanvasDoubleClick({
          clientX: touch.clientX,
          clientY: touch.clientY
        });
        didTapTwice = false;
        clearTimeout(tappedTwiceTimer);
      }
      if (event.touches.length === 2) {
        this.setState({
          selectedElementIds: makeNextSelectedElementIds({}, this.state),
          activeEmbeddable: null
        });
      }
    });
    __publicField(this, "onTouchEnd", (event) => {
      this.resetContextMenuTimer();
      if (event.touches.length > 0) {
        this.setState({
          previousSelectedElementIds: {},
          selectedElementIds: makeNextSelectedElementIds(
            this.state.previousSelectedElementIds,
            this.state
          )
        });
      } else {
        gesture.pointers.clear();
      }
    });
    __publicField(this, "pasteFromClipboard", withBatchedUpdates(
      async (event) => {
        const isPlainPaste = !!IS_PLAIN_PASTE;
        const target = document.activeElement;
        const isExcalidrawActive = this.excalidrawContainerRef.current?.contains(target);
        if (event && !isExcalidrawActive) {
          return;
        }
        const elementUnderCursor = document.elementFromPoint(
          this.lastViewportPosition.x,
          this.lastViewportPosition.y
        );
        if (event && (!(elementUnderCursor instanceof HTMLCanvasElement) || isWritableElement(target))) {
          return;
        }
        const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
          {
            clientX: this.lastViewportPosition.x,
            clientY: this.lastViewportPosition.y
          },
          this.state
        );
        let file = event?.clipboardData?.files[0];
        const data = await parseClipboard(event, isPlainPaste);
        if (!file && !isPlainPaste) {
          if (data.mixedContent) {
            return this.addElementsFromMixedContentPaste(data.mixedContent, {
              isPlainPaste,
              sceneX,
              sceneY
            });
          } else if (data.text) {
            const string = data.text.trim();
            if (string.startsWith("<svg") && string.endsWith("</svg>")) {
              file = SVGStringToFile(string);
            }
          }
        }
        if (isSupportedImageFile(file) && !data.spreadsheet) {
          if (!this.isToolSupported("image")) {
            this.setState({ errorMessage: t("errors.imageToolNotSupported") });
            return;
          }
          const imageElement = this.createImageElement({ sceneX, sceneY });
          this.insertImageElement(imageElement, file);
          this.initializeImageDimensions(imageElement);
          this.setState({
            selectedElementIds: makeNextSelectedElementIds(
              {
                [imageElement.id]: true
              },
              this.state
            )
          });
          return;
        }
        if (this.props.onPaste) {
          try {
            if (await this.props.onPaste(data, event) === false) {
              return;
            }
          } catch (error) {
            console.error(error);
          }
        }
        if (data.errorMessage) {
          this.setState({ errorMessage: data.errorMessage });
        } else if (data.spreadsheet && !isPlainPaste) {
          this.setState({
            pasteDialog: {
              data: data.spreadsheet,
              shown: true
            }
          });
        } else if (data.elements) {
          const elements = data.programmaticAPI ? convertToExcalidrawElements(
            data.elements
          ) : data.elements;
          this.addElementsFromPasteOrLibrary({
            elements,
            files: data.files || null,
            position: "cursor",
            retainSeed: isPlainPaste
          });
        } else if (data.text) {
          if (data.text && isMaybeMermaidDefinition(data.text)) {
            const api = await import("@excalidraw/mermaid-to-excalidraw");
            try {
              const { elements: skeletonElements, files } = await api.parseMermaidToExcalidraw(data.text);
              const elements = convertToExcalidrawElements(skeletonElements, {
                regenerateIds: true
              });
              this.addElementsFromPasteOrLibrary({
                elements,
                files,
                position: "cursor"
              });
              return;
            } catch (err) {
              console.warn(
                `parsing pasted text as mermaid definition failed: ${err.message}`
              );
            }
          }
          const nonEmptyLines = normalizeEOL(data.text).split(/\n+/).map((s) => s.trim()).filter(Boolean);
          const embbeddableUrls = nonEmptyLines.map((str) => maybeParseEmbedSrc(str)).filter((string) => {
            return embeddableURLValidator(string, this.props.validateEmbeddable) && (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(string) || getEmbedLink(string)?.type === "video");
          });
          if (!IS_PLAIN_PASTE && embbeddableUrls.length > 0 && // if there were non-embeddable text (lines) mixed in with embeddable
          // urls, ignore and paste as text
          embbeddableUrls.length === nonEmptyLines.length) {
            const embeddables = [];
            for (const url of embbeddableUrls) {
              const prevEmbeddable = embeddables[embeddables.length - 1];
              const embeddable = this.insertEmbeddableElement({
                sceneX: prevEmbeddable ? prevEmbeddable.x + prevEmbeddable.width + 20 : sceneX,
                sceneY,
                link: normalizeLink(url)
              });
              if (embeddable) {
                embeddables.push(embeddable);
              }
            }
            if (embeddables.length) {
              this.setState({
                selectedElementIds: Object.fromEntries(
                  embeddables.map((embeddable) => [embeddable.id, true])
                )
              });
            }
            return;
          }
          this.addTextFromPaste(data.text, isPlainPaste);
        }
        this.setActiveTool({ type: "selection" });
        event?.preventDefault();
      }
    ));
    __publicField(this, "addElementsFromPasteOrLibrary", (opts) => {
      const elements = restoreElements(opts.elements, null, void 0);
      const [minX, minY, maxX, maxY] = getCommonBounds(elements);
      const elementsCenterX = distance(minX, maxX) / 2;
      const elementsCenterY = distance(minY, maxY) / 2;
      const clientX = typeof opts.position === "object" ? opts.position.clientX : opts.position === "cursor" ? this.lastViewportPosition.x : this.state.width / 2 + this.state.offsetLeft;
      const clientY = typeof opts.position === "object" ? opts.position.clientY : opts.position === "cursor" ? this.lastViewportPosition.y : this.state.height / 2 + this.state.offsetTop;
      const { x, y } = viewportCoordsToSceneCoords(
        { clientX, clientY },
        this.state
      );
      const dx = x - elementsCenterX;
      const dy = y - elementsCenterY;
      const [gridX, gridY] = getGridPoint(dx, dy, this.getEffectiveGridSize());
      const newElements = duplicateElements(
        elements.map((element) => {
          return newElementWith(element, {
            x: element.x + gridX - minX,
            y: element.y + gridY - minY
          });
        }),
        {
          randomizeSeed: !opts.retainSeed
        }
      );
      const prevElements = this.scene.getElementsIncludingDeleted();
      let nextElements = [...prevElements, ...newElements];
      const mappedNewSceneElements = this.props.onDuplicate?.(
        nextElements,
        prevElements
      );
      nextElements = mappedNewSceneElements || nextElements;
      syncMovedIndices(nextElements, arrayToMap(newElements));
      const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
      if (topLayerFrame) {
        const eligibleElements = filterElementsEligibleAsFrameChildren(
          newElements,
          topLayerFrame
        );
        addElementsToFrame(
          nextElements,
          eligibleElements,
          topLayerFrame,
          this.state
        );
      }
      this.scene.replaceAllElements(nextElements);
      newElements.forEach((newElement2) => {
        if (isTextElement(newElement2) && isBoundToContainer(newElement2)) {
          const container = getContainerElement(
            newElement2,
            this.scene.getElementsMapIncludingDeleted()
          );
          redrawTextBoundingBox(
            newElement2,
            container,
            this.scene.getElementsMapIncludingDeleted()
          );
        }
      });
      if (isSafari) {
        Fonts.loadElementsFonts(newElements).then((fontFaces) => {
          this.fonts.onLoaded(fontFaces);
        });
      }
      if (opts.files) {
        this.addMissingFiles(opts.files);
      }
      this.store.shouldCaptureIncrement();
      const nextElementsToSelect = excludeElementsInFramesFromSelection(newElements);
      this.setState(
        {
          ...this.state,
          // keep sidebar (presumably the library) open if it's docked and
          // can fit.
          //
          // Note, we should close the sidebar only if we're dropping items
          // from library, not when pasting from clipboard. Alas.
          openSidebar: this.state.openSidebar && this.device.editor.canFitSidebar && editorJotaiStore.get(isSidebarDockedAtom) ? this.state.openSidebar : null,
          ...selectGroupsForSelectedElements(
            {
              editingGroupId: null,
              selectedElementIds: nextElementsToSelect.reduce(
                (acc, element) => {
                  if (!isBoundToContainer(element)) {
                    acc[element.id] = true;
                  }
                  return acc;
                },
                {}
              )
            },
            this.scene.getNonDeletedElements(),
            this.state,
            this
          )
        },
        () => {
          if (opts.files) {
            this.addNewImagesToImageCache();
          }
        }
      );
      this.setActiveTool({ type: "selection" });
      if (opts.fitToContent) {
        this.scrollToContent(newElements, {
          fitToContent: true,
          canvasOffsets: this.getEditorUIOffsets()
        });
      }
    });
    __publicField(this, "setAppState", (state, callback) => {
      this.setState(state, callback);
    });
    __publicField(this, "removePointer", (event) => {
      if (touchTimeout) {
        this.resetContextMenuTimer();
      }
      gesture.pointers.delete(event.pointerId);
    });
    __publicField(this, "toggleLock", (source = "ui") => {
      if (!this.state.activeTool.locked) {
        trackEvent(
          "toolbar",
          "toggleLock",
          `${source} (${this.device.editor.isMobile ? "mobile" : "desktop"})`
        );
      }
      this.setState((prevState) => {
        return {
          activeTool: {
            ...prevState.activeTool,
            ...updateActiveTool(
              this.state,
              prevState.activeTool.locked ? { type: "selection" } : prevState.activeTool
            ),
            locked: !prevState.activeTool.locked
          }
        };
      });
    });
    __publicField(this, "updateFrameRendering", (opts) => {
      this.setState((prevState) => {
        const next = typeof opts === "function" ? opts(prevState.frameRendering) : opts;
        return {
          frameRendering: {
            enabled: next?.enabled ?? prevState.frameRendering.enabled,
            clip: next?.clip ?? prevState.frameRendering.clip,
            name: next?.name ?? prevState.frameRendering.name,
            outline: next?.outline ?? prevState.frameRendering.outline
          }
        };
      });
    });
    __publicField(this, "togglePenMode", (force) => {
      this.setState((prevState) => {
        return {
          penMode: force ?? !prevState.penMode,
          penDetected: true
        };
      });
    });
    __publicField(this, "onHandToolToggle", () => {
      this.actionManager.executeAction(actionToggleHandTool);
    });
    /**
     * Zooms on canvas viewport center
     */
    __publicField(this, "zoomCanvas", (value) => {
      this.setState({
        ...getStateForZoom(
          {
            viewportX: this.state.width / 2 + this.state.offsetLeft,
            viewportY: this.state.height / 2 + this.state.offsetTop,
            nextZoom: getNormalizedZoom(value)
          },
          this.state
        )
      });
    });
    __publicField(this, "cancelInProgressAnimation", null);
    __publicField(this, "scrollToContent", (target = this.scene.getNonDeletedElements(), opts) => {
      if (typeof target === "string") {
        let id;
        if (isElementLink(target)) {
          id = parseElementLinkFromURL(target);
        } else {
          id = target;
        }
        if (id) {
          const elements = this.scene.getElementsFromId(id);
          if (elements?.length) {
            this.scrollToContent(elements, {
              fitToContent: opts?.fitToContent ?? true,
              animate: opts?.animate ?? true
            });
          } else if (isElementLink(target)) {
            this.setState({
              toast: {
                message: t("elementLink.notFound"),
                duration: 3e3,
                closable: true
              }
            });
          }
        }
        return;
      }
      this.cancelInProgressAnimation?.();
      const targetElements = Array.isArray(target) ? target : [target];
      let zoom = this.state.zoom;
      let scrollX = this.state.scrollX;
      let scrollY = this.state.scrollY;
      if (opts?.fitToContent || opts?.fitToViewport) {
        const { appState } = zoomToFit({
          canvasOffsets: opts.canvasOffsets,
          targetElements,
          appState: this.state,
          fitToViewport: !!opts?.fitToViewport,
          viewportZoomFactor: opts?.viewportZoomFactor,
          minZoom: opts?.minZoom,
          maxZoom: opts?.maxZoom
        });
        zoom = appState.zoom;
        scrollX = appState.scrollX;
        scrollY = appState.scrollY;
      } else {
        const scroll = calculateScrollCenter(targetElements, this.state);
        scrollX = scroll.scrollX;
        scrollY = scroll.scrollY;
      }
      if (opts?.animate) {
        const origScrollX = this.state.scrollX;
        const origScrollY = this.state.scrollY;
        const origZoom = this.state.zoom.value;
        const cancel = easeToValuesRAF({
          fromValues: {
            scrollX: origScrollX,
            scrollY: origScrollY,
            zoom: origZoom
          },
          toValues: { scrollX, scrollY, zoom: zoom.value },
          interpolateValue: (from, to, progress, key) => {
            if (key === "zoom") {
              return from * Math.pow(to / from, easeOut(progress));
            }
            return void 0;
          },
          onStep: ({ scrollX: scrollX2, scrollY: scrollY2, zoom: zoom2 }) => {
            this.setState({
              scrollX: scrollX2,
              scrollY: scrollY2,
              zoom: { value: zoom2 }
            });
          },
          onStart: () => {
            this.setState({ shouldCacheIgnoreZoom: true });
          },
          onEnd: () => {
            this.setState({ shouldCacheIgnoreZoom: false });
          },
          onCancel: () => {
            this.setState({ shouldCacheIgnoreZoom: false });
          },
          duration: opts?.duration ?? 500
        });
        this.cancelInProgressAnimation = () => {
          cancel();
          this.cancelInProgressAnimation = null;
        };
      } else {
        this.setState({ scrollX, scrollY, zoom });
      }
    });
    __publicField(this, "maybeUnfollowRemoteUser", () => {
      if (this.state.userToFollow) {
        this.setState({ userToFollow: null });
      }
    });
    /** use when changing scrollX/scrollY/zoom based on user interaction */
    __publicField(this, "translateCanvas", (state) => {
      this.cancelInProgressAnimation?.();
      this.maybeUnfollowRemoteUser();
      this.setState(state);
    });
    __publicField(this, "setToast", (toast) => {
      this.setState({ toast });
    });
    __publicField(this, "restoreFileFromShare", async () => {
      try {
        const webShareTargetCache = await caches.open("web-share-target");
        const response = await webShareTargetCache.match("shared-file");
        if (response) {
          const blob = await response.blob();
          const file = new File([blob], blob.name || "", { type: blob.type });
          this.loadFileToCanvas(file, null);
          await webShareTargetCache.delete("shared-file");
          window.history.replaceState(null, APP_NAME, window.location.pathname);
        }
      } catch (error) {
        this.setState({ errorMessage: error.message });
      }
    });
    /**
     * adds supplied files to existing files in the appState.
     * NOTE if file already exists in editor state, the file data is not updated
     * */
    __publicField(this, "addFiles", withBatchedUpdates(
      (files) => {
        const { addedFiles } = this.addMissingFiles(files);
        this.clearImageShapeCache(addedFiles);
        this.scene.triggerUpdate();
        this.addNewImagesToImageCache();
      }
    ));
    __publicField(this, "addMissingFiles", (files, replace = false) => {
      const nextFiles = replace ? {} : { ...this.files };
      const addedFiles = {};
      const _files = Array.isArray(files) ? files : Object.values(files);
      for (const fileData of _files) {
        if (nextFiles[fileData.id]) {
          continue;
        }
        addedFiles[fileData.id] = fileData;
        nextFiles[fileData.id] = fileData;
        if (fileData.mimeType === MIME_TYPES.svg) {
          try {
            const restoredDataURL = getDataURL_sync(
              normalizeSVG(dataURLToString(fileData.dataURL)),
              MIME_TYPES.svg
            );
            if (fileData.dataURL !== restoredDataURL) {
              fileData.version = (fileData.version ?? 1) + 1;
              fileData.dataURL = restoredDataURL;
            }
          } catch (error) {
            console.error(error);
          }
        }
      }
      this.files = nextFiles;
      return { addedFiles };
    });
    __publicField(this, "updateScene", withBatchedUpdates(
      (sceneData) => {
        const nextElements = syncInvalidIndices(sceneData.elements ?? []);
        if (sceneData.captureUpdate && sceneData.captureUpdate !== CaptureUpdateAction.EVENTUALLY) {
          const prevCommittedAppState = this.store.snapshot.appState;
          const prevCommittedElements = this.store.snapshot.elements;
          const nextCommittedAppState = sceneData.appState ? Object.assign({}, prevCommittedAppState, sceneData.appState) : prevCommittedAppState;
          const nextCommittedElements = sceneData.elements ? this.store.filterUncomittedElements(
            this.scene.getElementsMapIncludingDeleted(),
            // Only used to detect uncomitted local elements
            arrayToMap(nextElements)
            // We expect all (already reconciled) elements
          ) : prevCommittedElements;
          if (sceneData.captureUpdate === CaptureUpdateAction.IMMEDIATELY) {
            this.store.captureIncrement(
              nextCommittedElements,
              nextCommittedAppState
            );
          } else if (sceneData.captureUpdate === CaptureUpdateAction.NEVER) {
            this.store.updateSnapshot(
              nextCommittedElements,
              nextCommittedAppState
            );
          }
        }
        if (sceneData.appState) {
          this.setState(sceneData.appState);
        }
        if (sceneData.elements) {
          this.scene.replaceAllElements(nextElements);
        }
        if (sceneData.collaborators) {
          this.setState({ collaborators: sceneData.collaborators });
        }
      }
    ));
    __publicField(this, "triggerRender", (force) => {
      if (force === true) {
        this.scene.triggerUpdate();
      } else {
        this.setState({});
      }
    });
    /**
     * @returns whether the menu was toggled on or off
     */
    __publicField(this, "toggleSidebar", ({
      name,
      tab,
      force
    }) => {
      let nextName;
      if (force === void 0) {
        nextName = this.state.openSidebar?.name === name && this.state.openSidebar?.tab === tab ? null : name;
      } else {
        nextName = force ? name : null;
      }
      const nextState = nextName ? { name: nextName } : null;
      if (nextState && tab) {
        nextState.tab = tab;
      }
      this.setState({ openSidebar: nextState });
      return !!nextName;
    });
    __publicField(this, "updateCurrentCursorPosition", withBatchedUpdates(
      (event) => {
        this.lastViewportPosition.x = event.clientX;
        this.lastViewportPosition.y = event.clientY;
      }
    ));
    __publicField(this, "getEditorUIOffsets", () => {
      const toolbarBottom = this.excalidrawContainerRef?.current?.querySelector(".App-toolbar")?.getBoundingClientRect()?.bottom ?? 0;
      const sidebarRect = this.excalidrawContainerRef?.current?.querySelector(".sidebar")?.getBoundingClientRect();
      const propertiesPanelRect = this.excalidrawContainerRef?.current?.querySelector(".App-menu__left")?.getBoundingClientRect();
      const PADDING = 16;
      return getLanguage().rtl ? {
        top: toolbarBottom + PADDING,
        right: Math.max(
          this.state.width - (propertiesPanelRect?.left ?? this.state.width),
          0
        ) + PADDING,
        bottom: PADDING,
        left: Math.max(sidebarRect?.right ?? 0, 0) + PADDING
      } : {
        top: toolbarBottom + PADDING,
        right: Math.max(
          this.state.width - (sidebarRect?.left ?? this.state.width) + PADDING,
          0
        ),
        bottom: PADDING,
        left: Math.max(propertiesPanelRect?.right ?? 0, 0) + PADDING
      };
    });
    // Input handling
    __publicField(this, "onKeyDown", withBatchedUpdates(
      (event) => {
        if ("Proxy" in window && (!event.shiftKey && /^[A-Z]$/.test(event.key) || event.shiftKey && /^[a-z]$/.test(event.key))) {
          event = new Proxy(event, {
            get(ev, prop) {
              const value = ev[prop];
              if (typeof value === "function") {
                return value.bind(ev);
              }
              return prop === "key" ? (
                // CapsLock inverts capitalization based on ShiftKey, so invert
                // it back
                event.shiftKey ? ev.key.toUpperCase() : ev.key.toLowerCase()
              ) : value;
            }
          });
        }
        if (!isInputLike(event.target)) {
          if ((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) && this.state.croppingElementId) {
            this.finishImageCropping();
            return;
          }
          const selectedElements = getSelectedElements(
            this.scene.getNonDeletedElementsMap(),
            this.state
          );
          if (selectedElements.length === 1 && isImageElement(selectedElements[0]) && event.key === KEYS.ENTER) {
            this.startImageCropping(selectedElements[0]);
            return;
          }
          if (event.key === KEYS.ESCAPE && this.flowChartCreator.isCreatingChart) {
            this.flowChartCreator.clear();
            this.triggerRender(true);
            return;
          }
          const arrowKeyPressed = isArrowKey(event.key);
          if (event[KEYS.CTRL_OR_CMD] && arrowKeyPressed && !event.shiftKey) {
            event.preventDefault();
            const selectedElements2 = getSelectedElements(
              this.scene.getNonDeletedElementsMap(),
              this.state
            );
            if (selectedElements2.length === 1 && isFlowchartNodeElement(selectedElements2[0])) {
              this.flowChartCreator.createNodes(
                selectedElements2[0],
                this.scene.getNonDeletedElementsMap(),
                this.state,
                getLinkDirectionFromKey(event.key)
              );
            }
            if (this.flowChartCreator.pendingNodes?.length && !isElementCompletelyInViewport(
              this.flowChartCreator.pendingNodes,
              this.canvas.width / window.devicePixelRatio,
              this.canvas.height / window.devicePixelRatio,
              {
                offsetLeft: this.state.offsetLeft,
                offsetTop: this.state.offsetTop,
                scrollX: this.state.scrollX,
                scrollY: this.state.scrollY,
                zoom: this.state.zoom
              },
              this.scene.getNonDeletedElementsMap(),
              this.getEditorUIOffsets()
            )) {
              this.scrollToContent(this.flowChartCreator.pendingNodes, {
                animate: true,
                duration: 300,
                fitToContent: true,
                canvasOffsets: this.getEditorUIOffsets()
              });
            }
            return;
          }
          if (event.altKey) {
            const selectedElements2 = getSelectedElements(
              this.scene.getNonDeletedElementsMap(),
              this.state
            );
            if (selectedElements2.length === 1 && arrowKeyPressed) {
              event.preventDefault();
              const nextId = this.flowChartNavigator.exploreByDirection(
                selectedElements2[0],
                this.scene.getNonDeletedElementsMap(),
                getLinkDirectionFromKey(event.key)
              );
              if (nextId) {
                this.setState((prevState) => ({
                  selectedElementIds: makeNextSelectedElementIds(
                    {
                      [nextId]: true
                    },
                    prevState
                  )
                }));
                const nextNode = this.scene.getNonDeletedElementsMap().get(nextId);
                if (nextNode && !isElementCompletelyInViewport(
                  [nextNode],
                  this.canvas.width / window.devicePixelRatio,
                  this.canvas.height / window.devicePixelRatio,
                  {
                    offsetLeft: this.state.offsetLeft,
                    offsetTop: this.state.offsetTop,
                    scrollX: this.state.scrollX,
                    scrollY: this.state.scrollY,
                    zoom: this.state.zoom
                  },
                  this.scene.getNonDeletedElementsMap(),
                  this.getEditorUIOffsets()
                )) {
                  this.scrollToContent(nextNode, {
                    animate: true,
                    duration: 300,
                    canvasOffsets: this.getEditorUIOffsets()
                  });
                }
              }
              return;
            }
          }
        }
        if (event[KEYS.CTRL_OR_CMD] && event.key === KEYS.P && !event.shiftKey && !event.altKey) {
          this.setToast({
            message: t("commandPalette.shortcutHint", {
              shortcut: getShortcutFromShortcutName("commandPalette")
            })
          });
          event.preventDefault();
          return;
        }
        if (event[KEYS.CTRL_OR_CMD] && event.key.toLowerCase() === KEYS.V) {
          IS_PLAIN_PASTE = event.shiftKey;
          clearTimeout(IS_PLAIN_PASTE_TIMER);
          IS_PLAIN_PASTE_TIMER = window.setTimeout(() => {
            IS_PLAIN_PASTE = false;
          }, 100);
        }
        if (event[KEYS.CTRL_OR_CMD] && isWritableElement(event.target)) {
          if (event.code === CODES.MINUS || event.code === CODES.EQUAL) {
            event.preventDefault();
            return;
          }
        }
        if (
          // inside an input
          isWritableElement(event.target) && // unless pressing escape (finalize action)
          event.key !== KEYS.ESCAPE || // or unless using arrows (to move between buttons)
          isArrowKey(event.key) && isInputLike(event.target)
        ) {
          return;
        }
        if (event.key === KEYS.QUESTION_MARK) {
          this.setState({
            openDialog: { name: "help" }
          });
          return;
        } else if (event.key.toLowerCase() === KEYS.E && event.shiftKey && event[KEYS.CTRL_OR_CMD]) {
          event.preventDefault();
          this.setState({ openDialog: { name: "imageExport" } });
          return;
        }
        if (event.key === KEYS.PAGE_UP || event.key === KEYS.PAGE_DOWN) {
          let offset = (event.shiftKey ? this.state.width : this.state.height) / this.state.zoom.value;
          if (event.key === KEYS.PAGE_DOWN) {
            offset = -offset;
          }
          if (event.shiftKey) {
            this.translateCanvas((state) => ({
              scrollX: state.scrollX + offset
            }));
          } else {
            this.translateCanvas((state) => ({
              scrollY: state.scrollY + offset
            }));
          }
        }
        if (this.state.openDialog?.name === "elementLinkSelector") {
          return;
        }
        if (this.actionManager.handleKeyDown(event)) {
          return;
        }
        if (this.state.viewModeEnabled) {
          return;
        }
        if (event[KEYS.CTRL_OR_CMD] && this.state.isBindingEnabled) {
          this.setState({ isBindingEnabled: false });
        }
        if (isArrowKey(event.key)) {
          let selectedElements = this.scene.getSelectedElements({
            selectedElementIds: this.state.selectedElementIds,
            includeBoundTextElement: true,
            includeElementsInFrames: true
          });
          const elbowArrow = selectedElements.find(isElbowArrow);
          const arrowIdsToRemove = /* @__PURE__ */ new Set();
          selectedElements.filter(isElbowArrow).filter((arrow) => {
            const startElementNotInSelection = arrow.startBinding && !selectedElements.some(
              (el) => el.id === arrow.startBinding?.elementId
            );
            const endElementNotInSelection = arrow.endBinding && !selectedElements.some(
              (el) => el.id === arrow.endBinding?.elementId
            );
            return startElementNotInSelection || endElementNotInSelection;
          }).forEach((arrow) => arrowIdsToRemove.add(arrow.id));
          selectedElements = selectedElements.filter(
            (el) => !arrowIdsToRemove.has(el.id)
          );
          const step = this.getEffectiveGridSize() && (event.shiftKey ? ELEMENT_TRANSLATE_AMOUNT : this.getEffectiveGridSize()) || (event.shiftKey ? ELEMENT_SHIFT_TRANSLATE_AMOUNT : ELEMENT_TRANSLATE_AMOUNT);
          let offsetX = 0;
          let offsetY = 0;
          if (event.key === KEYS.ARROW_LEFT) {
            offsetX = -step;
          } else if (event.key === KEYS.ARROW_RIGHT) {
            offsetX = step;
          } else if (event.key === KEYS.ARROW_UP) {
            offsetY = -step;
          } else if (event.key === KEYS.ARROW_DOWN) {
            offsetY = step;
          }
          selectedElements.forEach((element) => {
            mutateElement(
              element,
              {
                x: element.x + offsetX,
                y: element.y + offsetY
              },
              false
            );
            updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
              simultaneouslyUpdated: selectedElements
            });
          });
          this.setState({
            suggestedBindings: getSuggestedBindingsForArrows(
              selectedElements.filter(
                (element) => element.id !== elbowArrow?.id || step !== 0
              ),
              this.scene.getNonDeletedElementsMap(),
              this.state.zoom
            )
          });
          this.scene.triggerUpdate();
          event.preventDefault();
        } else if (event.key === KEYS.ENTER) {
          const selectedElements = this.scene.getSelectedElements(this.state);
          if (selectedElements.length === 1) {
            const selectedElement = selectedElements[0];
            if (event[KEYS.CTRL_OR_CMD]) {
              if (isLinearElement(selectedElement)) {
                if (!this.state.editingLinearElement || this.state.editingLinearElement.elementId !== selectedElements[0].id) {
                  this.store.shouldCaptureIncrement();
                  if (!isElbowArrow(selectedElement)) {
                    this.setState({
                      editingLinearElement: new LinearElementEditor(
                        selectedElement
                      )
                    });
                  }
                }
              }
            } else if (isTextElement(selectedElement) || isValidTextContainer(selectedElement)) {
              let container;
              if (!isTextElement(selectedElement)) {
                container = selectedElement;
              }
              const midPoint = getContainerCenter(
                selectedElement,
                this.state,
                this.scene.getNonDeletedElementsMap()
              );
              const sceneX = midPoint.x;
              const sceneY = midPoint.y;
              this.startTextEditing({
                sceneX,
                sceneY,
                container
              });
              event.preventDefault();
              return;
            } else if (isFrameLikeElement(selectedElement)) {
              this.setState({
                editingFrame: selectedElement.id
              });
            }
          }
        } else if (!event.ctrlKey && !event.altKey && !event.metaKey && !this.state.newElement && !this.state.selectionElement && !this.state.selectedElementsAreBeingDragged) {
          const shape = findShapeByKey(event.key);
          if (shape) {
            if (this.state.activeTool.type !== shape) {
              trackEvent(
                "toolbar",
                shape,
                `keyboard (${this.device.editor.isMobile ? "mobile" : "desktop"})`
              );
            }
            if (shape === "arrow" && this.state.activeTool.type === "arrow") {
              this.setState((prevState) => ({
                currentItemArrowType: prevState.currentItemArrowType === ARROW_TYPE.sharp ? ARROW_TYPE.round : prevState.currentItemArrowType === ARROW_TYPE.round ? ARROW_TYPE.elbow : ARROW_TYPE.sharp
              }));
            }
            this.setActiveTool({ type: shape });
            event.stopPropagation();
          } else if (event.key === KEYS.Q) {
            this.toggleLock("keyboard");
            event.stopPropagation();
          }
        }
        if (event.key === KEYS.SPACE && gesture.pointers.size === 0) {
          isHoldingSpace = true;
          setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
          event.preventDefault();
        }
        if ((event.key === KEYS.G || event.key === KEYS.S) && !event.altKey && !event[KEYS.CTRL_OR_CMD]) {
          const selectedElements = this.scene.getSelectedElements(this.state);
          if (this.state.activeTool.type === "selection" && !selectedElements.length) {
            return;
          }
          if (event.key === KEYS.G && (hasBackground(this.state.activeTool.type) || selectedElements.some((element) => hasBackground(element.type)))) {
            this.setState({ openPopup: "elementBackground" });
            event.stopPropagation();
          }
          if (event.key === KEYS.S) {
            this.setState({ openPopup: "elementStroke" });
            event.stopPropagation();
          }
        }
        if (!event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key.toLowerCase() === KEYS.F) {
          const selectedElements = this.scene.getSelectedElements(this.state);
          if (this.state.activeTool.type === "selection" && !selectedElements.length) {
            return;
          }
          if (this.state.activeTool.type === "text" || selectedElements.find(
            (element) => isTextElement(element) || getBoundTextElement(
              element,
              this.scene.getNonDeletedElementsMap()
            )
          )) {
            event.preventDefault();
            this.setState({ openPopup: "fontFamily" });
          }
        }
        if (event.key === KEYS.K && !event.altKey && !event[KEYS.CTRL_OR_CMD]) {
          if (this.state.activeTool.type === "laser") {
            this.setActiveTool({ type: "selection" });
          } else {
            this.setActiveTool({ type: "laser" });
          }
          return;
        }
        if (event[KEYS.CTRL_OR_CMD] && (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE)) {
          editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
        }
        const lowerCased = event.key.toLocaleLowerCase();
        const isPickingStroke = lowerCased === KEYS.S && event.shiftKey;
        const isPickingBackground = event.key === KEYS.I || lowerCased === KEYS.G && event.shiftKey;
        if (isPickingStroke || isPickingBackground) {
          this.openEyeDropper({
            type: isPickingStroke ? "stroke" : "background"
          });
        }
      }
    ));
    __publicField(this, "onKeyUp", withBatchedUpdates((event) => {
      if (event.key === KEYS.SPACE) {
        if (this.state.viewModeEnabled || this.state.openDialog?.name === "elementLinkSelector") {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
        } else if (this.state.activeTool.type === "selection") {
          resetCursor(this.interactiveCanvas);
        } else {
          setCursorForShape(this.interactiveCanvas, this.state);
          this.setState({
            selectedElementIds: makeNextSelectedElementIds({}, this.state),
            selectedGroupIds: {},
            editingGroupId: null,
            activeEmbeddable: null
          });
        }
        isHoldingSpace = false;
      }
      if (!event[KEYS.CTRL_OR_CMD] && !this.state.isBindingEnabled) {
        this.setState({ isBindingEnabled: true });
      }
      if (isArrowKey(event.key)) {
        bindOrUnbindLinearElements(
          this.scene.getSelectedElements(this.state).filter(isLinearElement),
          this.scene.getNonDeletedElementsMap(),
          this.scene.getNonDeletedElements(),
          this.scene,
          isBindingEnabled(this.state),
          this.state.selectedLinearElement?.selectedPointsIndices ?? [],
          this.state.zoom
        );
        this.setState({ suggestedBindings: [] });
      }
      if (!event.altKey) {
        if (this.flowChartNavigator.isExploring) {
          this.flowChartNavigator.clear();
          this.syncActionResult({
            captureUpdate: CaptureUpdateAction.IMMEDIATELY
          });
        }
      }
      if (!event[KEYS.CTRL_OR_CMD]) {
        if (this.flowChartCreator.isCreatingChart) {
          if (this.flowChartCreator.pendingNodes?.length) {
            this.scene.insertElements(this.flowChartCreator.pendingNodes);
          }
          const firstNode = this.flowChartCreator.pendingNodes?.[0];
          if (firstNode) {
            this.setState((prevState) => ({
              selectedElementIds: makeNextSelectedElementIds(
                {
                  [firstNode.id]: true
                },
                prevState
              )
            }));
            if (!isElementCompletelyInViewport(
              [firstNode],
              this.canvas.width / window.devicePixelRatio,
              this.canvas.height / window.devicePixelRatio,
              {
                offsetLeft: this.state.offsetLeft,
                offsetTop: this.state.offsetTop,
                scrollX: this.state.scrollX,
                scrollY: this.state.scrollY,
                zoom: this.state.zoom
              },
              this.scene.getNonDeletedElementsMap(),
              this.getEditorUIOffsets()
            )) {
              this.scrollToContent(firstNode, {
                animate: true,
                duration: 300,
                canvasOffsets: this.getEditorUIOffsets()
              });
            }
          }
          this.flowChartCreator.clear();
          this.syncActionResult({
            captureUpdate: CaptureUpdateAction.IMMEDIATELY
          });
        }
      }
    }));
    // We purposely widen the `tool` type so this helper can be called with
    // any tool without having to type check it
    __publicField(this, "isToolSupported", (tool) => {
      return this.props.UIOptions.tools?.[tool] !== false;
    });
    __publicField(this, "setActiveTool", (tool) => {
      if (!this.isToolSupported(tool.type)) {
        console.warn(
          `"${tool.type}" tool is disabled via "UIOptions.canvasActions.tools.${tool.type}"`
        );
        return;
      }
      const nextActiveTool = updateActiveTool(this.state, tool);
      if (nextActiveTool.type === "hand") {
        setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
      } else if (!isHoldingSpace) {
        setCursorForShape(this.interactiveCanvas, {
          ...this.state,
          activeTool: nextActiveTool
        });
      }
      if (isToolIcon(document.activeElement)) {
        this.focusContainer();
      }
      if (!isLinearElementType(nextActiveTool.type)) {
        this.setState({ suggestedBindings: [] });
      }
      if (nextActiveTool.type === "image") {
        this.onImageAction({
          insertOnCanvasDirectly: (tool.type === "image" && tool.insertOnCanvasDirectly) ?? false
        });
      }
      this.setState((prevState) => {
        const commonResets = {
          snapLines: prevState.snapLines.length ? [] : prevState.snapLines,
          originSnapOffset: null,
          activeEmbeddable: null
        };
        if (nextActiveTool.type === "freedraw") {
          this.store.shouldCaptureIncrement();
        }
        if (nextActiveTool.type !== "selection") {
          return {
            ...prevState,
            activeTool: nextActiveTool,
            selectedElementIds: makeNextSelectedElementIds({}, prevState),
            selectedGroupIds: makeNextSelectedElementIds({}, prevState),
            editingGroupId: null,
            multiElement: null,
            ...commonResets
          };
        }
        return {
          ...prevState,
          activeTool: nextActiveTool,
          ...commonResets
        };
      });
    });
    __publicField(this, "setOpenDialog", (dialogType) => {
      this.setState({ openDialog: dialogType });
    });
    __publicField(this, "setCursor", (cursor) => {
      setCursor(this.interactiveCanvas, cursor);
    });
    __publicField(this, "resetCursor", () => {
      resetCursor(this.interactiveCanvas);
    });
    /**
     * returns whether user is making a gesture with >= 2 fingers (points)
     * on o touch screen (not on a trackpad). Currently only relates to Darwin
     * (iOS/iPadOS,MacOS), but may work on other devices in the future if
     * GestureEvent is standardized.
     */
    __publicField(this, "isTouchScreenMultiTouchGesture", () => {
      return gesture.pointers.size >= 2;
    });
    __publicField(this, "getName", () => {
      return this.state.name || this.props.name || `${t("labels.untitled")}-${getDateTime()}`;
    });
    // fires only on Safari
    __publicField(this, "onGestureStart", withBatchedUpdates((event) => {
      event.preventDefault();
      if (this.isTouchScreenMultiTouchGesture()) {
        this.setState({
          selectedElementIds: makeNextSelectedElementIds({}, this.state),
          activeEmbeddable: null
        });
      }
      gesture.initialScale = this.state.zoom.value;
    }));
    // fires only on Safari
    __publicField(this, "onGestureChange", withBatchedUpdates((event) => {
      event.preventDefault();
      if (this.isTouchScreenMultiTouchGesture()) {
        return;
      }
      const initialScale = gesture.initialScale;
      if (initialScale) {
        this.setState((state) => ({
          ...getStateForZoom(
            {
              viewportX: this.lastViewportPosition.x,
              viewportY: this.lastViewportPosition.y,
              nextZoom: getNormalizedZoom(initialScale * event.scale)
            },
            state
          )
        }));
      }
    }));
    // fires only on Safari
    __publicField(this, "onGestureEnd", withBatchedUpdates((event) => {
      event.preventDefault();
      if (this.isTouchScreenMultiTouchGesture()) {
        this.setState({
          previousSelectedElementIds: {},
          selectedElementIds: makeNextSelectedElementIds(
            this.state.previousSelectedElementIds,
            this.state
          )
        });
      }
      gesture.initialScale = null;
    }));
    __publicField(this, "startTextEditing", ({
      sceneX,
      sceneY,
      insertAtParentCenter = true,
      container,
      autoEdit = true
    }) => {
      let shouldBindToContainer = false;
      let parentCenterPosition = insertAtParentCenter && this.getTextWysiwygSnappedToCenterPosition(
        sceneX,
        sceneY,
        this.state,
        container
      );
      if (container && parentCenterPosition) {
        const boundTextElementToContainer = getBoundTextElement(
          container,
          this.scene.getNonDeletedElementsMap()
        );
        if (!boundTextElementToContainer) {
          shouldBindToContainer = true;
        }
      }
      let existingTextElement = null;
      const selectedElements = this.scene.getSelectedElements(this.state);
      if (selectedElements.length === 1) {
        if (isTextElement(selectedElements[0])) {
          existingTextElement = selectedElements[0];
        } else if (container) {
          existingTextElement = getBoundTextElement(
            selectedElements[0],
            this.scene.getNonDeletedElementsMap()
          );
        } else {
          existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
        }
      } else {
        existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
      }
      const fontFamily = existingTextElement?.fontFamily || this.state.currentItemFontFamily;
      const lineHeight = existingTextElement?.lineHeight || getLineHeight(fontFamily);
      const fontSize = this.state.currentItemFontSize;
      if (!existingTextElement && shouldBindToContainer && container && !isArrowElement(container)) {
        const fontString = {
          fontSize,
          fontFamily
        };
        const minWidth = getApproxMinLineWidth(
          getFontString(fontString),
          lineHeight
        );
        const minHeight = getApproxMinLineHeight(fontSize, lineHeight);
        const newHeight = Math.max(container.height, minHeight);
        const newWidth = Math.max(container.width, minWidth);
        mutateElement(container, { height: newHeight, width: newWidth });
        sceneX = container.x + newWidth / 2;
        sceneY = container.y + newHeight / 2;
        if (parentCenterPosition) {
          parentCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
            sceneX,
            sceneY,
            this.state,
            container
          );
        }
      }
      const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
        x: sceneX,
        y: sceneY
      });
      const element = existingTextElement ? existingTextElement : newTextElement({
        x: parentCenterPosition ? parentCenterPosition.elementCenterX : sceneX,
        y: parentCenterPosition ? parentCenterPosition.elementCenterY : sceneY,
        strokeColor: this.state.currentItemStrokeColor,
        backgroundColor: this.state.currentItemBackgroundColor,
        fillStyle: this.state.currentItemFillStyle,
        strokeWidth: this.state.currentItemStrokeWidth,
        strokeStyle: this.state.currentItemStrokeStyle,
        roughness: this.state.currentItemRoughness,
        opacity: this.state.currentItemOpacity,
        text: "",
        fontSize,
        fontFamily,
        textAlign: parentCenterPosition ? "center" : this.state.currentItemTextAlign,
        verticalAlign: parentCenterPosition ? VERTICAL_ALIGN.MIDDLE : DEFAULT_VERTICAL_ALIGN,
        containerId: shouldBindToContainer ? container?.id : void 0,
        groupIds: container?.groupIds ?? [],
        lineHeight,
        angle: container?.angle ?? 0,
        frameId: topLayerFrame ? topLayerFrame.id : null
      });
      if (!existingTextElement && shouldBindToContainer && container) {
        mutateElement(container, {
          boundElements: (container.boundElements || []).concat({
            type: "text",
            id: element.id
          })
        });
      }
      this.setState({ editingTextElement: element });
      if (!existingTextElement) {
        if (container && shouldBindToContainer) {
          const containerIndex = this.scene.getElementIndex(container.id);
          this.scene.insertElementAtIndex(element, containerIndex + 1);
        } else {
          this.scene.insertElement(element);
        }
      }
      if (autoEdit || existingTextElement || container) {
        this.handleTextWysiwyg(element, {
          isExistingElement: !!existingTextElement
        });
      } else {
        this.setState({
          newElement: element,
          multiElement: null
        });
      }
    });
    __publicField(this, "startImageCropping", (image) => {
      this.store.shouldCaptureIncrement();
      this.setState({
        croppingElementId: image.id
      });
    });
    __publicField(this, "finishImageCropping", () => {
      if (this.state.croppingElementId) {
        this.store.shouldCaptureIncrement();
        this.setState({
          croppingElementId: null
        });
      }
    });
    __publicField(this, "handleCanvasDoubleClick", (event) => {
      if (this.state.multiElement) {
        return;
      }
      if (this.state.activeTool.type !== "selection") {
        return;
      }
      const selectedElements = this.scene.getSelectedElements(this.state);
      let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
        event,
        this.state
      );
      if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) {
        if (event[KEYS.CTRL_OR_CMD] && (!this.state.editingLinearElement || this.state.editingLinearElement.elementId !== selectedElements[0].id) && !isElbowArrow(selectedElements[0])) {
          this.store.shouldCaptureIncrement();
          this.setState({
            editingLinearElement: new LinearElementEditor(selectedElements[0])
          });
          return;
        } else if (this.state.selectedLinearElement && isElbowArrow(selectedElements[0])) {
          const hitCoords = LinearElementEditor.getSegmentMidpointHitCoords(
            this.state.selectedLinearElement,
            { x: sceneX, y: sceneY },
            this.state,
            this.scene.getNonDeletedElementsMap()
          );
          const midPoint = hitCoords ? LinearElementEditor.getSegmentMidPointIndex(
            this.state.selectedLinearElement,
            this.state,
            hitCoords,
            this.scene.getNonDeletedElementsMap()
          ) : -1;
          if (midPoint && midPoint > -1) {
            this.store.shouldCaptureIncrement();
            LinearElementEditor.deleteFixedSegment(selectedElements[0], midPoint);
            const nextCoords = LinearElementEditor.getSegmentMidpointHitCoords(
              {
                ...this.state.selectedLinearElement,
                segmentMidPointHoveredCoords: null
              },
              { x: sceneX, y: sceneY },
              this.state,
              this.scene.getNonDeletedElementsMap()
            );
            const nextIndex = nextCoords ? LinearElementEditor.getSegmentMidPointIndex(
              this.state.selectedLinearElement,
              this.state,
              nextCoords,
              this.scene.getNonDeletedElementsMap()
            ) : null;
            this.setState({
              selectedLinearElement: {
                ...this.state.selectedLinearElement,
                pointerDownState: {
                  ...this.state.selectedLinearElement.pointerDownState,
                  segmentMidpoint: {
                    index: nextIndex,
                    value: hitCoords,
                    added: false
                  }
                },
                segmentMidPointHoveredCoords: nextCoords
              }
            });
            return;
          }
        }
      }
      if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
        this.startImageCropping(selectedElements[0]);
        return;
      }
      resetCursor(this.interactiveCanvas);
      const selectedGroupIds = getSelectedGroupIds(this.state);
      if (selectedGroupIds.length > 0) {
        const hitElement = this.getElementAtPosition(sceneX, sceneY);
        const selectedGroupId = hitElement && getSelectedGroupIdForElement(hitElement, this.state.selectedGroupIds);
        if (selectedGroupId) {
          this.store.shouldCaptureIncrement();
          this.setState((prevState) => ({
            ...prevState,
            ...selectGroupsForSelectedElements(
              {
                editingGroupId: selectedGroupId,
                selectedElementIds: { [hitElement.id]: true }
              },
              this.scene.getNonDeletedElements(),
              prevState,
              this
            )
          }));
          return;
        }
      }
      resetCursor(this.interactiveCanvas);
      if (!event[KEYS.CTRL_OR_CMD] && !this.state.viewModeEnabled) {
        const hitElement = this.getElementAtPosition(sceneX, sceneY);
        if (isIframeLikeElement(hitElement)) {
          this.setState({
            activeEmbeddable: { element: hitElement, state: "active" }
          });
          return;
        }
        const container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
        if (container) {
          if (hasBoundTextElement(container) || !isTransparent(container.backgroundColor) || hitElementItself({
            x: sceneX,
            y: sceneY,
            element: container,
            shape: getElementShape(
              container,
              this.scene.getNonDeletedElementsMap()
            ),
            threshold: this.getElementHitThreshold()
          })) {
            const midPoint = getContainerCenter(
              container,
              this.state,
              this.scene.getNonDeletedElementsMap()
            );
            sceneX = midPoint.x;
            sceneY = midPoint.y;
          }
        }
        this.startTextEditing({
          sceneX,
          sceneY,
          insertAtParentCenter: !event.altKey,
          container
        });
      }
    });
    __publicField(this, "getElementLinkAtPosition", (scenePointer, hitElement) => {
      const elements = this.scene.getNonDeletedElements();
      let hitElementIndex = -1;
      for (let index = elements.length - 1; index >= 0; index--) {
        const element = elements[index];
        if (hitElement && element.id === hitElement.id) {
          hitElementIndex = index;
        }
        if (element.link && index >= hitElementIndex && isPointHittingLink(
          element,
          this.scene.getNonDeletedElementsMap(),
          this.state,
          pointFrom(scenePointer.x, scenePointer.y),
          this.device.editor.isMobile
        )) {
          return element;
        }
      }
    });
    __publicField(this, "redirectToLink", (event, isTouchScreen) => {
      const draggedDistance = pointDistance(
        pointFrom(
          this.lastPointerDownEvent.clientX,
          this.lastPointerDownEvent.clientY
        ),
        pointFrom(
          this.lastPointerUpEvent.clientX,
          this.lastPointerUpEvent.clientY
        )
      );
      if (!this.hitLinkElement || draggedDistance > DRAGGING_THRESHOLD) {
        return;
      }
      const lastPointerDownCoords = viewportCoordsToSceneCoords(
        this.lastPointerDownEvent,
        this.state
      );
      const elementsMap = this.scene.getNonDeletedElementsMap();
      const lastPointerDownHittingLinkIcon = isPointHittingLink(
        this.hitLinkElement,
        elementsMap,
        this.state,
        pointFrom(lastPointerDownCoords.x, lastPointerDownCoords.y),
        this.device.editor.isMobile
      );
      const lastPointerUpCoords = viewportCoordsToSceneCoords(
        this.lastPointerUpEvent,
        this.state
      );
      const lastPointerUpHittingLinkIcon = isPointHittingLink(
        this.hitLinkElement,
        elementsMap,
        this.state,
        pointFrom(lastPointerUpCoords.x, lastPointerUpCoords.y),
        this.device.editor.isMobile
      );
      if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
        hideHyperlinkToolip();
        let url = this.hitLinkElement.link;
        if (url) {
          url = normalizeLink(url);
          let customEvent;
          if (this.props.onLinkOpen) {
            customEvent = wrapEvent("excalidraw-link" /* EXCALIDRAW_LINK */, event.nativeEvent);
            this.props.onLinkOpen(
              {
                ...this.hitLinkElement,
                link: url
              },
              customEvent
            );
          }
          if (!customEvent?.defaultPrevented) {
            const target = isLocalLink(url) ? "_self" : "_blank";
            const newWindow = window.open(void 0, target);
            if (newWindow) {
              newWindow.opener = null;
              newWindow.location = url;
            }
          }
        }
      }
    });
    __publicField(this, "getTopLayerFrameAtSceneCoords", (sceneCoords) => {
      const elementsMap = this.scene.getNonDeletedElementsMap();
      const frames = this.scene.getNonDeletedFramesLikes().filter(
        (frame) => isCursorInFrame(sceneCoords, frame, elementsMap)
      );
      return frames.length ? frames[frames.length - 1] : null;
    });
    __publicField(this, "handleCanvasPointerMove", (event) => {
      this.savePointer(event.clientX, event.clientY, this.state.cursorButton);
      this.lastPointerMoveEvent = event.nativeEvent;
      if (gesture.pointers.has(event.pointerId)) {
        gesture.pointers.set(event.pointerId, {
          x: event.clientX,
          y: event.clientY
        });
      }
      const initialScale = gesture.initialScale;
      if (gesture.pointers.size === 2 && gesture.lastCenter && initialScale && gesture.initialDistance) {
        const center = getCenter(gesture.pointers);
        const deltaX = center.x - gesture.lastCenter.x;
        const deltaY = center.y - gesture.lastCenter.y;
        gesture.lastCenter = center;
        const distance2 = getDistance(Array.from(gesture.pointers.values()));
        const scaleFactor = this.state.activeTool.type === "freedraw" && this.state.penMode ? 1 : distance2 / gesture.initialDistance;
        const nextZoom = scaleFactor ? getNormalizedZoom(initialScale * scaleFactor) : this.state.zoom.value;
        this.setState((state) => {
          const zoomState = getStateForZoom(
            {
              viewportX: center.x,
              viewportY: center.y,
              nextZoom
            },
            state
          );
          this.translateCanvas({
            zoom: zoomState.zoom,
            // 2x multiplier is just a magic number that makes this work correctly
            // on touchscreen devices (note: if we get report that panning is slower/faster
            // than actual movement, consider swapping with devicePixelRatio)
            scrollX: zoomState.scrollX + 2 * (deltaX / nextZoom),
            scrollY: zoomState.scrollY + 2 * (deltaY / nextZoom),
            shouldCacheIgnoreZoom: true
          });
        });
        this.resetShouldCacheIgnoreZoomDebounced();
      } else {
        gesture.lastCenter = gesture.initialDistance = gesture.initialScale = null;
      }
      if (isHoldingSpace || isPanning || isDraggingScrollBar || isHandToolActive(this.state)) {
        return;
      }
      const isPointerOverScrollBars = isOverScrollBars(
        currentScrollBars,
        event.clientX - this.state.offsetLeft,
        event.clientY - this.state.offsetTop
      );
      const isOverScrollBar = isPointerOverScrollBars.isOverEither;
      if (!this.state.newElement && !this.state.selectionElement && !this.state.selectedElementsAreBeingDragged && !this.state.multiElement) {
        if (isOverScrollBar) {
          resetCursor(this.interactiveCanvas);
        } else {
          setCursorForShape(this.interactiveCanvas, this.state);
        }
      }
      const scenePointer = viewportCoordsToSceneCoords(event, this.state);
      const { x: scenePointerX, y: scenePointerY } = scenePointer;
      if (!this.state.newElement && isActiveToolNonLinearSnappable(this.state.activeTool.type)) {
        const { originOffset, snapLines } = getSnapLinesAtPointer(
          this.scene.getNonDeletedElements(),
          this,
          {
            x: scenePointerX,
            y: scenePointerY
          },
          event,
          this.scene.getNonDeletedElementsMap()
        );
        this.setState((prevState) => {
          const nextSnapLines = updateStable(prevState.snapLines, snapLines);
          const nextOriginOffset = prevState.originSnapOffset ? updateStable(prevState.originSnapOffset, originOffset) : originOffset;
          if (prevState.snapLines === nextSnapLines && prevState.originSnapOffset === nextOriginOffset) {
            return null;
          }
          return {
            snapLines: nextSnapLines,
            originSnapOffset: nextOriginOffset
          };
        });
      } else if (!this.state.newElement && !this.state.selectedElementsAreBeingDragged && !this.state.selectionElement) {
        this.setState((prevState) => {
          if (prevState.snapLines.length) {
            return {
              snapLines: []
            };
          }
          return null;
        });
      }
      if (this.state.editingLinearElement && !this.state.editingLinearElement.isDragging) {
        const editingLinearElement = LinearElementEditor.handlePointerMove(
          event,
          scenePointerX,
          scenePointerY,
          this,
          this.scene.getNonDeletedElementsMap()
        );
        if (editingLinearElement && editingLinearElement !== this.state.editingLinearElement) {
          flushSync2(() => {
            this.setState({
              editingLinearElement
            });
          });
        }
        if (editingLinearElement?.lastUncommittedPoint != null) {
          this.maybeSuggestBindingAtCursor(
            scenePointer,
            editingLinearElement.elbowed
          );
        } else {
          flushSync2(() => {
            this.setState({ suggestedBindings: [] });
          });
        }
      }
      if (isBindingElementType(this.state.activeTool.type)) {
        const { newElement: newElement2 } = this.state;
        if (isBindingElement(newElement2, false)) {
          this.maybeSuggestBindingsForLinearElementAtCoords(
            newElement2,
            [scenePointer],
            this.state.startBoundElement
          );
        } else {
          this.maybeSuggestBindingAtCursor(scenePointer, false);
        }
      }
      if (this.state.multiElement) {
        const { multiElement } = this.state;
        const { x: rx, y: ry } = multiElement;
        const { points, lastCommittedPoint } = multiElement;
        const lastPoint = points[points.length - 1];
        setCursorForShape(this.interactiveCanvas, this.state);
        if (lastPoint === lastCommittedPoint) {
          if (pointDistance(
            pointFrom(scenePointerX - rx, scenePointerY - ry),
            lastPoint
          ) >= LINE_CONFIRM_THRESHOLD) {
            mutateElement(
              multiElement,
              {
                points: [
                  ...points,
                  pointFrom(scenePointerX - rx, scenePointerY - ry)
                ]
              },
              false
            );
          } else {
            setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
          }
        } else if (points.length > 2 && lastCommittedPoint && pointDistance(
          pointFrom(scenePointerX - rx, scenePointerY - ry),
          lastCommittedPoint
        ) < LINE_CONFIRM_THRESHOLD) {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
          mutateElement(
            multiElement,
            {
              points: points.slice(0, -1)
            },
            false
          );
        } else {
          const [gridX, gridY] = getGridPoint(
            scenePointerX,
            scenePointerY,
            event[KEYS.CTRL_OR_CMD] || isElbowArrow(multiElement) ? null : this.getEffectiveGridSize()
          );
          const [lastCommittedX, lastCommittedY] = multiElement?.lastCommittedPoint ?? [0, 0];
          let dxFromLastCommitted = gridX - rx - lastCommittedX;
          let dyFromLastCommitted = gridY - ry - lastCommittedY;
          if (shouldRotateWithDiscreteAngle(event)) {
            ({ width: dxFromLastCommitted, height: dyFromLastCommitted } = getLockedLinearCursorAlignSize(
              // actual coordinate of the last committed point
              lastCommittedX + rx,
              lastCommittedY + ry,
              // cursor-grid coordinate
              gridX,
              gridY
            ));
          }
          if (isPathALoop(points, this.state.zoom.value)) {
            setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
          }
          mutateElement(
            multiElement,
            {
              points: [
                ...points.slice(0, -1),
                pointFrom(
                  lastCommittedX + dxFromLastCommitted,
                  lastCommittedY + dyFromLastCommitted
                )
              ]
            },
            false,
            {
              isDragging: true
            }
          );
          this.triggerRender(false);
        }
        return;
      }
      const hasDeselectedButton = Boolean(event.buttons);
      if (hasDeselectedButton || this.state.activeTool.type !== "selection" && this.state.activeTool.type !== "text" && this.state.activeTool.type !== "eraser") {
        return;
      }
      const elements = this.scene.getNonDeletedElements();
      const selectedElements = this.scene.getSelectedElements(this.state);
      if (selectedElements.length === 1 && !isOverScrollBar && !this.state.editingLinearElement) {
        if (this.state.selectedLinearElement) {
          this.handleHoverSelectedLinearElement(
            this.state.selectedLinearElement,
            scenePointerX,
            scenePointerY
          );
        }
        if ((!this.state.selectedLinearElement || this.state.selectedLinearElement.hoverPointIndex === -1) && this.state.openDialog?.name !== "elementLinkSelector" && !(selectedElements.length === 1 && isElbowArrow(selectedElements[0]))) {
          const elementWithTransformHandleType = getElementWithTransformHandleType(
            elements,
            this.state,
            scenePointerX,
            scenePointerY,
            this.state.zoom,
            event.pointerType,
            this.scene.getNonDeletedElementsMap(),
            this.device
          );
          if (elementWithTransformHandleType && elementWithTransformHandleType.transformHandleType) {
            setCursor(
              this.interactiveCanvas,
              getCursorForResizingElement(elementWithTransformHandleType)
            );
            return;
          }
        }
      } else if (selectedElements.length > 1 && !isOverScrollBar && this.state.openDialog?.name !== "elementLinkSelector") {
        const transformHandleType = getTransformHandleTypeFromCoords(
          getCommonBounds(selectedElements),
          scenePointerX,
          scenePointerY,
          this.state.zoom,
          event.pointerType,
          this.device
        );
        if (transformHandleType) {
          setCursor(
            this.interactiveCanvas,
            getCursorForResizingElement({
              transformHandleType
            })
          );
          return;
        }
      }
      const hitElement = this.getElementAtPosition(
        scenePointer.x,
        scenePointer.y
      );
      this.hitLinkElement = this.getElementLinkAtPosition(
        scenePointer,
        hitElement
      );
      if (isEraserActive(this.state)) {
        return;
      }
      if (this.hitLinkElement && !this.state.selectedElementIds[this.hitLinkElement.id]) {
        setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
        showHyperlinkTooltip(
          this.hitLinkElement,
          this.state,
          this.scene.getNonDeletedElementsMap()
        );
      } else {
        hideHyperlinkToolip();
        if (hitElement && (hitElement.link || isEmbeddableElement(hitElement)) && this.state.selectedElementIds[hitElement.id] && !this.state.contextMenu && !this.state.showHyperlinkPopup) {
          this.setState({ showHyperlinkPopup: "info" });
        } else if (this.state.activeTool.type === "text") {
          setCursor(
            this.interactiveCanvas,
            isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR
          );
        } else if (this.state.viewModeEnabled) {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
        } else if (this.state.openDialog?.name === "elementLinkSelector") {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
        } else if (isOverScrollBar) {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
        } else if (this.state.selectedLinearElement) {
          this.handleHoverSelectedLinearElement(
            this.state.selectedLinearElement,
            scenePointerX,
            scenePointerY
          );
        } else if (
          // if using cmd/ctrl, we're not dragging
          !event[KEYS.CTRL_OR_CMD]
        ) {
          if ((hitElement || this.isHittingCommonBoundingBoxOfSelectedElements(
            scenePointer,
            selectedElements
          )) && !hitElement?.locked) {
            if (hitElement && isIframeLikeElement(hitElement) && this.isIframeLikeElementCenter(
              hitElement,
              event,
              scenePointerX,
              scenePointerY
            )) {
              setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
              this.setState({
                activeEmbeddable: { element: hitElement, state: "hover" }
              });
            } else if (!hitElement || !isElbowArrow(hitElement)) {
              setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
              if (this.state.activeEmbeddable?.state === "hover") {
                this.setState({ activeEmbeddable: null });
              }
            }
          }
        } else {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
        }
      }
      if (this.state.openDialog?.name === "elementLinkSelector" && hitElement) {
        this.setState((prevState) => {
          return {
            hoveredElementIds: updateStable(
              prevState.hoveredElementIds,
              selectGroupsForSelectedElements(
                {
                  editingGroupId: prevState.editingGroupId,
                  selectedElementIds: { [hitElement.id]: true }
                },
                this.scene.getNonDeletedElements(),
                prevState,
                this
              ).selectedElementIds
            )
          };
        });
      } else if (this.state.openDialog?.name === "elementLinkSelector" && !hitElement) {
        this.setState((prevState) => ({
          hoveredElementIds: updateStable(prevState.hoveredElementIds, {})
        }));
      }
    });
    __publicField(this, "handleEraser", (event, pointerDownState, scenePointer) => {
      this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
      let didChange = false;
      const processedGroups = /* @__PURE__ */ new Set();
      const nonDeletedElements = this.scene.getNonDeletedElements();
      const processElements = (elements) => {
        for (const element of elements) {
          if (element.locked) {
            return;
          }
          if (event.altKey) {
            if (this.elementsPendingErasure.delete(element.id)) {
              didChange = true;
            }
          } else if (!this.elementsPendingErasure.has(element.id)) {
            didChange = true;
            this.elementsPendingErasure.add(element.id);
          }
          if (didChange && element.groupIds?.length) {
            const shallowestGroupId = element.groupIds.at(-1);
            if (!processedGroups.has(shallowestGroupId)) {
              processedGroups.add(shallowestGroupId);
              const elems = getElementsInGroup(
                nonDeletedElements,
                shallowestGroupId
              );
              for (const elem of elems) {
                if (event.altKey) {
                  this.elementsPendingErasure.delete(elem.id);
                } else {
                  this.elementsPendingErasure.add(elem.id);
                }
              }
            }
          }
        }
      };
      const distance2 = pointDistance(
        pointFrom(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y),
        pointFrom(scenePointer.x, scenePointer.y)
      );
      const threshold = this.getElementHitThreshold();
      const p = { ...pointerDownState.lastCoords };
      let samplingInterval = 0;
      while (samplingInterval <= distance2) {
        const hitElements = this.getElementsAtPosition(p.x, p.y);
        processElements(hitElements);
        if (samplingInterval === distance2) {
          break;
        }
        samplingInterval = Math.min(samplingInterval + threshold, distance2);
        const distanceRatio = samplingInterval / distance2;
        const nextX = (1 - distanceRatio) * p.x + distanceRatio * scenePointer.x;
        const nextY = (1 - distanceRatio) * p.y + distanceRatio * scenePointer.y;
        p.x = nextX;
        p.y = nextY;
      }
      pointerDownState.lastCoords.x = scenePointer.x;
      pointerDownState.lastCoords.y = scenePointer.y;
      if (didChange) {
        for (const element of this.scene.getNonDeletedElements()) {
          if (isBoundToContainer(element) && (this.elementsPendingErasure.has(element.id) || this.elementsPendingErasure.has(element.containerId))) {
            if (event.altKey) {
              this.elementsPendingErasure.delete(element.id);
              this.elementsPendingErasure.delete(element.containerId);
            } else {
              this.elementsPendingErasure.add(element.id);
              this.elementsPendingErasure.add(element.containerId);
            }
          }
        }
        this.elementsPendingErasure = new Set(this.elementsPendingErasure);
        this.triggerRender();
      }
    });
    // set touch moving for mobile context menu
    __publicField(this, "handleTouchMove", (event) => {
      invalidateContextMenu = true;
    });
    __publicField(this, "handleCanvasPointerDown", (event) => {
      const target = event.target;
      if (target.setPointerCapture) {
        target.setPointerCapture(event.pointerId);
      }
      this.maybeCleanupAfterMissingPointerUp(event.nativeEvent);
      this.maybeUnfollowRemoteUser();
      if (this.state.searchMatches) {
        this.setState((state) => ({
          searchMatches: state.searchMatches.map((searchMatch) => ({
            ...searchMatch,
            focus: false
          }))
        }));
        editorJotaiStore.set(searchItemInFocusAtom, null);
      }
      if (this.state.contextMenu) {
        this.setState({ contextMenu: null });
      }
      if (this.state.snapLines) {
        this.setAppState({ snapLines: [] });
      }
      this.updateGestureOnPointerDown(event);
      if (event.pointerType === "touch" && this.state.newElement && this.state.newElement.type === "freedraw") {
        const element = this.state.newElement;
        this.updateScene({
          ...element.points.length < 10 ? {
            elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !== element.id)
          } : {},
          appState: {
            newElement: null,
            editingTextElement: null,
            startBoundElement: null,
            suggestedBindings: [],
            selectedElementIds: makeNextSelectedElementIds(
              Object.keys(this.state.selectedElementIds).filter((key) => key !== element.id).reduce((obj, key) => {
                obj[key] = this.state.selectedElementIds[key];
                return obj;
              }, {}),
              this.state
            )
          },
          captureUpdate: this.state.openDialog?.name === "elementLinkSelector" ? CaptureUpdateAction.EVENTUALLY : CaptureUpdateAction.NEVER
        });
        return;
      }
      const selection = document.getSelection();
      if (selection?.anchorNode) {
        selection.removeAllRanges();
      }
      this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
      if (!this.state.penDetected && event.pointerType === "pen") {
        this.setState((prevState) => {
          return {
            penMode: true,
            penDetected: true
          };
        });
      }
      if (!this.device.isTouchScreen && ["pen", "touch"].includes(event.pointerType)) {
        this.device = updateObject(this.device, { isTouchScreen: true });
      }
      if (isPanning) {
        return;
      }
      this.lastPointerDownEvent = event;
      if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) {
        return;
      }
      this.setState({
        lastPointerDownWith: event.pointerType,
        cursorButton: "down"
      });
      this.savePointer(event.clientX, event.clientY, "down");
      if (event.button === POINTER_BUTTON.ERASER && this.state.activeTool.type !== TOOL_TYPE.eraser) {
        this.setState(
          {
            activeTool: updateActiveTool(this.state, {
              type: TOOL_TYPE.eraser,
              lastActiveToolBeforeEraser: this.state.activeTool
            })
          },
          () => {
            this.handleCanvasPointerDown(event);
            const onPointerUp2 = () => {
              unsubPointerUp();
              unsubCleanup?.();
              if (isEraserActive(this.state)) {
                this.setState({
                  activeTool: updateActiveTool(this.state, {
                    ...this.state.activeTool.lastActiveTool || {
                      type: TOOL_TYPE.selection
                    },
                    lastActiveToolBeforeEraser: null
                  })
                });
              }
            };
            const unsubPointerUp = addEventListener(
              window,
              "pointerup" /* POINTER_UP */,
              onPointerUp2,
              {
                once: true
              }
            );
            let unsubCleanup;
            requestAnimationFrame(() => {
              unsubCleanup = this.missingPointerEventCleanupEmitter.once(onPointerUp2);
            });
          }
        );
        return;
      }
      if (event.button !== POINTER_BUTTON.MAIN && event.button !== POINTER_BUTTON.TOUCH && event.button !== POINTER_BUTTON.ERASER) {
        return;
      }
      if (gesture.pointers.size > 1) {
        return;
      }
      const pointerDownState = this.initialPointerDownState(event);
      this.setState({
        selectedElementsAreBeingDragged: false
      });
      if (this.handleDraggingScrollBar(event, pointerDownState)) {
        return;
      }
      this.clearSelectionIfNotUsingSelection();
      this.updateBindingEnabledOnPointerMove(event);
      if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
        return;
      }
      const allowOnPointerDown = !this.state.penMode || event.pointerType !== "touch" || this.state.activeTool.type === "selection" || this.state.activeTool.type === "text" || this.state.activeTool.type === "image";
      if (!allowOnPointerDown) {
        return;
      }
      if (this.state.activeTool.type === "text") {
        this.handleTextOnPointerDown(event, pointerDownState);
      } else if (this.state.activeTool.type === "arrow" || this.state.activeTool.type === "line") {
        this.handleLinearElementOnPointerDown(
          event,
          this.state.activeTool.type,
          pointerDownState
        );
      } else if (this.state.activeTool.type === "image") {
        setCursor(this.interactiveCanvas, CURSOR_TYPE.CROSSHAIR);
        const pendingImageElement = this.state.pendingImageElementId && this.scene.getElement(this.state.pendingImageElementId);
        if (!pendingImageElement) {
          return;
        }
        this.setState({
          newElement: pendingImageElement,
          pendingImageElementId: null,
          multiElement: null
        });
        const { x, y } = viewportCoordsToSceneCoords(event, this.state);
        const frame = this.getTopLayerFrameAtSceneCoords({ x, y });
        mutateElement(pendingImageElement, {
          x,
          y,
          frameId: frame ? frame.id : null
        });
      } else if (this.state.activeTool.type === "freedraw") {
        this.handleFreeDrawElementOnPointerDown(
          event,
          this.state.activeTool.type,
          pointerDownState
        );
      } else if (this.state.activeTool.type === "custom") {
        setCursorForShape(this.interactiveCanvas, this.state);
      } else if (this.state.activeTool.type === TOOL_TYPE.frame || this.state.activeTool.type === TOOL_TYPE.magicframe) {
        this.createFrameElementOnPointerDown(
          pointerDownState,
          this.state.activeTool.type
        );
      } else if (this.state.activeTool.type === "laser") {
        this.laserTrails.startPath(
          pointerDownState.lastCoords.x,
          pointerDownState.lastCoords.y
        );
      } else if (this.state.activeTool.type !== "eraser" && this.state.activeTool.type !== "hand") {
        this.createGenericElementOnPointerDown(
          this.state.activeTool.type,
          pointerDownState
        );
      }
      this.props?.onPointerDown?.(this.state.activeTool, pointerDownState);
      this.onPointerDownEmitter.trigger(
        this.state.activeTool,
        pointerDownState,
        event
      );
      if (this.state.activeTool.type === "eraser") {
        this.eraserTrail.startPath(
          pointerDownState.lastCoords.x,
          pointerDownState.lastCoords.y
        );
      }
      const onPointerMove = this.onPointerMoveFromPointerDownHandler(pointerDownState);
      const onPointerUp = this.onPointerUpFromPointerDownHandler(pointerDownState);
      const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState);
      const onKeyUp = this.onKeyUpFromPointerDownHandler(pointerDownState);
      this.missingPointerEventCleanupEmitter.once(
        (_event) => onPointerUp(_event || event.nativeEvent)
      );
      if (!this.state.viewModeEnabled || this.state.activeTool.type === "laser") {
        window.addEventListener("pointermove" /* POINTER_MOVE */, onPointerMove);
        window.addEventListener("pointerup" /* POINTER_UP */, onPointerUp);
        window.addEventListener("keydown" /* KEYDOWN */, onKeyDown);
        window.addEventListener("keyup" /* KEYUP */, onKeyUp);
        pointerDownState.eventListeners.onMove = onPointerMove;
        pointerDownState.eventListeners.onUp = onPointerUp;
        pointerDownState.eventListeners.onKeyUp = onKeyUp;
        pointerDownState.eventListeners.onKeyDown = onKeyDown;
      }
    });
    __publicField(this, "handleCanvasPointerUp", (event) => {
      this.removePointer(event);
      this.lastPointerUpEvent = event;
      const scenePointer = viewportCoordsToSceneCoords(
        { clientX: event.clientX, clientY: event.clientY },
        this.state
      );
      const clicklength = event.timeStamp - (this.lastPointerDownEvent?.timeStamp ?? 0);
      if (this.device.editor.isMobile && clicklength < 300) {
        const hitElement = this.getElementAtPosition(
          scenePointer.x,
          scenePointer.y
        );
        if (isIframeLikeElement(hitElement) && this.isIframeLikeElementCenter(
          hitElement,
          event,
          scenePointer.x,
          scenePointer.y
        )) {
          this.handleEmbeddableCenterClick(hitElement);
          return;
        }
      }
      if (this.device.isTouchScreen) {
        const hitElement = this.getElementAtPosition(
          scenePointer.x,
          scenePointer.y
        );
        this.hitLinkElement = this.getElementLinkAtPosition(
          scenePointer,
          hitElement
        );
      }
      if (this.hitLinkElement && !this.state.selectedElementIds[this.hitLinkElement.id]) {
        if (clicklength < 300 && isIframeLikeElement(this.hitLinkElement) && !isPointHittingLinkIcon(
          this.hitLinkElement,
          this.scene.getNonDeletedElementsMap(),
          this.state,
          pointFrom(scenePointer.x, scenePointer.y)
        )) {
          this.handleEmbeddableCenterClick(this.hitLinkElement);
        } else {
          this.redirectToLink(event, this.device.isTouchScreen);
        }
      } else if (this.state.viewModeEnabled) {
        this.setState({
          activeEmbeddable: null,
          selectedElementIds: {}
        });
      }
    });
    __publicField(this, "maybeOpenContextMenuAfterPointerDownOnTouchDevices", (event) => {
      if (event.pointerType === "touch") {
        invalidateContextMenu = false;
        if (touchTimeout) {
          invalidateContextMenu = true;
        } else {
          touchTimeout = window.setTimeout(() => {
            touchTimeout = 0;
            if (!invalidateContextMenu) {
              this.handleCanvasContextMenu(event);
            }
          }, TOUCH_CTX_MENU_TIMEOUT);
        }
      }
    });
    __publicField(this, "resetContextMenuTimer", () => {
      clearTimeout(touchTimeout);
      touchTimeout = 0;
      invalidateContextMenu = false;
    });
    /**
     * pointerup may not fire in certian cases (user tabs away...), so in order
     * to properly cleanup pointerdown state, we need to fire any hanging
     * pointerup handlers manually
     */
    __publicField(this, "maybeCleanupAfterMissingPointerUp", (event) => {
      lastPointerUp?.();
      this.missingPointerEventCleanupEmitter.trigger(event).clear();
    });
    // Returns whether the event is a panning
    __publicField(this, "handleCanvasPanUsingWheelOrSpaceDrag", (event) => {
      if (!(gesture.pointers.size <= 1 && (event.button === POINTER_BUTTON.WHEEL || event.button === POINTER_BUTTON.MAIN && isHoldingSpace || isHandToolActive(this.state) || this.state.viewModeEnabled))) {
        return false;
      }
      isPanning = true;
      this.focusContainer();
      if (!this.state.editingTextElement) {
        event.preventDefault();
      }
      let nextPastePrevented = false;
      const isLinux = typeof window === void 0 ? false : /Linux/.test(window.navigator.platform);
      setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING);
      let { clientX: lastX, clientY: lastY } = event;
      const onPointerMove = withBatchedUpdatesThrottled((event2) => {
        const deltaX = lastX - event2.clientX;
        const deltaY = lastY - event2.clientY;
        lastX = event2.clientX;
        lastY = event2.clientY;
        if (isLinux && !nextPastePrevented && (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1)) {
          nextPastePrevented = true;
          const preventNextPaste = (event3) => {
            document.body.removeEventListener("paste" /* PASTE */, preventNextPaste);
            event3.stopPropagation();
          };
          const enableNextPaste = () => {
            setTimeout(() => {
              document.body.removeEventListener("paste" /* PASTE */, preventNextPaste);
              window.removeEventListener("pointerup" /* POINTER_UP */, enableNextPaste);
            }, 100);
          };
          document.body.addEventListener("paste" /* PASTE */, preventNextPaste);
          window.addEventListener("pointerup" /* POINTER_UP */, enableNextPaste);
        }
        this.translateCanvas({
          scrollX: this.state.scrollX - deltaX / this.state.zoom.value,
          scrollY: this.state.scrollY - deltaY / this.state.zoom.value
        });
      });
      const teardown = withBatchedUpdates(
        lastPointerUp = () => {
          lastPointerUp = null;
          isPanning = false;
          if (!isHoldingSpace) {
            if (this.state.viewModeEnabled) {
              setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
            } else {
              setCursorForShape(this.interactiveCanvas, this.state);
            }
          }
          this.setState({
            cursorButton: "up"
          });
          this.savePointer(event.clientX, event.clientY, "up");
          window.removeEventListener("pointermove" /* POINTER_MOVE */, onPointerMove);
          window.removeEventListener("pointerup" /* POINTER_UP */, teardown);
          window.removeEventListener("blur" /* BLUR */, teardown);
          onPointerMove.flush();
        }
      );
      window.addEventListener("blur" /* BLUR */, teardown);
      window.addEventListener("pointermove" /* POINTER_MOVE */, onPointerMove, {
        passive: true
      });
      window.addEventListener("pointerup" /* POINTER_UP */, teardown);
      return true;
    });
    __publicField(this, "clearSelectionIfNotUsingSelection", () => {
      if (this.state.activeTool.type !== "selection") {
        this.setState({
          selectedElementIds: makeNextSelectedElementIds({}, this.state),
          selectedGroupIds: {},
          editingGroupId: null,
          activeEmbeddable: null
        });
      }
    });
    /**
     * @returns whether the pointer event has been completely handled
     */
    __publicField(this, "handleSelectionOnPointerDown", (event, pointerDownState) => {
      if (this.state.activeTool.type === "selection") {
        const elements = this.scene.getNonDeletedElements();
        const elementsMap = this.scene.getNonDeletedElementsMap();
        const selectedElements = this.scene.getSelectedElements(this.state);
        if (selectedElements.length === 1 && !this.state.editingLinearElement && !isElbowArrow(selectedElements[0]) && !(this.state.selectedLinearElement && this.state.selectedLinearElement.hoverPointIndex !== -1)) {
          const elementWithTransformHandleType = getElementWithTransformHandleType(
            elements,
            this.state,
            pointerDownState.origin.x,
            pointerDownState.origin.y,
            this.state.zoom,
            event.pointerType,
            this.scene.getNonDeletedElementsMap(),
            this.device
          );
          if (elementWithTransformHandleType != null) {
            if (elementWithTransformHandleType.transformHandleType === "rotation") {
              this.setState({
                resizingElement: elementWithTransformHandleType.element
              });
              pointerDownState.resize.handleType = elementWithTransformHandleType.transformHandleType;
            } else if (this.state.croppingElementId) {
              pointerDownState.resize.handleType = elementWithTransformHandleType.transformHandleType;
            } else {
              this.setState({
                resizingElement: elementWithTransformHandleType.element
              });
              pointerDownState.resize.handleType = elementWithTransformHandleType.transformHandleType;
            }
          }
        } else if (selectedElements.length > 1) {
          pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(
            getCommonBounds(selectedElements),
            pointerDownState.origin.x,
            pointerDownState.origin.y,
            this.state.zoom,
            event.pointerType,
            this.device
          );
        }
        if (pointerDownState.resize.handleType) {
          pointerDownState.resize.isResizing = true;
          pointerDownState.resize.offset = tupleToCoors(
            getResizeOffsetXY(
              pointerDownState.resize.handleType,
              selectedElements,
              elementsMap,
              pointerDownState.origin.x,
              pointerDownState.origin.y
            )
          );
          if (selectedElements.length === 1 && isLinearElement(selectedElements[0]) && selectedElements[0].points.length === 2) {
            pointerDownState.resize.arrowDirection = getResizeArrowDirection(
              pointerDownState.resize.handleType,
              selectedElements[0]
            );
          }
        } else {
          if (this.state.selectedLinearElement) {
            const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
            const ret = LinearElementEditor.handlePointerDown(
              event,
              this,
              this.store,
              pointerDownState.origin,
              linearElementEditor,
              this.scene
            );
            if (ret.hitElement) {
              pointerDownState.hit.element = ret.hitElement;
            }
            if (ret.linearElementEditor) {
              this.setState({ selectedLinearElement: ret.linearElementEditor });
              if (this.state.editingLinearElement) {
                this.setState({ editingLinearElement: ret.linearElementEditor });
              }
            }
            if (ret.didAddPoint) {
              return true;
            }
          }
          pointerDownState.hit.element = pointerDownState.hit.element ?? this.getElementAtPosition(
            pointerDownState.origin.x,
            pointerDownState.origin.y
          );
          this.hitLinkElement = this.getElementLinkAtPosition(
            pointerDownState.origin,
            pointerDownState.hit.element
          );
          if (this.hitLinkElement) {
            return true;
          }
          if (this.state.croppingElementId && pointerDownState.hit.element?.id !== this.state.croppingElementId) {
            this.finishImageCropping();
          }
          if (pointerDownState.hit.element) {
            const hitLinkElement = this.getElementLinkAtPosition(
              {
                x: pointerDownState.origin.x,
                y: pointerDownState.origin.y
              },
              pointerDownState.hit.element
            );
            if (hitLinkElement) {
              return false;
            }
          }
          pointerDownState.hit.allHitElements = this.getElementsAtPosition(
            pointerDownState.origin.x,
            pointerDownState.origin.y
          );
          const hitElement = pointerDownState.hit.element;
          const someHitElementIsSelected = pointerDownState.hit.allHitElements.some(
            (element) => this.isASelectedElement(element)
          );
          if ((hitElement === null || !someHitElementIsSelected) && !event.shiftKey && !pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements) {
            this.clearSelection(hitElement);
          }
          if (this.state.editingLinearElement) {
            this.setState({
              selectedElementIds: makeNextSelectedElementIds(
                {
                  [this.state.editingLinearElement.elementId]: true
                },
                this.state
              )
            });
          } else if (hitElement != null) {
            if (event[KEYS.CTRL_OR_CMD]) {
              if (!this.state.selectedElementIds[hitElement.id]) {
                pointerDownState.hit.wasAddedToSelection = true;
              }
              this.setState((prevState) => ({
                ...editGroupForSelectedElement(prevState, hitElement),
                previousSelectedElementIds: this.state.selectedElementIds
              }));
              return false;
            }
            if (!this.state.selectedElementIds[hitElement.id]) {
              if (this.state.editingGroupId && !isElementInGroup(hitElement, this.state.editingGroupId)) {
                this.setState({
                  selectedElementIds: makeNextSelectedElementIds({}, this.state),
                  selectedGroupIds: {},
                  editingGroupId: null,
                  activeEmbeddable: null
                });
              }
              if (!someHitElementIsSelected && !pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements) {
                this.setState((prevState) => {
                  let nextSelectedElementIds = {
                    ...prevState.selectedElementIds,
                    [hitElement.id]: true
                  };
                  const previouslySelectedElements = [];
                  Object.keys(prevState.selectedElementIds).forEach((id) => {
                    const element = this.scene.getElement(id);
                    element && previouslySelectedElements.push(element);
                  });
                  if (isFrameLikeElement(hitElement)) {
                    getFrameChildren(
                      previouslySelectedElements,
                      hitElement.id
                    ).forEach((element) => {
                      delete nextSelectedElementIds[element.id];
                    });
                  } else if (hitElement.frameId) {
                    if (nextSelectedElementIds[hitElement.frameId]) {
                      delete nextSelectedElementIds[hitElement.id];
                    }
                  } else {
                    const groupIds = hitElement.groupIds;
                    const framesInGroups = new Set(
                      groupIds.flatMap(
                        (gid) => getElementsInGroup(
                          this.scene.getNonDeletedElements(),
                          gid
                        )
                      ).filter((element) => isFrameLikeElement(element)).map((frame) => frame.id)
                    );
                    if (framesInGroups.size > 0) {
                      previouslySelectedElements.forEach((element) => {
                        if (element.frameId && framesInGroups.has(element.frameId)) {
                          delete nextSelectedElementIds[element.id];
                          element.groupIds.flatMap(
                            (gid) => getElementsInGroup(
                              this.scene.getNonDeletedElements(),
                              gid
                            )
                          ).forEach((element2) => {
                            delete nextSelectedElementIds[element2.id];
                          });
                        }
                      });
                    }
                  }
                  if (prevState.openDialog?.name === "elementLinkSelector") {
                    if (!hitElement.groupIds.some(
                      (gid) => prevState.selectedGroupIds[gid]
                    )) {
                      nextSelectedElementIds = {
                        [hitElement.id]: true
                      };
                    }
                  }
                  return {
                    ...selectGroupsForSelectedElements(
                      {
                        editingGroupId: prevState.editingGroupId,
                        selectedElementIds: nextSelectedElementIds
                      },
                      this.scene.getNonDeletedElements(),
                      prevState,
                      this
                    ),
                    showHyperlinkPopup: hitElement.link || isEmbeddableElement(hitElement) ? "info" : false
                  };
                });
                pointerDownState.hit.wasAddedToSelection = true;
              }
            }
          }
          this.setState({
            previousSelectedElementIds: this.state.selectedElementIds
          });
        }
      }
      return false;
    });
    __publicField(this, "handleTextOnPointerDown", (event, pointerDownState) => {
      if (this.state.editingTextElement) {
        return;
      }
      let sceneX = pointerDownState.origin.x;
      let sceneY = pointerDownState.origin.y;
      const element = this.getElementAtPosition(sceneX, sceneY, {
        includeBoundTextElement: true
      });
      let container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
      if (hasBoundTextElement(element)) {
        container = element;
        sceneX = element.x + element.width / 2;
        sceneY = element.y + element.height / 2;
      }
      this.startTextEditing({
        sceneX,
        sceneY,
        insertAtParentCenter: !event.altKey,
        container,
        autoEdit: false
      });
      resetCursor(this.interactiveCanvas);
      if (!this.state.activeTool.locked) {
        this.setState({
          activeTool: updateActiveTool(this.state, { type: "selection" })
        });
      }
    });
    __publicField(this, "handleFreeDrawElementOnPointerDown", (event, elementType, pointerDownState) => {
      const [gridX, gridY] = getGridPoint(
        pointerDownState.origin.x,
        pointerDownState.origin.y,
        null
      );
      const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
        x: gridX,
        y: gridY
      });
      const simulatePressure = event.pressure === 0.5;
      const element = newFreeDrawElement({
        type: elementType,
        x: gridX,
        y: gridY,
        strokeColor: this.state.currentItemStrokeColor,
        backgroundColor: this.state.currentItemBackgroundColor,
        fillStyle: this.state.currentItemFillStyle,
        strokeWidth: this.state.currentItemStrokeWidth,
        strokeStyle: this.state.currentItemStrokeStyle,
        roughness: this.state.currentItemRoughness,
        opacity: this.state.currentItemOpacity,
        roundness: null,
        simulatePressure,
        locked: false,
        frameId: topLayerFrame ? topLayerFrame.id : null,
        points: [pointFrom(0, 0)],
        pressures: simulatePressure ? [] : [event.pressure]
      });
      this.scene.insertElement(element);
      this.setState((prevState) => {
        const nextSelectedElementIds = {
          ...prevState.selectedElementIds
        };
        delete nextSelectedElementIds[element.id];
        return {
          selectedElementIds: makeNextSelectedElementIds(
            nextSelectedElementIds,
            prevState
          )
        };
      });
      const boundElement = getHoveredElementForBinding(
        pointerDownState.origin,
        this.scene.getNonDeletedElements(),
        this.scene.getNonDeletedElementsMap(),
        this.state.zoom
      );
      this.setState({
        newElement: element,
        startBoundElement: boundElement,
        suggestedBindings: []
      });
    });
    __publicField(this, "insertIframeElement", ({
      sceneX,
      sceneY,
      width,
      height
    }) => {
      const [gridX, gridY] = getGridPoint(
        sceneX,
        sceneY,
        this.lastPointerDownEvent?.[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const element = newIframeElement({
        type: "iframe",
        x: gridX,
        y: gridY,
        strokeColor: "transparent",
        backgroundColor: "transparent",
        fillStyle: this.state.currentItemFillStyle,
        strokeWidth: this.state.currentItemStrokeWidth,
        strokeStyle: this.state.currentItemStrokeStyle,
        roughness: this.state.currentItemRoughness,
        roundness: this.getCurrentItemRoundness("iframe"),
        opacity: this.state.currentItemOpacity,
        locked: false,
        width,
        height
      });
      this.scene.insertElement(element);
      return element;
    });
    //create rectangle element with youtube top left on nearest grid point width / hight 640/360
    __publicField(this, "insertEmbeddableElement", ({
      sceneX,
      sceneY,
      link
    }) => {
      const [gridX, gridY] = getGridPoint(
        sceneX,
        sceneY,
        this.lastPointerDownEvent?.[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const embedLink = getEmbedLink(link);
      if (!embedLink) {
        return;
      }
      if (embedLink.error instanceof URIError) {
        this.setToast({
          message: t("toast.unrecognizedLinkFormat"),
          closable: true
        });
      }
      const element = newEmbeddableElement({
        type: "embeddable",
        x: gridX,
        y: gridY,
        strokeColor: "transparent",
        backgroundColor: "transparent",
        fillStyle: this.state.currentItemFillStyle,
        strokeWidth: this.state.currentItemStrokeWidth,
        strokeStyle: this.state.currentItemStrokeStyle,
        roughness: this.state.currentItemRoughness,
        roundness: this.getCurrentItemRoundness("embeddable"),
        opacity: this.state.currentItemOpacity,
        locked: false,
        width: embedLink.intrinsicSize.w,
        height: embedLink.intrinsicSize.h,
        link
      });
      this.scene.insertElement(element);
      return element;
    });
    __publicField(this, "createImageElement", ({
      sceneX,
      sceneY,
      addToFrameUnderCursor = true
    }) => {
      const [gridX, gridY] = getGridPoint(
        sceneX,
        sceneY,
        this.lastPointerDownEvent?.[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const topLayerFrame = addToFrameUnderCursor ? this.getTopLayerFrameAtSceneCoords({
        x: gridX,
        y: gridY
      }) : null;
      const element = newImageElement({
        type: "image",
        x: gridX,
        y: gridY,
        strokeColor: this.state.currentItemStrokeColor,
        backgroundColor: this.state.currentItemBackgroundColor,
        fillStyle: this.state.currentItemFillStyle,
        strokeWidth: this.state.currentItemStrokeWidth,
        strokeStyle: this.state.currentItemStrokeStyle,
        roughness: this.state.currentItemRoughness,
        roundness: null,
        opacity: this.state.currentItemOpacity,
        locked: false,
        frameId: topLayerFrame ? topLayerFrame.id : null
      });
      return element;
    });
    __publicField(this, "handleLinearElementOnPointerDown", (event, elementType, pointerDownState) => {
      if (this.state.multiElement) {
        const { multiElement } = this.state;
        if (multiElement.type === "line" && isPathALoop(multiElement.points, this.state.zoom.value)) {
          mutateElement(multiElement, {
            lastCommittedPoint: multiElement.points[multiElement.points.length - 1]
          });
          this.actionManager.executeAction(actionFinalize);
          return;
        }
        if (isElbowArrow(multiElement) && multiElement.points.length > 1) {
          mutateElement(multiElement, {
            lastCommittedPoint: multiElement.points[multiElement.points.length - 1]
          });
          this.actionManager.executeAction(actionFinalize);
          return;
        }
        const { x: rx, y: ry, lastCommittedPoint } = multiElement;
        if (multiElement.points.length > 1 && lastCommittedPoint && pointDistance(
          pointFrom(
            pointerDownState.origin.x - rx,
            pointerDownState.origin.y - ry
          ),
          lastCommittedPoint
        ) < LINE_CONFIRM_THRESHOLD) {
          this.actionManager.executeAction(actionFinalize);
          return;
        }
        this.setState((prevState) => ({
          selectedElementIds: makeNextSelectedElementIds(
            {
              ...prevState.selectedElementIds,
              [multiElement.id]: true
            },
            prevState
          )
        }));
        mutateElement(multiElement, {
          lastCommittedPoint: multiElement.points[multiElement.points.length - 1]
        });
        setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
      } else {
        const [gridX, gridY] = getGridPoint(
          pointerDownState.origin.x,
          pointerDownState.origin.y,
          event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
        );
        const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
          x: gridX,
          y: gridY
        });
        const { currentItemStartArrowhead, currentItemEndArrowhead } = this.state;
        const [startArrowhead, endArrowhead] = elementType === "arrow" ? [currentItemStartArrowhead, currentItemEndArrowhead] : [null, null];
        const element = elementType === "arrow" ? newArrowElement({
          type: elementType,
          x: gridX,
          y: gridY,
          strokeColor: this.state.currentItemStrokeColor,
          backgroundColor: this.state.currentItemBackgroundColor,
          fillStyle: this.state.currentItemFillStyle,
          strokeWidth: this.state.currentItemStrokeWidth,
          strokeStyle: this.state.currentItemStrokeStyle,
          roughness: this.state.currentItemRoughness,
          opacity: this.state.currentItemOpacity,
          roundness: this.state.currentItemArrowType === ARROW_TYPE.round ? { type: ROUNDNESS.PROPORTIONAL_RADIUS } : (
            // note, roundness doesn't have any effect for elbow arrows,
            // but it's best to set it to null as well
            null
          ),
          startArrowhead,
          endArrowhead,
          locked: false,
          frameId: topLayerFrame ? topLayerFrame.id : null,
          elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
          fixedSegments: this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null
        }) : newLinearElement({
          type: elementType,
          x: gridX,
          y: gridY,
          strokeColor: this.state.currentItemStrokeColor,
          backgroundColor: this.state.currentItemBackgroundColor,
          fillStyle: this.state.currentItemFillStyle,
          strokeWidth: this.state.currentItemStrokeWidth,
          strokeStyle: this.state.currentItemStrokeStyle,
          roughness: this.state.currentItemRoughness,
          opacity: this.state.currentItemOpacity,
          roundness: this.state.currentItemRoundness === "round" ? { type: ROUNDNESS.PROPORTIONAL_RADIUS } : null,
          locked: false,
          frameId: topLayerFrame ? topLayerFrame.id : null
        });
        this.setState((prevState) => {
          const nextSelectedElementIds = {
            ...prevState.selectedElementIds
          };
          delete nextSelectedElementIds[element.id];
          return {
            selectedElementIds: makeNextSelectedElementIds(
              nextSelectedElementIds,
              prevState
            )
          };
        });
        mutateElement(element, {
          points: [...element.points, pointFrom(0, 0)]
        });
        const boundElement = getHoveredElementForBinding(
          pointerDownState.origin,
          this.scene.getNonDeletedElements(),
          this.scene.getNonDeletedElementsMap(),
          this.state.zoom,
          isElbowArrow(element),
          isElbowArrow(element)
        );
        this.scene.insertElement(element);
        this.setState({
          newElement: element,
          startBoundElement: boundElement,
          suggestedBindings: []
        });
      }
    });
    __publicField(this, "createGenericElementOnPointerDown", (elementType, pointerDownState) => {
      const [gridX, gridY] = getGridPoint(
        pointerDownState.origin.x,
        pointerDownState.origin.y,
        this.lastPointerDownEvent?.[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
        x: gridX,
        y: gridY
      });
      const baseElementAttributes = {
        x: gridX,
        y: gridY,
        strokeColor: this.state.currentItemStrokeColor,
        backgroundColor: this.state.currentItemBackgroundColor,
        fillStyle: this.state.currentItemFillStyle,
        strokeWidth: this.state.currentItemStrokeWidth,
        strokeStyle: this.state.currentItemStrokeStyle,
        roughness: this.state.currentItemRoughness,
        opacity: this.state.currentItemOpacity,
        roundness: this.getCurrentItemRoundness(elementType),
        locked: false,
        frameId: topLayerFrame ? topLayerFrame.id : null
      };
      let element;
      if (elementType === "embeddable") {
        element = newEmbeddableElement({
          type: "embeddable",
          ...baseElementAttributes
        });
      } else {
        element = newElement({
          type: elementType,
          ...baseElementAttributes
        });
      }
      if (element.type === "selection") {
        this.setState({
          selectionElement: element
        });
      } else {
        this.scene.insertElement(element);
        this.setState({
          multiElement: null,
          newElement: element
        });
      }
    });
    __publicField(this, "createFrameElementOnPointerDown", (pointerDownState, type) => {
      const [gridX, gridY] = getGridPoint(
        pointerDownState.origin.x,
        pointerDownState.origin.y,
        this.lastPointerDownEvent?.[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const constructorOpts = {
        x: gridX,
        y: gridY,
        opacity: this.state.currentItemOpacity,
        locked: false,
        ...FRAME_STYLE
      };
      const frame = type === TOOL_TYPE.magicframe ? newMagicFrameElement(constructorOpts) : newFrameElement(constructorOpts);
      this.scene.insertElement(frame);
      this.setState({
        multiElement: null,
        newElement: frame
      });
    });
    __publicField(this, "restoreReadyToEraseElements", () => {
      this.elementsPendingErasure = /* @__PURE__ */ new Set();
      this.triggerRender();
    });
    __publicField(this, "eraseElements", () => {
      let didChange = false;
      const elements = this.scene.getElementsIncludingDeleted().map((ele) => {
        if (this.elementsPendingErasure.has(ele.id) || ele.frameId && this.elementsPendingErasure.has(ele.frameId) || isBoundToContainer(ele) && this.elementsPendingErasure.has(ele.containerId)) {
          didChange = true;
          return newElementWith(ele, { isDeleted: true });
        }
        return ele;
      });
      this.elementsPendingErasure = /* @__PURE__ */ new Set();
      if (didChange) {
        this.store.shouldCaptureIncrement();
        this.scene.replaceAllElements(elements);
      }
    });
    __publicField(this, "initializeImage", async ({
      imageFile,
      imageElement: _imageElement,
      showCursorImagePreview = false
    }) => {
      if (!isSupportedImageFile(imageFile)) {
        throw new Error(t("errors.unsupportedFileType"));
      }
      const mimeType = imageFile.type;
      setCursor(this.interactiveCanvas, "wait");
      if (mimeType === MIME_TYPES.svg) {
        try {
          imageFile = SVGStringToFile(
            normalizeSVG(await imageFile.text()),
            imageFile.name
          );
        } catch (error) {
          console.warn(error);
          throw new Error(t("errors.svgImageInsertError"));
        }
      }
      const fileId = await (this.props.generateIdForFile?.(
        imageFile
      ) || generateIdFromFile(imageFile));
      if (!fileId) {
        console.warn(
          "Couldn't generate file id or the supplied `generateIdForFile` didn't resolve to one."
        );
        throw new Error(t("errors.imageInsertError"));
      }
      const existingFileData = this.files[fileId];
      if (!existingFileData?.dataURL) {
        try {
          imageFile = await resizeImageFile(imageFile, {
            maxWidthOrHeight: DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT
          });
        } catch (error) {
          console.error(
            "Error trying to resizing image file on insertion",
            error
          );
        }
        if (imageFile.size > MAX_ALLOWED_FILE_BYTES) {
          throw new Error(
            t("errors.fileTooBig", {
              maxSize: `${Math.trunc(MAX_ALLOWED_FILE_BYTES / 1024 / 1024)}MB`
            })
          );
        }
      }
      if (showCursorImagePreview) {
        const dataURL2 = this.files[fileId]?.dataURL;
        const resizedFile = dataURL2 && dataURLToFile(dataURL2);
        this.setImagePreviewCursor(resizedFile || imageFile);
      }
      const dataURL = this.files[fileId]?.dataURL || await getDataURL(imageFile);
      const imageElement = mutateElement(
        _imageElement,
        {
          fileId
        },
        false
      );
      return new Promise(
        async (resolve, reject) => {
          try {
            this.addMissingFiles([
              {
                mimeType,
                id: fileId,
                dataURL,
                created: Date.now(),
                lastRetrieved: Date.now()
              }
            ]);
            const cachedImageData = this.imageCache.get(fileId);
            if (!cachedImageData) {
              this.addNewImagesToImageCache();
              await this.updateImageCache([imageElement]);
            }
            if (cachedImageData?.image instanceof Promise) {
              await cachedImageData.image;
            }
            if (this.state.pendingImageElementId !== imageElement.id && this.state.newElement?.id !== imageElement.id) {
              this.initializeImageDimensions(imageElement, true);
            }
            resolve(imageElement);
          } catch (error) {
            console.error(error);
            reject(new Error(t("errors.imageInsertError")));
          } finally {
            if (!showCursorImagePreview) {
              resetCursor(this.interactiveCanvas);
            }
          }
        }
      );
    });
    /**
     * inserts image into elements array and rerenders
     */
    __publicField(this, "insertImageElement", async (imageElement, imageFile, showCursorImagePreview) => {
      if (!this.isToolSupported("image")) {
        this.setState({ errorMessage: t("errors.imageToolNotSupported") });
        return;
      }
      this.scene.insertElement(imageElement);
      try {
        return await this.initializeImage({
          imageFile,
          imageElement,
          showCursorImagePreview
        });
      } catch (error) {
        mutateElement(imageElement, {
          isDeleted: true
        });
        this.actionManager.executeAction(actionFinalize);
        this.setState({
          errorMessage: error.message || t("errors.imageInsertError")
        });
        return null;
      }
    });
    __publicField(this, "setImagePreviewCursor", async (imageFile) => {
      const cursorImageSizePx = 96;
      let imagePreview;
      try {
        imagePreview = await resizeImageFile(imageFile, {
          maxWidthOrHeight: cursorImageSizePx
        });
      } catch (e) {
        if (e.cause === "UNSUPPORTED") {
          throw new Error(t("errors.unsupportedFileType"));
        }
        throw e;
      }
      let previewDataURL = await getDataURL(imagePreview);
      if (imageFile.type === MIME_TYPES.svg) {
        const img = await loadHTMLImageElement(previewDataURL);
        let height = Math.min(img.height, cursorImageSizePx);
        let width = height * (img.width / img.height);
        if (width > cursorImageSizePx) {
          width = cursorImageSizePx;
          height = width * (img.height / img.width);
        }
        const canvas = document.createElement("canvas");
        canvas.height = height;
        canvas.width = width;
        const context = canvas.getContext("2d");
        context.drawImage(img, 0, 0, width, height);
        previewDataURL = canvas.toDataURL(MIME_TYPES.svg);
      }
      if (this.state.pendingImageElementId) {
        setCursor(this.interactiveCanvas, `url(${previewDataURL}) 4 4, auto`);
      }
    });
    __publicField(this, "onImageAction", async ({
      insertOnCanvasDirectly
    }) => {
      try {
        const clientX = this.state.width / 2 + this.state.offsetLeft;
        const clientY = this.state.height / 2 + this.state.offsetTop;
        const { x, y } = viewportCoordsToSceneCoords(
          { clientX, clientY },
          this.state
        );
        const imageFile = await fileOpen({
          description: "Image",
          extensions: Object.keys(
            IMAGE_MIME_TYPES
          )
        });
        const imageElement = this.createImageElement({
          sceneX: x,
          sceneY: y,
          addToFrameUnderCursor: false
        });
        if (insertOnCanvasDirectly) {
          this.insertImageElement(imageElement, imageFile);
          this.initializeImageDimensions(imageElement);
          this.setState(
            {
              selectedElementIds: makeNextSelectedElementIds(
                { [imageElement.id]: true },
                this.state
              )
            },
            () => {
              this.actionManager.executeAction(actionFinalize);
            }
          );
        } else {
          this.setState(
            {
              pendingImageElementId: imageElement.id
            },
            () => {
              this.insertImageElement(
                imageElement,
                imageFile,
                /* showCursorImagePreview */
                true
              );
            }
          );
        }
      } catch (error) {
        if (error.name !== "AbortError") {
          console.error(error);
        } else {
          console.warn(error);
        }
        this.setState(
          {
            pendingImageElementId: null,
            newElement: null,
            activeTool: updateActiveTool(this.state, { type: "selection" })
          },
          () => {
            this.actionManager.executeAction(actionFinalize);
          }
        );
      }
    });
    __publicField(this, "initializeImageDimensions", (imageElement, forceNaturalSize = false) => {
      const image = isInitializedImageElement(imageElement) && this.imageCache.get(imageElement.fileId)?.image;
      if (!image || image instanceof Promise) {
        if (imageElement.width < DRAGGING_THRESHOLD / this.state.zoom.value && imageElement.height < DRAGGING_THRESHOLD / this.state.zoom.value) {
          const placeholderSize = 100 / this.state.zoom.value;
          mutateElement(imageElement, {
            x: imageElement.x - placeholderSize / 2,
            y: imageElement.y - placeholderSize / 2,
            width: placeholderSize,
            height: placeholderSize
          });
        }
        return;
      }
      if (forceNaturalSize || // if user-created bounding box is below threshold, assume the
      // intention was to click instead of drag, and use the image's
      // intrinsic size
      imageElement.width < DRAGGING_THRESHOLD / this.state.zoom.value && imageElement.height < DRAGGING_THRESHOLD / this.state.zoom.value) {
        const minHeight = Math.max(this.state.height - 120, 160);
        const maxHeight = Math.min(
          minHeight,
          Math.floor(this.state.height * 0.5) / this.state.zoom.value
        );
        const height = Math.min(image.naturalHeight, maxHeight);
        const width = height * (image.naturalWidth / image.naturalHeight);
        const x = imageElement.x + imageElement.width / 2 - width / 2;
        const y = imageElement.y + imageElement.height / 2 - height / 2;
        mutateElement(imageElement, {
          x,
          y,
          width,
          height,
          crop: null
        });
      }
    });
    /** updates image cache, refreshing updated elements and/or setting status
        to error for images that fail during <img> element creation */
    __publicField(this, "updateImageCache", async (elements, files = this.files) => {
      const { updatedFiles, erroredFiles } = await updateImageCache({
        imageCache: this.imageCache,
        fileIds: elements.map((element) => element.fileId),
        files
      });
      if (updatedFiles.size || erroredFiles.size) {
        for (const element of elements) {
          if (updatedFiles.has(element.fileId)) {
            ShapeCache.delete(element);
          }
        }
      }
      if (erroredFiles.size) {
        this.scene.replaceAllElements(
          this.scene.getElementsIncludingDeleted().map((element) => {
            if (isInitializedImageElement(element) && erroredFiles.has(element.fileId)) {
              return newElementWith(element, {
                status: "error"
              });
            }
            return element;
          })
        );
      }
      return { updatedFiles, erroredFiles };
    });
    /** adds new images to imageCache and re-renders if needed */
    __publicField(this, "addNewImagesToImageCache", async (imageElements = getInitializedImageElements(
      this.scene.getNonDeletedElements()
    ), files = this.files) => {
      const uncachedImageElements = imageElements.filter(
        (element) => !element.isDeleted && !this.imageCache.has(element.fileId)
      );
      if (uncachedImageElements.length) {
        const { updatedFiles } = await this.updateImageCache(
          uncachedImageElements,
          files
        );
        if (updatedFiles.size) {
          this.scene.triggerUpdate();
        }
      }
    });
    /** generally you should use `addNewImagesToImageCache()` directly if you need
     *  to render new images. This is just a failsafe  */
    __publicField(this, "scheduleImageRefresh", throttle3(() => {
      this.addNewImagesToImageCache();
    }, IMAGE_RENDER_TIMEOUT));
    __publicField(this, "updateBindingEnabledOnPointerMove", (event) => {
      const shouldEnableBinding = shouldEnableBindingForPointerEvent(event);
      if (this.state.isBindingEnabled !== shouldEnableBinding) {
        this.setState({ isBindingEnabled: shouldEnableBinding });
      }
    });
    __publicField(this, "maybeSuggestBindingAtCursor", (pointerCoords, considerAll) => {
      const hoveredBindableElement = getHoveredElementForBinding(
        pointerCoords,
        this.scene.getNonDeletedElements(),
        this.scene.getNonDeletedElementsMap(),
        this.state.zoom,
        false,
        considerAll
      );
      this.setState({
        suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : []
      });
    });
    __publicField(this, "maybeSuggestBindingsForLinearElementAtCoords", (linearElement, pointerCoords, oppositeBindingBoundElement) => {
      if (!pointerCoords.length) {
        return;
      }
      const suggestedBindings = pointerCoords.reduce(
        (acc, coords) => {
          const hoveredBindableElement = getHoveredElementForBinding(
            coords,
            this.scene.getNonDeletedElements(),
            this.scene.getNonDeletedElementsMap(),
            this.state.zoom,
            isElbowArrow(linearElement),
            isElbowArrow(linearElement)
          );
          if (hoveredBindableElement != null && !isLinearElementSimpleAndAlreadyBound(
            linearElement,
            oppositeBindingBoundElement?.id,
            hoveredBindableElement
          )) {
            acc.push(hoveredBindableElement);
          }
          return acc;
        },
        []
      );
      this.setState({ suggestedBindings });
    });
    __publicField(this, "handleInteractiveCanvasRef", (canvas) => {
      if (canvas !== null) {
        this.interactiveCanvas = canvas;
        this.interactiveCanvas.addEventListener(
          "touchstart" /* TOUCH_START */,
          this.onTouchStart,
          { passive: false }
        );
        this.interactiveCanvas.addEventListener("touchend" /* TOUCH_END */, this.onTouchEnd);
      } else {
        this.interactiveCanvas?.removeEventListener(
          "touchstart" /* TOUCH_START */,
          this.onTouchStart
        );
        this.interactiveCanvas?.removeEventListener(
          "touchend" /* TOUCH_END */,
          this.onTouchEnd
        );
      }
    });
    __publicField(this, "handleAppOnDrop", async (event) => {
      const { file, fileHandle } = await getFileFromEvent(event);
      const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
        event,
        this.state
      );
      try {
        if (isSupportedImageFile(file) && this.isToolSupported("image")) {
          if (file?.type === MIME_TYPES.png || file?.type === MIME_TYPES.svg) {
            try {
              const scene = await loadFromBlob(
                file,
                this.state,
                this.scene.getElementsIncludingDeleted(),
                fileHandle
              );
              this.syncActionResult({
                ...scene,
                appState: {
                  ...scene.appState || this.state,
                  isLoading: false
                },
                replaceFiles: true,
                captureUpdate: CaptureUpdateAction.IMMEDIATELY
              });
              return;
            } catch (error) {
              if (error.name !== "EncodingError") {
                throw new Error(t("alerts.couldNotLoadInvalidFile"));
              }
            }
          }
          const imageElement = this.createImageElement({ sceneX, sceneY });
          this.insertImageElement(imageElement, file);
          this.initializeImageDimensions(imageElement);
          this.setState({
            selectedElementIds: makeNextSelectedElementIds(
              { [imageElement.id]: true },
              this.state
            )
          });
          return;
        }
      } catch (error) {
        return this.setState({
          isLoading: false,
          errorMessage: error.message
        });
      }
      const libraryJSON = event.dataTransfer.getData(MIME_TYPES.excalidrawlib);
      if (libraryJSON && typeof libraryJSON === "string") {
        try {
          const libraryItems = parseLibraryJSON(libraryJSON);
          this.addElementsFromPasteOrLibrary({
            elements: distributeLibraryItemsOnSquareGrid(libraryItems),
            position: event,
            files: null
          });
        } catch (error) {
          this.setState({ errorMessage: error.message });
        }
        return;
      }
      if (file) {
        await this.loadFileToCanvas(file, fileHandle);
      }
      if (event.dataTransfer?.types?.includes("text/plain")) {
        const text = event.dataTransfer?.getData("text");
        if (text && embeddableURLValidator(text, this.props.validateEmbeddable) && (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(text) || getEmbedLink(text)?.type === "video")) {
          const embeddable = this.insertEmbeddableElement({
            sceneX,
            sceneY,
            link: normalizeLink(text)
          });
          if (embeddable) {
            this.setState({ selectedElementIds: { [embeddable.id]: true } });
          }
        }
      }
    });
    __publicField(this, "loadFileToCanvas", async (file, fileHandle) => {
      file = await normalizeFile(file);
      try {
        const elements = this.scene.getElementsIncludingDeleted();
        let ret;
        try {
          ret = await loadSceneOrLibraryFromBlob(
            file,
            this.state,
            elements,
            fileHandle
          );
        } catch (error) {
          const imageSceneDataError = error instanceof ImageSceneDataError;
          if (imageSceneDataError && error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" && !this.isToolSupported("image")) {
            this.setState({
              isLoading: false,
              errorMessage: t("errors.imageToolNotSupported")
            });
            return;
          }
          const errorMessage = imageSceneDataError ? t("alerts.cannotRestoreFromImage") : t("alerts.couldNotLoadInvalidFile");
          this.setState({
            isLoading: false,
            errorMessage
          });
        }
        if (!ret) {
          return;
        }
        if (ret.type === MIME_TYPES.excalidraw) {
          syncInvalidIndices(elements.concat(ret.data.elements));
          this.store.updateSnapshot(arrayToMap(elements), this.state);
          this.setState({ isLoading: true });
          this.syncActionResult({
            ...ret.data,
            appState: {
              ...ret.data.appState || this.state,
              isLoading: false
            },
            replaceFiles: true,
            captureUpdate: CaptureUpdateAction.IMMEDIATELY
          });
        } else if (ret.type === MIME_TYPES.excalidrawlib) {
          await this.library.updateLibrary({
            libraryItems: file,
            merge: true,
            openLibraryMenu: true
          }).catch((error) => {
            console.error(error);
            this.setState({ errorMessage: t("errors.importLibraryError") });
          });
        }
      } catch (error) {
        this.setState({ isLoading: false, errorMessage: error.message });
      }
    });
    __publicField(this, "handleCanvasContextMenu", (event) => {
      event.preventDefault();
      if (("pointerType" in event.nativeEvent && event.nativeEvent.pointerType === "touch" || "pointerType" in event.nativeEvent && event.nativeEvent.pointerType === "pen" && // always allow if user uses a pen secondary button
      event.button !== POINTER_BUTTON.SECONDARY) && this.state.activeTool.type !== "selection") {
        return;
      }
      const { x, y } = viewportCoordsToSceneCoords(event, this.state);
      const element = this.getElementAtPosition(x, y, {
        preferSelected: true,
        includeLockedElements: true
      });
      const selectedElements = this.scene.getSelectedElements(this.state);
      const isHittingCommonBoundBox = this.isHittingCommonBoundingBoxOfSelectedElements(
        { x, y },
        selectedElements
      );
      const type = element || isHittingCommonBoundBox ? "element" : "canvas";
      const container = this.excalidrawContainerRef.current;
      const { top: offsetTop, left: offsetLeft } = container.getBoundingClientRect();
      const left = event.clientX - offsetLeft;
      const top = event.clientY - offsetTop;
      trackEvent("contextMenu", "openContextMenu", type);
      this.setState(
        {
          ...element && !this.state.selectedElementIds[element.id] ? {
            ...this.state,
            ...selectGroupsForSelectedElements(
              {
                editingGroupId: this.state.editingGroupId,
                selectedElementIds: { [element.id]: true }
              },
              this.scene.getNonDeletedElements(),
              this.state,
              this
            ),
            selectedLinearElement: isLinearElement(element) ? new LinearElementEditor(element) : null
          } : this.state,
          showHyperlinkPopup: false
        },
        () => {
          this.setState({
            contextMenu: { top, left, items: this.getContextMenuItems(type) }
          });
        }
      );
    });
    __publicField(this, "maybeDragNewGenericElement", (pointerDownState, event, informMutation = true) => {
      const selectionElement = this.state.selectionElement;
      const pointerCoords = pointerDownState.lastCoords;
      if (selectionElement && this.state.activeTool.type !== "eraser") {
        dragNewElement({
          newElement: selectionElement,
          elementType: this.state.activeTool.type,
          originX: pointerDownState.origin.x,
          originY: pointerDownState.origin.y,
          x: pointerCoords.x,
          y: pointerCoords.y,
          width: distance(pointerDownState.origin.x, pointerCoords.x),
          height: distance(pointerDownState.origin.y, pointerCoords.y),
          shouldMaintainAspectRatio: shouldMaintainAspectRatio(event),
          shouldResizeFromCenter: shouldResizeFromCenter(event),
          zoom: this.state.zoom.value,
          informMutation
        });
        return;
      }
      const newElement2 = this.state.newElement;
      if (!newElement2) {
        return;
      }
      let [gridX, gridY] = getGridPoint(
        pointerCoords.x,
        pointerCoords.y,
        event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const image = isInitializedImageElement(newElement2) && this.imageCache.get(newElement2.fileId)?.image;
      const aspectRatio = image && !(image instanceof Promise) ? image.width / image.height : null;
      this.maybeCacheReferenceSnapPoints(event, [newElement2]);
      const { snapOffset, snapLines } = snapNewElement(
        newElement2,
        this,
        event,
        {
          x: pointerDownState.originInGrid.x + (this.state.originSnapOffset?.x ?? 0),
          y: pointerDownState.originInGrid.y + (this.state.originSnapOffset?.y ?? 0)
        },
        {
          x: gridX - pointerDownState.originInGrid.x,
          y: gridY - pointerDownState.originInGrid.y
        },
        this.scene.getNonDeletedElementsMap()
      );
      gridX += snapOffset.x;
      gridY += snapOffset.y;
      this.setState({
        snapLines
      });
      dragNewElement({
        newElement: newElement2,
        elementType: this.state.activeTool.type,
        originX: pointerDownState.originInGrid.x,
        originY: pointerDownState.originInGrid.y,
        x: gridX,
        y: gridY,
        width: distance(pointerDownState.originInGrid.x, gridX),
        height: distance(pointerDownState.originInGrid.y, gridY),
        shouldMaintainAspectRatio: isImageElement(newElement2) ? !shouldMaintainAspectRatio(event) : shouldMaintainAspectRatio(event),
        shouldResizeFromCenter: shouldResizeFromCenter(event),
        zoom: this.state.zoom.value,
        widthAspectRatio: aspectRatio,
        originOffset: this.state.originSnapOffset,
        informMutation
      });
      this.setState({
        newElement: newElement2
      });
      if (this.state.activeTool.type === TOOL_TYPE.frame || this.state.activeTool.type === TOOL_TYPE.magicframe) {
        this.setState({
          elementsToHighlight: getElementsInResizingFrame(
            this.scene.getNonDeletedElements(),
            newElement2,
            this.state,
            this.scene.getNonDeletedElementsMap()
          )
        });
      }
    });
    __publicField(this, "maybeHandleCrop", (pointerDownState, event) => {
      if (!this.state.croppingElementId) {
        return false;
      }
      const transformHandleType = pointerDownState.resize.handleType;
      const pointerCoords = pointerDownState.lastCoords;
      const [x, y] = getGridPoint(
        pointerCoords.x - pointerDownState.resize.offset.x,
        pointerCoords.y - pointerDownState.resize.offset.y,
        event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const croppingElement = this.scene.getNonDeletedElementsMap().get(this.state.croppingElementId);
      if (transformHandleType && croppingElement && isImageElement(croppingElement)) {
        const croppingAtStateStart = pointerDownState.originalElements.get(
          croppingElement.id
        );
        const image = isInitializedImageElement(croppingElement) && this.imageCache.get(croppingElement.fileId)?.image;
        if (croppingAtStateStart && isImageElement(croppingAtStateStart) && image && !(image instanceof Promise)) {
          const [gridX, gridY] = getGridPoint(
            pointerCoords.x,
            pointerCoords.y,
            event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
          );
          const dragOffset = {
            x: gridX - pointerDownState.originInGrid.x,
            y: gridY - pointerDownState.originInGrid.y
          };
          this.maybeCacheReferenceSnapPoints(event, [croppingElement]);
          const { snapOffset, snapLines } = snapResizingElements(
            [croppingElement],
            [croppingAtStateStart],
            this,
            event,
            dragOffset,
            transformHandleType
          );
          mutateElement(
            croppingElement,
            cropElement(
              croppingElement,
              transformHandleType,
              image.naturalWidth,
              image.naturalHeight,
              x + snapOffset.x,
              y + snapOffset.y,
              event.shiftKey ? croppingAtStateStart.width / croppingAtStateStart.height : void 0
            )
          );
          updateBoundElements(
            croppingElement,
            this.scene.getNonDeletedElementsMap(),
            {
              newSize: {
                width: croppingElement.width,
                height: croppingElement.height
              }
            }
          );
          this.setState({
            isCropping: transformHandleType && transformHandleType !== "rotation",
            snapLines
          });
        }
        return true;
      }
      return false;
    });
    __publicField(this, "maybeHandleResize", (pointerDownState, event) => {
      const selectedElements = this.scene.getSelectedElements(this.state);
      const selectedFrames = selectedElements.filter(
        (element) => isFrameLikeElement(element)
      );
      const transformHandleType = pointerDownState.resize.handleType;
      if (
        // Frames cannot be rotated.
        selectedFrames.length > 0 && transformHandleType === "rotation" || // Elbow arrows cannot be transformed (resized or rotated).
        selectedElements.length === 1 && isElbowArrow(selectedElements[0]) || // Do not resize when in crop mode
        this.state.croppingElementId
      ) {
        return false;
      }
      this.setState({
        // TODO: rename this state field to "isScaling" to distinguish
        // it from the generic "isResizing" which includes scaling and
        // rotating
        isResizing: transformHandleType && transformHandleType !== "rotation",
        isRotating: transformHandleType === "rotation",
        activeEmbeddable: null
      });
      const pointerCoords = pointerDownState.lastCoords;
      let [resizeX, resizeY] = getGridPoint(
        pointerCoords.x - pointerDownState.resize.offset.x,
        pointerCoords.y - pointerDownState.resize.offset.y,
        event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      const frameElementsOffsetsMap = /* @__PURE__ */ new Map();
      selectedFrames.forEach((frame) => {
        const elementsInFrame = getFrameChildren(
          this.scene.getNonDeletedElements(),
          frame.id
        );
        elementsInFrame.forEach((element) => {
          frameElementsOffsetsMap.set(frame.id + element.id, {
            x: element.x - frame.x,
            y: element.y - frame.y
          });
        });
      });
      if (!this.state.selectedElementsAreBeingDragged) {
        const [gridX, gridY] = getGridPoint(
          pointerCoords.x,
          pointerCoords.y,
          event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
        );
        const dragOffset = {
          x: gridX - pointerDownState.originInGrid.x,
          y: gridY - pointerDownState.originInGrid.y
        };
        const originalElements = [...pointerDownState.originalElements.values()];
        this.maybeCacheReferenceSnapPoints(event, selectedElements);
        const { snapOffset, snapLines } = snapResizingElements(
          selectedElements,
          getSelectedElements(originalElements, this.state),
          this,
          event,
          dragOffset,
          transformHandleType
        );
        resizeX += snapOffset.x;
        resizeY += snapOffset.y;
        this.setState({
          snapLines
        });
      }
      if (transformElements(
        pointerDownState.originalElements,
        transformHandleType,
        selectedElements,
        this.scene.getElementsMapIncludingDeleted(),
        this.scene,
        shouldRotateWithDiscreteAngle(event),
        shouldResizeFromCenter(event),
        selectedElements.some((element) => isImageElement(element)) ? !shouldMaintainAspectRatio(event) : shouldMaintainAspectRatio(event),
        resizeX,
        resizeY,
        pointerDownState.resize.center.x,
        pointerDownState.resize.center.y
      )) {
        const suggestedBindings = getSuggestedBindingsForArrows(
          selectedElements,
          this.scene.getNonDeletedElementsMap(),
          this.state.zoom
        );
        const elementsToHighlight = /* @__PURE__ */ new Set();
        selectedFrames.forEach((frame) => {
          getElementsInResizingFrame(
            this.scene.getNonDeletedElements(),
            frame,
            this.state,
            this.scene.getNonDeletedElementsMap()
          ).forEach((element) => elementsToHighlight.add(element));
        });
        this.setState({
          elementsToHighlight: [...elementsToHighlight],
          suggestedBindings
        });
        return true;
      }
      return false;
    });
    __publicField(this, "getContextMenuItems", (type) => {
      const options = [];
      options.push(actionCopyAsPng, actionCopyAsSvg);
      if (type === "canvas") {
        if (this.state.viewModeEnabled) {
          return [
            ...options,
            actionToggleGridMode,
            actionToggleZenMode,
            actionToggleViewMode,
            actionToggleStats
          ];
        }
        return [
          actionPaste,
          CONTEXT_MENU_SEPARATOR,
          actionCopyAsPng,
          actionCopyAsSvg,
          copyText,
          CONTEXT_MENU_SEPARATOR,
          actionSelectAll,
          actionUnlockAllElements,
          CONTEXT_MENU_SEPARATOR,
          actionToggleGridMode,
          actionToggleObjectsSnapMode,
          actionToggleZenMode,
          actionToggleViewMode,
          actionToggleStats
        ];
      }
      options.push(copyText);
      if (this.state.viewModeEnabled) {
        return [actionCopy, ...options];
      }
      return [
        CONTEXT_MENU_SEPARATOR,
        actionCut,
        actionCopy,
        actionPaste,
        CONTEXT_MENU_SEPARATOR,
        actionSelectAllElementsInFrame,
        actionRemoveAllElementsFromFrame,
        actionWrapSelectionInFrame,
        CONTEXT_MENU_SEPARATOR,
        actionToggleCropEditor,
        CONTEXT_MENU_SEPARATOR,
        ...options,
        CONTEXT_MENU_SEPARATOR,
        actionCopyStyles,
        actionPasteStyles,
        CONTEXT_MENU_SEPARATOR,
        actionGroup,
        actionTextAutoResize,
        actionUnbindText,
        actionBindText,
        actionWrapTextInContainer,
        actionUngroup,
        CONTEXT_MENU_SEPARATOR,
        actionAddToLibrary,
        CONTEXT_MENU_SEPARATOR,
        actionSendBackward,
        actionBringForward,
        actionSendToBack,
        actionBringToFront,
        CONTEXT_MENU_SEPARATOR,
        actionFlipHorizontal,
        actionFlipVertical,
        CONTEXT_MENU_SEPARATOR,
        actionToggleLinearEditor,
        CONTEXT_MENU_SEPARATOR,
        actionLink,
        actionCopyElementLink,
        CONTEXT_MENU_SEPARATOR,
        actionDuplicateSelection,
        actionToggleElementLock,
        CONTEXT_MENU_SEPARATOR,
        actionDeleteSelected
      ];
    });
    __publicField(this, "handleWheel", withBatchedUpdates(
      (event) => {
        if (!(event.target instanceof HTMLCanvasElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLIFrameElement)) {
          if (event[KEYS.CTRL_OR_CMD]) {
            event.preventDefault();
          }
          return;
        }
        event.preventDefault();
        if (isPanning) {
          return;
        }
        const { deltaX, deltaY } = event;
        if (event.metaKey || event.ctrlKey) {
          const sign = Math.sign(deltaY);
          const MAX_STEP = ZOOM_STEP * 100;
          const absDelta = Math.abs(deltaY);
          let delta = deltaY;
          if (absDelta > MAX_STEP) {
            delta = MAX_STEP * sign;
          }
          let newZoom = this.state.zoom.value - delta / 100;
          newZoom += Math.log10(Math.max(1, this.state.zoom.value)) * -sign * // reduced amplification for small deltas (small movements on a trackpad)
          Math.min(1, absDelta / 20);
          this.translateCanvas((state) => ({
            ...getStateForZoom(
              {
                viewportX: this.lastViewportPosition.x,
                viewportY: this.lastViewportPosition.y,
                nextZoom: getNormalizedZoom(newZoom)
              },
              state
            ),
            shouldCacheIgnoreZoom: true
          }));
          this.resetShouldCacheIgnoreZoomDebounced();
          return;
        }
        if (event.shiftKey) {
          this.translateCanvas(({ zoom, scrollX }) => ({
            // on Mac, shift+wheel tends to result in deltaX
            scrollX: scrollX - (deltaY || deltaX) / zoom.value
          }));
          return;
        }
        this.translateCanvas(({ zoom, scrollX, scrollY }) => ({
          scrollX: scrollX - deltaX / zoom.value,
          scrollY: scrollY - deltaY / zoom.value
        }));
      }
    ));
    __publicField(this, "savePointer", (x, y, button) => {
      if (!x || !y) {
        return;
      }
      const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
        { clientX: x, clientY: y },
        this.state
      );
      if (isNaN(sceneX) || isNaN(sceneY)) {
      }
      const pointer = {
        x: sceneX,
        y: sceneY,
        tool: this.state.activeTool.type === "laser" ? "laser" : "pointer"
      };
      this.props.onPointerUpdate?.({
        pointer,
        button,
        pointersMap: gesture.pointers
      });
    });
    __publicField(this, "resetShouldCacheIgnoreZoomDebounced", debounce(() => {
      if (!this.unmounted) {
        this.setState({ shouldCacheIgnoreZoom: false });
      }
    }, 300));
    __publicField(this, "updateDOMRect", (cb) => {
      if (this.excalidrawContainerRef?.current) {
        const excalidrawContainer = this.excalidrawContainerRef.current;
        const {
          width,
          height,
          left: offsetLeft,
          top: offsetTop
        } = excalidrawContainer.getBoundingClientRect();
        const {
          width: currentWidth,
          height: currentHeight,
          offsetTop: currentOffsetTop,
          offsetLeft: currentOffsetLeft
        } = this.state;
        if (width === currentWidth && height === currentHeight && offsetLeft === currentOffsetLeft && offsetTop === currentOffsetTop) {
          if (cb) {
            cb();
          }
          return;
        }
        this.setState(
          {
            width,
            height,
            offsetLeft,
            offsetTop
          },
          () => {
            cb && cb();
          }
        );
      }
    });
    __publicField(this, "refresh", () => {
      this.setState({ ...this.getCanvasOffsets() });
    });
    const defaultAppState = getDefaultAppState();
    const {
      excalidrawAPI,
      viewModeEnabled = false,
      zenModeEnabled = false,
      gridModeEnabled = false,
      objectsSnapModeEnabled = false,
      theme = defaultAppState.theme,
      name = `${t("labels.untitled")}-${getDateTime()}`
    } = props;
    this.state = {
      ...defaultAppState,
      theme,
      isLoading: true,
      ...this.getCanvasOffsets(),
      viewModeEnabled,
      zenModeEnabled,
      objectsSnapModeEnabled,
      gridModeEnabled: gridModeEnabled ?? defaultAppState.gridModeEnabled,
      name,
      width: window.innerWidth,
      height: window.innerHeight
    };
    this.id = nanoid();
    this.library = new library_default(this);
    this.actionManager = new ActionManager(
      this.syncActionResult,
      () => this.state,
      () => this.scene.getElementsIncludingDeleted(),
      this
    );
    this.scene = new Scene_default();
    this.canvas = document.createElement("canvas");
    this.rc = rough.canvas(this.canvas);
    this.renderer = new Renderer(this.scene);
    this.visibleElements = [];
    this.store = new Store();
    this.history = new History();
    if (excalidrawAPI) {
      const api = {
        updateScene: this.updateScene,
        updateLibrary: this.library.updateLibrary,
        addFiles: this.addFiles,
        resetScene: this.resetScene,
        getSceneElementsIncludingDeleted: this.getSceneElementsIncludingDeleted,
        history: {
          clear: this.resetHistory
        },
        scrollToContent: this.scrollToContent,
        getSceneElements: this.getSceneElements,
        getAppState: () => this.state,
        getFiles: () => this.files,
        getName: this.getName,
        registerAction: (action) => {
          this.actionManager.registerAction(action);
        },
        refresh: this.refresh,
        setToast: this.setToast,
        id: this.id,
        setActiveTool: this.setActiveTool,
        setCursor: this.setCursor,
        resetCursor: this.resetCursor,
        updateFrameRendering: this.updateFrameRendering,
        toggleSidebar: this.toggleSidebar,
        onChange: (cb) => this.onChangeEmitter.on(cb),
        onPointerDown: (cb) => this.onPointerDownEmitter.on(cb),
        onPointerUp: (cb) => this.onPointerUpEmitter.on(cb),
        onScrollChange: (cb) => this.onScrollChangeEmitter.on(cb),
        onUserFollow: (cb) => this.onUserFollowEmitter.on(cb)
      };
      if (typeof excalidrawAPI === "function") {
        excalidrawAPI(api);
      } else {
        console.error("excalidrawAPI should be a function!");
      }
    }
    this.excalidrawContainerValue = {
      container: this.excalidrawContainerRef.current,
      id: this.id
    };
    this.fonts = new Fonts(this.scene);
    this.history = new History();
    this.actionManager.registerAll(actions);
    this.actionManager.registerAction(
      createUndoAction(this.history, this.store)
    );
    this.actionManager.registerAction(
      createRedoAction(this.history, this.store)
    );
  }
  onWindowMessage(event) {
    if (event.origin !== "https://player.vimeo.com" && event.origin !== "https://www.youtube.com") {
      return;
    }
    let data = null;
    try {
      data = JSON.parse(event.data);
    } catch (e) {
    }
    if (!data) {
      return;
    }
    switch (event.origin) {
      case "https://player.vimeo.com":
        if (data.method === "paused") {
          let source = null;
          const iframes = document.body.querySelectorAll(
            "iframe.excalidraw__embeddable"
          );
          if (!iframes) {
            break;
          }
          for (const iframe of iframes) {
            if (iframe.contentWindow === event.source) {
              source = iframe.contentWindow;
            }
          }
          source?.postMessage(
            JSON.stringify({
              method: data.value ? "play" : "pause",
              value: true
            }),
            "*"
          );
        }
        break;
      case "https://www.youtube.com":
        if (data.event === "infoDelivery" && data.info && data.id && typeof data.info.playerState === "number") {
          const id = data.id;
          const playerState = data.info.playerState;
          if (Object.values(YOUTUBE_STATES).includes(playerState)) {
            YOUTUBE_VIDEO_STATES.set(
              id,
              playerState
            );
          }
        }
        break;
    }
  }
  cacheEmbeddableRef(element, ref) {
    if (ref) {
      this.iFrameRefs.set(element.id, ref);
    }
  }
  getHTMLIFrameElement(element) {
    return this.iFrameRefs.get(element.id);
  }
  handleEmbeddableCenterClick(element) {
    if (this.state.activeEmbeddable?.element === element && this.state.activeEmbeddable?.state === "active") {
      return;
    }
    setTimeout(() => {
      this.setState({
        activeEmbeddable: { element, state: "active" },
        selectedElementIds: { [element.id]: true },
        newElement: null,
        selectionElement: null
      });
    }, 100);
    if (isIframeElement(element)) {
      return;
    }
    const iframe = this.getHTMLIFrameElement(element);
    if (!iframe?.contentWindow) {
      return;
    }
    if (iframe.src.includes("youtube")) {
      const state = YOUTUBE_VIDEO_STATES.get(element.id);
      if (!state) {
        YOUTUBE_VIDEO_STATES.set(element.id, YOUTUBE_STATES.UNSTARTED);
        iframe.contentWindow.postMessage(
          JSON.stringify({
            event: "listening",
            id: element.id
          }),
          "*"
        );
      }
      switch (state) {
        case YOUTUBE_STATES.PLAYING:
        case YOUTUBE_STATES.BUFFERING:
          iframe.contentWindow?.postMessage(
            JSON.stringify({
              event: "command",
              func: "pauseVideo",
              args: ""
            }),
            "*"
          );
          break;
        default:
          iframe.contentWindow?.postMessage(
            JSON.stringify({
              event: "command",
              func: "playVideo",
              args: ""
            }),
            "*"
          );
      }
    }
    if (iframe.src.includes("player.vimeo.com")) {
      iframe.contentWindow.postMessage(
        JSON.stringify({
          method: "paused"
          //video play/pause in onWindowMessage handler
        }),
        "*"
      );
    }
  }
  isIframeLikeElementCenter(el, event, sceneX, sceneY) {
    return el && !event.altKey && !event.shiftKey && !event.metaKey && !event.ctrlKey && (this.state.activeEmbeddable?.element !== el || this.state.activeEmbeddable?.state === "hover" || !this.state.activeEmbeddable) && sceneX >= el.x + el.width / 3 && sceneX <= el.x + 2 * el.width / 3 && sceneY >= el.y + el.height / 3 && sceneY <= el.y + 2 * el.height / 3;
  }
  renderEmbeddables() {
    const scale = this.state.zoom.value;
    const normalizedWidth = this.state.width;
    const normalizedHeight = this.state.height;
    const embeddableElements = this.scene.getNonDeletedElements().filter(
      (el) => isEmbeddableElement(el) && this.embedsValidationStatus.get(el.id) === true || isIframeElement(el)
    );
    return /* @__PURE__ */ jsx147(Fragment23, { children: embeddableElements.map((el) => {
      const { x, y } = sceneCoordsToViewportCoords(
        { sceneX: el.x, sceneY: el.y },
        this.state
      );
      const isVisible = isElementInViewport(
        el,
        normalizedWidth,
        normalizedHeight,
        this.state,
        this.scene.getNonDeletedElementsMap()
      );
      const hasBeenInitialized = this.initializedEmbeds.has(el.id);
      if (isVisible && !hasBeenInitialized) {
        this.initializedEmbeds.add(el.id);
      }
      const shouldRender = isVisible || hasBeenInitialized;
      if (!shouldRender) {
        return null;
      }
      let src;
      if (isIframeElement(el)) {
        src = null;
        const data = (el.customData?.generationData ?? this.magicGenerations.get(el.id)) || {
          status: "error",
          message: "No generation data",
          code: "ERR_NO_GENERATION_DATA"
        };
        if (data.status === "done") {
          const html = data.html;
          src = {
            intrinsicSize: { w: el.width, h: el.height },
            type: "document",
            srcdoc: () => {
              return html;
            }
          };
        } else if (data.status === "pending") {
          src = {
            intrinsicSize: { w: el.width, h: el.height },
            type: "document",
            srcdoc: () => {
              return createSrcDoc(`
                    <style>
                      html, body {
                        width: 100%;
                        height: 100%;
                        color: ${this.state.theme === THEME.DARK ? "white" : "black"};
                      }
                      body {
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        flex-direction: column;
                        gap: 1rem;
                      }

                      .Spinner {
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        margin-left: auto;
                        margin-right: auto;
                      }

                      .Spinner svg {
                        animation: rotate 1.6s linear infinite;
                        transform-origin: center center;
                        width: 40px;
                        height: 40px;
                      }

                      .Spinner circle {
                        stroke: currentColor;
                        animation: dash 1.6s linear 0s infinite;
                        stroke-linecap: round;
                      }

                      @keyframes rotate {
                        100% {
                          transform: rotate(360deg);
                        }
                      }

                      @keyframes dash {
                        0% {
                          stroke-dasharray: 1, 300;
                          stroke-dashoffset: 0;
                        }
                        50% {
                          stroke-dasharray: 150, 300;
                          stroke-dashoffset: -200;
                        }
                        100% {
                          stroke-dasharray: 1, 300;
                          stroke-dashoffset: -280;
                        }
                      }
                    </style>
                    <div class="Spinner">
                      <svg
                        viewBox="0 0 100 100"
                      >
                        <circle
                          cx="50"
                          cy="50"
                          r="46"
                          stroke-width="8"
                          fill="none"
                          stroke-miter-limit="10"
                        />
                      </svg>
                    </div>
                    <div>Generating...</div>
                  `);
            }
          };
        } else {
          let message;
          if (data.code === "ERR_GENERATION_INTERRUPTED") {
            message = "Generation was interrupted...";
          } else {
            message = data.message || "Generation failed";
          }
          src = {
            intrinsicSize: { w: el.width, h: el.height },
            type: "document",
            srcdoc: () => {
              return createSrcDoc(`
                    <style>
                    html, body {
                      height: 100%;
                    }
                      body {
                        display: flex;
                        flex-direction: column;
                        align-items: center;
                        justify-content: center;
                        color: ${COLOR_PALETTE.red[3]};
                      }
                      h1, h3 {
                        margin-top: 0;
                        margin-bottom: 0.5rem;
                      }
                    </style>
                    <h1>Error!</h1>
                    <h3>${message}</h3>
                  `);
            }
          };
        }
      } else {
        src = getEmbedLink(toValidURL(el.link || ""));
      }
      const isActive = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "active";
      const isHovered = this.state.activeEmbeddable?.element === el && this.state.activeEmbeddable?.state === "hover";
      return /* @__PURE__ */ jsx147(
        "div",
        {
          className: clsx55("excalidraw__embeddable-container", {
            "is-hovered": isHovered
          }),
          style: {
            transform: isVisible ? `translate(${x - this.state.offsetLeft}px, ${y - this.state.offsetTop}px) scale(${scale})` : "none",
            display: isVisible ? "block" : "none",
            opacity: getRenderOpacity(
              el,
              getContainingFrame(el, this.scene.getNonDeletedElementsMap()),
              this.elementsPendingErasure,
              null,
              this.state.openDialog?.name === "elementLinkSelector" ? DEFAULT_REDUCED_GLOBAL_ALPHA : 1
            ),
            ["--embeddable-radius"]: `${getCornerRadius(
              Math.min(el.width, el.height),
              el
            )}px`
          },
          children: /* @__PURE__ */ jsxs78(
            "div",
            {
              className: "excalidraw__embeddable-container__inner",
              style: {
                width: isVisible ? `${el.width}px` : 0,
                height: isVisible ? `${el.height}px` : 0,
                transform: isVisible ? `rotate(${el.angle}rad)` : "none",
                pointerEvents: isActive ? POINTER_EVENTS.enabled : POINTER_EVENTS.disabled
              },
              children: [
                isHovered && /* @__PURE__ */ jsx147("div", { className: "excalidraw__embeddable-hint", children: t("buttons.embeddableInteractionButton") }),
                /* @__PURE__ */ jsx147(
                  "div",
                  {
                    className: "excalidraw__embeddable__outer",
                    style: {
                      padding: `${el.strokeWidth}px`
                    },
                    children: (isEmbeddableElement(el) ? this.props.renderEmbeddable?.(el, this.state) : null) ?? /* @__PURE__ */ jsx147(
                      "iframe",
                      {
                        ref: (ref) => this.cacheEmbeddableRef(el, ref),
                        className: "excalidraw__embeddable",
                        srcDoc: src?.type === "document" ? src.srcdoc(this.state.theme) : void 0,
                        src: src?.type !== "document" ? src?.link ?? "" : void 0,
                        scrolling: "no",
                        referrerPolicy: "no-referrer-when-downgrade",
                        title: "Excalidraw Embedded Content",
                        allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
                        allowFullScreen: true,
                        sandbox: `${src?.sandbox?.allowSameOrigin ? "allow-same-origin" : ""} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads`
                      }
                    )
                  }
                )
              ]
            }
          )
        },
        el.id
      );
    }) });
  }
  toggleOverscrollBehavior(event) {
    document.documentElement.style.overscrollBehaviorX = event.type === "pointerenter" ? "none" : "auto";
  }
  render() {
    const selectedElements = this.scene.getSelectedElements(this.state);
    const { renderTopRightUI, renderCustomStats } = this.props;
    const sceneNonce = this.scene.getSceneNonce();
    const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
      sceneNonce,
      zoom: this.state.zoom,
      offsetLeft: this.state.offsetLeft,
      offsetTop: this.state.offsetTop,
      scrollX: this.state.scrollX,
      scrollY: this.state.scrollY,
      height: this.state.height,
      width: this.state.width,
      editingTextElement: this.state.editingTextElement,
      newElementId: this.state.newElement?.id,
      pendingImageElementId: this.state.pendingImageElementId
    });
    this.visibleElements = visibleElements;
    const allElementsMap = this.scene.getNonDeletedElementsMap();
    const shouldBlockPointerEvents = (
      // default back to `--ui-pointerEvents` flow if setPointerCapture
      // not supported
      "setPointerCapture" in HTMLElement.prototype ? false : this.state.selectionElement || this.state.newElement || this.state.selectedElementsAreBeingDragged || this.state.resizingElement || this.state.activeTool.type === "laser" && // technically we can just test on this once we make it more safe
      this.state.cursorButton === "down"
    );
    const firstSelectedElement = selectedElements[0];
    return /* @__PURE__ */ jsx147(
      "div",
      {
        className: clsx55("excalidraw excalidraw-container", {
          "excalidraw--view-mode": this.state.viewModeEnabled || this.state.openDialog?.name === "elementLinkSelector",
          "excalidraw--mobile": this.device.editor.isMobile
        }),
        style: {
          ["--ui-pointerEvents"]: shouldBlockPointerEvents ? POINTER_EVENTS.disabled : POINTER_EVENTS.enabled,
          ["--right-sidebar-width"]: "302px"
        },
        ref: this.excalidrawContainerRef,
        onDrop: this.handleAppOnDrop,
        tabIndex: 0,
        onKeyDown: this.props.handleKeyboardGlobally ? void 0 : this.onKeyDown,
        onPointerEnter: this.toggleOverscrollBehavior,
        onPointerLeave: this.toggleOverscrollBehavior,
        children: /* @__PURE__ */ jsx147(AppContext.Provider, { value: this, children: /* @__PURE__ */ jsx147(AppPropsContext.Provider, { value: this.props, children: /* @__PURE__ */ jsx147(
          ExcalidrawContainerContext.Provider,
          {
            value: this.excalidrawContainerValue,
            children: /* @__PURE__ */ jsx147(DeviceContext.Provider, { value: this.device, children: /* @__PURE__ */ jsx147(ExcalidrawSetAppStateContext.Provider, { value: this.setAppState, children: /* @__PURE__ */ jsx147(ExcalidrawAppStateContext.Provider, { value: this.state, children: /* @__PURE__ */ jsxs78(
              ExcalidrawElementsContext.Provider,
              {
                value: this.scene.getNonDeletedElements(),
                children: [
                  /* @__PURE__ */ jsxs78(
                    ExcalidrawActionManagerContext.Provider,
                    {
                      value: this.actionManager,
                      children: [
                        /* @__PURE__ */ jsx147(
                          LayerUI_default,
                          {
                            canvas: this.canvas,
                            appState: this.state,
                            files: this.files,
                            setAppState: this.setAppState,
                            actionManager: this.actionManager,
                            elements: this.scene.getNonDeletedElements(),
                            onLockToggle: this.toggleLock,
                            onPenModeToggle: this.togglePenMode,
                            onHandToolToggle: this.onHandToolToggle,
                            langCode: getLanguage().code,
                            renderTopRightUI,
                            renderCustomStats,
                            showExitZenModeBtn: typeof this.props?.zenModeEnabled === "undefined" && this.state.zenModeEnabled,
                            UIOptions: this.props.UIOptions,
                            onExportImage: this.onExportImage,
                            renderWelcomeScreen: !this.state.isLoading && this.state.showWelcomeScreen && this.state.activeTool.type === "selection" && !this.state.zenModeEnabled && !this.scene.getElementsIncludingDeleted().length,
                            app: this,
                            isCollaborating: this.props.isCollaborating,
                            generateLinkForSelection: this.props.generateLinkForSelection,
                            children: this.props.children
                          }
                        ),
                        /* @__PURE__ */ jsx147("div", { className: "excalidraw-textEditorContainer" }),
                        /* @__PURE__ */ jsx147("div", { className: "excalidraw-contextMenuContainer" }),
                        /* @__PURE__ */ jsx147("div", { className: "excalidraw-eye-dropper-container" }),
                        /* @__PURE__ */ jsx147(
                          SVGLayer,
                          {
                            trails: [this.laserTrails, this.eraserTrail]
                          }
                        ),
                        selectedElements.length === 1 && this.state.openDialog?.name !== "elementLinkSelector" && this.state.showHyperlinkPopup && /* @__PURE__ */ jsx147(
                          Hyperlink,
                          {
                            element: firstSelectedElement,
                            elementsMap: allElementsMap,
                            setAppState: this.setAppState,
                            onLinkOpen: this.props.onLinkOpen,
                            setToast: this.setToast,
                            updateEmbedValidationStatus: this.updateEmbedValidationStatus
                          },
                          firstSelectedElement.id
                        ),
                        this.props.aiEnabled !== false && selectedElements.length === 1 && isMagicFrameElement(firstSelectedElement) && /* @__PURE__ */ jsx147(
                          ElementCanvasButtons,
                          {
                            element: firstSelectedElement,
                            elementsMap,
                            children: /* @__PURE__ */ jsx147(
                              ElementCanvasButton,
                              {
                                title: t("labels.convertToCode"),
                                icon: MagicIcon,
                                checked: false,
                                onChange: () => this.onMagicFrameGenerate(
                                  firstSelectedElement,
                                  "button"
                                )
                              }
                            )
                          }
                        ),
                        selectedElements.length === 1 && isIframeElement(firstSelectedElement) && firstSelectedElement.customData?.generationData?.status === "done" && /* @__PURE__ */ jsxs78(
                          ElementCanvasButtons,
                          {
                            element: firstSelectedElement,
                            elementsMap,
                            children: [
                              /* @__PURE__ */ jsx147(
                                ElementCanvasButton,
                                {
                                  title: t("labels.copySource"),
                                  icon: copyIcon,
                                  checked: false,
                                  onChange: () => this.onIframeSrcCopy(firstSelectedElement)
                                }
                              ),
                              /* @__PURE__ */ jsx147(
                                ElementCanvasButton,
                                {
                                  title: "Enter fullscreen",
                                  icon: fullscreenIcon,
                                  checked: false,
                                  onChange: () => {
                                    const iframe = this.getHTMLIFrameElement(
                                      firstSelectedElement
                                    );
                                    if (iframe) {
                                      try {
                                        iframe.requestFullscreen();
                                        this.setState({
                                          activeEmbeddable: {
                                            element: firstSelectedElement,
                                            state: "active"
                                          },
                                          selectedElementIds: {
                                            [firstSelectedElement.id]: true
                                          },
                                          newElement: null,
                                          selectionElement: null
                                        });
                                      } catch (err) {
                                        console.warn(err);
                                        this.setState({
                                          errorMessage: "Couldn't enter fullscreen"
                                        });
                                      }
                                    }
                                  }
                                }
                              )
                            ]
                          }
                        ),
                        this.state.toast !== null && /* @__PURE__ */ jsx147(
                          Toast,
                          {
                            message: this.state.toast.message,
                            onClose: () => this.setToast(null),
                            duration: this.state.toast.duration,
                            closable: this.state.toast.closable
                          }
                        ),
                        this.state.contextMenu && /* @__PURE__ */ jsx147(
                          ContextMenu,
                          {
                            items: this.state.contextMenu.items,
                            top: this.state.contextMenu.top,
                            left: this.state.contextMenu.left,
                            actionManager: this.actionManager,
                            onClose: (callback) => {
                              this.setState({ contextMenu: null }, () => {
                                this.focusContainer();
                                callback?.();
                              });
                            }
                          }
                        ),
                        /* @__PURE__ */ jsx147(
                          StaticCanvas_default,
                          {
                            canvas: this.canvas,
                            rc: this.rc,
                            elementsMap,
                            allElementsMap,
                            visibleElements,
                            sceneNonce,
                            selectionNonce: this.state.selectionElement?.versionNonce,
                            scale: window.devicePixelRatio,
                            appState: this.state,
                            renderConfig: {
                              imageCache: this.imageCache,
                              isExporting: false,
                              renderGrid: isGridModeEnabled(this),
                              canvasBackgroundColor: this.state.viewBackgroundColor,
                              embedsValidationStatus: this.embedsValidationStatus,
                              elementsPendingErasure: this.elementsPendingErasure,
                              pendingFlowchartNodes: this.flowChartCreator.pendingNodes
                            }
                          }
                        ),
                        this.state.newElement && /* @__PURE__ */ jsx147(
                          NewElementCanvas_default,
                          {
                            appState: this.state,
                            scale: window.devicePixelRatio,
                            rc: this.rc,
                            elementsMap,
                            allElementsMap,
                            renderConfig: {
                              imageCache: this.imageCache,
                              isExporting: false,
                              renderGrid: false,
                              canvasBackgroundColor: this.state.viewBackgroundColor,
                              embedsValidationStatus: this.embedsValidationStatus,
                              elementsPendingErasure: this.elementsPendingErasure,
                              pendingFlowchartNodes: null
                            }
                          }
                        ),
                        /* @__PURE__ */ jsx147(
                          InteractiveCanvas_default,
                          {
                            containerRef: this.excalidrawContainerRef,
                            canvas: this.interactiveCanvas,
                            elementsMap,
                            visibleElements,
                            allElementsMap,
                            selectedElements,
                            sceneNonce,
                            selectionNonce: this.state.selectionElement?.versionNonce,
                            scale: window.devicePixelRatio,
                            appState: this.state,
                            device: this.device,
                            renderInteractiveSceneCallback: this.renderInteractiveSceneCallback,
                            handleCanvasRef: this.handleInteractiveCanvasRef,
                            onContextMenu: this.handleCanvasContextMenu,
                            onPointerMove: this.handleCanvasPointerMove,
                            onPointerUp: this.handleCanvasPointerUp,
                            onPointerCancel: this.removePointer,
                            onTouchMove: this.handleTouchMove,
                            onPointerDown: this.handleCanvasPointerDown,
                            onDoubleClick: this.handleCanvasDoubleClick
                          }
                        ),
                        this.state.userToFollow && /* @__PURE__ */ jsx147(
                          FollowMode_default,
                          {
                            width: this.state.width,
                            height: this.state.height,
                            userToFollow: this.state.userToFollow,
                            onDisconnect: this.maybeUnfollowRemoteUser
                          }
                        ),
                        this.renderFrameNames()
                      ]
                    }
                  ),
                  this.renderEmbeddables()
                ]
              }
            ) }) }) })
          }
        ) }) })
      }
    );
  }
  setPlugins(plugins) {
    Object.assign(this.plugins, plugins);
  }
  async onMagicFrameGenerate(magicFrame, source) {
    const generateDiagramToCode = this.plugins.diagramToCode?.generate;
    if (!generateDiagramToCode) {
      this.setState({
        errorMessage: "No diagram to code plugin found"
      });
      return;
    }
    const magicFrameChildren = getElementsOverlappingFrame(
      this.scene.getNonDeletedElements(),
      magicFrame
    ).filter((el) => !isMagicFrameElement(el));
    if (!magicFrameChildren.length) {
      if (source === "button") {
        this.setState({ errorMessage: "Cannot generate from an empty frame" });
        trackEvent("ai", "generate (no-children)", "d2c");
      } else {
        this.setActiveTool({ type: "magicframe" });
      }
      return;
    }
    const frameElement = this.insertIframeElement({
      sceneX: magicFrame.x + magicFrame.width + 30,
      sceneY: magicFrame.y,
      width: magicFrame.width,
      height: magicFrame.height
    });
    if (!frameElement) {
      return;
    }
    this.updateMagicGeneration({
      frameElement,
      data: { status: "pending" }
    });
    this.setState({
      selectedElementIds: { [frameElement.id]: true }
    });
    trackEvent("ai", "generate (start)", "d2c");
    try {
      const { html } = await generateDiagramToCode({
        frame: magicFrame,
        children: magicFrameChildren
      });
      trackEvent("ai", "generate (success)", "d2c");
      if (!html.trim()) {
        this.updateMagicGeneration({
          frameElement,
          data: {
            status: "error",
            code: "ERR_OAI",
            message: "Nothing genereated :("
          }
        });
        return;
      }
      const parsedHtml = html.includes("<!DOCTYPE html>") && html.includes("</html>") ? html.slice(
        html.indexOf("<!DOCTYPE html>"),
        html.indexOf("</html>") + "</html>".length
      ) : html;
      this.updateMagicGeneration({
        frameElement,
        data: { status: "done", html: parsedHtml }
      });
    } catch (error) {
      trackEvent("ai", "generate (failed)", "d2c");
      this.updateMagicGeneration({
        frameElement,
        data: {
          status: "error",
          code: "ERR_OAI",
          message: error.message || "Unknown error during generation"
        }
      });
    }
  }
  onIframeSrcCopy(element) {
    if (element.customData?.generationData?.status === "done") {
      copyTextToSystemClipboard(element.customData.generationData.html);
      this.setToast({
        message: "copied to clipboard",
        closable: false,
        duration: 1500
      });
    }
  }
  clearImageShapeCache(filesMap) {
    const files = filesMap ?? this.files;
    this.scene.getNonDeletedElements().forEach((element) => {
      if (isInitializedImageElement(element) && files[element.fileId]) {
        this.imageCache.delete(element.fileId);
        ShapeCache.delete(element);
      }
    });
  }
  async componentDidMount() {
    this.unmounted = false;
    this.excalidrawContainerValue.container = this.excalidrawContainerRef.current;
    if (define_import_meta_env_default.MODE === ENV.TEST || define_import_meta_env_default.DEV) {
      const setState = this.setState.bind(this);
      Object.defineProperties(window.h, {
        state: {
          configurable: true,
          get: () => {
            return this.state;
          }
        },
        setState: {
          configurable: true,
          value: (...args) => {
            return this.setState(...args);
          }
        },
        app: {
          configurable: true,
          value: this
        },
        history: {
          configurable: true,
          value: this.history
        },
        store: {
          configurable: true,
          value: this.store
        },
        fonts: {
          configurable: true,
          value: this.fonts
        }
      });
    }
    this.store.onStoreIncrementEmitter.on((increment) => {
      this.history.record(increment.elementsChange, increment.appStateChange);
    });
    this.scene.onUpdate(this.triggerRender);
    this.addEventListeners();
    if (this.props.autoFocus && this.excalidrawContainerRef.current) {
      this.focusContainer();
    }
    if (
      // bounding rects don't work in tests so updating
      // the state on init would result in making the test enviro run
      // in mobile breakpoint (0 width/height), making everything fail
      !isTestEnv()
    ) {
      this.refreshViewportBreakpoints();
      this.refreshEditorBreakpoints();
    }
    if (supportsResizeObserver && this.excalidrawContainerRef.current) {
      this.resizeObserver = new ResizeObserver(() => {
        this.refreshEditorBreakpoints();
        this.updateDOMRect();
      });
      this.resizeObserver?.observe(this.excalidrawContainerRef.current);
    }
    const searchParams = new URLSearchParams(window.location.search.slice(1));
    if (searchParams.has("web-share-target")) {
      this.restoreFileFromShare();
    } else {
      this.updateDOMRect(this.initializeScene);
    }
    if (isBrave() && !isMeasureTextSupported()) {
      this.setState({
        errorMessage: /* @__PURE__ */ jsx147(BraveMeasureTextError_default, {})
      });
    }
  }
  componentWillUnmount() {
    window.launchQueue?.setConsumer(() => {
    });
    this.renderer.destroy();
    this.scene.destroy();
    this.scene = new Scene_default();
    this.fonts = new Fonts(this.scene);
    this.renderer = new Renderer(this.scene);
    this.files = {};
    this.imageCache.clear();
    this.resizeObserver?.disconnect();
    this.unmounted = true;
    this.removeEventListeners();
    this.library.destroy();
    this.laserTrails.stop();
    this.eraserTrail.stop();
    this.onChangeEmitter.clear();
    this.store.onStoreIncrementEmitter.clear();
    ShapeCache.destroy();
    SnapCache.destroy();
    clearTimeout(touchTimeout);
    isSomeElementSelected.clearCache();
    selectGroupsForSelectedElements.clearCache();
    touchTimeout = 0;
    document.documentElement.style.overscrollBehaviorX = "";
  }
  removeEventListeners() {
    this.onRemoveEventListenersEmitter.trigger();
  }
  addEventListeners() {
    this.removeEventListeners();
    if (this.props.handleKeyboardGlobally) {
      this.onRemoveEventListenersEmitter.once(
        addEventListener(document, "keydown" /* KEYDOWN */, this.onKeyDown, false)
      );
    }
    this.onRemoveEventListenersEmitter.once(
      addEventListener(
        this.excalidrawContainerRef.current,
        "wheel" /* WHEEL */,
        this.handleWheel,
        { passive: false }
      ),
      addEventListener(window, "message" /* MESSAGE */, this.onWindowMessage, false),
      addEventListener(document, "pointerup" /* POINTER_UP */, this.removePointer, {
        passive: false
      }),
      // #3553
      addEventListener(document, "copy" /* COPY */, this.onCopy, { passive: false }),
      addEventListener(document, "keyup" /* KEYUP */, this.onKeyUp, { passive: true }),
      addEventListener(
        document,
        "pointermove" /* POINTER_MOVE */,
        this.updateCurrentCursorPosition,
        { passive: false }
      ),
      // rerender text elements on font load to fix #637 && #1553
      addEventListener(
        document.fonts,
        "loadingdone",
        (event) => {
          const fontFaces = event.fontfaces;
          this.fonts.onLoaded(fontFaces);
        },
        { passive: false }
      ),
      // Safari-only desktop pinch zoom
      addEventListener(
        document,
        "gesturestart" /* GESTURE_START */,
        this.onGestureStart,
        false
      ),
      addEventListener(
        document,
        "gesturechange" /* GESTURE_CHANGE */,
        this.onGestureChange,
        false
      ),
      addEventListener(
        document,
        "gestureend" /* GESTURE_END */,
        this.onGestureEnd,
        false
      ),
      addEventListener(
        window,
        "focus" /* FOCUS */,
        () => {
          this.maybeCleanupAfterMissingPointerUp(null);
          this.triggerRender(true);
        },
        { passive: false }
      )
    );
    if (this.state.viewModeEnabled) {
      return;
    }
    this.onRemoveEventListenersEmitter.once(
      addEventListener(
        document,
        "fullscreenchange" /* FULLSCREENCHANGE */,
        this.onFullscreenChange,
        { passive: false }
      ),
      addEventListener(document, "paste" /* PASTE */, this.pasteFromClipboard, {
        passive: false
      }),
      addEventListener(document, "cut" /* CUT */, this.onCut, { passive: false }),
      addEventListener(window, "resize" /* RESIZE */, this.onResize, false),
      addEventListener(window, "unload" /* UNLOAD */, this.onUnload, false),
      addEventListener(window, "blur" /* BLUR */, this.onBlur, false),
      addEventListener(
        this.excalidrawContainerRef.current,
        "wheel" /* WHEEL */,
        this.handleWheel,
        { passive: false }
      ),
      addEventListener(
        this.excalidrawContainerRef.current,
        "dragover" /* DRAG_OVER */,
        this.disableEvent,
        false
      ),
      addEventListener(
        this.excalidrawContainerRef.current,
        "drop" /* DROP */,
        this.disableEvent,
        false
      )
    );
    if (this.props.detectScroll) {
      this.onRemoveEventListenersEmitter.once(
        addEventListener(
          getNearestScrollableContainer(this.excalidrawContainerRef.current),
          "scroll" /* SCROLL */,
          this.onScroll,
          { passive: false }
        )
      );
    }
  }
  componentDidUpdate(prevProps, prevState) {
    this.updateEmbeddables();
    const elements = this.scene.getElementsIncludingDeleted();
    const elementsMap = this.scene.getElementsMapIncludingDeleted();
    const nonDeletedElementsMap = this.scene.getNonDeletedElementsMap();
    if (!this.state.showWelcomeScreen && !elements.length) {
      this.setState({ showWelcomeScreen: true });
    }
    if (prevProps.UIOptions.dockedSidebarBreakpoint !== this.props.UIOptions.dockedSidebarBreakpoint) {
      this.refreshEditorBreakpoints();
    }
    const hasFollowedPersonLeft = prevState.userToFollow && !this.state.collaborators.has(prevState.userToFollow.socketId);
    if (hasFollowedPersonLeft) {
      this.maybeUnfollowRemoteUser();
    }
    if (prevState.zoom.value !== this.state.zoom.value || prevState.scrollX !== this.state.scrollX || prevState.scrollY !== this.state.scrollY) {
      this.props?.onScrollChange?.(
        this.state.scrollX,
        this.state.scrollY,
        this.state.zoom
      );
      this.onScrollChangeEmitter.trigger(
        this.state.scrollX,
        this.state.scrollY,
        this.state.zoom
      );
    }
    if (prevState.userToFollow !== this.state.userToFollow) {
      if (prevState.userToFollow) {
        this.onUserFollowEmitter.trigger({
          userToFollow: prevState.userToFollow,
          action: "UNFOLLOW"
        });
      }
      if (this.state.userToFollow) {
        this.onUserFollowEmitter.trigger({
          userToFollow: this.state.userToFollow,
          action: "FOLLOW"
        });
      }
    }
    if (Object.keys(this.state.selectedElementIds).length && isEraserActive(this.state)) {
      this.setState({
        activeTool: updateActiveTool(this.state, { type: "selection" })
      });
    }
    if (this.state.activeTool.type === "eraser" && prevState.theme !== this.state.theme) {
      setEraserCursor(this.interactiveCanvas, this.state.theme);
    }
    if (prevState.activeTool.type === "selection" && this.state.activeTool.type !== "selection" && this.state.showHyperlinkPopup) {
      this.setState({ showHyperlinkPopup: false });
    }
    if (prevProps.langCode !== this.props.langCode) {
      this.updateLanguage();
    }
    if (isEraserActive(prevState) && !isEraserActive(this.state)) {
      this.eraserTrail.endPath();
    }
    if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
      this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
    }
    if (prevState.viewModeEnabled !== this.state.viewModeEnabled) {
      this.addEventListeners();
      this.deselectElements();
    }
    if ((prevState.openDialog?.name === "elementLinkSelector" || this.state.openDialog?.name === "elementLinkSelector") && prevState.openDialog?.name !== this.state.openDialog?.name) {
      this.deselectElements();
      this.setState({
        hoveredElementIds: {}
      });
    }
    if (prevProps.zenModeEnabled !== this.props.zenModeEnabled) {
      this.setState({ zenModeEnabled: !!this.props.zenModeEnabled });
    }
    if (prevProps.theme !== this.props.theme && this.props.theme) {
      this.setState({ theme: this.props.theme });
    }
    this.excalidrawContainerRef.current?.classList.toggle(
      "theme--dark",
      this.state.theme === THEME.DARK
    );
    if (this.state.editingLinearElement && !this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
      setTimeout(() => {
        this.state.editingLinearElement && this.actionManager.executeAction(actionFinalize);
      });
    }
    if (this.state.editingTextElement?.isDeleted) {
      this.setState({ editingTextElement: null });
    }
    if (this.state.selectedLinearElement && !this.state.selectedElementIds[this.state.selectedLinearElement.elementId]) {
      this.setState({ selectedLinearElement: null });
    }
    const { multiElement } = prevState;
    if (prevState.activeTool !== this.state.activeTool && multiElement != null && isBindingEnabled(this.state) && isBindingElement(multiElement, false)) {
      maybeBindLinearElement(
        multiElement,
        this.state,
        tupleToCoors(
          LinearElementEditor.getPointAtIndexGlobalCoordinates(
            multiElement,
            -1,
            nonDeletedElementsMap
          )
        ),
        this.scene.getNonDeletedElementsMap(),
        this.scene.getNonDeletedElements()
      );
    }
    this.store.commit(elementsMap, this.state);
    if (!this.state.isLoading) {
      this.props.onChange?.(elements, this.state, this.files);
      this.onChangeEmitter.trigger(elements, this.state, this.files);
    }
  }
  static resetTapTwice() {
    didTapTwice = false;
  }
  // TODO rewrite this to paste both text & images at the same time if
  // pasted data contains both
  async addElementsFromMixedContentPaste(mixedContent, {
    isPlainPaste,
    sceneX,
    sceneY
  }) {
    if (!isPlainPaste && mixedContent.some((node) => node.type === "imageUrl") && this.isToolSupported("image")) {
      const imageURLs = mixedContent.filter((node) => node.type === "imageUrl").map((node) => node.value);
      const responses = await Promise.all(
        imageURLs.map(async (url) => {
          try {
            return { file: await ImageURLToFile(url) };
          } catch (error2) {
            let errorMessage = error2.message;
            if (error2.cause === "FETCH_ERROR") {
              errorMessage = t("errors.failedToFetchImage");
            } else if (error2.cause === "UNSUPPORTED") {
              errorMessage = t("errors.unsupportedFileType");
            }
            return { errorMessage };
          }
        })
      );
      let y = sceneY;
      let firstImageYOffsetDone = false;
      const nextSelectedIds = {};
      for (const response of responses) {
        if (response.file) {
          const imageElement = this.createImageElement({
            sceneX,
            sceneY: y
          });
          const initializedImageElement = await this.insertImageElement(
            imageElement,
            response.file
          );
          if (initializedImageElement) {
            if (!firstImageYOffsetDone) {
              firstImageYOffsetDone = true;
              y -= initializedImageElement.height / 2;
            }
            mutateElement(initializedImageElement, { y }, false);
            y = imageElement.y + imageElement.height + 25;
            nextSelectedIds[imageElement.id] = true;
          }
        }
      }
      this.setState({
        selectedElementIds: makeNextSelectedElementIds(
          nextSelectedIds,
          this.state
        )
      });
      const error = responses.find((response) => !!response.errorMessage);
      if (error && error.errorMessage) {
        this.setState({ errorMessage: error.errorMessage });
      }
    } else {
      const textNodes = mixedContent.filter((node) => node.type === "text");
      if (textNodes.length) {
        this.addTextFromPaste(
          textNodes.map((node) => node.value).join("\n\n"),
          isPlainPaste
        );
      }
    }
  }
  addTextFromPaste(text, isPlainPaste = false) {
    const { x, y } = viewportCoordsToSceneCoords(
      {
        clientX: this.lastViewportPosition.x,
        clientY: this.lastViewportPosition.y
      },
      this.state
    );
    const textElementProps = {
      x,
      y,
      strokeColor: this.state.currentItemStrokeColor,
      backgroundColor: this.state.currentItemBackgroundColor,
      fillStyle: this.state.currentItemFillStyle,
      strokeWidth: this.state.currentItemStrokeWidth,
      strokeStyle: this.state.currentItemStrokeStyle,
      roundness: null,
      roughness: this.state.currentItemRoughness,
      opacity: this.state.currentItemOpacity,
      text,
      fontSize: this.state.currentItemFontSize,
      fontFamily: this.state.currentItemFontFamily,
      textAlign: DEFAULT_TEXT_ALIGN,
      verticalAlign: DEFAULT_VERTICAL_ALIGN,
      locked: false
    };
    const fontString = getFontString({
      fontSize: textElementProps.fontSize,
      fontFamily: textElementProps.fontFamily
    });
    const lineHeight = getLineHeight(textElementProps.fontFamily);
    const [x1, , x2] = getVisibleSceneBounds(this.state);
    const maxTextWidth = Math.max(Math.min((x2 - x1) * 0.5, 800), 200);
    const LINE_GAP = 10;
    let currentY = y;
    const lines = isPlainPaste ? [text] : text.split("\n");
    const textElements = lines.reduce(
      (acc, line, idx) => {
        const originalText = normalizeText(line).trim();
        if (originalText.length) {
          const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
            x,
            y: currentY
          });
          let metrics = measureText(originalText, fontString, lineHeight);
          const isTextUnwrapped = metrics.width > maxTextWidth;
          const text2 = isTextUnwrapped ? wrapText(originalText, fontString, maxTextWidth) : originalText;
          metrics = isTextUnwrapped ? measureText(text2, fontString, lineHeight) : metrics;
          const startX = x - metrics.width / 2;
          const startY = currentY - metrics.height / 2;
          const element = newTextElement({
            ...textElementProps,
            x: startX,
            y: startY,
            text: text2,
            originalText,
            lineHeight,
            autoResize: !isTextUnwrapped,
            frameId: topLayerFrame ? topLayerFrame.id : null
          });
          acc.push(element);
          currentY += element.height + LINE_GAP;
        } else {
          const prevLine = lines[idx - 1]?.trim();
          if (prevLine) {
            currentY += getLineHeightInPx(textElementProps.fontSize, lineHeight) + LINE_GAP;
          }
        }
        return acc;
      },
      []
    );
    if (textElements.length === 0) {
      return;
    }
    this.scene.insertElements(textElements);
    this.setState({
      selectedElementIds: makeNextSelectedElementIds(
        Object.fromEntries(textElements.map((el) => [el.id, true])),
        this.state
      )
    });
    if (!isPlainPaste && textElements.length > 1 && PLAIN_PASTE_TOAST_SHOWN === false && !this.device.editor.isMobile) {
      this.setToast({
        message: t("toast.pasteAsSingleElement", {
          shortcut: getShortcutKey("CtrlOrCmd+Shift+V")
        }),
        duration: 5e3
      });
      PLAIN_PASTE_TOAST_SHOWN = true;
    }
    this.store.shouldCaptureIncrement();
  }
  handleTextWysiwyg(element, {
    isExistingElement = false
  }) {
    const elementsMap = this.scene.getElementsMapIncludingDeleted();
    const updateElement = (nextOriginalText, isDeleted) => {
      this.scene.replaceAllElements([
        // Not sure why we include deleted elements as well hence using deleted elements map
        ...this.scene.getElementsIncludingDeleted().map((_element) => {
          if (_element.id === element.id && isTextElement(_element)) {
            return newElementWith(_element, {
              originalText: nextOriginalText,
              isDeleted: isDeleted ?? _element.isDeleted,
              // returns (wrapped) text and new dimensions
              ...refreshTextDimensions(
                _element,
                getContainerElement(_element, elementsMap),
                elementsMap,
                nextOriginalText
              )
            });
          }
          return _element;
        })
      ]);
    };
    textWysiwyg({
      id: element.id,
      canvas: this.canvas,
      getViewportCoords: (x, y) => {
        const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
          {
            sceneX: x,
            sceneY: y
          },
          this.state
        );
        return [
          viewportX - this.state.offsetLeft,
          viewportY - this.state.offsetTop
        ];
      },
      onChange: withBatchedUpdates((nextOriginalText) => {
        updateElement(nextOriginalText, false);
        if (isNonDeletedElement(element)) {
          updateBoundElements(element, this.scene.getNonDeletedElementsMap());
        }
      }),
      onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => {
        const isDeleted = !nextOriginalText.trim();
        updateElement(nextOriginalText, isDeleted);
        if (!isDeleted && viaKeyboard) {
          const elementIdToSelect = element.containerId ? element.containerId : element.id;
          flushSync2(() => {
            this.setState((prevState) => ({
              selectedElementIds: makeNextSelectedElementIds(
                {
                  ...prevState.selectedElementIds,
                  [elementIdToSelect]: true
                },
                prevState
              )
            }));
          });
        }
        if (isDeleted) {
          fixBindingsAfterDeletion(this.scene.getNonDeletedElements(), [
            element
          ]);
        }
        if (!isDeleted || isExistingElement) {
          this.store.shouldCaptureIncrement();
        }
        flushSync2(() => {
          this.setState({
            newElement: null,
            editingTextElement: null
          });
        });
        if (this.state.activeTool.locked) {
          setCursorForShape(this.interactiveCanvas, this.state);
        }
        this.focusContainer();
      }),
      element,
      excalidrawContainer: this.excalidrawContainerRef.current,
      app: this,
      // when text is selected, it's hard (at least on iOS) to re-position the
      // caret (i.e. deselect). There's not much use for always selecting
      // the text on edit anyway (and users can select-all from contextmenu
      // if needed)
      autoSelect: !this.device.isTouchScreen
    });
    this.deselectElements();
    updateElement(element.originalText, false);
  }
  deselectElements() {
    this.setState({
      selectedElementIds: makeNextSelectedElementIds({}, this.state),
      selectedGroupIds: {},
      editingGroupId: null,
      activeEmbeddable: null
    });
  }
  getTextElementAtPosition(x, y) {
    const element = this.getElementAtPosition(x, y, {
      includeBoundTextElement: true
    });
    if (element && isTextElement(element) && !element.isDeleted) {
      return element;
    }
    return null;
  }
  getElementAtPosition(x, y, opts) {
    const allHitElements = this.getElementsAtPosition(
      x,
      y,
      opts?.includeBoundTextElement,
      opts?.includeLockedElements
    );
    if (allHitElements.length > 1) {
      if (opts?.preferSelected) {
        for (let index = allHitElements.length - 1; index > -1; index--) {
          if (this.state.selectedElementIds[allHitElements[index].id]) {
            return allHitElements[index];
          }
        }
      }
      const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
      return hitElementItself({
        x,
        y,
        element: elementWithHighestZIndex,
        shape: getElementShape(
          elementWithHighestZIndex,
          this.scene.getNonDeletedElementsMap()
        ),
        // when overlapping, we would like to be more precise
        // this also avoids the need to update past tests
        threshold: this.getElementHitThreshold() / 2,
        frameNameBound: isFrameLikeElement(elementWithHighestZIndex) ? this.frameNameBoundsCache.get(elementWithHighestZIndex) : null
      }) ? elementWithHighestZIndex : allHitElements[allHitElements.length - 2];
    }
    if (allHitElements.length === 1) {
      return allHitElements[0];
    }
    return null;
  }
  getElementsAtPosition(x, y, includeBoundTextElement = false, includeLockedElements = false) {
    const iframeLikes = [];
    const elementsMap = this.scene.getNonDeletedElementsMap();
    const elements = (includeBoundTextElement && includeLockedElements ? this.scene.getNonDeletedElements() : this.scene.getNonDeletedElements().filter(
      (element) => (includeLockedElements || !element.locked) && (includeBoundTextElement || !(isTextElement(element) && element.containerId))
    )).filter((el) => this.hitElement(x, y, el)).filter((element) => {
      const containingFrame = getContainingFrame(element, elementsMap);
      return containingFrame && this.state.frameRendering.enabled && this.state.frameRendering.clip ? isCursorInFrame({ x, y }, containingFrame, elementsMap) : true;
    }).filter((el) => {
      if (isIframeElement(el)) {
        iframeLikes.push(el);
        return false;
      }
      return true;
    }).concat(iframeLikes);
    return elements;
  }
  getElementHitThreshold() {
    return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
  }
  hitElement(x, y, element, considerBoundingBox = true) {
    if (considerBoundingBox && this.state.selectedElementIds[element.id] && shouldShowBoundingBox([element], this.state)) {
      const selectionShape = getSelectionBoxShape(
        element,
        this.scene.getNonDeletedElementsMap(),
        isImageElement(element) ? 0 : this.getElementHitThreshold()
      );
      if (isPointInShape(pointFrom(x, y), selectionShape)) {
        return true;
      }
    }
    const hitBoundTextOfElement = hitElementBoundText(
      x,
      y,
      getBoundTextShape(element, this.scene.getNonDeletedElementsMap())
    );
    if (hitBoundTextOfElement) {
      return true;
    }
    return hitElementItself({
      x,
      y,
      element,
      shape: getElementShape(element, this.scene.getNonDeletedElementsMap()),
      threshold: this.getElementHitThreshold(),
      frameNameBound: isFrameLikeElement(element) ? this.frameNameBoundsCache.get(element) : null
    });
  }
  getTextBindableContainerAtPosition(x, y) {
    const elements = this.scene.getNonDeletedElements();
    const selectedElements = this.scene.getSelectedElements(this.state);
    if (selectedElements.length === 1) {
      return isTextBindableContainer(selectedElements[0], false) ? selectedElements[0] : null;
    }
    let hitElement = null;
    for (let index = elements.length - 1; index >= 0; --index) {
      if (elements[index].isDeleted) {
        continue;
      }
      const [x1, y1, x2, y2] = getElementAbsoluteCoords(
        elements[index],
        this.scene.getNonDeletedElementsMap()
      );
      if (isArrowElement(elements[index]) && hitElementItself({
        x,
        y,
        element: elements[index],
        shape: getElementShape(
          elements[index],
          this.scene.getNonDeletedElementsMap()
        ),
        threshold: this.getElementHitThreshold()
      })) {
        hitElement = elements[index];
        break;
      } else if (x1 < x && x < x2 && y1 < y && y < y2) {
        hitElement = elements[index];
        break;
      }
    }
    return isTextBindableContainer(hitElement, false) ? hitElement : null;
  }
  handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
    const elementsMap = this.scene.getNonDeletedElementsMap();
    const element = LinearElementEditor.getElement(
      linearElementEditor.elementId,
      elementsMap
    );
    if (!element) {
      return;
    }
    if (this.state.selectedLinearElement) {
      let hoverPointIndex = -1;
      let segmentMidPointHoveredCoords = null;
      if (hitElementItself({
        x: scenePointerX,
        y: scenePointerY,
        element,
        shape: getElementShape(
          element,
          this.scene.getNonDeletedElementsMap()
        )
      })) {
        hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
          element,
          elementsMap,
          this.state.zoom,
          scenePointerX,
          scenePointerY
        );
        segmentMidPointHoveredCoords = LinearElementEditor.getSegmentMidpointHitCoords(
          linearElementEditor,
          { x: scenePointerX, y: scenePointerY },
          this.state,
          this.scene.getNonDeletedElementsMap()
        );
        const isHoveringAPointHandle = isElbowArrow(element) ? hoverPointIndex === 0 || hoverPointIndex === element.points.length - 1 : hoverPointIndex >= 0;
        if (isHoveringAPointHandle || segmentMidPointHoveredCoords) {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
        } else if (this.hitElement(scenePointerX, scenePointerY, element)) {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
        }
      } else if (this.hitElement(scenePointerX, scenePointerY, element)) {
        if (
          // Ebow arrows can only be moved when unconnected
          !isElbowArrow(element) || !(element.startBinding || element.endBinding)
        ) {
          setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
        }
      }
      if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
        this.setState({
          selectedLinearElement: {
            ...this.state.selectedLinearElement,
            hoverPointIndex
          }
        });
      }
      if (!LinearElementEditor.arePointsEqual(
        this.state.selectedLinearElement.segmentMidPointHoveredCoords,
        segmentMidPointHoveredCoords
      )) {
        this.setState({
          selectedLinearElement: {
            ...this.state.selectedLinearElement,
            segmentMidPointHoveredCoords
          }
        });
      }
    } else {
      setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
    }
  }
  updateGestureOnPointerDown(event) {
    gesture.pointers.set(event.pointerId, {
      x: event.clientX,
      y: event.clientY
    });
    if (gesture.pointers.size === 2) {
      gesture.lastCenter = getCenter(gesture.pointers);
      gesture.initialScale = this.state.zoom.value;
      gesture.initialDistance = getDistance(
        Array.from(gesture.pointers.values())
      );
    }
  }
  initialPointerDownState(event) {
    const origin = viewportCoordsToSceneCoords(event, this.state);
    const selectedElements = this.scene.getSelectedElements(this.state);
    const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
    const isElbowArrowOnly = selectedElements.findIndex(isElbowArrow) === 0;
    return {
      origin,
      withCmdOrCtrl: event[KEYS.CTRL_OR_CMD],
      originInGrid: tupleToCoors(
        getGridPoint(
          origin.x,
          origin.y,
          event[KEYS.CTRL_OR_CMD] || isElbowArrowOnly ? null : this.getEffectiveGridSize()
        )
      ),
      scrollbars: isOverScrollBars(
        currentScrollBars,
        event.clientX - this.state.offsetLeft,
        event.clientY - this.state.offsetTop
      ),
      // we need to duplicate because we'll be updating this state
      lastCoords: { ...origin },
      originalElements: this.scene.getNonDeletedElements().reduce((acc, element) => {
        acc.set(element.id, deepCopyElement(element));
        return acc;
      }, /* @__PURE__ */ new Map()),
      resize: {
        handleType: false,
        isResizing: false,
        offset: { x: 0, y: 0 },
        arrowDirection: "origin",
        center: { x: (maxX + minX) / 2, y: (maxY + minY) / 2 }
      },
      hit: {
        element: null,
        allHitElements: [],
        wasAddedToSelection: false,
        hasBeenDuplicated: false,
        hasHitCommonBoundingBoxOfSelectedElements: this.isHittingCommonBoundingBoxOfSelectedElements(
          origin,
          selectedElements
        )
      },
      drag: {
        hasOccurred: false,
        offset: null
      },
      eventListeners: {
        onMove: null,
        onUp: null,
        onKeyUp: null,
        onKeyDown: null
      },
      boxSelection: {
        hasOccurred: false
      }
    };
  }
  // Returns whether the event is a dragging a scrollbar
  handleDraggingScrollBar(event, pointerDownState) {
    if (!(pointerDownState.scrollbars.isOverEither && !this.state.multiElement)) {
      return false;
    }
    isDraggingScrollBar = true;
    pointerDownState.lastCoords.x = event.clientX;
    pointerDownState.lastCoords.y = event.clientY;
    const onPointerMove = withBatchedUpdatesThrottled((event2) => {
      const target = event2.target;
      if (!(target instanceof HTMLElement)) {
        return;
      }
      this.handlePointerMoveOverScrollbars(event2, pointerDownState);
    });
    const onPointerUp = withBatchedUpdates(() => {
      lastPointerUp = null;
      isDraggingScrollBar = false;
      setCursorForShape(this.interactiveCanvas, this.state);
      this.setState({
        cursorButton: "up"
      });
      this.savePointer(event.clientX, event.clientY, "up");
      window.removeEventListener("pointermove" /* POINTER_MOVE */, onPointerMove);
      window.removeEventListener("pointerup" /* POINTER_UP */, onPointerUp);
      onPointerMove.flush();
    });
    lastPointerUp = onPointerUp;
    window.addEventListener("pointermove" /* POINTER_MOVE */, onPointerMove);
    window.addEventListener("pointerup" /* POINTER_UP */, onPointerUp);
    return true;
  }
  isASelectedElement(hitElement) {
    return hitElement != null && this.state.selectedElementIds[hitElement.id];
  }
  isHittingCommonBoundingBoxOfSelectedElements(point, selectedElements) {
    if (selectedElements.length < 2) {
      return false;
    }
    const threshold = this.getElementHitThreshold();
    const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
    return point.x > x1 - threshold && point.x < x2 + threshold && point.y > y1 - threshold && point.y < y2 + threshold;
  }
  getCurrentItemRoundness(elementType) {
    return this.state.currentItemRoundness === "round" ? {
      type: isUsingAdaptiveRadius(elementType) ? ROUNDNESS.ADAPTIVE_RADIUS : ROUNDNESS.PROPORTIONAL_RADIUS
    } : null;
  }
  maybeCacheReferenceSnapPoints(event, selectedElements, recomputeAnyways = false) {
    if (isSnappingEnabled({
      event,
      app: this,
      selectedElements
    }) && (recomputeAnyways || !SnapCache.getReferenceSnapPoints())) {
      SnapCache.setReferenceSnapPoints(
        getReferenceSnapPoints(
          this.scene.getNonDeletedElements(),
          selectedElements,
          this.state,
          this.scene.getNonDeletedElementsMap()
        )
      );
    }
  }
  maybeCacheVisibleGaps(event, selectedElements, recomputeAnyways = false) {
    if (isSnappingEnabled({
      event,
      app: this,
      selectedElements
    }) && (recomputeAnyways || !SnapCache.getVisibleGaps())) {
      SnapCache.setVisibleGaps(
        getVisibleGaps(
          this.scene.getNonDeletedElements(),
          selectedElements,
          this.state,
          this.scene.getNonDeletedElementsMap()
        )
      );
    }
  }
  onKeyDownFromPointerDownHandler(pointerDownState) {
    return withBatchedUpdates((event) => {
      if (this.maybeHandleResize(pointerDownState, event)) {
        return;
      }
      this.maybeDragNewGenericElement(pointerDownState, event);
    });
  }
  onKeyUpFromPointerDownHandler(pointerDownState) {
    return withBatchedUpdates((event) => {
      event.key === KEYS.ALT && event.preventDefault();
      if (this.maybeHandleResize(pointerDownState, event)) {
        return;
      }
      this.maybeDragNewGenericElement(pointerDownState, event);
    });
  }
  onPointerMoveFromPointerDownHandler(pointerDownState) {
    return withBatchedUpdatesThrottled((event) => {
      if (this.state.openDialog?.name === "elementLinkSelector") {
        return;
      }
      const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
      if (this.state.selectedLinearElement && this.state.selectedLinearElement.elbowed && this.state.selectedLinearElement.pointerDownState.segmentMidpoint.index) {
        const [gridX2, gridY2] = getGridPoint(
          pointerCoords.x,
          pointerCoords.y,
          event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
        );
        let index = this.state.selectedLinearElement.pointerDownState.segmentMidpoint.index;
        if (index < 0) {
          const nextCoords = LinearElementEditor.getSegmentMidpointHitCoords(
            {
              ...this.state.selectedLinearElement,
              segmentMidPointHoveredCoords: null
            },
            { x: gridX2, y: gridY2 },
            this.state,
            this.scene.getNonDeletedElementsMap()
          );
          index = nextCoords ? LinearElementEditor.getSegmentMidPointIndex(
            this.state.selectedLinearElement,
            this.state,
            nextCoords,
            this.scene.getNonDeletedElementsMap()
          ) : -1;
        }
        const ret = LinearElementEditor.moveFixedSegment(
          this.state.selectedLinearElement,
          index,
          gridX2,
          gridY2,
          this.scene.getNonDeletedElementsMap()
        );
        flushSync2(() => {
          if (this.state.selectedLinearElement) {
            this.setState({
              selectedLinearElement: {
                ...this.state.selectedLinearElement,
                segmentMidPointHoveredCoords: ret.segmentMidPointHoveredCoords,
                pointerDownState: ret.pointerDownState
              }
            });
          }
        });
        return;
      }
      const lastPointerCoords = this.lastPointerMoveCoords ?? pointerDownState.origin;
      this.lastPointerMoveCoords = pointerCoords;
      if (pointerDownState.drag.offset === null) {
        pointerDownState.drag.offset = tupleToCoors(
          getDragOffsetXY(
            this.scene.getSelectedElements(this.state),
            pointerDownState.origin.x,
            pointerDownState.origin.y
          )
        );
      }
      const target = event.target;
      if (!(target instanceof HTMLElement)) {
        return;
      }
      if (this.handlePointerMoveOverScrollbars(event, pointerDownState)) {
        return;
      }
      if (isEraserActive(this.state)) {
        this.handleEraser(event, pointerDownState, pointerCoords);
        return;
      }
      if (this.state.activeTool.type === "laser") {
        this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
      }
      const [gridX, gridY] = getGridPoint(
        pointerCoords.x,
        pointerCoords.y,
        event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
      );
      if (!pointerDownState.drag.hasOccurred && (this.state.activeTool.type === "arrow" || this.state.activeTool.type === "line")) {
        if (pointDistance(
          pointFrom(pointerCoords.x, pointerCoords.y),
          pointFrom(pointerDownState.origin.x, pointerDownState.origin.y)
        ) < DRAGGING_THRESHOLD) {
          return;
        }
      }
      if (pointerDownState.resize.isResizing) {
        pointerDownState.lastCoords.x = pointerCoords.x;
        pointerDownState.lastCoords.y = pointerCoords.y;
        if (this.maybeHandleCrop(pointerDownState, event)) {
          return true;
        }
        if (this.maybeHandleResize(pointerDownState, event)) {
          return true;
        }
      }
      const elementsMap = this.scene.getNonDeletedElementsMap();
      if (this.state.selectedLinearElement) {
        const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
        if (LinearElementEditor.shouldAddMidpoint(
          this.state.selectedLinearElement,
          pointerCoords,
          this.state,
          elementsMap
        )) {
          const ret = LinearElementEditor.addMidpoint(
            this.state.selectedLinearElement,
            pointerCoords,
            this,
            !event[KEYS.CTRL_OR_CMD],
            elementsMap
          );
          if (!ret) {
            return;
          }
          flushSync2(() => {
            if (this.state.selectedLinearElement) {
              this.setState({
                selectedLinearElement: {
                  ...this.state.selectedLinearElement,
                  pointerDownState: ret.pointerDownState,
                  selectedPointsIndices: ret.selectedPointsIndices
                }
              });
            }
            if (this.state.editingLinearElement) {
              this.setState({
                editingLinearElement: {
                  ...this.state.editingLinearElement,
                  pointerDownState: ret.pointerDownState,
                  selectedPointsIndices: ret.selectedPointsIndices
                }
              });
            }
          });
          return;
        } else if (linearElementEditor.pointerDownState.segmentMidpoint.value !== null && !linearElementEditor.pointerDownState.segmentMidpoint.added) {
          return;
        }
        const didDrag = LinearElementEditor.handlePointDragging(
          event,
          this,
          pointerCoords.x,
          pointerCoords.y,
          (element, pointsSceneCoords) => {
            this.maybeSuggestBindingsForLinearElementAtCoords(
              element,
              pointsSceneCoords
            );
          },
          linearElementEditor,
          this.scene
        );
        if (didDrag) {
          pointerDownState.lastCoords.x = pointerCoords.x;
          pointerDownState.lastCoords.y = pointerCoords.y;
          pointerDownState.drag.hasOccurred = true;
          if (this.state.editingLinearElement && !this.state.editingLinearElement.isDragging) {
            this.setState({
              editingLinearElement: {
                ...this.state.editingLinearElement,
                isDragging: true
              }
            });
          }
          if (!this.state.selectedLinearElement.isDragging) {
            this.setState({
              selectedLinearElement: {
                ...this.state.selectedLinearElement,
                isDragging: true
              }
            });
          }
          return;
        }
      }
      const hasHitASelectedElement = pointerDownState.hit.allHitElements.some(
        (element) => this.isASelectedElement(element)
      );
      const isSelectingPointsInLineEditor = this.state.editingLinearElement && event.shiftKey && this.state.editingLinearElement.elementId === pointerDownState.hit.element?.id;
      if ((hasHitASelectedElement || pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements) && !isSelectingPointsInLineEditor) {
        const selectedElements = this.scene.getSelectedElements(this.state);
        if (selectedElements.every((element) => element.locked)) {
          return;
        }
        const selectedElementsHasAFrame = selectedElements.find(
          (e) => isFrameLikeElement(e)
        );
        const topLayerFrame = this.getTopLayerFrameAtSceneCoords(pointerCoords);
        const frameToHighlight = topLayerFrame && !selectedElementsHasAFrame ? topLayerFrame : null;
        if (this.state.frameToHighlight !== frameToHighlight) {
          flushSync2(() => {
            this.setState({ frameToHighlight });
          });
        }
        pointerDownState.drag.hasOccurred = true;
        if (selectedElements.length > 0 && !pointerDownState.withCmdOrCtrl && !this.state.editingTextElement && this.state.activeEmbeddable?.state !== "active") {
          const dragOffset = {
            x: pointerCoords.x - pointerDownState.origin.x,
            y: pointerCoords.y - pointerDownState.origin.y
          };
          const originalElements = [
            ...pointerDownState.originalElements.values()
          ];
          const lockDirection = event.shiftKey;
          if (lockDirection) {
            const distanceX = Math.abs(dragOffset.x);
            const distanceY = Math.abs(dragOffset.y);
            const lockX = lockDirection && distanceX < distanceY;
            const lockY = lockDirection && distanceX > distanceY;
            if (lockX) {
              dragOffset.x = 0;
            }
            if (lockY) {
              dragOffset.y = 0;
            }
          }
          if (this.state.croppingElementId) {
            const croppingElement = this.scene.getNonDeletedElementsMap().get(this.state.croppingElementId);
            if (croppingElement && isImageElement(croppingElement) && croppingElement.crop !== null && pointerDownState.hit.element === croppingElement) {
              const crop = croppingElement.crop;
              const image = isInitializedImageElement(croppingElement) && this.imageCache.get(croppingElement.fileId)?.image;
              if (image && !(image instanceof Promise)) {
                const instantDragOffset = vectorScale(
                  vector(
                    pointerCoords.x - lastPointerCoords.x,
                    pointerCoords.y - lastPointerCoords.y
                  ),
                  Math.max(this.state.zoom.value, 2)
                );
                const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
                  croppingElement,
                  elementsMap
                );
                const topLeft = vectorFromPoint(
                  pointRotateRads(
                    pointFrom(x1, y1),
                    pointFrom(cx, cy),
                    croppingElement.angle
                  )
                );
                const topRight = vectorFromPoint(
                  pointRotateRads(
                    pointFrom(x2, y1),
                    pointFrom(cx, cy),
                    croppingElement.angle
                  )
                );
                const bottomLeft = vectorFromPoint(
                  pointRotateRads(
                    pointFrom(x1, y2),
                    pointFrom(cx, cy),
                    croppingElement.angle
                  )
                );
                const topEdge = vectorNormalize(
                  vectorSubtract(topRight, topLeft)
                );
                const leftEdge = vectorNormalize(
                  vectorSubtract(bottomLeft, topLeft)
                );
                const offsetVector = vector(
                  vectorDot(instantDragOffset, topEdge),
                  vectorDot(instantDragOffset, leftEdge)
                );
                const nextCrop = {
                  ...crop,
                  x: clamp(
                    crop.x - offsetVector[0] * Math.sign(croppingElement.scale[0]),
                    0,
                    image.naturalWidth - crop.width
                  ),
                  y: clamp(
                    crop.y - offsetVector[1] * Math.sign(croppingElement.scale[1]),
                    0,
                    image.naturalHeight - crop.height
                  )
                };
                mutateElement(croppingElement, {
                  crop: nextCrop
                });
                return;
              }
            }
          }
          this.maybeCacheVisibleGaps(event, selectedElements);
          this.maybeCacheReferenceSnapPoints(event, selectedElements);
          const { snapOffset, snapLines } = snapDraggedElements(
            originalElements,
            dragOffset,
            this,
            event,
            this.scene.getNonDeletedElementsMap()
          );
          this.setState({ snapLines });
          if (!this.state.editingFrame) {
            dragSelectedElements(
              pointerDownState,
              selectedElements,
              dragOffset,
              this.scene,
              snapOffset,
              event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize()
            );
          }
          this.setState({
            selectedElementsAreBeingDragged: true,
            // element is being dragged and selectionElement that was created on pointer down
            // should be removed
            selectionElement: null
          });
          if (selectedElements.length !== 1 || !isElbowArrow(selectedElements[0])) {
            this.setState({
              suggestedBindings: getSuggestedBindingsForArrows(
                selectedElements,
                this.scene.getNonDeletedElementsMap(),
                this.state.zoom
              )
            });
          }
          if (event.altKey && !pointerDownState.hit.hasBeenDuplicated) {
            pointerDownState.hit.hasBeenDuplicated = true;
            const nextElements = [];
            const elementsToAppend = [];
            const groupIdMap = /* @__PURE__ */ new Map();
            const oldIdToDuplicatedId = /* @__PURE__ */ new Map();
            const hitElement = pointerDownState.hit.element;
            const selectedElementIds = new Set(
              this.scene.getSelectedElements({
                selectedElementIds: this.state.selectedElementIds,
                includeBoundTextElement: true,
                includeElementsInFrames: true
              }).map((element) => element.id)
            );
            const elements = this.scene.getElementsIncludingDeleted();
            for (const element of elements) {
              const isInSelection = selectedElementIds.has(element.id) || // case: the state.selectedElementIds might not have been
              // updated yet by the time this mousemove event is fired
              element.id === hitElement?.id && pointerDownState.hit.wasAddedToSelection;
              if (Math.abs(element.x) > 1e7 || Math.abs(element.x) > 1e7 || Math.abs(element.width) > 1e7 || Math.abs(element.height) > 1e7) {
                console.error(
                  `Alt+dragging element in scene with invalid dimensions`,
                  element.x,
                  element.y,
                  element.width,
                  element.height,
                  isInSelection
                );
                return;
              }
              if (isInSelection) {
                const duplicatedElement = duplicateElement(
                  this.state.editingGroupId,
                  groupIdMap,
                  element
                );
                if (Math.abs(duplicatedElement.x) > 1e7 || Math.abs(duplicatedElement.x) > 1e7 || Math.abs(duplicatedElement.width) > 1e7 || Math.abs(duplicatedElement.height) > 1e7) {
                  console.error(
                    `Alt+dragging duplicated element with invalid dimensions`,
                    duplicatedElement.x,
                    duplicatedElement.y,
                    duplicatedElement.width,
                    duplicatedElement.height
                  );
                  return;
                }
                const origElement = pointerDownState.originalElements.get(
                  element.id
                );
                if (Math.abs(origElement.x) > 1e7 || Math.abs(origElement.x) > 1e7 || Math.abs(origElement.width) > 1e7 || Math.abs(origElement.height) > 1e7) {
                  console.error(
                    `Alt+dragging duplicated element with invalid dimensions`,
                    origElement.x,
                    origElement.y,
                    origElement.width,
                    origElement.height
                  );
                  return;
                }
                mutateElement(duplicatedElement, {
                  x: origElement.x,
                  y: origElement.y
                });
                pointerDownState.originalElements.set(
                  duplicatedElement.id,
                  duplicatedElement
                );
                nextElements.push(duplicatedElement);
                elementsToAppend.push(element);
                oldIdToDuplicatedId.set(element.id, duplicatedElement.id);
              } else {
                nextElements.push(element);
              }
            }
            let nextSceneElements = [
              ...nextElements,
              ...elementsToAppend
            ];
            const mappedNewSceneElements = this.props.onDuplicate?.(
              nextSceneElements,
              elements
            );
            nextSceneElements = mappedNewSceneElements || nextSceneElements;
            syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
            bindTextToShapeAfterDuplication(
              nextElements,
              elementsToAppend,
              oldIdToDuplicatedId
            );
            fixBindingsAfterDuplication(
              nextSceneElements,
              elementsToAppend,
              oldIdToDuplicatedId,
              "duplicatesServeAsOld"
            );
            bindElementsToFramesAfterDuplication(
              nextSceneElements,
              elementsToAppend,
              oldIdToDuplicatedId
            );
            this.scene.replaceAllElements(nextSceneElements);
            this.maybeCacheVisibleGaps(event, selectedElements, true);
            this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
          }
          return;
        }
      }
      if (this.state.selectionElement) {
        pointerDownState.lastCoords.x = pointerCoords.x;
        pointerDownState.lastCoords.y = pointerCoords.y;
        this.maybeDragNewGenericElement(pointerDownState, event);
      } else {
        const newElement2 = this.state.newElement;
        if (!newElement2) {
          return;
        }
        if (newElement2.type === "freedraw") {
          const points = newElement2.points;
          const dx = pointerCoords.x - newElement2.x;
          const dy = pointerCoords.y - newElement2.y;
          const lastPoint = points.length > 0 && points[points.length - 1];
          const discardPoint = lastPoint && lastPoint[0] === dx && lastPoint[1] === dy;
          if (!discardPoint) {
            const pressures = newElement2.simulatePressure ? newElement2.pressures : [...newElement2.pressures, event.pressure];
            mutateElement(
              newElement2,
              {
                points: [...points, pointFrom(dx, dy)],
                pressures
              },
              false
            );
            this.setState({
              newElement: newElement2
            });
          }
        } else if (isLinearElement(newElement2)) {
          pointerDownState.drag.hasOccurred = true;
          const points = newElement2.points;
          let dx = gridX - newElement2.x;
          let dy = gridY - newElement2.y;
          if (shouldRotateWithDiscreteAngle(event) && points.length === 2) {
            ({ width: dx, height: dy } = getLockedLinearCursorAlignSize(
              newElement2.x,
              newElement2.y,
              pointerCoords.x,
              pointerCoords.y
            ));
          }
          if (points.length === 1) {
            mutateElement(
              newElement2,
              {
                points: [...points, pointFrom(dx, dy)]
              },
              false
            );
          } else if (points.length === 2 || points.length > 1 && isElbowArrow(newElement2)) {
            mutateElement(
              newElement2,
              {
                points: [...points.slice(0, -1), pointFrom(dx, dy)]
              },
              false,
              { isDragging: true }
            );
          }
          this.setState({
            newElement: newElement2
          });
          if (isBindingElement(newElement2, false)) {
            this.maybeSuggestBindingsForLinearElementAtCoords(
              newElement2,
              [pointerCoords],
              this.state.startBoundElement
            );
          }
        } else {
          pointerDownState.lastCoords.x = pointerCoords.x;
          pointerDownState.lastCoords.y = pointerCoords.y;
          this.maybeDragNewGenericElement(pointerDownState, event, false);
        }
      }
      if (this.state.activeTool.type === "selection") {
        pointerDownState.boxSelection.hasOccurred = true;
        const elements = this.scene.getNonDeletedElements();
        if (this.state.editingLinearElement) {
          LinearElementEditor.handleBoxSelection(
            event,
            this.state,
            this.setState.bind(this),
            this.scene.getNonDeletedElementsMap()
          );
        } else {
          let shouldReuseSelection = true;
          if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
            if (pointerDownState.withCmdOrCtrl && pointerDownState.hit.element) {
              this.setState(
                (prevState) => selectGroupsForSelectedElements(
                  {
                    ...prevState,
                    selectedElementIds: {
                      [pointerDownState.hit.element.id]: true
                    }
                  },
                  this.scene.getNonDeletedElements(),
                  prevState,
                  this
                )
              );
            } else {
              shouldReuseSelection = false;
            }
          }
          const elementsWithinSelection = this.state.selectionElement ? getElementsWithinSelection(
            elements,
            this.state.selectionElement,
            this.scene.getNonDeletedElementsMap(),
            false
          ) : [];
          this.setState((prevState) => {
            const nextSelectedElementIds = {
              ...shouldReuseSelection && prevState.selectedElementIds,
              ...elementsWithinSelection.reduce(
                (acc, element) => {
                  acc[element.id] = true;
                  return acc;
                },
                {}
              )
            };
            if (pointerDownState.hit.element) {
              if (!elementsWithinSelection.length) {
                nextSelectedElementIds[pointerDownState.hit.element.id] = true;
              } else {
                delete nextSelectedElementIds[pointerDownState.hit.element.id];
              }
            }
            prevState = !shouldReuseSelection ? { ...prevState, selectedGroupIds: {}, editingGroupId: null } : prevState;
            return {
              ...selectGroupsForSelectedElements(
                {
                  editingGroupId: prevState.editingGroupId,
                  selectedElementIds: nextSelectedElementIds
                },
                this.scene.getNonDeletedElements(),
                prevState,
                this
              ),
              // select linear element only when we haven't box-selected anything else
              selectedLinearElement: elementsWithinSelection.length === 1 && isLinearElement(elementsWithinSelection[0]) ? new LinearElementEditor(elementsWithinSelection[0]) : null,
              showHyperlinkPopup: elementsWithinSelection.length === 1 && (elementsWithinSelection[0].link || isEmbeddableElement(elementsWithinSelection[0])) ? "info" : false
            };
          });
        }
      }
    });
  }
  // Returns whether the pointer move happened over either scrollbar
  handlePointerMoveOverScrollbars(event, pointerDownState) {
    if (pointerDownState.scrollbars.isOverHorizontal) {
      const x = event.clientX;
      const dx = x - pointerDownState.lastCoords.x;
      this.translateCanvas({
        scrollX: this.state.scrollX - dx / this.state.zoom.value
      });
      pointerDownState.lastCoords.x = x;
      return true;
    }
    if (pointerDownState.scrollbars.isOverVertical) {
      const y = event.clientY;
      const dy = y - pointerDownState.lastCoords.y;
      this.translateCanvas({
        scrollY: this.state.scrollY - dy / this.state.zoom.value
      });
      pointerDownState.lastCoords.y = y;
      return true;
    }
    return false;
  }
  onPointerUpFromPointerDownHandler(pointerDownState) {
    return withBatchedUpdates((childEvent) => {
      this.removePointer(childEvent);
      if (pointerDownState.eventListeners.onMove) {
        pointerDownState.eventListeners.onMove.flush();
      }
      const {
        newElement: newElement2,
        resizingElement,
        croppingElementId,
        multiElement,
        activeTool,
        isResizing,
        isRotating,
        isCropping
      } = this.state;
      this.setState((prevState) => ({
        isResizing: false,
        isRotating: false,
        isCropping: false,
        resizingElement: null,
        selectionElement: null,
        frameToHighlight: null,
        elementsToHighlight: null,
        cursorButton: "up",
        snapLines: updateStable(prevState.snapLines, []),
        originSnapOffset: null
      }));
      this.lastPointerMoveCoords = null;
      SnapCache.setReferenceSnapPoints(null);
      SnapCache.setVisibleGaps(null);
      this.savePointer(childEvent.clientX, childEvent.clientY, "up");
      this.setState({
        selectedElementsAreBeingDragged: false
      });
      const elementsMap = this.scene.getNonDeletedElementsMap();
      if (pointerDownState.drag.hasOccurred && pointerDownState.hit?.element?.id) {
        const element = elementsMap.get(pointerDownState.hit.element.id);
        if (isBindableElement(element)) {
          element.boundElements?.filter((e) => e.type === "arrow").map((e) => elementsMap.get(e.id)).filter((e) => isElbowArrow(e)).forEach((e) => {
            !!e && mutateElement(e, {}, true);
          });
        }
      }
      if (this.state.editingLinearElement) {
        if (!pointerDownState.boxSelection.hasOccurred && pointerDownState.hit?.element?.id !== this.state.editingLinearElement.elementId) {
          this.actionManager.executeAction(actionFinalize);
        } else {
          const editingLinearElement = LinearElementEditor.handlePointerUp(
            childEvent,
            this.state.editingLinearElement,
            this.state,
            this.scene
          );
          if (editingLinearElement !== this.state.editingLinearElement) {
            this.setState({
              editingLinearElement,
              suggestedBindings: []
            });
          }
        }
      } else if (this.state.selectedLinearElement) {
        if (this.state.selectedLinearElement.elbowed) {
          const element = LinearElementEditor.getElement(
            this.state.selectedLinearElement.elementId,
            this.scene.getNonDeletedElementsMap()
          );
          if (element) {
            mutateElement(element, {}, true);
          }
        }
        if (pointerDownState.hit?.element?.id !== this.state.selectedLinearElement.elementId) {
          const selectedELements = this.scene.getSelectedElements(this.state);
          if (selectedELements.length > 1) {
            this.setState({ selectedLinearElement: null });
          }
        } else {
          const linearElementEditor = LinearElementEditor.handlePointerUp(
            childEvent,
            this.state.selectedLinearElement,
            this.state,
            this.scene
          );
          const { startBindingElement, endBindingElement } = linearElementEditor;
          const element = this.scene.getElement(linearElementEditor.elementId);
          if (isBindingElement(element)) {
            bindOrUnbindLinearElement(
              element,
              startBindingElement,
              endBindingElement,
              elementsMap,
              this.scene
            );
          }
          if (linearElementEditor !== this.state.selectedLinearElement) {
            this.setState({
              selectedLinearElement: {
                ...linearElementEditor,
                selectedPointsIndices: null
              },
              suggestedBindings: []
            });
          }
        }
      }
      this.missingPointerEventCleanupEmitter.clear();
      window.removeEventListener(
        "pointermove" /* POINTER_MOVE */,
        pointerDownState.eventListeners.onMove
      );
      window.removeEventListener(
        "pointerup" /* POINTER_UP */,
        pointerDownState.eventListeners.onUp
      );
      window.removeEventListener(
        "keydown" /* KEYDOWN */,
        pointerDownState.eventListeners.onKeyDown
      );
      window.removeEventListener(
        "keyup" /* KEYUP */,
        pointerDownState.eventListeners.onKeyUp
      );
      if (this.state.pendingImageElementId) {
        this.setState({ pendingImageElementId: null });
      }
      this.props?.onPointerUp?.(activeTool, pointerDownState);
      this.onPointerUpEmitter.trigger(
        this.state.activeTool,
        pointerDownState,
        childEvent
      );
      if (newElement2?.type === "freedraw") {
        const pointerCoords = viewportCoordsToSceneCoords(
          childEvent,
          this.state
        );
        const points = newElement2.points;
        let dx = pointerCoords.x - newElement2.x;
        let dy = pointerCoords.y - newElement2.y;
        if (dx === points[0][0] && dy === points[0][1]) {
          dy += 1e-4;
          dx += 1e-4;
        }
        const pressures = newElement2.simulatePressure ? [] : [...newElement2.pressures, childEvent.pressure];
        mutateElement(newElement2, {
          points: [...points, pointFrom(dx, dy)],
          pressures,
          lastCommittedPoint: pointFrom(dx, dy)
        });
        this.actionManager.executeAction(actionFinalize);
        return;
      }
      if (isImageElement(newElement2)) {
        const imageElement = newElement2;
        try {
          this.initializeImageDimensions(imageElement);
          this.setState(
            {
              selectedElementIds: makeNextSelectedElementIds(
                { [imageElement.id]: true },
                this.state
              )
            },
            () => {
              this.actionManager.executeAction(actionFinalize);
            }
          );
        } catch (error) {
          console.error(error);
          this.scene.replaceAllElements(
            this.scene.getElementsIncludingDeleted().filter((el) => el.id !== imageElement.id)
          );
          this.actionManager.executeAction(actionFinalize);
        }
        return;
      }
      if (isLinearElement(newElement2)) {
        if (newElement2.points.length > 1) {
          this.store.shouldCaptureIncrement();
        }
        const pointerCoords = viewportCoordsToSceneCoords(
          childEvent,
          this.state
        );
        if (!pointerDownState.drag.hasOccurred && newElement2 && !multiElement) {
          mutateElement(newElement2, {
            points: [
              ...newElement2.points,
              pointFrom(
                pointerCoords.x - newElement2.x,
                pointerCoords.y - newElement2.y
              )
            ]
          });
          this.setState({
            multiElement: newElement2,
            newElement: newElement2
          });
        } else if (pointerDownState.drag.hasOccurred && !multiElement) {
          if (isBindingEnabled(this.state) && isBindingElement(newElement2, false)) {
            maybeBindLinearElement(
              newElement2,
              this.state,
              pointerCoords,
              this.scene.getNonDeletedElementsMap(),
              this.scene.getNonDeletedElements()
            );
          }
          this.setState({ suggestedBindings: [], startBoundElement: null });
          if (!activeTool.locked) {
            resetCursor(this.interactiveCanvas);
            this.setState((prevState) => ({
              newElement: null,
              activeTool: updateActiveTool(this.state, {
                type: "selection"
              }),
              selectedElementIds: makeNextSelectedElementIds(
                {
                  ...prevState.selectedElementIds,
                  [newElement2.id]: true
                },
                prevState
              ),
              selectedLinearElement: new LinearElementEditor(newElement2)
            }));
          } else {
            this.setState((prevState) => ({
              newElement: null
            }));
          }
          this.scene.triggerUpdate();
        }
        return;
      }
      if (isTextElement(newElement2)) {
        const minWidth = getMinTextElementWidth(
          getFontString({
            fontSize: newElement2.fontSize,
            fontFamily: newElement2.fontFamily
          }),
          newElement2.lineHeight
        );
        if (newElement2.width < minWidth) {
          mutateElement(newElement2, {
            autoResize: true
          });
        }
        this.resetCursor();
        this.handleTextWysiwyg(newElement2, {
          isExistingElement: true
        });
      }
      if (activeTool.type !== "selection" && newElement2 && isInvisiblySmallElement(newElement2)) {
        this.updateScene({
          elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !== newElement2.id),
          appState: {
            newElement: null
          },
          captureUpdate: CaptureUpdateAction.NEVER
        });
        return;
      }
      if (isFrameLikeElement(newElement2)) {
        const elementsInsideFrame = getElementsInNewFrame(
          this.scene.getElementsIncludingDeleted(),
          newElement2,
          this.scene.getNonDeletedElementsMap()
        );
        this.scene.replaceAllElements(
          addElementsToFrame(
            this.scene.getElementsMapIncludingDeleted(),
            elementsInsideFrame,
            newElement2,
            this.state
          )
        );
      }
      if (newElement2) {
        mutateElement(newElement2, getNormalizedDimensions(newElement2));
        this.scene.triggerUpdate();
      }
      if (pointerDownState.drag.hasOccurred) {
        const sceneCoords = viewportCoordsToSceneCoords(childEvent, this.state);
        if (this.state.selectedLinearElement && this.state.selectedLinearElement.isDragging) {
          const linearElement = this.scene.getElement(
            this.state.selectedLinearElement.elementId
          );
          if (linearElement?.frameId) {
            const frame = getContainingFrame(linearElement, elementsMap);
            if (frame && linearElement) {
              if (!elementOverlapsWithFrame(
                linearElement,
                frame,
                this.scene.getNonDeletedElementsMap()
              )) {
                mutateElement(linearElement, {
                  groupIds: []
                });
                removeElementsFromFrame(
                  [linearElement],
                  this.scene.getNonDeletedElementsMap()
                );
                this.scene.triggerUpdate();
              }
            }
          }
        } else {
          const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
          const selectedElements = this.scene.getSelectedElements(this.state);
          let nextElements = this.scene.getElementsMapIncludingDeleted();
          const updateGroupIdsAfterEditingGroup = (elements) => {
            if (elements.length > 0) {
              for (const element of elements) {
                const index = element.groupIds.indexOf(
                  this.state.editingGroupId
                );
                mutateElement(
                  element,
                  {
                    groupIds: element.groupIds.slice(0, index)
                  },
                  false
                );
              }
              nextElements.forEach((element) => {
                if (element.groupIds.length && getElementsInGroup(
                  nextElements,
                  element.groupIds[element.groupIds.length - 1]
                ).length < 2) {
                  mutateElement(
                    element,
                    {
                      groupIds: []
                    },
                    false
                  );
                }
              });
              this.setState({
                editingGroupId: null
              });
            }
          };
          if (topLayerFrame && !this.state.selectedElementIds[topLayerFrame.id]) {
            const elementsToAdd = selectedElements.filter(
              (element) => element.frameId !== topLayerFrame.id && isElementInFrame(element, nextElements, this.state)
            );
            if (this.state.editingGroupId) {
              updateGroupIdsAfterEditingGroup(elementsToAdd);
            }
            nextElements = addElementsToFrame(
              nextElements,
              elementsToAdd,
              topLayerFrame,
              this.state
            );
          } else if (!topLayerFrame) {
            if (this.state.editingGroupId) {
              const elementsToRemove = selectedElements.filter(
                (element) => element.frameId && !isElementInFrame(element, nextElements, this.state)
              );
              updateGroupIdsAfterEditingGroup(elementsToRemove);
            }
          }
          nextElements = updateFrameMembershipOfSelectedElements(
            nextElements,
            this.state,
            this
          );
          this.scene.replaceAllElements(nextElements);
        }
      }
      if (resizingElement) {
        this.store.shouldCaptureIncrement();
      }
      if (resizingElement && isInvisiblySmallElement(resizingElement)) {
        this.updateScene({
          elements: this.scene.getElementsIncludingDeleted().filter((el) => el.id !== resizingElement.id),
          captureUpdate: CaptureUpdateAction.NEVER
        });
      }
      if (pointerDownState.resize.isResizing) {
        let nextElements = updateFrameMembershipOfSelectedElements(
          this.scene.getElementsIncludingDeleted(),
          this.state,
          this
        );
        const selectedFrames = this.scene.getSelectedElements(this.state).filter(
          (element) => isFrameLikeElement(element)
        );
        for (const frame of selectedFrames) {
          nextElements = replaceAllElementsInFrame(
            nextElements,
            getElementsInResizingFrame(
              this.scene.getElementsIncludingDeleted(),
              frame,
              this.state,
              elementsMap
            ),
            frame,
            this
          );
        }
        this.scene.replaceAllElements(nextElements);
      }
      const hitElement = pointerDownState.hit.element;
      if (this.state.selectedLinearElement?.elementId !== hitElement?.id && isLinearElement(hitElement)) {
        const selectedElements = this.scene.getSelectedElements(this.state);
        if (selectedElements.length === 1) {
          this.setState({
            selectedLinearElement: new LinearElementEditor(hitElement)
          });
        }
      }
      if (
        // not in the cropping mode at all
        !croppingElementId || // in the cropping mode
        croppingElementId && // not cropping and no hit element
        (!hitElement && !isCropping || // hitting something else
        hitElement && hitElement.id !== croppingElementId)
      ) {
        this.finishImageCropping();
      }
      const pointerStart = this.lastPointerDownEvent;
      const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
      if (isEraserActive(this.state) && pointerStart && pointerEnd) {
        this.eraserTrail.endPath();
        const draggedDistance = pointDistance(
          pointFrom(pointerStart.clientX, pointerStart.clientY),
          pointFrom(pointerEnd.clientX, pointerEnd.clientY)
        );
        if (draggedDistance === 0) {
          const scenePointer = viewportCoordsToSceneCoords(
            {
              clientX: pointerEnd.clientX,
              clientY: pointerEnd.clientY
            },
            this.state
          );
          const hitElements = this.getElementsAtPosition(
            scenePointer.x,
            scenePointer.y
          );
          hitElements.forEach(
            (hitElement2) => this.elementsPendingErasure.add(hitElement2.id)
          );
        }
        this.eraseElements();
        return;
      } else if (this.elementsPendingErasure.size) {
        this.restoreReadyToEraseElements();
      }
      if (hitElement && !pointerDownState.drag.hasOccurred && !pointerDownState.hit.wasAddedToSelection && // if we're editing a line, pointerup shouldn't switch selection if
      // box selected
      (!this.state.editingLinearElement || !pointerDownState.boxSelection.hasOccurred)) {
        if (childEvent.shiftKey && !this.state.editingLinearElement) {
          if (this.state.selectedElementIds[hitElement.id]) {
            if (isSelectedViaGroup(this.state, hitElement)) {
              this.setState((_prevState) => {
                const nextSelectedElementIds = {
                  ..._prevState.selectedElementIds
                };
                for (const groupedElement of hitElement.groupIds.flatMap(
                  (groupId) => getElementsInGroup(
                    this.scene.getNonDeletedElements(),
                    groupId
                  )
                )) {
                  delete nextSelectedElementIds[groupedElement.id];
                }
                return {
                  selectedGroupIds: {
                    ..._prevState.selectedElementIds,
                    ...hitElement.groupIds.map((gId) => ({ [gId]: false })).reduce((prev, acc) => ({ ...prev, ...acc }), {})
                  },
                  selectedElementIds: makeNextSelectedElementIds(
                    nextSelectedElementIds,
                    _prevState
                  )
                };
              });
            } else if (!this.state.selectedLinearElement?.isDragging) {
              this.setState((prevState) => {
                const newSelectedElementIds = {
                  ...prevState.selectedElementIds
                };
                delete newSelectedElementIds[hitElement.id];
                const newSelectedElements = getSelectedElements(
                  this.scene.getNonDeletedElements(),
                  { selectedElementIds: newSelectedElementIds }
                );
                return {
                  ...selectGroupsForSelectedElements(
                    {
                      editingGroupId: prevState.editingGroupId,
                      selectedElementIds: newSelectedElementIds
                    },
                    this.scene.getNonDeletedElements(),
                    prevState,
                    this
                  ),
                  // set selectedLinearElement only if thats the only element selected
                  selectedLinearElement: newSelectedElements.length === 1 && isLinearElement(newSelectedElements[0]) ? new LinearElementEditor(newSelectedElements[0]) : prevState.selectedLinearElement
                };
              });
            }
          } else if (hitElement.frameId && this.state.selectedElementIds[hitElement.frameId]) {
            this.setState((prevState) => {
              const nextSelectedElementIds = {
                ...prevState.selectedElementIds,
                [hitElement.id]: true
              };
              delete nextSelectedElementIds[hitElement.frameId];
              (this.scene.getElement(hitElement.frameId)?.groupIds ?? []).flatMap(
                (gid) => getElementsInGroup(this.scene.getNonDeletedElements(), gid)
              ).forEach((element) => {
                delete nextSelectedElementIds[element.id];
              });
              return {
                ...selectGroupsForSelectedElements(
                  {
                    editingGroupId: prevState.editingGroupId,
                    selectedElementIds: nextSelectedElementIds
                  },
                  this.scene.getNonDeletedElements(),
                  prevState,
                  this
                ),
                showHyperlinkPopup: hitElement.link || isEmbeddableElement(hitElement) ? "info" : false
              };
            });
          } else {
            this.setState((_prevState) => ({
              selectedElementIds: makeNextSelectedElementIds(
                {
                  ..._prevState.selectedElementIds,
                  [hitElement.id]: true
                },
                _prevState
              )
            }));
          }
        } else {
          this.setState((prevState) => ({
            ...selectGroupsForSelectedElements(
              {
                editingGroupId: prevState.editingGroupId,
                selectedElementIds: { [hitElement.id]: true }
              },
              this.scene.getNonDeletedElements(),
              prevState,
              this
            ),
            selectedLinearElement: isLinearElement(hitElement) && // Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
            // Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
            prevState.selectedLinearElement?.elementId !== hitElement.id ? new LinearElementEditor(hitElement) : prevState.selectedLinearElement
          }));
        }
      }
      if (
        // not elbow midpoint dragged
        !(hitElement && isElbowArrow(hitElement)) && // not dragged
        !pointerDownState.drag.hasOccurred && // not resized
        !this.state.isResizing && // only hitting the bounding box of the previous hit element
        (hitElement && hitElementBoundingBoxOnly(
          {
            x: pointerDownState.origin.x,
            y: pointerDownState.origin.y,
            element: hitElement,
            shape: getElementShape(
              hitElement,
              this.scene.getNonDeletedElementsMap()
            ),
            threshold: this.getElementHitThreshold(),
            frameNameBound: isFrameLikeElement(hitElement) ? this.frameNameBoundsCache.get(hitElement) : null
          },
          elementsMap
        ) || !hitElement && pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements)
      ) {
        if (this.state.editingLinearElement) {
          this.setState({ editingLinearElement: null });
        } else {
          this.setState({
            selectedElementIds: makeNextSelectedElementIds({}, this.state),
            selectedGroupIds: {},
            editingGroupId: null,
            activeEmbeddable: null
          });
        }
        setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
        return;
      }
      if (!activeTool.locked && activeTool.type !== "freedraw" && newElement2) {
        this.setState((prevState) => ({
          selectedElementIds: makeNextSelectedElementIds(
            {
              ...prevState.selectedElementIds,
              [newElement2.id]: true
            },
            prevState
          ),
          showHyperlinkPopup: isEmbeddableElement(newElement2) && !newElement2.link ? "editor" : prevState.showHyperlinkPopup
        }));
      }
      if (activeTool.type !== "selection" || isSomeElementSelected(this.scene.getNonDeletedElements(), this.state) || !isShallowEqual(
        this.state.previousSelectedElementIds,
        this.state.selectedElementIds
      )) {
        this.store.shouldCaptureIncrement();
      }
      if (pointerDownState.drag.hasOccurred || isResizing || isRotating || isCropping) {
        const linearElements = this.scene.getSelectedElements(this.state).filter(isLinearElement);
        bindOrUnbindLinearElements(
          linearElements,
          this.scene.getNonDeletedElementsMap(),
          this.scene.getNonDeletedElements(),
          this.scene,
          isBindingEnabled(this.state),
          this.state.selectedLinearElement?.selectedPointsIndices ?? [],
          this.state.zoom
        );
      }
      if (activeTool.type === "laser") {
        this.laserTrails.endPath();
        return;
      }
      if (!activeTool.locked && activeTool.type !== "freedraw") {
        resetCursor(this.interactiveCanvas);
        this.setState({
          newElement: null,
          suggestedBindings: [],
          activeTool: updateActiveTool(this.state, { type: "selection" })
        });
      } else {
        this.setState({
          newElement: null,
          suggestedBindings: []
        });
      }
      if (hitElement && this.lastPointerUpEvent && this.lastPointerDownEvent && this.lastPointerUpEvent.timeStamp - this.lastPointerDownEvent.timeStamp < 300 && gesture.pointers.size <= 1 && isIframeLikeElement(hitElement) && this.isIframeLikeElementCenter(
        hitElement,
        this.lastPointerUpEvent,
        pointerDownState.origin.x,
        pointerDownState.origin.y
      )) {
        this.handleEmbeddableCenterClick(hitElement);
      }
    });
  }
  clearSelection(hitElement) {
    this.setState((prevState) => ({
      selectedElementIds: makeNextSelectedElementIds({}, prevState),
      activeEmbeddable: null,
      selectedGroupIds: {},
      // Continue editing the same group if the user selected a different
      // element from it
      editingGroupId: prevState.editingGroupId && hitElement != null && isElementInGroup(hitElement, prevState.editingGroupId) ? prevState.editingGroupId : null
    }));
    this.setState({
      selectedElementIds: makeNextSelectedElementIds({}, this.state),
      activeEmbeddable: null,
      previousSelectedElementIds: this.state.selectedElementIds
    });
  }
  getTextWysiwygSnappedToCenterPosition(x, y, appState, container) {
    if (container) {
      let elementCenterX = container.x + container.width / 2;
      let elementCenterY = container.y + container.height / 2;
      const elementCenter = getContainerCenter(
        container,
        appState,
        this.scene.getNonDeletedElementsMap()
      );
      if (elementCenter) {
        elementCenterX = elementCenter.x;
        elementCenterY = elementCenter.y;
      }
      const distanceToCenter = Math.hypot(
        x - elementCenterX,
        y - elementCenterY
      );
      const isSnappedToCenter = distanceToCenter < TEXT_TO_CENTER_SNAP_THRESHOLD;
      if (isSnappedToCenter) {
        const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
          { sceneX: elementCenterX, sceneY: elementCenterY },
          appState
        );
        return { viewportX, viewportY, elementCenterX, elementCenterY };
      }
    }
  }
  getCanvasOffsets() {
    if (this.excalidrawContainerRef?.current) {
      const excalidrawContainer = this.excalidrawContainerRef.current;
      const { left, top } = excalidrawContainer.getBoundingClientRect();
      return {
        offsetLeft: left,
        offsetTop: top
      };
    }
    return {
      offsetLeft: 0,
      offsetTop: 0
    };
  }
  async updateLanguage() {
    const currentLang2 = languages.find((lang) => lang.code === this.props.langCode) || defaultLang;
    await setLanguage(currentLang2);
    this.setAppState({});
  }
};
var createTestHook = () => {
  if (define_import_meta_env_default.MODE === ENV.TEST || define_import_meta_env_default.DEV) {
    window.h = window.h || {};
    Object.defineProperties(window.h, {
      elements: {
        configurable: true,
        get() {
          return this.app?.scene.getElementsIncludingDeleted();
        },
        set(elements) {
          return this.app?.scene.replaceAllElements(
            syncInvalidIndices(elements)
          );
        }
      },
      scene: {
        configurable: true,
        get() {
          return this.app?.scene;
        }
      }
    });
  }
};
createTestHook();
var App_default = App;

// polyfill.ts
var polyfill = () => {
  if (!Array.prototype.at) {
    Object.defineProperty(Array.prototype, "at", {
      value: function(n) {
        n = Math.trunc(n) || 0;
        if (n < 0) {
          n += this.length;
        }
        if (n < 0 || n >= this.length) {
          return void 0;
        }
        return this[n];
      },
      writable: true,
      enumerable: false,
      configurable: true
    });
  }
  if (!Element.prototype.replaceChildren) {
    Element.prototype.replaceChildren = function(...nodes) {
      this.innerHTML = "";
      this.append(...nodes);
    };
  }
};
var polyfill_default = polyfill;

// components/footer/FooterCenter.tsx
import clsx56 from "clsx";
import { jsx as jsx148 } from "react/jsx-runtime";
var FooterCenter = ({ children }) => {
  const { FooterCenterTunnel } = useTunnels();
  const appState = useUIAppState();
  return /* @__PURE__ */ jsx148(FooterCenterTunnel.In, { children: /* @__PURE__ */ jsx148(
    "div",
    {
      className: clsx56("footer-center zen-mode-transition", {
        "layer-ui__wrapper__footer-left--transition-bottom": appState.zenModeEnabled
      }),
      children
    }
  ) });
};
var FooterCenter_default = FooterCenter;
FooterCenter.displayName = "FooterCenter";

// components/ExcalidrawLogo.tsx
import { jsx as jsx149, jsxs as jsxs79 } from "react/jsx-runtime";
var LogoIcon = () => /* @__PURE__ */ jsx149(
  "svg",
  {
    viewBox: "0 0 40 40",
    fill: "none",
    xmlns: "http://www.w3.org/2000/svg",
    className: "ExcalidrawLogo-icon",
    children: /* @__PURE__ */ jsx149(
      "path",
      {
        d: "M39.9 32.889a.326.326 0 0 0-.279-.056c-2.094-3.083-4.774-6-7.343-8.833l-.419-.472a.212.212 0 0 0-.056-.139.586.586 0 0 0-.167-.111l-.084-.083-.056-.056c-.084-.167-.28-.278-.475-.167-.782.39-1.507.973-2.206 1.528-.92.722-1.842 1.445-2.708 2.25a8.405 8.405 0 0 0-.977 1.028c-.14.194-.028.361.14.444-.615.611-1.23 1.223-1.843 1.861a.315.315 0 0 0-.084.223c0 .083.056.166.111.194l1.09.833v.028c1.535 1.528 4.244 3.611 7.12 5.861.418.334.865.667 1.284 1 .195.223.39.473.558.695.084.11.28.139.391.055.056.056.14.111.196.167a.398.398 0 0 0 .167.056.255.255 0 0 0 .224-.111.394.394 0 0 0 .055-.167c.029 0 .028.028.056.028a.318.318 0 0 0 .224-.084l5.082-5.528a.309.309 0 0 0 0-.444Zm-14.63-1.917a.485.485 0 0 0 .111.14c.586.5 1.2 1 1.843 1.555l-2.569-1.945-.251-.166c-.056-.028-.112-.084-.168-.111l-.195-.167.056-.056.055-.055.112-.111c.866-.861 2.346-2.306 3.1-3.028-.81.805-2.43 3.167-2.095 3.944Zm8.767 6.89-2.122-1.612a44.713 44.713 0 0 0-2.625-2.5c1.145.861 2.122 1.611 2.262 1.75 1.117.972 1.06.806 1.815 1.445l.921.666a1.06 1.06 0 0 1-.251.25Zm.558.416-.056-.028c.084-.055.168-.111.252-.194l-.196.222ZM1.089 5.75c.055.361.14.722.195 1.056.335 1.833.67 3.5 1.284 4.75l.252.944c.084.361.223.806.363.917 1.424 1.25 3.602 3.11 5.947 4.889a.295.295 0 0 0 .363 0s0 .027.028.027a.254.254 0 0 0 .196.084.318.318 0 0 0 .223-.084c2.988-3.305 5.221-6.027 6.813-8.305.112-.111.14-.278.14-.417.111-.111.195-.25.307-.333.111-.111.111-.306 0-.39l-.028-.027c0-.055-.028-.139-.084-.167-.698-.666-1.2-1.138-1.731-1.638-.922-.862-1.871-1.75-3.881-3.75l-.028-.028c-.028-.028-.056-.056-.112-.056-.558-.194-1.703-.389-3.127-.639C6.087 2.223 3.21 1.723.614.944c0 0-.168 0-.196.028l-.083.084c-.028.027-.056.055-.224.11h.056-.056c.028.167.028.278.084.473 0 .055.112.5.112.555l.782 3.556Zm15.496 3.278-.335-.334c.084.112.196.195.335.334Zm-3.546 4.666-.056.056c0-.028.028-.056.056-.056Zm-2.038-10c.168.167.866.834 1.033.973-.726-.334-2.54-1.167-3.379-1.445.838.167 1.983.334 2.346.472ZM1.424 2.306c.419.722.754 3.222 1.089 5.666-.196-.778-.335-1.555-.503-2.278-.251-1.277-.503-2.416-.838-3.416.056 0 .14 0 .252.028Zm-.168-.584c-.112 0-.223-.028-.307-.028 0-.027 0-.055-.028-.055.14 0 .223.028.335.083Zm-1.089.222c0-.027 0-.027 0 0ZM39.453 1.333c.028-.11-.558-.61-.363-.639.42-.027.42-.666 0-.666-.558.028-1.144.166-1.675.25-.977.194-1.982.389-2.96.61-2.205.473-4.383.973-6.561 1.557-.67.194-1.424.333-2.066.666-.224.111-.196.333-.084.472-.056.028-.084.028-.14.056-.195.028-.363.056-.558.083-.168.028-.252.167-.224.334 0 .027.028.083.028.11-1.173 1.556-2.485 3.195-3.909 4.945-1.396 1.611-2.876 3.306-4.356 5.056-4.719 5.5-10.052 11.75-15.943 17.25a.268.268 0 0 0 0 .389c.028.027.056.055.084.055-.084.084-.168.14-.252.222-.056.056-.084.111-.084.167a.605.605 0 0 0-.111.139c-.112.111-.112.305.028.389.111.11.307.11.39-.028.029-.028.029-.056.056-.056a.44.44 0 0 1 .615 0c.335.362.67.723.977 1.028l-.698-.583c-.112-.111-.307-.083-.39.028-.113.11-.085.305.027.389l7.427 6.194c.056.056.112.056.196.056s.14-.028.195-.084l.168-.166c.028.027.083.027.111.027.084 0 .14-.027.196-.083 10.052-10.055 18.15-17.639 27.42-24.417.083-.055.111-.166.111-.25.112 0 .196-.083.251-.194 1.704-5.194 2.039-9.806 2.15-12.083v-.028c0-.028.028-.056.028-.083.028-.056.028-.084.028-.084a1.626 1.626 0 0 0-.111-1.028ZM21.472 9.5c.446-.5.893-1.028 1.34-1.5-2.876 3.778-7.65 9.583-14.408 16.5 4.607-5.083 9.242-10.333 13.068-15ZM5.193 35.778h.084-.084Zm3.462 3.194c-.027-.028-.027-.028 0-.028v.028Zm4.16-3.583c.224-.25.448-.472.699-.722 0 0 0 .027.028.027-.252.223-.475.445-.726.695Zm1.146-1.111c.14-.14.279-.334.446-.5l.028-.028c1.648-1.694 3.351-3.389 5.082-5.111l.028-.028c.419-.333.921-.694 1.368-1.028a379.003 379.003 0 0 0-6.952 6.695ZM24.794 6.472c-.921 1.195-1.954 2.778-2.82 4.028-2.736 3.944-11.532 13.583-11.727 13.75a1976.983 1976.983 0 0 1-8.042 7.639l-.167.167c-.14-.167-.14-.417.028-.556C14.49 19.861 22.03 10.167 25.074 5.917c-.084.194-.14.36-.28.555Zm4.83 5.695c-1.116-.64-1.646-1.64-1.34-2.611l.084-.334c.028-.083.084-.194.14-.277.307-.5.754-.917 1.257-1.167.027 0 .055 0 .083-.028-.028-.056-.028-.139-.028-.222.028-.167.14-.278.335-.278.335 0 1.369.306 1.76.639.111.083.223.194.335.305.14.167.363.445.474.667.056.028.112.306.196.445.056.222.111.472.084.694-.028.028 0 .194-.028.194a2.668 2.668 0 0 1-.363 1.028c-.028.028-.028.056-.056.084l-.028.027c-.14.223-.335.417-.53.556-.643.444-1.369.583-2.095.389 0 0-.195-.084-.28-.111Zm8.154-.834a39.098 39.098 0 0 1-.893 3.167c0 .028-.028.083 0 .111-.056 0-.084.028-.14.056-2.206 1.61-4.356 3.305-6.506 5.028 1.843-1.64 3.686-3.306 5.613-4.945.558-.5.949-1.139 1.06-1.861l.28-1.667v-.055c.14-.334.67-.195.586.166Z",
        fill: "currentColor"
      }
    )
  }
);
var LogoText = () => /* @__PURE__ */ jsxs79(
  "svg",
  {
    viewBox: "0 0 450 55",
    xmlns: "http://www.w3.org/2000/svg",
    fill: "none",
    className: "ExcalidrawLogo-text",
    children: [
      /* @__PURE__ */ jsx149(
        "path",
        {
          d: "M429.27 96.74c2.47-1.39 4.78-3.02 6.83-4.95 1.43-1.35 2.73-2.86 3.81-4.51-.66.9-1.4 1.77-2.23 2.59-2.91 2.84-5.72 5.09-8.42 6.87h.01ZM343.6 69.36c.33 3.13.58 6.27.79 9.4.09 1.37.18 2.75.25 4.12-.12-4.46-.27-8.93-.5-13.39-.11-2.08-.24-4.16-.4-6.24-.06 1.79-.11 3.85-.13 6.11h-.01ZM378.47 98.34c.01-.37.07-1.13.01-6.51-.11 1.9-.22 3.81-.31 5.71-.07 1.42-.22 2.91-.16 4.35.39.03.78.07 1.17.1-.92-.85-.76-2.01-.72-3.66l.01.01ZM344.09 86.12c-.09-2.41-.22-4.83-.39-7.24v12.21c.15-.05.32-.09.47-.14.05-1.61-.03-3.23-.09-4.83h.01ZM440.69 66.79c-.22-.34-.45-.67-.69-.99-3.71-4.87-9.91-7.14-15.65-8.55-1.05-.26-2.12-.49-3.18-.71 2.29.59 4.48 1.26 6.64 2.02 7.19 2.54 10.57 5.41 12.88 8.23ZM305.09 72.46l1.2 3.6c.84 2.53 1.67 5.06 2.46 7.61.24.78.5 1.57.73 2.36.22-.04.44-.08.67-.12a776.9 776.9 0 0 1-5.01-13.57c-.02.04-.03.09-.05.13v-.01ZM345.49 90.25v.31c1.48-.42 3.05-.83 4.66-1.2-1.56.25-3.12.52-4.66.89ZM371.02 90.22c0-.57-.04-1.14-.11-1.71-.06-.02-.12-.04-.19-.05-.21-.05-.43-.08-.65-.11.42.16.74.88.95 1.87ZM398.93 54.23c-.13 0-.27-.01-.4-.02l.03.4c.11-.15.23-.27.37-.38ZM401.57 62.28v-.15c-1.22-.24-2.86-.61-3.23-1.25-.09-.15-.18-.51-.27-.98-.09.37-.2.73-.33 1.09 1.24.56 2.52.98 3.83 1.29ZM421.73 88.68c-2.97 1.65-6.28 3.12-9.69 3.68v.18c4.72-.14 11.63-3.85 16.33-8.38-2.04 1.75-4.33 3.24-6.63 4.53l-.01-.01ZM411.28 80.92c-.05-1.2-.09-2.4-.15-3.6-.21 5.66-.46 11.38-.47 14.51.24-.02.48-.04.71-.07.15-3.61.05-7.23-.09-10.83v-.01Z",
          transform: "translate(-144.023 -51.76)"
        }
      ),
      /* @__PURE__ */ jsx149(
        "path",
        {
          d: "M425.38 67.41c-3.5-1.45-7.19-2.57-14.06-3.62.09 1.97.06 4.88-.03 8.12.03.04.06.09.06.15.19 1.36.28 2.73.37 4.1.25 3.77.39 7.55.41 11.33 0 1.38-.01 2.76-.07 4.13 1.4-.25 2.78-.65 4.12-1.15 4.07-1.5 7.94-3.78 11.28-6.54 2.33-1.92 5.13-4.49 5.88-7.58.63-3.53-2.45-6.68-7.97-8.96l.01.02ZM411.35 92.53v-.06l-.34.03c.11.01.22.03.34.03ZM314.26 64.06c-.23-.59-.47-1.17-.7-1.75.57 1.62 1.11 3.25 1.6 4.9l.15.54 2.35 6.05c.32.82.66 1.64.98 2.46-1.38-4.1-2.83-8.17-4.39-12.2h.01ZM156.82 103.07c-.18.13-.38.23-.58.33 1.32-.03 2.66-.2 3.93-.34.86-.09 1.72-.22 2.58-.33-2.12.1-4.12.17-5.94.34h.01ZM210.14 68.88s.03.04.05.07c.18-.31.39-.64.58-.96-.21.3-.42.6-.64.89h.01ZM201.65 82.8c-.5.77-1.02 1.56-1.49 2.37 1.11-1.55 2.21-3.1 3.2-4.59-.23.23-.49.51-.75.79-.32.47-.65.95-.96 1.43ZM194.03 98.66c-.33-.4-.65-.84-1.05-1.17-.24-.2-.07-.49.17-.56-.23-.26-.42-.5-.63-.75 1.51-2.55 3.93-5.87 6.4-9.28-.17-.08-.29-.28-.2-.49.04-.09.09-.17.13-.26-1.21 1.78-2.42 3.55-3.61 5.33-.87 1.31-1.74 2.64-2.54 4-.29.5-.63 1.04-.87 1.61.81.65 1.63 1.27 2.47 1.88-.09-.11-.18-.21-.27-.32v.01ZM307.79 82.93c-1-3.17-2.05-6.32-3.1-9.48-1.62 4.08-3.69 9.17-6.16 15.19 3.32-1.04 6.77-1.87 10.27-2.5-.32-1.08-.67-2.15-1.01-3.21ZM149.5 80.7c.05-1.71.04-3.43 0-5.14-.1 2.26-.16 4.51-.22 6.77-.02.73-.03 1.46-.04 2.19.14-1.27.2-2.55.24-3.82h.02ZM228.98 98.3c.39 1.25.91 3.03.94 3.91.06-.03.12-.07.17-.1.08-1.29-.55-2.65-1.11-3.81ZM307.72 53.36c.81.5 1.53 1.04 2.07 1.49-.38-.8-.78-1.58-1.21-2.35-.17.03-.34.06-.51.11-.43.12-.86.26-1.29.41.35-.01.53.1.94.34ZM283.69 96.14c3.91-7.25 6.89-13.35 8.88-18.15l1.1-2.66c-1.27 2.64-2.56 5.27-3.83 7.9-1.53 3.15-3.06 6.31-4.58 9.47-.87 1.81-1.76 3.62-2.54 5.47.04.02.07.04.11.07.05.05.1.09.15.14.05-.73.27-1.48.71-2.24ZM289.92 103.23s-.04.01-.05.03c0-.02.04-.03.05-.04.05-.05.11-.1.16-.15l.21-.21c-.55 0-1.5-.27-2.55-.72.4.26.8.51 1.22.74.24.13.48.26.73.37.05.02.1.03.14.05a.27.27 0 0 1 .08-.07h.01ZM269.23 68.49c-.39-.19-.82-.48-1.33-.87-3.06-1.56-6.31-2.78-9.36-2.35-3.5.49-5.7 1.11-7.74 2.44 5.71-2.6 12.82-2.07 18.44.79l-.01-.01ZM177.87 53.69l1.06.03c-.96-.22-2-.25-2.89-.3-4.95-.26-9.99.33-14.86 1.19-2.44.43-4.88.95-7.28 1.59 9.09-1.76 15.69-2.77 23.97-2.51ZM219.85 55.51c-.18.12-.36.27-.56.45-.45.53-.86 1.11-1.26 1.66-1.91 2.61-3.71 5.31-5.57 7.95l-.12.18 8.05-10.11c-.18-.05-.36-.1-.55-.13h.01ZM510.71 54.1c.12-.15.29-.3.53-.45.69-.4 3.72-.63 5.87-.74-.36-.02-.73-.04-1.09-.05-1.84-.03-3.67.09-5.49.35.05.3.12.59.18.88v.01ZM510.76 86.02c1.37-3.07 2.49-6.27 3.57-9.46.55-1.64 1.12-3.3 1.6-4.97-1.59 4.01-3.67 9.14-6.2 15.3.24-.08.5-.14.74-.22.1-.22.19-.44.29-.65ZM566.95 75.76c.11-.02.23.03.31.11-.05-.13-.09-.26-.14-.39-.05.09-.11.18-.17.28ZM511.33 86.41c3.08-.89 6.24-1.62 9.46-2.14-1.51-3.98-2.98-7.96-4.39-11.87-.05.15-.09.31-.14.46-1.02 3.32-2.15 6.61-3.39 9.85-.48 1.25-.98 2.49-1.53 3.7h-.01ZM578.24 74.45c.11-.44.23-.87.35-1.31-.31.7-.64 1.39-.97 2.08.09.21.19.4.28.61.12-.46.23-.92.35-1.38h-.01ZM520.62 53.11c-.09 0-.18-.01-.28-.02.38.34.29 1.08.93 2.53l6.65 17.15c2.2 5.68 4.69 11.36 7.41 16.87l1.06 2.17c-2.95-7.05-5.92-14.08-8.87-21.13-1.58-3.79-3.16-7.59-4.7-11.4-.78-1.92-1.73-3.89-2.25-5.91-.03-.1 0-.19.04-.26h.01ZM578.78 77.87c1.45-5.77 3.07-10.43 3.58-13.36.05-.34.16-.88.31-1.55-.67 1.79-1.37 3.56-2.08 5.33-.12.43-.23.86-.35 1.29-.65 2.43-1.29 4.86-1.9 7.3.14.33.29.65.43 1l.01-.01ZM545.3 94.66c.02-.44.03-.83.05-1.12.02-1.01.05-2.02.11-3.02.03-6.66-.46-14.33-1.46-22.8-.13-.42-.27-1.24-.56-2.89 0-.02 0-.04-.01-.06.62 6.61.95 13.25 1.32 19.87.17 3.08.33 6.16.52 9.23.02.25.03.52.04.78l-.01.01ZM580.77 102.81c.13.2.27.38.37.49.27-.11.53-.22.8-.32-.43.09-.82.05-1.17-.16v-.01ZM530.48 104.07h.33c-.36-.13-.71-.32-1.04-.56.14.24.3.47.45.7.06-.08.14-.13.26-.13v-.01ZM542.63 58.82c.06.23.11.47.15.71.14-.33.36-.62.7-.86-.28.05-.57.11-.85.15ZM583.81 57.87c.15-.7.29-1.41.42-2.11-.14.45-.28.9-.42 1.34-.46 1.44-.89 2.89-1.31 4.34.44-1.19.88-2.37 1.31-3.57ZM523.62 91.48c-4.66 1.17-9.05 2.89-14.02 5.27 4.65-1.84 9.48-3.29 14.28-4.63-.09-.22-.17-.41-.26-.64ZM460.64 78.3c-.04-2.9-.11-5.81-.28-8.71-.1-1.68-.17-3.43-.5-5.09-.07.02-.14.03-.2.05.3 6.54.45 12.17.51 17.12.17-.07.34-.14.51-.2 0-1.06-.01-2.11-.03-3.17h-.01ZM470.63 63.24c-3.38-.26-6.81.32-10.1 1.1.41 2.01.47 4.14.57 6.18.18 3.55.25 7.11.27 10.67 3.31-1.38 6.5-3.12 9.3-5.35 1.96-1.56 3.86-3.41 5.02-5.66.73-1.41 1.19-3.22.26-4.65-1.09-1.7-3.46-2.14-5.32-2.29ZM460.29 63.68c1-.24 2.01-.46 3.04-.65-1.15.16-2.37.38-3.71.69v.13c.07-.02.15-.04.22-.05.11-.13.3-.18.45-.11v-.01ZM457.24 100.96c.43-.03.86-.07 1.29-.11.14-.49.27-.99.38-1.49-.44.7-1 1.23-1.67 1.6ZM482.88 104.98c-.18.23-.36.38-.55.47.14.09.27.19.4.28a70.76 70.76 0 0 0 4.37-4.63c.76-.89 1.52-1.81 2.19-2.77-.3-.27-.61-.53-.92-.79-.07 1.94-4.62 6.32-5.49 7.45v-.01Z",
          transform: "translate(-144.023 -51.76)"
        }
      ),
      /* @__PURE__ */ jsx149(
        "path",
        {
          d: "M474.36 63.31c-.4-.16-.84-.27-1.29-.37 1.56.42 3.08 1.22 3.76 2.74.62 1.4.32 2.95-.28 4.32.7-1.22.94-2.34.74-3.47-.24-1.33-1.19-2.54-2.93-3.21v-.01ZM477.34 89.18c-1.2-.81-2.4-1.62-3.6-2.42-.14.1-.26.19-.4.29 1.4.67 2.73 1.39 4 2.13ZM465.88 93.85c.37.25.74.5 1.1.75.46.32.92.65 1.38.97-1.57-1.2-2.01-1.61-2.49-1.72h.01ZM574.92 90.06c-2.28-5.21-4.93-11.13-5.67-12.26-.1-.15-1.57-3.01-1.63-3.08 0 0-.01.02-.02.02.4 1.37 1.09 2.69 1.65 3.99 2.14 4.95 4.36 9.86 6.67 14.73.6 1.26 1.21 2.52 1.83 3.78-.75-2.01-1.64-4.45-2.83-7.18ZM448.73 65.29c.1.2.22.38.34.57.22-.02.43-.06.65-.08v-.08c-.14-.05-.25 0-.99-.41ZM460.16 94.81c-.02.31-.06.59-.1.89-.03 1.71-.33 3.43-.79 5.07.15-.02.3-.03.45-.05.01-.04.02-.08.03-.11.09-.34.15-.69.2-1.03.17-1.07.25-2.16.33-3.24.05-.69.08-1.39.12-2.08-.27.1-.27.26-.24.57v-.02Z",
          transform: "translate(-144.023 -51.76)"
        }
      ),
      /* @__PURE__ */ jsx149(
        "path",
        {
          d: "m328.67 98.12-3.22-6.58c-1.29-2.63-2.53-5.29-3.72-7.97-.25-.85-.52-1.69-.79-2.53-.81-2.57-1.67-5.12-2.55-7.67-1.92-5.53-3.9-11.08-6.32-16.41-.72-1.58-1.46-3.44-2.63-4.79-.03-.17-.16-.29-.34-.36a.282.282 0 0 0-.23-.04c-.06-.01-.12 0-.18.01-.74.06-1.5.38-2.19.61-2.22.77-4.4 1.64-6.63 2.38-.03-.08-.06-.16-.09-.25-.15-.42-.82-.24-.67.19.03.09.07.19.1.28l-.18.06c-.36.11-.28.6 0 .68.18 1.18.63 2.36.98 3.49.03.09.06.17.08.26-.08.23-.17.46-.24.64-.37.98-.79 1.94-1.21 2.9-1.27 2.89-2.62 5.75-3.98 8.6-3.18 6.67-6.44 13.31-9.64 19.97-1.08 2.25-2.2 4.5-3.15 6.81-.13.32.24.5.5.37 1.34 1.33 2.84 2.5 4.4 3.57.65.44 1.31.87 2.01 1.24.4.22.86.48 1.33.5.24.01.35-.19.33-.37.11-.1.21-.21.28-.28.41-.41.81-.84 1.2-1.26.85-.92 1.69-1.87 2.5-2.84 6.31-2.34 12.6-4.31 18.71-5.84 2.14 5.3 3.43 8.43 3.97 9.58.55 1.05 1.15 1.88 1.82 2.52 1.32.56 6.96-.03 9.23-1.96.87-1.28 1.19-2.67.93-4.15-.09-.5-.22-.95-.4-1.33l-.01-.03Zm-20.09-45.61c.43.77.83 1.56 1.21 2.35-.54-.45-1.27-.99-2.07-1.49-.42-.24-.6-.35-.94-.34.43-.15.85-.29 1.29-.41.17-.05.34-.08.51-.11Zm-25.86 45.66c.78-1.85 1.67-3.66 2.54-5.47 1.51-3.16 3.05-6.31 4.58-9.47 1.28-2.63 2.56-5.26 3.83-7.9l-1.1 2.66c-1.99 4.79-4.97 10.9-8.88 18.15-.43.76-.66 1.51-.71 2.24-.05-.05-.1-.09-.15-.14a.259.259 0 0 0-.11-.07Zm6.24 4.71c-.42-.23-.82-.48-1.22-.74 1.05.45 2 .72 2.55.72l-.21.21c-.05.05-.11.1-.16.15-.01.01-.04.03-.05.04 0-.02.03-.02.05-.03a.27.27 0 0 0-.08.07c-.05-.02-.1-.03-.14-.05-.25-.1-.49-.24-.73-.37h-.01Zm15.73-29.43c1.05 3.15 2.1 6.31 3.1 9.48.34 1.06.69 2.13 1.01 3.21-3.5.63-6.95 1.46-10.27 2.5 2.48-6.03 4.54-11.11 6.16-15.19Zm4.79 12.57c-.23-.79-.49-1.58-.73-2.36-.79-2.54-1.63-5.08-2.46-7.61l-1.2-3.6c.02-.04.04-.09.05-.13 1.6 4.45 3.28 9 5.01 13.57l-.67.12v.01Zm5.83-18.27-.15-.54c-.49-1.64-1.03-3.28-1.6-4.9.23.58.47 1.17.7 1.75 1.56 4.03 3.01 8.1 4.39 12.2-.33-.82-.67-1.64-.98-2.46l-2.35-6.05h-.01ZM390.43 79.37c-.13-10.43-.22-17.5-.24-19.97-.24-1.6.21-2.88-.65-3.65-.14-.13-.32-.23-.52-.32h.03c.45 0 .45-.69 0-.7-1.75-.03-3.5-.04-5.25-.14-1.38-.08-2.76-.21-4.15-.31-.07 0-.12.01-.17.04-.21-.07-.47.03-.45.31l.03.45c-.11.14-.19.3-.22.5-.21 1.26-.32 13.67-.36 23.59-.32 5.79-.67 11.57-.97 17.36-.09 1.73-.29 3.54-.21 5.3-.39.02-.38.64.04.69v.12c.05.44.74.45.7 0v-.06c1.1.09 2.2.21 3.3.3 1.14.19 2.44.2 3.29.17 1.73-.05 2.92-.05 3.8-.37.45-.05.9-.11 1.35-.17.44-.06.25-.73-.19-.67h-.01c.24-.32.45-.72.62-1.25.66-1.84.41-6.36.34-11.33l-.13-9.9.02.01Zm-12.26 18.17c.09-1.91.2-3.81.31-5.71.06 5.38 0 6.14-.01 6.51-.05 1.65-.21 2.81.72 3.66-.39-.04-.78-.07-1.17-.1-.06-1.44.09-2.93.16-4.35l-.01-.01ZM588.97 53.85c-2.06-.25-3.17-.51-3.76-.6a.3.3 0 0 1 .04-.08c.22-.39-.39-.75-.6-.35-.56 1.02-.9 2.19-1.26 3.29-.61 1.88-1.17 3.78-1.72 5.68-.63 2.19-1.24 4.39-1.83 6.59-.81 2.03-1.67 4.05-2.61 6.03-1.7-3.64-3.11-6.04-4.03-7.57-2.26-3.74-2.85-5.48-3.57-6.08l.31-.09c.43-.12.25-.8-.19-.67-1.06.3-2.12.6-3.17.95-.93.32-1.85.69-2.76 1.07-.13.05-.19.16-.22.27-.04.02-.08.05-.11.07-.04-.06-.07-.12-.11-.18a.354.354 0 0 0-.48-.12c-.16.09-.22.32-.13.48l.33.54c0 .09.02.18.06.28.51 1.16.78 1.38.72 1.47-2.42 3.44-5.41 7.86-6.2 9.1-1.27 1.97-2.01 3.14-2.45 3.84l-.91-6.56-.43-4.1c-.19-1.85-.37-3.23-.53-4.13-.19-1.1-.3-2.15-.45-3.16-.2-1.36-.29-2.06-.47-2.42h.04c.45.02.45-.68 0-.7-3.43-.16-6.81.94-10.17 1.48-.24-.22-.73-.04-.58.32.24.59.33 1.25.43 1.87.17 1.06.29 2.13.4 3.2.32 3.09.53 6.2.74 9.3.44 6.75.77 13.51 1.17 20.26.11 1.95.13 3.96.46 5.89.05.3.37.31.55.14.74 1.71 2.87 1.27 6.13 1.27 1.34 0 2.39.04 2.99-.11.02.32.48.53.63.18 3.61-8.26 7.41-16.46 12.05-24.2.03-.05.04-.1.05-.15.3.73.64 1.45.94 2.16.97 2.26 1.97 4.52 2.98 6.76 2.26 5.03 4.54 10.07 7.09 14.96.47.9.94 1.79 1.47 2.65.2.32.4.67.66.96-.18.25 0 .68.34.54.91-.38 1.82-.75 2.76-1.07 1.04-.35 2.11-.65 3.17-.95.39-.11.28-.66-.07-.68.62-.4.95-.96.87-1.91-.3-3.34.72-7.47.86-8.52l2.14-11.43c1.75-10.74 3.13-17.51 3.23-20.86.02-.49.08-2.84.13-3.24.17-1.25.48-1-4.96-1.65l.03-.02Zm-46.19 5.67c-.04-.24-.09-.48-.15-.71l.85-.15c-.34.24-.56.53-.7.86Zm1.95 25.12c-.36-6.63-.7-13.26-1.32-19.87 0 .02 0 .04.01.06.29 1.65.44 2.47.56 2.89 1 8.46 1.5 16.14 1.46 22.8-.06.99-.1 2-.11 3.02-.01.29-.03.68-.05 1.12-.01-.26-.03-.53-.04-.78-.19-3.08-.35-6.16-.52-9.23l.01-.01Zm36.4 18.66c-.11-.11-.24-.29-.37-.49.35.21.74.26 1.17.16-.27.11-.53.22-.8.32v.01Zm-.89-33.72c.12-.43.23-.86.35-1.29.71-1.77 1.41-3.55 2.08-5.33-.15.68-.26 1.22-.31 1.55-.5 2.94-2.13 7.59-3.58 13.36-.15-.35-.29-.66-.43-1 .61-2.44 1.25-4.87 1.9-7.3l-.01.01Zm3.56-12.48c.14-.44.28-.89.42-1.34-.13.7-.27 1.41-.42 2.11-.43 1.19-.86 2.38-1.31 3.57.42-1.45.85-2.9 1.31-4.34Zm-5.22 16.05c-.11.44-.23.87-.35 1.31-.12.46-.23.92-.35 1.38-.1-.22-.19-.4-.28-.61.34-.69.66-1.38.97-2.08h.01Zm-11.64 2.62c.06-.1.12-.19.17-.28.05.13.09.26.14.39a.398.398 0 0 0-.31-.11Zm2.3 2.98c-.56-1.3-1.25-2.63-1.65-3.99 0 0 .01-.02.02-.02.06.08 1.52 2.93 1.63 3.08.73 1.13 3.38 7.04 5.67 12.26 1.2 2.73 2.08 5.17 2.83 7.18-.62-1.25-1.23-2.51-1.83-3.78-2.31-4.87-4.53-9.78-6.67-14.73ZM275.92 87.03c-1.06-2.18-1.13-3.45-2.44-2.93-1.52.57-2.94 1.3-4.5 2.1-1.4.72-2.68 1.44-3.92 2.12.01-.25-.24-.5-.51-.34-4.8 2.93-12.41 4.7-17.28 1.31-1.98-1.77-3.32-4.15-3.97-5.78-.29-.95-.49-1.94-.63-2.93-.14-3.34 1.58-6.53 3.9-9.12.8-.79 1.68-1.51 2.66-2.12 3.7-2.3 8.22-3.07 12.51-2.51 2.71.35 5.32 1.24 7.71 2.55.39.22.75-.39.35-.6-.18-.1-.37-.18-.55-.27.56.27 1.03.33 1.51.19l-.48.39c-.15.11-.23.3-.13.48.09.15.33.24.48.13 1.3-.97 2.46-2.09 3.45-3.37.37-.29.64-.6.65-.97v-.02c.08-.33-.03-.7-.21-1.08-.31-.87-.98-2.01-2.19-3.26-2.43-2.52-3.79-3.45-5.68-4.26-1.14-.49-3.12-1.06-4.42-1.23-3.28-.42-10.64-1.21-18.18 4.11-7.74 5.46-11.94 12.3-12.23 20.61-.08 2.06.04 3.98.34 5.71.74 4.18 2.57 8 5.44 11.34 4.26 4.99 9.76 7.52 16.34 7.52 4.85 0 9.69-1.77 14.89-4.62.23-.12.45-.23.68-.35 2.19-1.1 4.37-2.23 6.46-3.5.49-.3 1.03-.61 1.5-.98 1.47-.87 1.11-1.12.49-2.95-.39-1.14-.76-2.7-2.06-5.36l.02-.01Zm-17.38-21.76c3.05-.42 6.31.79 9.36 2.35.51.39.94.68 1.33.87-5.61-2.86-12.72-3.39-18.44-.79 2.05-1.33 4.24-1.95 7.74-2.44l.01.01ZM443.67 72.67c-.4-2.2-1.15-4.33-2.37-6.22-1.49-2.32-3.58-4.19-5.91-5.64-6.17-3.81-13.75-5.11-20.83-6.01-3.23-.41-6.47-.69-9.72-.92l-1.39-.12c-.85-.07-1.52-.1-2.05-.1-1.08-.06-2.17-.12-3.25-.17-.08 0-.14.02-.19.05-.1.05-.18.14-.16.3.27 2.55-.01 5.12-.92 7.52-.15.38.4.56.62.28 1.32.59 2.68 1.05 4.08 1.37 0 2.78-.14 7.58-.33 12.91 0 0 0 .02-.01.03-.61 3.66-.79 7.42-1 11.12-.23 4.01-.43 8.03-.44 12.05 0 .64 0 1.28.03 1.93.02.31 0 .68.15.96.06.11.14.16.24.17-.2.17-.21.54.11.59 3.83.67 7.78.71 11.68.25 2.3-.19 4.87-.65 7.65-1.56 1.85-.54 3.67-1.18 5.43-1.91 7.2-3.02 14.31-8.07 17.35-15.53.76-1.86 1.17-3.8 1.31-5.75.3-1.93.28-3.82-.09-5.58l.01-.02Zm-19.32-15.42c5.74 1.41 11.94 3.68 15.65 8.55.25.32.47.65.69.99-2.3-2.82-5.68-5.69-12.88-8.23-2.16-.76-4.35-1.43-6.64-2.02 1.06.21 2.13.45 3.18.71Zm-25.82-3.04c.13 0 .27.01.4.02-.14.1-.26.23-.37.38 0-.13-.02-.26-.03-.4Zm34.82 22.17c-.75 3.09-3.55 5.66-5.88 7.58-3.35 2.76-7.21 5.03-11.28 6.54-1.33.49-2.71.9-4.12 1.15.06-1.38.08-2.76.07-4.13-.02-3.78-.16-7.56-.41-11.33-.09-1.37-.18-2.74-.37-4.1 0-.06-.03-.11-.06-.15.09-3.25.12-6.16.03-8.12 6.86 1.05 10.56 2.17 14.06 3.62 5.52 2.28 8.59 5.44 7.97 8.96l-.01-.02Zm-22 16.15c-.12 0-.23-.02-.34-.03l.34-.03v.06Zm-.69-.7c0-3.13.26-8.84.47-14.51.06 1.2.11 2.41.15 3.6.15 3.6.25 7.23.09 10.83-.24.03-.48.05-.71.07v.01Zm-12.33-30.94c.37.63 2.01 1.01 3.23 1.25v.15c-1.31-.31-2.59-.73-3.83-1.29.12-.36.23-.72.33-1.09.08.48.18.84.27.98Zm13.7 31.65v-.18c3.41-.56 6.71-2.02 9.69-3.68 2.31-1.28 4.59-2.78 6.63-4.53-4.69 4.53-11.61 8.24-16.33 8.38l.01.01Zm24.07-.75c-2.05 1.93-4.37 3.56-6.83 4.95 2.7-1.78 5.52-4.03 8.42-6.87.82-.82 1.56-1.69 2.23-2.59-1.08 1.65-2.38 3.16-3.81 4.51h-.01ZM187.16 92.14c-.79-2.47-2.1-7.12-3.1-6.87-.19-.01-2.09.77-4.08 1.54-3.06 1.18-5.91 2.13-10.09 2.82-2.74.42-5.87 1.01-10.61 1.06.04-3.34.05-6.01.05-7.99 7.97-.65 12.33-2.11 16.37-3.55 1.11-.39 2.69-1.01 2.63-1.8-.08-.35-.55-1.39-1.17-2.61-.47-1.16-.98-2.31-1.61-3.38-.42-.71-1.04-1.69-1.86-2.06-.11-.08-.22-.13-.29-.12-.02 0-.04 0-.07.01-.19-.04-.39-.05-.6-.01-.17.03-.24.15-.25.28-.04.02-.09.04-.14.05-4.33 1.48-8.85 2.33-13.24 3.61a499.1 499.1 0 0 0-.31-8.19c4.51-.99 8.88-1.38 13.11-1.82 3.68-.38 6.28.12 7.47.34.59.11.9.16 1.16.18h.1c-.1.37.44.66.62.28.02-.04.03-.08.05-.13.15.2.53.22.62-.1.17-.58.19-1.21.21-1.81v-.36c.03-.15.05-.3.07-.45.52-2.47.33-5.09-.64-7.44-.11-.27-.44-.28-.6-.14-.08-.21-.15-.42-.24-.62-.19-.41-.79-.05-.6.35.03.07.05.15.09.22-.98-.42-2.15-.54-3.17-.63-2.17-.19-4.37-.14-6.54 0-5.7.35-11.4 1.3-16.91 2.79-2.08.56-4.13 1.22-6.14 2-4.54 1.05-3.79 1.51-2.17 6.07.18.51.46 1.68.54 1.94.82 2.47 1.08 2.13 3.1 2.13s0 .05 0 .08h.52c-.48 2.66-.51 5.45-.62 8.13-.15 3.48-.22 6.96-.28 10.45 0 .41-.01.82-.02 1.23-.16.29-.33.57-.51.85-.05.38-.09.77-.14 1.18-.42 3.52-.59 6.48-.52 8.8v.34c.02.47.05.76.06.87.16 1.57-.26 3.47 1.35 3.79 1.61.32 3.5.55 4.85.55.11 0 .22-.02.33-.02 1.79.24 3.67.05 5.45-.12 2.85-.28 5.69-.7 8.51-1.19 3.03-.53 6.05-1.14 9.04-1.86 2.4-.58 4.82-1.19 7.13-2.06.51-.19 1.73-.57 2.46-1.14 1.81-.68 2.18-1 1.57-2.67-.23-.62-.48-1.49-.91-2.78l-.03-.02Zm-11.12-38.71c.89.05 1.93.08 2.89.3-.33 0-.68-.02-1.06-.03-8.28-.26-14.88.75-23.97 2.51 2.41-.64 4.85-1.16 7.28-1.59 4.87-.86 9.91-1.45 14.86-1.19Zm-26.53 22.13c.03 1.71.04 3.43 0 5.14-.04 1.27-.11 2.55-.24 3.82 0-.73.02-1.46.04-2.19.05-2.26.12-4.51.22-6.77h-.02Zm6.73 27.85c.2-.1.4-.21.58-.33 1.82-.17 3.82-.24 5.94-.34-.86.11-1.72.24-2.58.33-1.27.14-2.61.31-3.93.34h-.01ZM534.48 85.44c-3.52-8.38-7.07-16.75-10.5-25.17-.63-1.54-1.25-3.09-1.86-4.65-.31-.8-.65-1.6-.87-2.43-.04-.17-.17-.24-.31-.25.1-.2 0-.51-.29-.53-1.59-.08-3.18-.22-4.78-.25-1.96-.03-3.91.13-5.84.42-.31.05-.31.38-.13.56-.03.06-.05.14-.04.22.23 1.54.63 3.06 1.16 4.53.13.35.27.7.41 1.06l-2.68 6.18c-.11.03-.2.09-.25.22-.67 1.9-1.52 3.73-2.34 5.56a536.85 536.85 0 0 1-3.9 8.45c-2.64 5.64-5.34 11.25-7.91 16.93-.44.97-.88 1.94-1.29 2.93-.2.48-.47 1-.55 1.52v.05c-.02.12.02.26.16.34 1.19.73 2.41 1.41 3.66 2.05 1.2.62 2.45 1.25 3.76 1.61.43.12.62-.55.19-.67-1.13-.31-2.2-.83-3.24-1.36 1.09.36 2.1.69 2.75.93 2.82 1.01 2.38 1.1 4.3-3.75 2.1-1.09 4.34-1.96 6.53-2.79 4.35-1.64 8.8-3.03 13.27-4.29.82 2.01 1.77 3.97 2.72 5.92.35.83.62 1.45.79 1.82.22.42.45.8.69 1.15.17.33.33.67.5 1 .42.8.84 1.63 1.4 2.35.23.29.6 0 .55-.31 1.53-.02 3.06-.07 4.58-.27.92-.12 1.82-.32 2.71-.54 1.39-.27 3.85-1.11 3.74-1.42-.67-1.96-1.55-3.87-2.34-5.78-1.57-3.78-3.16-7.56-4.75-11.33v-.01Zm-11.65-26.16c1.54 3.81 3.12 7.6 4.7 11.4 2.94 7.05 5.91 14.09 8.87 21.13l-1.06-2.17c-2.71-5.51-5.2-11.19-7.41-16.87l-6.65-17.15c-.65-1.45-.55-2.19-.93-2.53.09 0 .18.01.28.02a.29.29 0 0 0-.04.26c.52 2.02 1.47 3.98 2.25 5.91h-.01Zm-6.58 13.58c.05-.15.09-.31.14-.46 1.41 3.92 2.88 7.9 4.39 11.87-3.22.52-6.38 1.25-9.46 2.14.55-1.22 1.05-2.46 1.53-3.7 1.24-3.24 2.37-6.53 3.39-9.85h.01Zm-.23-20c.36 0 .73.03 1.09.05-2.15.1-5.18.33-5.87.74-.24.15-.41.3-.53.45-.06-.29-.13-.58-.18-.88 1.82-.26 3.65-.39 5.49-.35v-.01Zm-.09 18.72c-.49 1.67-1.05 3.33-1.6 4.97-1.07 3.19-2.19 6.38-3.57 9.46-.09.21-.19.43-.29.65-.25.07-.5.14-.74.22 2.53-6.16 4.61-11.29 6.2-15.3Zm-6.34 25.16c4.97-2.38 9.37-4.1 14.02-5.27l.26.64c-4.8 1.35-9.63 2.8-14.28 4.63Zm20.17 6.76c.33.23.68.42 1.04.56h-.33c-.12 0-.21.06-.26.13-.15-.23-.31-.45-.45-.7v.01ZM226.57 91.75c-3.55-4.74-6.68-9.11-9.31-12.99 9.2-15.25 10.05-17.81 10.35-18.38.17-.34 1.09-2.27.64-2.53-1.13-.65-1.03-.65-2.97-1.71-1.19-.65-3.04-1.61-4.53-2.12-1.71-.59-1.24-.36-3 2.77-.06.1-.11.2-.17.3-.75 1.02-1.48 2.05-2.2 3.09-1.88 2.71-3.73 5.45-5.69 8.1-3.68-4.91-6.88-8.76-9.51-11.43-.15-.15-.3-.29-.46-.42-1.27-1.28-7.24 3.53-7.93 5.58-.09.09-.19.16-.28.25-.27.26.03.64.33.58.19.65.5 1.29.94 1.91 3.85 5.06 7.19 9.76 9.94 14-1.23 2.61-3.06 5-4.67 7.38l-2.28 3.33c-.5.66-.93 1.23-1.29 1.69-.67.93-2.09 2.61-2.3 3.87-.51.85-1.16 1.84-1.29 2.83-.06.44.61.63.67.19.01-.08.04-.15.06-.22 1.36 1.08 2.76 2.11 4.19 3.11 1.3.91 2.62 1.85 4.04 2.56.21.1.4 0 .48-.17.24.07.48.14.72.2.44.1.62-.57.19-.67-2.02-.48-3.77-1.57-5.23-3.02-.47-.46-.9-.96-1.32-1.46 1.74 1.35 4.2 2.89 5.89 4.14 1.39 1.03 2.85-2.27 4.22-4.2 1.86-2.64 3.96-5.86 5.52-8.29l10.39 14.51c.67.81 1.14 1.21 1.57 1.36-.05.24.12.51.41.4 1.53-.58 3.05-1.19 4.54-1.87 1.52-.69 3.06-1.45 4.36-2.5a.28.28 0 0 0 .12-.23c1.66-1.1.81-1.74-1.41-4.91-1.13-1.58-1.71-2.36-3.7-5.01l-.03-.02Zm2.41 6.54c.56 1.15 1.19 2.52 1.11 3.81-.06.04-.12.07-.17.1-.03-.88-.55-2.66-.94-3.91Zm-16.51-32.73c1.86-2.65 3.65-5.35 5.57-7.95.4-.55.81-1.13 1.26-1.66.19-.18.38-.33.56-.45.18.03.36.08.55.13l-8.05 10.11.12-.18h-.01ZM192.7 95.48c.79-1.37 1.66-2.69 2.54-4 1.19-1.79 2.4-3.56 3.61-5.33-.04.09-.09.17-.13.26-.1.22.03.41.2.49-2.47 3.42-4.89 6.73-6.4 9.28.21.24.4.48.63.75-.24.07-.4.36-.17.56.4.33.72.77 1.05 1.17.09.11.18.21.27.32-.84-.61-1.66-1.24-2.47-1.88.24-.57.58-1.11.87-1.61v-.01Zm7.46-10.32c.47-.81.98-1.59 1.49-2.37.31-.48.64-.95.96-1.43.26-.29.52-.56.75-.79-.99 1.48-2.09 3.03-3.2 4.59Zm10.03-16.22s-.03-.05-.05-.07c.22-.29.43-.59.64-.89-.2.32-.4.65-.58.96h-.01ZM371.54 87.96c-.01-.08-.01-.16-.03-.23-.06-.38-.58-.29-.66.03-.3-.05-.6-.08-.81-.11-1.14-.15-2.29-.19-3.44-.2 1.04-.09 2.09-.18 3.14-.23.45-.02.45-.72 0-.7-6.57.35-13.14 1.23-19.65 2.11-1.53.21-3.05.42-4.57.68-.01 0-.02.01-.04.01-.04-3.33-.13-6.66-.24-9.99-.19-5.7-.4-11.41-.88-17.1-.13-1.51-.23-3.07-.49-4.58 0-.25 0-.48-.02-.68-.06-1.19-.04-2.61-.68-2.78-.16-.07-.72-.16-1.5-.24.22-.17.16-.62-.2-.63-1.19-.04-2.39.09-3.57.23-1.2.14-2.41.32-3.59.6-.16-.1-.41-.06-.5.12-.06.02-.13.03-.19.05-.35.1-.29.55-.03.66-.26.6-.19 2.27-.21 3-.02.66-.66 33.73-.9 40.3-.03.65.06 1.12.04 1.45-.16 3.05.87 4.96 6.34 3.93 1.09-.08 2.75-.77 5.36-1.43 4.13-1.04 5.78-1.52 6.2-1.65 6.43-1.69 6.78-1.97 11.72-2.43.55-.05 4.8-.38 6.03-.3.64.04 1.19.07 1.65.1.09 0 .16-.03.24-.05.1.27.56.33.66-.02.39-1.32.61-2.71.78-4.08.2-1.61.29-3.24.15-4.86.24.03.52-.23.38-.53-.09-.2-.27-.33-.49-.43v-.02Zm-.63.56c.07.57.11 1.14.11 1.71-.21-.99-.53-1.71-.95-1.87.22.03.44.06.65.11.06.01.12.04.19.05Zm-25.41 1.73c1.54-.36 3.1-.64 4.66-.89-1.61.37-3.18.77-4.66 1.2v-.31Zm-.86-7.37c-.07-1.37-.16-2.75-.25-4.12-.21-3.13-.45-6.27-.79-9.4.02-2.25.08-4.31.13-6.11.16 2.08.29 4.16.4 6.24.23 4.46.38 8.93.5 13.39h.01Zm-.94-4c.16 2.41.29 4.83.39 7.24.06 1.6.14 3.22.09 4.83-.15.05-.32.09-.47.14V78.88h-.01ZM483.72 92.83c-3.05-2.28-6.22-4.4-9.38-6.51 8.86-6.49 13.49-12.95 13.73-19.23.04-.76 0-1.5-.13-2.2-.67-3.82-3.5-6.68-8.39-8.48.13.04.27.08.4.13 3.92 1.39 7.74 4.23 8.5 8.56.34 1.95-.05 3.96-.98 5.69-.21.4.39.75.6.35 1.86-3.46 1.46-7.55-.97-10.63-3.53-4.47-9.76-5.88-15.16-6.16-2.32-.12-4.64-.04-6.95.19-6 .32-12.71 1.68-17.63 3.21-.37.11-.67.23-.92.35-.2-.17-.62.02-.57.37v.03c-.64.68-.18 1.64.48 3.21.38.91.67 1.89 1.15 2.58.32.76.68 1.51 1.13 2.19.14.21.38.19.53.07.19-.02.38-.05.57-.08v1.57c-.06.06-.1.13-.11.23-.27 4.18-.34 8.38-.48 12.57l-.3 9.03c-.24 3.91-.44 6.77-.46 7.26-.05.88-.11 1.95.07 2.81-.01.22-.02.43-.04.65 0 .11-.02.23-.03.35 0 .05-.03.27-.01.16-.05.4.5.59.64.28.05.04.12.08.2.08 1.75.13 3.5.28 5.25.3 1.69.02 3.38-.12 5.06-.32.08.23.36.39.55.15.06-.08.11-.17.16-.26.18-.09.24-.32.18-.48.05-.2.1-.4.13-.6.16-.86.25-1.74.33-2.62.11-1.17.17-2.34.23-3.51.15-.01.32-.03.52-.04.36-.03 1.73-.15 2.06-.15.39 0 .7-.02.95-.04 1.76 1.11 3.45 2.35 5.14 3.55 2.83 2.01 5.64 4.04 8.47 6.04 1.42 1 2.85 2 4.29 2.97.1.06.19.07.27.04.08 0 .17-.02.25-.1 1.61-1.56 3.15-3.18 4.6-4.88.75-.88 1.49-1.78 2.15-2.73.01.01.03.02.04.03.34.3.83-.2.49-.49-2.16-1.9-4.34-3.76-6.64-5.48l.03-.01Zm-6.38-3.65a55.72 55.72 0 0 0-4-2.13c.14-.1.26-.19.4-.29 1.2.81 2.4 1.61 3.6 2.42Zm-20.1 11.78c.67-.37 1.23-.91 1.67-1.6-.11.5-.24 1-.38 1.49-.43.04-.86.08-1.29.11Zm2.38-37.24c1.34-.31 2.56-.52 3.71-.69-1.03.19-2.04.41-3.04.65-.14-.07-.34-.02-.45.11-.07.02-.15.04-.22.05v-.13.01Zm.04.84c.07-.02.14-.03.2-.05.34 1.66.41 3.41.5 5.09.17 2.9.24 5.81.28 8.71l.03 3.17c-.17.07-.34.14-.51.2-.06-4.96-.21-10.58-.51-17.12h.01Zm16.04 5.62c-1.16 2.25-3.06 4.1-5.02 5.66-2.8 2.23-5.99 3.97-9.3 5.35-.01-3.56-.09-7.12-.27-10.67-.1-2.04-.16-4.16-.57-6.18 3.3-.78 6.72-1.36 10.1-1.1 1.85.14 4.23.59 5.32 2.29.92 1.43.46 3.24-.26 4.65Zm.85-.18c.6-1.37.9-2.92.28-4.32-.67-1.52-2.2-2.32-3.76-2.74.46.1.89.21 1.29.37 1.74.67 2.69 1.88 2.93 3.21.2 1.13-.05 2.25-.74 3.47V70Zm-27.47-4.14c-.12-.19-.23-.38-.34-.57.74.42.85.36.99.41v.08c-.22.03-.43.06-.65.08Zm11.21 30.46c-.08 1.08-.16 2.17-.33 3.24-.05.35-.11.69-.2 1.03 0 .04-.02.07-.03.11-.15.02-.3.04-.45.05.45-1.64.76-3.36.79-5.07.03-.29.08-.57.1-.89-.03-.31-.03-.47.24-.57-.04.69-.07 1.39-.12 2.08v.02Zm5.6-2.47c.48.11.92.52 2.49 1.72-.46-.32-.92-.65-1.38-.97-.37-.25-.73-.5-1.1-.75h-.01Zm21.23 7.24a70.76 70.76 0 0 1-4.37 4.63c-.14-.09-.27-.19-.4-.28.19-.09.37-.24.55-.47.87-1.14 5.43-5.51 5.49-7.45.31.26.62.53.92.79-.67.97-1.42 1.88-2.19 2.77v.01Z",
          fill: "currentColor",
          transform: "translate(-144.023 -51.76)"
        }
      )
    ]
  }
);
var ExcalidrawLogo = ({
  style,
  size = "small",
  withText
}) => {
  return /* @__PURE__ */ jsxs79("div", { className: `ExcalidrawLogo is-${size}`, style, children: [
    /* @__PURE__ */ jsx149(LogoIcon, {}),
    withText && /* @__PURE__ */ jsx149(LogoText, {})
  ] });
};

// components/welcome-screen/WelcomeScreen.Center.tsx
import { Fragment as Fragment24, jsx as jsx150, jsxs as jsxs80 } from "react/jsx-runtime";
var WelcomeScreenMenuItemContent = ({
  icon,
  shortcut,
  children
}) => {
  const device = useDevice();
  return /* @__PURE__ */ jsxs80(Fragment24, { children: [
    /* @__PURE__ */ jsx150("div", { className: "welcome-screen-menu-item__icon", children: icon }),
    /* @__PURE__ */ jsx150("div", { className: "welcome-screen-menu-item__text", children }),
    shortcut && !device.editor.isMobile && /* @__PURE__ */ jsx150("div", { className: "welcome-screen-menu-item__shortcut", children: shortcut })
  ] });
};
WelcomeScreenMenuItemContent.displayName = "WelcomeScreenMenuItemContent";
var WelcomeScreenMenuItem = ({
  onSelect,
  children,
  icon,
  shortcut,
  className = "",
  ...props
}) => {
  return /* @__PURE__ */ jsx150(
    "button",
    {
      ...props,
      type: "button",
      className: `welcome-screen-menu-item ${className}`,
      onClick: onSelect,
      children: /* @__PURE__ */ jsx150(WelcomeScreenMenuItemContent, { icon, shortcut, children })
    }
  );
};
WelcomeScreenMenuItem.displayName = "WelcomeScreenMenuItem";
var WelcomeScreenMenuItemLink = ({
  children,
  href,
  icon,
  shortcut,
  className = "",
  ...props
}) => {
  return /* @__PURE__ */ jsx150(
    "a",
    {
      ...props,
      className: `welcome-screen-menu-item ${className}`,
      href,
      target: "_blank",
      rel: "noreferrer",
      children: /* @__PURE__ */ jsx150(WelcomeScreenMenuItemContent, { icon, shortcut, children })
    }
  );
};
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
var Center = ({ children }) => {
  const { WelcomeScreenCenterTunnel } = useTunnels();
  return /* @__PURE__ */ jsx150(WelcomeScreenCenterTunnel.In, { children: /* @__PURE__ */ jsx150("div", { className: "welcome-screen-center", children: children || /* @__PURE__ */ jsxs80(Fragment24, { children: [
    /* @__PURE__ */ jsx150(Logo, {}),
    /* @__PURE__ */ jsx150(Heading, { children: t("welcomeScreen.defaults.center_heading") }),
    /* @__PURE__ */ jsxs80(Menu, { children: [
      /* @__PURE__ */ jsx150(MenuItemLoadScene, {}),
      /* @__PURE__ */ jsx150(MenuItemHelp, {})
    ] })
  ] }) }) });
};
Center.displayName = "Center";
var Logo = ({ children }) => {
  return /* @__PURE__ */ jsx150("div", { className: "welcome-screen-center__logo excalifont welcome-screen-decor", children: children || /* @__PURE__ */ jsx150(ExcalidrawLogo, { withText: true }) });
};
Logo.displayName = "Logo";
var Heading = ({ children }) => {
  return /* @__PURE__ */ jsx150("div", { className: "welcome-screen-center__heading welcome-screen-decor excalifont", children });
};
Heading.displayName = "Heading";
var Menu = ({ children }) => {
  return /* @__PURE__ */ jsx150("div", { className: "welcome-screen-menu", children });
};
Menu.displayName = "Menu";
var MenuItemHelp = () => {
  const actionManager = useExcalidrawActionManager();
  return /* @__PURE__ */ jsx150(
    WelcomeScreenMenuItem,
    {
      onSelect: () => actionManager.executeAction(actionShortcuts),
      shortcut: "?",
      icon: HelpIcon,
      children: t("helpDialog.title")
    }
  );
};
MenuItemHelp.displayName = "MenuItemHelp";
var MenuItemLoadScene = () => {
  const appState = useUIAppState();
  const actionManager = useExcalidrawActionManager();
  if (appState.viewModeEnabled) {
    return null;
  }
  return /* @__PURE__ */ jsx150(
    WelcomeScreenMenuItem,
    {
      onSelect: () => actionManager.executeAction(actionLoadScene),
      shortcut: getShortcutFromShortcutName("loadScene"),
      icon: LoadIcon,
      children: t("buttons.load")
    }
  );
};
MenuItemLoadScene.displayName = "MenuItemLoadScene";
var MenuItemLiveCollaborationTrigger = ({
  onSelect
}) => {
  const { t: t2 } = useI18n();
  return /* @__PURE__ */ jsx150(WelcomeScreenMenuItem, { shortcut: null, onSelect, icon: usersIcon, children: t2("labels.liveCollaboration") });
};
MenuItemLiveCollaborationTrigger.displayName = "MenuItemLiveCollaborationTrigger";
Center.Logo = Logo;
Center.Heading = Heading;
Center.Menu = Menu;
Center.MenuItem = WelcomeScreenMenuItem;
Center.MenuItemLink = WelcomeScreenMenuItemLink;
Center.MenuItemHelp = MenuItemHelp;
Center.MenuItemLoadScene = MenuItemLoadScene;
Center.MenuItemLiveCollaborationTrigger = MenuItemLiveCollaborationTrigger;

// components/welcome-screen/WelcomeScreen.Hints.tsx
import { jsx as jsx151, jsxs as jsxs81 } from "react/jsx-runtime";
var MenuHint = ({ children }) => {
  const { WelcomeScreenMenuHintTunnel } = useTunnels();
  return /* @__PURE__ */ jsx151(WelcomeScreenMenuHintTunnel.In, { children: /* @__PURE__ */ jsxs81("div", { className: "excalifont welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu", children: [
    WelcomeScreenMenuArrow,
    /* @__PURE__ */ jsx151("div", { className: "welcome-screen-decor-hint__label", children: children || t("welcomeScreen.defaults.menuHint") })
  ] }) });
};
MenuHint.displayName = "MenuHint";
var ToolbarHint = ({ children }) => {
  const { WelcomeScreenToolbarHintTunnel } = useTunnels();
  return /* @__PURE__ */ jsx151(WelcomeScreenToolbarHintTunnel.In, { children: /* @__PURE__ */ jsxs81("div", { className: "excalifont welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar", children: [
    /* @__PURE__ */ jsx151("div", { className: "welcome-screen-decor-hint__label", children: children || t("welcomeScreen.defaults.toolbarHint") }),
    WelcomeScreenTopToolbarArrow
  ] }) });
};
ToolbarHint.displayName = "ToolbarHint";
var HelpHint = ({ children }) => {
  const { WelcomeScreenHelpHintTunnel } = useTunnels();
  return /* @__PURE__ */ jsx151(WelcomeScreenHelpHintTunnel.In, { children: /* @__PURE__ */ jsxs81("div", { className: "excalifont welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help", children: [
    /* @__PURE__ */ jsx151("div", { children: children || t("welcomeScreen.defaults.helpHint") }),
    WelcomeScreenHelpArrow
  ] }) });
};
HelpHint.displayName = "HelpHint";

// components/welcome-screen/WelcomeScreen.tsx
import { Fragment as Fragment25, jsx as jsx152, jsxs as jsxs82 } from "react/jsx-runtime";
var WelcomeScreen = (props) => {
  return /* @__PURE__ */ jsx152(Fragment25, { children: props.children || /* @__PURE__ */ jsxs82(Fragment25, { children: [
    /* @__PURE__ */ jsx152(Center, {}),
    /* @__PURE__ */ jsx152(MenuHint, {}),
    /* @__PURE__ */ jsx152(ToolbarHint, {}),
    /* @__PURE__ */ jsx152(HelpHint, {})
  ] }) });
};
WelcomeScreen.displayName = "WelcomeScreen";
WelcomeScreen.Center = Center;
WelcomeScreen.Hints = { MenuHint, ToolbarHint, HelpHint };
var WelcomeScreen_default = WelcomeScreen;

// components/live-collaboration/LiveCollaborationTrigger.tsx
import clsx57 from "clsx";
import { jsx as jsx153, jsxs as jsxs83 } from "react/jsx-runtime";
var LiveCollaborationTrigger2 = ({
  isCollaborating,
  onSelect,
  ...rest
}) => {
  const appState = useUIAppState();
  const showIconOnly = appState.width < 830;
  return /* @__PURE__ */ jsxs83(
    Button,
    {
      ...rest,
      className: clsx57("collab-button", { active: isCollaborating }),
      type: "button",
      onSelect,
      style: { position: "relative", width: showIconOnly ? void 0 : "auto" },
      title: t("labels.liveCollaboration"),
      children: [
        showIconOnly ? share : t("labels.share"),
        appState.collaborators.size > 0 && /* @__PURE__ */ jsx153("div", { className: "CollabButton-collaborators", children: appState.collaborators.size })
      ]
    }
  );
};
var LiveCollaborationTrigger_default = LiveCollaborationTrigger2;
LiveCollaborationTrigger2.displayName = "LiveCollaborationTrigger";

// data/reconcile.ts
import throttle4 from "lodash.throttle";
var shouldDiscardRemoteElement = (localAppState, local, remote) => {
  if (local && // local element is being edited
  (local.id === localAppState.editingTextElement?.id || local.id === localAppState.resizingElement?.id || local.id === localAppState.newElement?.id || // TODO: Is this still valid? As newElement is selection element, which is never part of the elements array
  // local element is newer
  local.version > remote.version || // resolve conflicting edits deterministically by taking the one with
  // the lowest versionNonce
  local.version === remote.version && local.versionNonce < remote.versionNonce)) {
    return true;
  }
  return false;
};
var validateIndicesThrottled = throttle4(
  (orderedElements, localElements, remoteElements) => {
    if (define_import_meta_env_default.DEV || define_import_meta_env_default.MODE === ENV.TEST || window?.DEBUG_FRACTIONAL_INDICES) {
      const elements = syncInvalidIndices(
        orderedElements.map((x) => ({ ...x }))
      );
      validateFractionalIndices(elements, {
        // throw in dev & test only, to remain functional on `DEBUG_FRACTIONAL_INDICES`
        shouldThrow: define_import_meta_env_default.DEV || define_import_meta_env_default.MODE === ENV.TEST,
        includeBoundTextValidation: true,
        reconciliationContext: {
          localElements,
          remoteElements
        }
      });
    }
  },
  1e3 * 60,
  { leading: true, trailing: false }
);
var reconcileElements = (localElements, remoteElements, localAppState) => {
  const localElementsMap = arrayToMap(localElements);
  const reconciledElements = [];
  const added = /* @__PURE__ */ new Set();
  for (const remoteElement of remoteElements) {
    if (!added.has(remoteElement.id)) {
      const localElement = localElementsMap.get(remoteElement.id);
      const discardRemoteElement = shouldDiscardRemoteElement(
        localAppState,
        localElement,
        remoteElement
      );
      if (localElement && discardRemoteElement) {
        reconciledElements.push(localElement);
        added.add(localElement.id);
      } else {
        reconciledElements.push(remoteElement);
        added.add(remoteElement.id);
      }
    }
  }
  for (const localElement of localElements) {
    if (!added.has(localElement.id)) {
      reconciledElements.push(localElement);
      added.add(localElement.id);
    }
  }
  const orderedElements = orderByFractionalIndex(reconciledElements);
  validateIndicesThrottled(orderedElements, localElements, remoteElements);
  syncInvalidIndices(orderedElements);
  return orderedElements;
};

// components/TTDDialog/TTDDialogTrigger.tsx
import { jsx as jsx154, jsxs as jsxs84 } from "react/jsx-runtime";
var TTDDialogTrigger = ({
  children,
  icon
}) => {
  const { TTDDialogTriggerTunnel } = useTunnels();
  const setAppState = useExcalidrawSetAppState();
  return /* @__PURE__ */ jsx154(TTDDialogTriggerTunnel.In, { children: /* @__PURE__ */ jsxs84(
    DropdownMenu_default.Item,
    {
      onSelect: () => {
        trackEvent("ai", "dialog open", "ttd");
        setAppState({ openDialog: { name: "ttd", tab: "text-to-diagram" } });
      },
      icon: icon ?? brainIcon,
      children: [
        children ?? t("labels.textToDiagram"),
        /* @__PURE__ */ jsx154(DropdownMenu_default.Item.Badge, { children: "AI" })
      ]
    }
  ) });
};
TTDDialogTrigger.displayName = "TTDDialogTrigger";

// components/DiagramToCodePlugin/DiagramToCodePlugin.tsx
import { useLayoutEffect as useLayoutEffect9 } from "react";
var DiagramToCodePlugin = (props) => {
  const app = useApp();
  useLayoutEffect9(() => {
    app.setPlugins({
      diagramToCode: { generate: props.generate }
    });
  }, [app, props.generate]);
  return null;
};

// index.tsx
import { jsx as jsx155 } from "react/jsx-runtime";
polyfill_default();
var ExcalidrawBase = (props) => {
  const {
    onChange,
    initialData,
    excalidrawAPI,
    isCollaborating = false,
    onPointerUpdate,
    renderTopRightUI,
    langCode = defaultLang.code,
    viewModeEnabled,
    zenModeEnabled,
    gridModeEnabled,
    libraryReturnUrl,
    theme,
    name,
    renderCustomStats,
    onPaste,
    detectScroll = true,
    handleKeyboardGlobally = false,
    onLibraryChange,
    autoFocus = false,
    generateIdForFile,
    onLinkOpen,
    generateLinkForSelection,
    onPointerDown,
    onPointerUp,
    onScrollChange,
    onDuplicate,
    children,
    validateEmbeddable,
    renderEmbeddable,
    aiEnabled,
    showDeprecatedFonts
  } = props;
  const canvasActions = props.UIOptions?.canvasActions;
  const UIOptions = {
    ...props.UIOptions,
    canvasActions: {
      ...DEFAULT_UI_OPTIONS.canvasActions,
      ...canvasActions
    },
    tools: {
      image: props.UIOptions?.tools?.image ?? true
    }
  };
  if (canvasActions?.export) {
    UIOptions.canvasActions.export.saveFileToDisk = canvasActions.export?.saveFileToDisk ?? DEFAULT_UI_OPTIONS.canvasActions.export.saveFileToDisk;
  }
  if (UIOptions.canvasActions.toggleTheme === null && typeof theme === "undefined") {
    UIOptions.canvasActions.toggleTheme = true;
  }
  useEffect45(() => {
    const importPolyfill = async () => {
      await import("canvas-roundrect-polyfill");
    };
    importPolyfill();
    const handleTouchMove = (event) => {
      if (typeof event.scale === "number" && event.scale !== 1) {
        event.preventDefault();
      }
    };
    document.addEventListener("touchmove", handleTouchMove, {
      passive: false
    });
    return () => {
      document.removeEventListener("touchmove", handleTouchMove);
    };
  }, []);
  return /* @__PURE__ */ jsx155(EditorJotaiProvider, { store: editorJotaiStore, children: /* @__PURE__ */ jsx155(InitializeApp, { langCode, theme, children: /* @__PURE__ */ jsx155(
    App_default,
    {
      onChange,
      initialData,
      excalidrawAPI,
      isCollaborating,
      onPointerUpdate,
      renderTopRightUI,
      langCode,
      viewModeEnabled,
      zenModeEnabled,
      gridModeEnabled,
      libraryReturnUrl,
      theme,
      name,
      renderCustomStats,
      UIOptions,
      onPaste,
      detectScroll,
      handleKeyboardGlobally,
      onLibraryChange,
      autoFocus,
      generateIdForFile,
      onLinkOpen,
      generateLinkForSelection,
      onPointerDown,
      onPointerUp,
      onScrollChange,
      onDuplicate,
      validateEmbeddable,
      renderEmbeddable,
      aiEnabled: aiEnabled !== false,
      showDeprecatedFonts,
      children
    }
  ) }) });
};
var areEqual5 = (prevProps, nextProps) => {
  if (prevProps.children !== nextProps.children) {
    return false;
  }
  const {
    initialData: prevInitialData,
    UIOptions: prevUIOptions = {},
    ...prev
  } = prevProps;
  const {
    initialData: nextInitialData,
    UIOptions: nextUIOptions = {},
    ...next
  } = nextProps;
  const prevUIOptionsKeys = Object.keys(prevUIOptions);
  const nextUIOptionsKeys = Object.keys(nextUIOptions);
  if (prevUIOptionsKeys.length !== nextUIOptionsKeys.length) {
    return false;
  }
  const isUIOptionsSame = prevUIOptionsKeys.every((key) => {
    if (key === "canvasActions") {
      const canvasOptionKeys = Object.keys(
        prevUIOptions.canvasActions
      );
      return canvasOptionKeys.every((key2) => {
        if (key2 === "export" && prevUIOptions?.canvasActions?.export && nextUIOptions?.canvasActions?.export) {
          return prevUIOptions.canvasActions.export.saveFileToDisk === nextUIOptions.canvasActions.export.saveFileToDisk;
        }
        return prevUIOptions?.canvasActions?.[key2] === nextUIOptions?.canvasActions?.[key2];
      });
    }
    return prevUIOptions[key] === nextUIOptions[key];
  });
  return isUIOptionsSame && isShallowEqual(prev, next);
};
var Excalidraw = React44.memo(ExcalidrawBase, areEqual5);
Excalidraw.displayName = "Excalidraw";
export {
  Button,
  CaptureUpdateAction,
  DEFAULT_LASER_COLOR,
  DefaultSidebar,
  DiagramToCodePlugin,
  Excalidraw,
  FONT_FAMILY,
  FooterCenter_default as Footer,
  LiveCollaborationTrigger_default as LiveCollaborationTrigger,
  MIME_TYPES,
  MainMenu_default as MainMenu,
  ROUNDNESS,
  Sidebar,
  Stats,
  THEME,
  TTDDialog,
  TTDDialogTrigger,
  UserIdleState,
  WelcomeScreen_default as WelcomeScreen,
  bumpVersion,
  convertToExcalidrawElements,
  defaultLang,
  elementPartiallyOverlapsWithOrContainsBBox,
  elementsOverlappingBBox,
  exportToBlob,
  exportToCanvas2 as exportToCanvas,
  exportToClipboard,
  exportToSvg2 as exportToSvg,
  getCommonBounds,
  getDataURL,
  getFreeDrawSvgPath,
  getLibraryItemsHash,
  getNonDeletedElements,
  getSceneVersion,
  getTextFromElements,
  getVisibleSceneBounds,
  hashElementsVersion,
  hashString,
  isElementInsideBBox,
  isElementLink,
  isInvisiblySmallElement,
  isLinearElement,
  languages,
  loadFromBlob,
  loadLibraryFromBlob,
  loadSceneOrLibraryFromBlob,
  mergeLibraryItems,
  mutateElement,
  newElementWith,
  normalizeLink,
  parseLibraryTokensFromUrl,
  reconcileElements,
  restore,
  restoreAppState,
  restoreElements,
  restoreLibraryItems,
  sceneCoordsToViewportCoords,
  serializeAsJSON,
  serializeLibraryAsJSON,
  setCustomTextMetricsProvider,
  useDevice,
  useHandleLibrary,
  useI18n,
  viewportCoordsToSceneCoords,
  zoomToFitBounds
};
//# sourceMappingURL=index.js.map
