Compare commits

...
933 Commits
Author SHA1 Message Date
David Luzar e1b7aacb35 feat(packages/excalidraw): replace the export functions with exportScene (#12347)
* [export] rewrite - init

* restore elements + filter deleted in export API (restores #11470 behavior)

* fix snaps

* port export callers added on master to the new API + snaps

- renderOverrides (#12100), sticky notes (#12064) and arrow-label hole
  (#10947) tests called the old positional exportToSvg/exportToCanvas
- embedded-scene snapshots gain master's new element fields (created,
  baseFontSize, labelPosition); decoded payloads match master's

Claude Opus 5.5 (xhigh)

* restore elements in embedded scene + json clipboard export (restores #12060 behavior)

- master's #12060 made every public export entry point restore its input
  (legacy elements reach lower-level code with `created: null`), incl. the
  PNG metadata embed and the json clipboard copy in the removed
  @excalidraw/utils wrappers
- SVG embed now serializes the restored, non-deleted elements (same as
  master's utils path), PNG embed serializes restoreElements(data.elements)
  and json clipboard copies the non-deleted restored elements
- port #12060's utils tests to the new API, observing renderers/serializer
  instead of the (now identical) scene export functions

Claude Opus 5.5 (xhigh)

* make config.canvasBackgroundColor the single export background knob

- background resolved once in configExportDimension; exportToSvg no longer
  reads appState.exportBackground (now omitted from ExportSceneData's
  appState type), and honors `canvasBackgroundColor: false`
- outline arrowheads (circle/triangle/diamond_outline, cardinality_zero_*)
  are filled with the resolved background color, also when the background
  isn't painted (as on master) — the canvas path filled them transparent,
  showing the arrow's shaft through them; SVG ignored the config color
- JPEG: `canvasBackgroundColor: false` falls back to the default background
  (+ warning) instead of rendering black; no more warning for the default
- callers (exportCanvas, ImageExportDialog, library/chart/publish previews,
  AI) pass the background via config only

Claude Opus 5.5 (xhigh)

* resolve export theme before creating frame labels

- frame labels were colored by appState.exportWithDarkMode, ignoring
  config.theme (export dialog's dark toggle), and in dark exports the
  dark-theme label color got dark-filtered again by the renderer
  (#7a7a7a -> #848484)
- theme is now resolved once in configExportDimension; dark labels are
  pre-reverted so they render with the editor's frame name color

Claude Opus 5.5 (xhigh)

* drop appState.exportWithDarkMode theme fallback in export

- `config.theme` is the only theme input (defaults to light); the type
  already omitted `exportWithDarkMode` from `data.appState`
- callers that relied on the fallback (PublishLibrary item preview, AI
  frame export) pass `theme` explicitly, keeping their behavior

Claude Opus 5.5 (xhigh)

* exportToClipboard: don't restore appState before exportToSvg (already restored there)

Claude Opus 5.5 (xhigh)

* remove unused getExportSize, type getCanvasSize's [x, y, width, height] (not Bounds)

Claude Opus 5.5 (xhigh)

* fix typos in export comments/JSDoc

Claude Opus 5.5 (xhigh)

* port with-script-in-browser example to the new export API

- exportToSvg/Blob/Canvas/Clipboard take `{ data, config }`; theme via
  `config.theme`, padding 10 kept explicit (new default is 0)
- dropped the no-op `width`/`height` appState overrides from the SVG export

Claude Opus 5.5 (xhigh)

* call getDimensions once, ignore it when widthOrHeight is set

- with an explicit `fit: "contain"`, getDimensions ran twice (the second
  time on its own output); it now supplies width/height (+ scale) once,
  and `contain` scales the content into that frame
- getDimensions + widthOrHeight both applied; widthOrHeight now wins (with
  a warning), like width/height/maxWidthOrHeight already did
- `fit: "none"` (the E+ export server path) renders identically

Claude Opus 5.5 (xhigh)

* type the export size options as mutually exclusive

- `width`/`height`, `maxWidthOrHeight`, `widthOrHeight` and `getDimensions`
  are now a union where the unused options are `?: never`, so combining
  them is a type error (runtime still ignores/warns for JS callers)
- the `never` keys are remapped over `keyof`, so every option keeps its
  JSDoc in completions & hovers (verified via the TS language service)

Claude Opus 5.5 (xhigh)

* replace getDimensions with a `scale` function, round the canvas size

- `config.scale` also accepts `(width, height) => number`, called with the
  export dimensions at scale 1 (padding included) — covers content-dependent
  scaling (e.g. E+'s pixel budgets) without the getDimensions round trip
- remove `getDimensions`: its returned `scale` replaced `config.scale` and
  multiplied the returned size, so callers thinking in output pixels had
  to divide it back out
- exportToCanvas rounds the canvas size instead of letting the canvas
  truncate it (float noise like 28.999999999999996 lost a pixel); half
  pixels now round up (frame label area 20.5px, mermaid preview 89.5px)

Claude Opus 5.5 (xhigh)

* align fit/padding/position export docs with the code

- `fit` defaults to "contain" when `width`/`height` is set, otherwise
  "none" (the doc said the opposite); dropped the unimplemented
  "x/y force none" claim
- padding is always subtracted for `maxWidthOrHeight`/`widthOrHeight`
  (they ignore `fit`), not added
- `position` values are "center" | "topLeft" (doc listed "none")

Claude Opus 5.5 (xhigh)

* derive a missing export width/height from the aspect ratio

- with `fit: "contain"` (the default when a size is given), passing only
  `width` or `height` now derives the other from the content's aspect
  ratio, as documented — it defaulted to the content's unscaled size,
  e.g. `{ height: 50 }` of 200x100 content gave a 200x50 canvas with
  60x30 content in wide empty bands (now 80x50 with padding 10)
- with `fit: "none"` the missing side stays the content extent

Claude Opus 5.5 (xhigh)

* default export `fit` to "none" when x/y is set

- `{ x, y, width, height }` was scaled to fit with `x`/`y` silently
  ignored (`fit` defaulted to "contain" whenever a size was given); an
  explicit region now crops by default, `fit: "contain"` still opts in
  (and ignores `x`/`y`, as now documented)

Claude Opus 5.5 (xhigh)

* ignore export x/y when the content is scaled to fit

- with `fit: "contain"`, `maxWidthOrHeight` or `widthOrHeight`, `x`/`y`
  shifted the fitted content by a mix of scene units and pixels; they're
  now dropped with a warning, which the previous commit already
  documented (its "ignored" claim was based on a coincidental probe)

Claude Opus 5.5 (xhigh)

* pad an export x/y region on all sides

- with `position: "center"` (the default), the centering adjustment also
  ran on axes pinned by `x`/`y`, shifting the region by the padding: the
  canvas started exactly at `x`/`y` with all padding on the right/bottom
  (`position: "topLeft"` padded all sides); `position` now only applies to
  unpinned axes, as documented

Claude Opus 5.5 (xhigh)

* consolidate exportToCanvas/Blob/Svg into exportScene({ type, data, config })

- `type: "canvas" | "png" | "jpeg" | "svg"`, return type per `type`
  (overloads, plus a union overload for dynamic callers); per-type options
  are type-checked: `quality` only for jpeg, `embedScene` for png/svg,
  `renderEmbeddables`/`skipInliningFonts`/`reuseImages` for svg
- the format replaces `mimeType` (and its "image/jpg" typo handling)
- `appState.exportEmbedScene` -> `config.embedScene` (no longer read
  from `data.appState`); `data.files` is optional
- `config.createCanvas` -> `config.environment.createCanvas` (where
  #11997's render environment and richtext's text metrics will go)
- exportToCanvas/Blob/Svg are no longer exported (no aliases);
  exportToClipboard stays, routed through exportScene
- callers, the with-script-in-browser example and tests ported

Claude Opus 5.5 (xhigh)

* export: take only scene settings from data.appState, frameRendering -> config.frames

- `data.appState` is typed as (and stripped at runtime to) the scene
  settings a .excalidraw file stores; the renderer state is built from
  those + defaults, so editor state passed in (a live AppState: open
  dialog, cropping, frame highlight…) can't affect the output. It only
  supplies the default background and what `embedScene` embeds
- `appState.frameRendering` -> `config.frames: { name, outline, clip }`
- editor callers translate their export settings via one internal
  `getExportConfigFromAppState`; `exportCanvas` drops its redundant
  exportBackground/viewBackgroundColor options
- fixes the editor's SVG export ignoring `exportScale` (the branch's
  exportCanvas SVG path had dropped it; master passed it)

Claude Opus 5.5 (xhigh)

* type export positioning options as width/height-only

- `fit`, `position`, `x`, `y`, `origin` move into the size-option union's
  `width`/`height` member, so combining them with `maxWidthOrHeight` /
  `widthOrHeight` (which ignore them) is a type error; runtime unchanged
  (still ignores/warns for JS callers)
- docs stay visible in completions (same `keyof` remap as the size options)

Claude Opus 5.5 (xhigh)

* move export `loadFonts` into `config.environment`

- font loading is a host capability, next to `createCanvas` (svg gets
  `environment: { loadFonts }` only, as it creates no canvas)

Claude Opus 5.5 (xhigh)

* changelog: export API (exportScene, config, migration table)

Claude Opus 5.5 (xhigh)

* export: default padding back to 10, canvas at least 1px

- export bounds are geometric (no stroke width), so padding 0 cut half
  of the strokes on the content's edge and made flat content (a line)
  zero-height — the library/chart/publish previews, which inherited
  master's default of 10, rendered blank thumbnails for lines
- canvas dimensions never go below 1px (a 0-sized canvas can't be
  encoded); SVG snapshot sizes are back to master's

Claude Opus 5.5 (xhigh)

* export: clamp padding against the fitted size, before deriving from it

- the padding clamp used the content's unpadded size, so flat content (a
  horizontal line) lost its padding with maxWidthOrHeight/widthOrHeight
  (`{ padding: 10, maxWidthOrHeight: 400 }` → 200x0); it now only
  considers the target being fitted into
- a missing width/height was derived before clamping
  (`{ height: 10, padding: 10 }` → 0x10, SVG viewBox NaN/Infinity)
- a zero-extent content axis no longer yields a NaN scale under contain

Claude Opus 5.5 (xhigh)

* export: clip frame exports to the frame

- frame clipping was off for exportingFrame (to avoid the rounded
  per-element clip), relying on the canvas/SVG edge — which only crops
  when the export is exactly the frame; a larger export (size options)
  painted children and overlapping elements into the margins, and SVG
  frame exports had stopped clipping (master clipped them)
- everything drawn is now clipped to the frame with square corners: a
  canvas clip after the background (`renderConfig.clipToFrame`), and an
  SVG group with the frame's clip path; the background isn't clipped

Claude Opus 5.5 (xhigh)

* changelog: fill in PR numbers for the placeholder links

Claude Opus 5.5 (xhigh)
2026-10-18 20:24:17 +02:00
Arne ReiandMahiro Hirakawa 4c37956ba5 fix(editor): detect line height from the migrated font props on restore (#12033)
* fix(editor): detect line height from the migrated font props on restore

The text branch of `restoreElement` parses the legacy `font` prop into local
`fontSize` and `fontFamily`, but line-height detection kept reading
`element.fontSize` and `element.fontFamily`. A diagram old enough to carry
`font` has neither, so `detectLineHeight` divided by `undefined` and the
element was restored with `lineHeight: NaN`. A non-numeric `height` produced
the same result.

Move the detection into `restoreLineHeight`, pass it the already-migrated
values, and fall back to the font family's fixed line height when the
detected value isn't a usable number.

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mahiro Hirakawa <mahirohirakawa@glovrex.com>
2026-10-14 18:29:25 +02:00
David Luzar 541653c7c9 fix(editor): frame membership, clipping and cut fixes (#12270)
* fix(editor): a frame snapped to an element's edges takes it in

A new or resized frame takes the elements (and groups) within it by their
outlines (not counting the stroke), to a hair beyond its edges, which
snapping leaves them at.

Claude Opus 5.5 (1M context), effort: medium

* fix(editor): a frame always clips its children

As in SVG exports — a child inside the frame may still reach out of it with
its stroke — without checking geometry, but for children being dragged,
which are clipped only while still in it, as their drop decides. The
outline check remains for elements dragged into a frame.

Claude Opus 5.5 (1M context), effort: medium

* fix(editor): cutting a frame takes its children along

Unlike deleting it, which leaves them: they're on the clipboard with the
frame, so pasting it back would duplicate them.

Claude Opus 5.5 (1M context), effort: medium
2026-10-12 14:33:19 +02:00
Milos Vetesnik 4f310b7996 feat(app): restyle Excalidraw+ upgrade button as stacked trial CTA (#12269)
* feat: restyle Excalidraw+ upgrade button as stacked trial CTA with sparkle

* feat: use gift icon instead of sparkle on Excalidraw+ button
2026-10-12 13:28:00 +02:00
David Luzar 704db6803c feat(editor): w to wrap/unwrap selection (#12264)
* fix(editor): keep the text selected along with its container after unbinding

Claude Opus 5.5 (1M context), effort: medium

* feat(editor): `w` wraps or unwraps the selection

- a text and an empty container: binds the text
- texts (with any labeled containers): wraps each text in a container
- labeled containers: unwraps the texts and removes the containers,
  moving their arrows onto the texts; the next `w` on the untouched
  selection brings the same containers back where the texts are now
- a frame with its children, or all of a frame's children: removes the frame
- anything else without a frame: wraps it in a frame

"Wrap selection in frame" frames an image (and what lies on it) edge to
edge, and removes the frame when all of its children are selected.

Claude Opus 5.5 (1M context), effort: medium

* fix(editor): unwrapping an arrow drops its point editor

The selection unwrapping (and rewrapping, and removing a frame) leaves is
resolved with its line editor, so a deleted arrow's editor doesn't linger.

Claude Opus 5.5 (1M context), effort: medium

* fix(editor): rewrapping a note keeps the label's font size and color

A font size picked while the label was unwrapped becomes its base size, and
the note takes the label's ink (shared with binding via `getStickyNoteInk`).

Claude Opus 5.5 (1M context), effort: medium

* fix(editor): resizing an unwrapped text doesn't move its rewrapped container

A font size change keeps the text's alignment edge and middle in place, so
the move is measured there rather than at the top left.

Claude Opus 5.5 (1M context), effort: medium

* fix(editor): binding, unbinding and framing resolve the arrow point editor

Binding text to an arrow selects it with its point editor; wrapping a lone
arrow in a frame, or unbinding an arrow's label (selecting both), drops it.

Claude Opus 5.5 (1M context), effort: medium

* feat(editor): wrapping a text draws a default rectangle in the text's color

Not the current item's style — a wrapped text is a label, and the rectangle
matches it.

Claude Opus 5.5 (1M context), effort: medium

* feat(editor): unbinding keeps the text as it rendered, at a fixed width

The freed text keeps its wrapping, width and position (not auto-resizing);
rewrapping it restores auto-resize. A fixed-width text keeps its top left
through font size changes, so a rewrapped container measures the text's
move there.

Claude Opus 5.5 (1M context), effort: medium
2026-10-12 11:35:45 +02:00
David Luzar 2df993fb39 fix(editor): resizing text by a corner follows the cursor on the axis it grows most (#12262)
Text scaled by the height alone, so a corner dragged sideways didn't follow
the cursor; it now scales as resizing keeping the aspect ratio does (which
text always does), by the axis that grows most.

Claude Opus 5.5 (1M), xhigh effort
2026-10-11 16:48:34 +02:00
David Luzar ef53e5417b feat(editor): crop editor modifiers as resizing, and double-click a handle to uncrop (#12260)
* fix(editor): shift in the crop editor: shift-click keeps it open, shift-drag moves the image along one axis

- shift-clicking the image being cropped no longer toggles it out of the
  selection (which ended cropping)
- dragging the image within its crop follows the drag offset, axis-locked
  with shift as dragging elements is, applied by its change since the
  previous pointer move (pointerDownState.drag.lastOffset) so the image
  still moves back right away after being dragged past its edge
- drops App.previousPointerMoveCoords, which only the crop drag used

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): alt-drag a crop handle to crop from the center

- cropElementFromCenter crops the opposite side by as much as the dragged
  one, keeping the element's center, as far as the image reaches on the
  nearer side; with shift, keeping the aspect ratio
- no alt drag threshold in the crop editor: alt-clicks don't cycle the
  selection there

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): clicking a crop handle no longer leaves the crop editor

A press on a crop handle hits no element, so a click on one without a move
in between counted as a click outside the image.

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): double-click a crop handle to uncrop its sides

- in the crop editor, double-clicking a side's handle uncrops that side, a
  corner's its two sides, as dragging the handle out as far as the image
  reaches does (uncropElementSides)
- with alt, the opposite side(s) too, so alt on a corner uncrops it all;
  alt double-clicks aren't ignored in the crop editor, which doesn't cycle
  the selection

Claude Opus 5.5 (1M), xhigh effort

* refactor(element): uncrop and crop from the center through one bounds helper

- cropElementToBounds crops the image to bounds relative to the element
  (rotated and flipped with it); uncropElementSides and cropElementFromCenter
  only compute those bounds
- uncropElementSides no longer drives cropElement with a pointer past the
  image's edge, copying the crop it mutates, nor recurses for the mirrored
  side
- maybeUncropSides takes the selected image, and the pointer type from
  appState.lastPointerDownWith

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): arrows bound to an image follow the sides uncropped by a double-click

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): crop from the drag's start, as resizing does, so alt and shift apply mid-drag

- maybeHandleCrop crops the image as it was at pointer down
  (pointerDownState.originalElements) rather than as of the previous pointer
  move, and maybeHandleResize crops in the crop editor, so pressing or
  releasing alt or shift mid-drag re-crops right away, as resizing does
- cropElement no longer mutates the crop of the element it's given (which is
  now the drag's start state)

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): no invisible rotation handle in the crop editor

The crop editor draws no rotation handle, but pressing where it would be hit
it, and dragging it cropped the image ("rotation" includes "n"). A press
there is now one outside the image, which leaves the editor.

Claude Opus 5.5 (1M), xhigh effort

* refactor(element): crop an image as resizing it would resize it

- cropElement sizes the crop from the pointer as resizing does
  (getNextSingleWidthAndHeightFromPointer, from the center too), keeps it
  within the image from what stays put (the opposite side, or the center),
  and crops through cropElementToBounds; it replaces the per-handle cases,
  recomputeOrigin and cropElementFromCenter
- options as resizing's: { shouldMaintainAspectRatio, shouldResizeFromCenter }
  rather than a width aspect ratio (the element's, which is the drag's start)
- keeping the aspect ratio, a corner crops by the axis it grows most,
  relatively, as resizing does (rather than by pixels), and an axis dragged
  past what stays put has the minimal size rather than flipping
- an image already smaller than the minimal size crops no smaller, rather
  than jumping to it

Claude Opus 5.5 (1M), xhigh effort

* fix(element): the image no longer shifts by a pixel while cropping it

The crop editor drew the cropped image from its own bitmap, snapped to whole
pixels by its own corner, over the faded uncropped image's: as the corner
moved by fractions of a pixel (cropping from the center, or keeping the
aspect ratio along a side), the image within the crop shifted by a pixel
against the faded rest. Within the crop, it now draws the uncropped image's
bitmap at full opacity, clipped to the crop.

Claude Opus 5.5 (1M), xhigh effort
2026-10-11 16:37:37 +02:00
David Luzar e65eca999e refactor(editor): app.selection, and the rest of the selection tool out of App (#12258)
* refactor(editor): add AppSelection (app.selection) to select, add to, remove from and clear the selection

- select(element) for click-select, the alt-click cycle and the context menu;
  add(element) for the selection tool and the lasso's touch select (was a
  near-copy); remove(element) for shift-click; clear() replaces
  App.deselectElements and three inline copies
- isASelectedElement / isHittingCommonBoundingBoxOfSelectedElements move from
  the selection tool
- select() drops a stale line editor of a no longer selected arrow; the
  context menu no longer spreads all of this.state into its setState
- tests for the selection's frame, link selector and line editor rules

Claude Opus 5.5 (1M), xhigh effort

* refactor(editor): make the app.selection API generic over elements or ids, one or many

- select / add / remove take elements or ids, one or many (missing and
  deleted ones skipped)
- their rules become pure functions in App.selection.ts, for callers that
  need the next selection state rather than a setState (actions,
  duplication): getSelectionStateForElements (moved from @excalidraw/element,
  now keeping the current line editor), getSelectionStateAddingElements,
  getSelectionStateRemovingElements — one pass over the elements however
  many are added or removed
- remove() recomputes selectedGroupIds (it put element ids into it and lost
  the other selected groups' flags), and leaves group editing once nothing
  is selected
- the link popup on adding an element is the pointer's, not add()'s

Claude Opus 5.5 (1M), xhigh effort

* refactor(editor): move selectGroupsForSelectedElements from @excalidraw/element into App.selection.ts

- editor state (selected group ids, the edited group), used by the editor
  only; moved as is
- also ends the groups.ts <-> selection.ts import cycle in @excalidraw/element

Claude Opus 5.5 (1M), xhigh effort

* refactor(editor): move dragging the selection and box selection out of App into AppSelectionTool

- handleSelectionDragOnPointerMove: dragging the selection (or the image
  within its crop), alt-drag duplicating past the threshold
- handleBoxSelectionOnPointerMove: box selection (and of the line editor's
  points)
- handleSelectionDragOnPointerUp: frame membership of the dragged selection
- pure moves, verified mechanically
- tests for shift-dragging along one axis and moving the image within its
  crop (both untested through the pointer)

Claude Opus 5.5 (1M), xhigh effort

* refactor(editor): one check for the selection's transform handle under the pointer

- selectionTool.getTransformHandleAt() replaces the two copies in the hover
  cursor and on pointer down (line editor, elbow arrows, two-point lines,
  mobile lines, hovered line points; a lone element's or the selection's
  common bounds)
- pointer down now also ignores the handles in the element link selector,
  as the hover cursor did
- test for the resize cursor over the handles (untested)

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): app.selection shows the link popup of what it selects

- select / add / remove show the link popup of a lone selected element with
  a link (or an embeddable) and hide it otherwise; clear hides it — it was
  left as is, so select() showed it only if a previous selection had
- the link editor stays open when its element is selected again
- the pointer and lasso call sites no longer set it themselves

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): app.selection ends what hangs on the deselected elements and skips locked ones

- cropping ends unless the cropped image stays the selection, and an active
  embed is deactivated once deselected (the line editor already ended once
  its line is deselected)
- select / add skip locked elements, unless includeLocked — which the
  context menu passes, to offer unlocking them
- captureUpdate option, as updateScene's, to record the change for undo
- deep option on select (the elements themselves, editing their shared
  group), which Ctrl+click now goes through
- text editing isn't ended here: the editor ends itself on blur, and
  submitting it mid pointer event would commit half the event's state

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): app.selection ends text editing, and add / remove / clear always end cropping

- select / add of anything but the edited text (or its container) submit
  the text editor — after the current event (it submits with a flushSync),
  and only if that editing session is still open; clear / remove leave it
  (starting text editing clears the selection)
- add / remove / clear end cropping; select keeps it only when selecting the
  cropped image again (a click inside it in the crop editor)

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): app.selection derives the line editor from the final selection, and frame / deep fixes

- the line editor is set from the selection once groups are expanded: an
  arrow selected with its group (e.g. by alt-click cycling) gets none, so
  dragging from its point moves the group rather than reshaping the arrow;
  add() sets it too (adding a lone arrow left Edit / Cmd+Enter throwing)
- children of frames selected through the groups of the selected (or added)
  elements are left out, also ones selected in the same call — and adding
  within an edited group no longer deselects as if the edited group's frames
  were selected
- deep selection never expands groups (it did for elements not sharing one)
- a click-selected elbow arrow now gets its line editor on pointerdown, so
  that click's pointerup normalizes a hand-built route one gesture earlier

Claude Opus 5.5 (1M), xhigh effort

* feat(packages/excalidraw): excalidrawAPI.selection

- app.selection on the imperative API — select / add / remove / clear,
  taking elements or ids, one or many, with the deep / includeLocked /
  captureUpdate options; the type exposes only those four
- they're arrow properties, so they keep `this` when taken off the object
- changelog entry

Claude Opus 5.5 (1M), xhigh effort
2026-10-10 23:34:00 +02:00
David Luzar 4c00f31ddc feat(editor): autoplay Google Drive videos when the embed is activated (#12246)
- Drive's player has no postMessage API (unlike YouTube/Vimeo), but its
  /preview page honors `autoplay=1`, so the first activation reloads the
  embed with it: a single click plays the video in view mode
- reloaded via `contentWindow.location.replace` so it adds no history
  entry, and only once per iframe so re-activating doesn't restart it

Claude Opus 5.5 (1M), xhigh effort
2026-10-08 11:11:53 +02:00
David Luzar 2559257bb2 fix(editor): pan once and keep editing on a wheel-button drag over the text editor (#12244)
- the text editor's window pointerdown listener was added in capture mode
  but removed without it, so every past editing session kept panning too:
  the pan was multiplied by the number of texts edited since load
- the pan focuses the container, which blurred the editor while blur still
  submitted it; submit is now disabled before the pan starts, and the
  editor is refocused on release

Claude Opus 5.5 (1M), xhigh effort
2026-10-07 20:31:58 +02:00
David Luzar 53973c3a42 feat(editor): alt-click to cycle the selection through overlapping elements (#12237)
* feat(editor): alt-click to cycle selection through overlapping elements

- alt+click on the selected element (or group) selects the one below it
  under the pointer, wrapping around to the topmost
- inside an edited group, cycles through that group's elements only
- alt+drag duplicates only past a 10px threshold, so alt-clicks don't
- alt+double-click no longer creates/edits text (except with autoshape)

Claude Opus 5.5 (xhigh)

* refactor(editor): move the selection tool out of App into AppSelectionTool (app.selectionTool)

- handleSelectionOnPointerDown, clearSelection(IfNotUsingSelection),
  isASelectedElement, isHittingCommonBoundingBoxOfSelectedElements and the
  alt-click cycle target move as is
- the click selection of the pointer up handler (alt-click cycle, shift
  toggle, click select, bbox-only deselect) becomes
  handleSelectionOnPointerUp
- the selection's transform handles, maybeHandleResize / maybeHandleCrop,
  move as is
- tests for alt-click cycling, and for cropping by dragging a crop handle

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): alt-click cycles the selection on its resize handles too

- alt-resizing starts only past the 10px drag threshold, as alt-drag
  duplicating does, so an alt-click on a resize handle can cycle
- an alt-click on the selected element's resize handle (over the element)
  cycles the selection, but not once a resize happened

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): hint at alt-click cycling while Alt is held

- holding Alt over a single selected element (or group) with the selection
  or lasso tool shows "Alt-click to cycle selection, or drag to duplicate"
- app.modifiers tracks the held Alt, Shift and Ctrl/Cmd outside appState,
  synced from key and pointer events and reset on blur;
  app.modifiers.useHeld(modifier?) re-renders only on the given one
- the hover refresh after a pointer up replays the held modifiers, so it no
  longer releases them (the hint flickered on every alt-click)
- selectionTool.canCycleSelection() shares the cycle rules between the
  hint and the click

Claude Opus 5.5 (1M), xhigh effort
2026-10-06 17:31:23 +02:00
David Luzar ed10ac7dca feat(editor): alt-drag the text being edited to duplicate it (#12219)
* feat(editor): alt-drag the text being edited to duplicate it

- alt-pressing the text editor ends the editing and hands the press over
  to the canvas, so the drag duplicates the text (or its container)
- the handed-over press always moves what was edited, never resize
  handles or an arrow's points, midpoint or label beneath the editor
- on drop, the duplicate is edited with all of its text selected (a list
  item's text only, as when alt-dragged on the canvas)

Claude Opus 5.5 (xhigh)

* fix(editor): no arrow hover highlights while editing its label

- starting to edit text clears the hover state of the arrow's editor
  (kept up for the submit), so a midpoint hovered before the double-click
  doesn't stay highlighted
- the arrow's hover affordances don't apply while editing text

Claude Opus 5.5 (xhigh)

* fix(editor): don't drop arrow hover updates on the same pointer move

- the hovered point, midpoint and focus point were each set from
  `this.state`, so when one move changed several of them (e.g. from the
  midpoint straight onto an endpoint), only the last update survived

Claude Opus 5.5 (xhigh)
2026-10-01 16:39:40 +02:00
David Luzar 9ba66ed9b5 feat(editor): auto increment numbers/lists on duplicate (#12189)
* feat(editor): auto increment numbers/lists on duplicate

- duplicating a text or a label (shape, arrow, sticky note) that starts with a list marker advances it: `1.` `1)` `(1)` `[1]`, `a.` `a)` `(a)` (`a`–`h`, then roman numerals `i`–`xcix`), and lone numbers (`1`)
- markers must be followed by whitespace (`1.5`, `e.g.` aren't lists); whole lists are left as they are
- numbers count up to 42, letters up to `i`
- only if nothing but list items is duplicated (a label goes with its container); several items only if they're just markers (`1.` `2.`), continuing from the highest of them (`1.`, `3.`, `5.` -> `6.`, `7.`, `8.`)
- the increment is a separate undo step after the duplication; alt-drag shows it from the start of the drag

Opus 5.5 (1M context), xhigh

* feat(editor): edit the text of an alt-duplicated list item

- on alt-drag drop, a single list item with text after its marker enters the text editor with that text selected (`2. |foo|`)
- not when duplicated along with other elements, nor when the drag doesn't end with a genuine pointerup (pointercancel, missing-pointerup cleanup)
- `textWysiwyg` / `startTextEditing` take an `initialSelection`

Opus 5.5 (1M context), xhigh
2026-10-01 15:08:33 +02:00
David Luzar 1919728724 fix(editor): reach text containers below transparent ones (#12210)
The text tool (hover and click), and a double-click with nothing
selected, took the top-most shape whose bounds held the pointer. A
transparent shape stacked above a nested one kept the inner one out of
reach at its center, and an arrow below a transparent shape couldn't get
a label.

- only a filled element or an image (the rule arrow binding uses, now
  exported as isOpaqueForBinding) or an arrow ends the search
- of the containers found, the innermost one whose center is at the
  pointer wins, else the top-most as before
- an arrow counts only where its path is hit, not anywhere in its bounds
- a frame's children aren't hit outside the frame, where they're clipped
  (App.isClippedByFrameAt, shared with getElementsAtPosition)

Claude Opus 5.5 (1M), xhigh effort
2026-09-30 23:26:42 +02:00
David Luzar 35e854ecf7 feat(app): label E+ banner Upgrade / Sign in (#12204)
Claude Opus 5.5 (xhigh)
2026-09-29 17:44:41 +02:00
Renan-MouraN 5a406e5187 fix(app): correct typo in Mermaid input placeholder (#12186)
fix: correct typo in Mermaid input placeholder
2026-09-28 22:18:02 +00:00
David Luzar e283f724c8 refactor(editor): move the clipboard out of App into AppClipboard (app.clipboard) (#12197)
* refactor(editor): move the clipboard out of App into AppClipboard (app.clipboard)

A pure move of the clipboard handling out of App.tsx (13,478 → 12,942
lines) into App.clipboard.ts: onCut and onCopy (the document's cut/copy
events, through their actions), pasteFromClipboard (the paste event,
parsed, and handed to a host's onPaste first), insertClipboardContent
(inserting the pasted data as whatever it is: images or SVG code,
elements, a Mermaid definition, embeddable URLs, a spreadsheet as a
chart, mixed content, or text), addElementsFromMixedContentPaste and
addTextFromPaste. The plain-paste flags move along as module-level state;
Ctrl/Cmd+V's marking of a plain paste, and its reset, are App's former
lines behind app.clipboard.onPasteShortcut and clearPlainPaste.

Staying in App: addElementsFromPasteOrLibrary and insertImages, shared
with drop, the library and text-to-diagram (insertImages becomes public
for the clipboard), and onIframeSrcCopy, which belongs to the magic frame.

Callers go through `app.clipboard`: no delegate is left on App, and
AppClassProperties exposes `clipboard` (as it does `textTool`) instead of
`pasteFromClipboard`. Beyond the move, `this.` becomes `this.app.` for
what stays on App, onCut and onCopy become public, and the container ref
comes in as a dependency. The moved code is identical to the removed,
modulo those rewrites, whitespace and trailing commas (checked
mechanically).

Claude Opus 5.5 (1M), xhigh effort

* test(editor): cover copy/cut events and more paste routes

Only 80% of the clipboard code ran under the suite, and nothing went
through the document's copy and cut events, which is how Ctrl/Cmd+C/X
reach the editor in a browser. Now a copy event copies the selection and
a cut event cuts it; a host's onPaste returning false keeps the paste
from being inserted; a pasted spreadsheet opens the charts dialog; and
with the image tool off, a paste of mixed content inserts its text.

Claude Opus 5.5 (1M), xhigh effort
2026-09-28 23:51:36 +02:00
David Luzar 3947883063 refactor(editor): move text editing out of App into AppText (app.text) (#12195)
* refactor(editor): move text editing out of App into AppText (app.text)

A pure move of the text editing session, and of the text helpers around
it, out of App.tsx (14,250 → 13,478 lines) into App.text.ts. It sits next
to AppTextTool, which keeps the text tool's pointer interaction.

AppText holds:
- the session: startTextEditing (creates or picks the text to edit, and
  opens the editor), handleTextWysiwyg (the editor's lifecycle) and the
  open editor's submit handler
- the part of the canvas a typed or pasted text is kept within:
  getTextSidePanels, getTextViewportOffsets and getMaxTextWidth (paste,
  which stays in App for now, calls into them)
- where a new text goes: getTextCreationGridPoint and
  getTextWysiwygSnappedToCenterPosition
- the queries that find the text or container to edit:
  getTextElementAtPosition, getTextBindableContainerAtPosition,
  getSelectedTextElement, getSelectedTextEditingContainerAtPosition and
  isEditingTextContent
- the auto-resize handle: isHittingTextAutoResizeHandle and
  handleTextAutoResizeHandlePointerDown

Callers go through `app.text`, startTextEditing's included: no delegate
is left on App (no host or test used it). Beyond the move, `this.`
becomes `this.app.` for what stays on App, members called from outside
become public (App.deselectElements too), and the container ref and the
styles panel mode come in as dependencies, as AppViewport's do. The moved
code is identical to the removed, modulo those rewrites, whitespace and
trailing commas (checked mechanically).

Claude Opus 5.5 (1M), xhigh effort

* test(editor): click the text auto-resize handle through the pointer

The handle that turns a fixed-width text back into a growing one was only
covered through its action: nothing hovered or clicked it. Hovering it on
a selected wrapped text now shows the pointer cursor, and clicking it
unwraps the text, through AppText's hit test and pointerdown handler.

Claude Opus 5.5 (1M), xhigh effort
2026-09-28 22:18:13 +02:00
David Luzar 3d056dbc8a feat(editor): wrap text at 800 and keep it clear of the side panels (#12192)
* feat(editor): wrap typed & pasted text at 800, clear of the side panels

A free text growing as it's typed stopped only at the view's width
(#12175), so on a large screen it ran to 2000+ px before wrapping, and a
pasted text wrapped at half the view. Both now follow one rule, in
`getMaxTextWidth`:

- no wider than TEXT_MAX_WRAP_WIDTH (800, in @excalidraw/common) in scene
  units, at any zoom: the paste's former cap, now shared. A paste no
  longer wraps at half the view.
- nor wider than the canvas between the side panels, less
  TEXT_VIEWPORT_PADDING at each side: the docked sidebar, as before; the
  full styles panel (desktop), which shows next to a text while it's
  edited, and once it's pasted and selected (pasting with nothing
  selected, its room is reserved: as last measured, else approximately);
  and the stats panel, only for a text beside it, i.e. with rows within
  that padding of it, as it covers only the top of the canvas' side.

A text that starts wrapping is brought in between the same panels, as
before. A paste is laid out as if the stats panel weren't there, and
again, clear of it, only if it lands beside it. As the stats panel grows
to show a selection's properties, with it open the paste is rendered
selected first (not captured yet, via `flushSync`), so the panels are
measured as they'll be; laid out again, it stays the same elements, and
one undo step. A wrapped paste is then moved into view (as far as the view
would have scrolled), instead of scrolling the view.

The stats panel is marked as side viewport UI (`data-viewport-ui="side"`,
named "stats"), so the viewport offsets' other users (zoom to fit, scroll
to content, search, a host's `setViewport` with UI offsets) keep clear of
it too while it's open. `AppViewport` gains `getSideInsets(names,
{ reserve })`, generalizing `getSidebarInsets`, and `getSideUIRect(name)`;
`getOffsets` shares the reserve fallback through `getReservedSide`.

Tests: the cap at 50% and 200%; a typed text clear of the styles and the
stats panel, and of the stats panel only beside it; a paste wrapping as a
typed one, with the styles panel's room reserved; a paste clear of the
stats panel only where it lands beside it, and of the stats panel grown to
show it, as one undo step; a wrapped paste moved into view, the view not
scrolled.

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): pan the canvas when the caret goes under the stats or styles panel

The canvas follows the caret past the view's edge, and behind the docked
sidebar, through the editor's box, which the browser scrolls to reveal a
caret (#12175). The stats and the full styles panel don't span the box's
height, so a caret typed in under them stayed out of sight.

After each input, once the update (and any scroll it made) is in the
app's state, the editor works out where the caret is: its line from
`getWrappedTextLines`, its x from the width of the line before it,
rotated with the text. In a line with RTL characters (`hasRTLChars`, new
in @excalidraw/common) the x comes from the browser's bidirectional
layout of the line instead, in its paragraph's direction (the editor's
`dir="auto"` makes it `unicode-bidi: plaintext`: each paragraph goes its
first strong character's way): with Hebrew then English, the caret after
the English is mid-line, not at the line's edge. That's measured through
a range in an off-screen mirror of the line, shared with the
click-to-caret lookup. If the caret's under one of those panels, the
canvas pans to bring it out on the canvas' side, with
CARET_FOLLOW_PADDING to spare. Only then: a text beside or below a panel
stays where it is.

Tests: typing past the stats panel's left edge pans the canvas under it,
but not below it; the caret in a bidirectional line, a paragraph after
one going the other way, is found where the browser lays it out.

Claude Opus 5.5 (1M), xhigh effort
2026-09-28 21:06:41 +02:00
2d3707b816 feat(editor): add showHints to command palette (#12187)
* feat(editor): route showHints through the action system (#12165 follow-up)

`showHints` was the only item in the Preferences submenu that flipped
appState directly instead of going through an action, so it was missing
everything the action system provides: no command palette entry, no
`trackEvent`, no `viewMode` declaration.

- add `actionToggleShowHints`, modelled on `actionToggleMidpointSnapping`
  (`viewMode: false` — hints live in the toolbar, which view mode hides)
- register it under `ActionName` and export it from `actions/index.ts`
- `PreferencesToggleShowHintsItem` now calls `executeAction`
- list it in the command palette's editor commands
- document `appState.showHints` /
  `MainMenu.DefaultItems.Preferences.ToggleShowHints` in the CHANGELOG,
  which the original commit never touched

No keyboard shortcut: like `arrowBinding` and `midpointSnapping`, this is
a set-once preference, and the natural mnemonic (`Alt+H`) is already
distribute-horizontally.

Adds `tests/showHints.test.tsx` — the preference previously had no
behavioural coverage at all, only incidental snapshot entries.

* chore(editor): drop showHints tests & changelog entry

Both will be rewritten in a follow-up PR.

Opus 5.5 (1M context), xhigh

---------

Co-authored-by: hiennd <hiennd@aidia.vn>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-28 09:17:05 +00:00
David Luzarandhiennd 834e5af631 fix(editor): follow-ups to zen mode polish & delayed tooltips (#12188)
* fix(editor): follow-ups to zen mode polish & delayed tooltips (#12165)

Tooltip state (timer + warm window) is shared by all instances, since they
share one tooltip node. Without an owner, any unrelated Tooltip unmounting
(a collaborator leaving, a sidebar breakpoint) cancelled or hid a tooltip
the user was still hovering. Track the owning item and only hide from the
paths that own it; `hideTooltip` no longer creates the tooltip node just to
hide it.

Zen mode hides or slides controls without the pointer moving, so nothing
fired pointerleave to retract their tooltips — markZenModeTransition now
hides the tooltip up front.

`:focus-visible` was not excluded from the chrome-less zen-mode rules, so
the keyboard focus ring (a box-shadow) disappeared for the main menu
trigger, scroll-back-to-content and exit-zen-mode buttons.

The zoom buttons lost their native `title`, which was the only channel
carrying their shortcut to keyboard/AT users — expose it via
`aria-keyshortcuts`, as Tools.tsx does.

Also: schedule the zen-mode timer on `ownerWindow` like every other
DOM-touching timer in App.tsx, re-read the container ref when unflagging
(and unflag on unmount), and drop the footer-left `--transition-left`
class, which never matched a selector.

Adds the missing coverage for the transition-attribute lifecycle, the
`openMenu`-across-view-mode fix, and the tooltip delay/warm window.

Split out of #12187 and ported onto the #12173 Tooltip rework: the owner
is set in `showTooltip`, and its MutationObserver already retracts a
tooltip whose wrapper got `disabled`, so that extra effect is dropped, as
is the delayed-show label ref (`showTooltip` takes the label by value).

Opus 5.5 (1M context), xhigh

* refactor(editor): drop tooltip owner tracking, trim tests

Since #12173, `showTooltip`'s MutationObserver retracts a visible tooltip
whose wrapper leaves the DOM, and its `isConnected` check drops a pending
one. That makes the Tooltip unmount effect, which hid whatever tooltip was
showing, redundant: removing it fixes the unrelated-unmount bug on its own,
without tracking an owner.

Also drop zenMode.test.tsx and the tooltip tests that duplicated others.

Opus 5.5 (1M context), xhigh

---------

Co-authored-by: hiennd <hiennd@aidia.vn>
2026-09-28 10:14:30 +02:00
Dinesh Yadavanddwelle 438d89861f docs(repo): remove obsolete CodeSandbox contribution workflow (#11908)
* docs: remove obsolete CodeSandbox contribution workflow

https://codesandbox.io/p/github/excalidraw/excalidraw (forking the
whole repo as an editable sandbox to submit PRs from) no longer works.
It was documented in two places — the Contributing guide and the
Development guide — as an alternative to the manual clone/fork setup.

This doesn't touch the other CodeSandbox links in the docs (README,
installation/integration/development guides for the @excalidraw/excalidraw
package), which point at the separate with-script-in-browser usage example
and are unrelated to this contribution workflow.

* chore: trigger CI

Claude Opus 5.5 (low)

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-27 20:53:17 +00:00
David Luzar 02fc9f3505 fix(packages/excalidraw): setViewport stops following a collaborator (#12184)
Navigating with `setViewport` takes the view over from the followed user,
as a user's pan or zoom does, so it now asks to stop following
(`requestUnfollow`: `onUserFollow` with `action: "UNFOLLOW"`, when
`userToFollow` is set). `setViewport(null)`, which only clears a lock, and
a target that doesn't resolve don't navigate and leave following alone.

Following itself doesn't go through `setViewport`: excalidraw-app and
Excalidraw+ both apply the followed user's viewport with `updateScene` +
`zoomToFitBounds`, so it's unaffected.

Tests: navigating emits the UNFOLLOW intent once; clearing a lock and an
unresolved target don't.

Claude Opus 5.5 (1M), xhigh effort
2026-09-27 19:47:29 +02:00
David Luzar 74423812ec perf(editor): avoid rebuilding id sets on every drag (#12183)
perf(element): reuse one moved-element ID set when dragging

dragSelectedElements passed a fresh Array.from(elementsToUpdate) to every
element's updateBoundElements call. Each call missed the per-array ID-set
cache and rebuilt a selection-sized set, so a drag step was quadratic in the
number of dragged elements.

Build the array once per drag step, pass it to every call, and reuse its
cached set (getSimultaneouslyUpdatedElementIds, now exported) for the
unbinding checks too. Document that callers must pass one array instance
per operation and must not change its membership afterwards.

Dragging 1K / 2K / 4K selected rectangles in the app went from 45 / 160 /
643 ms to 18.5 / 31 / 54 ms per frame (same tab, same window, dev build).

Add coverage for the shared array, grouped labels across z-order, frames
with their children, bound arrows, bindings, unbinding, and unchanged
coordinates.

Claude Opus 5.5 (1M context), reasoning effort 40
2026-09-27 18:45:39 +02:00
Nicolas OgaandClaude Opus 5.5 84e3f5a40c perf(editor): avoid rebuilding id sets on every resize (#12180)
perf: avoid rebuilding simultaneously-updated id set per element on multi-element resize

updateBoundElements() rebuilt a Set of all simultaneouslyUpdated ids on
every call. Multi-element resize/rotate call it once per selected element
with the same array, making each pointer move O(n^2). Cache the set per
array in a WeakMap.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:01:38 +00:00
Alexandre Kohler 974f054baf fix(editor): embed YouTube live links (#12178) 2026-09-27 07:49:24 +00:00
afed9e6e27 fix(editor): handle the save-as shortcut while editing text (#11922)
* fix(editor): handle the save-as shortcut while editing text

Cmd/Ctrl+Shift+S did nothing while a text editor was open — the editor stayed
open and no save happened. Plain Cmd/Ctrl+S worked.

The wysiwyg keydown handler only branches on `actionSaveToActiveFile`, whose
keyTest requires `!event.shiftKey`, so the shift variant matched nothing and was
swallowed. This adds the sibling branch for `actionSaveFileToDisk`, mirroring
what the plain shortcut already does: commit the text, then run the action.

Complements #9281, which fixed the plain shortcut in the same handler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(editor): assert the save-as shortcut actually dispatches the save

Review feedback: the regression test only asserted that editing ended, which
`handleSubmit()` satisfies on its own — the test still passed with the
`executeAction(actionSaveFileToDisk)` call deleted, so half the fix was unpinned.

Now spies on `saveAsJSON`, which is what the action ultimately reaches, and
captures `editingTextElement` at the moment it runs so the ordering (commit,
then save) is pinned too. Verified: removing the dispatch now fails with
`expected "saveAsJSON" to be called 1 times, but got 0 times`.

Also adds a negative case — closing the editor with Escape must not save — so
the spy proves the save is tied to the shortcut rather than to teardown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(editor): dispatch text-editor save shortcuts as keyboard actions, trim tests

- pass the "keyboard" source for both save shortcuts in the wysiwyg keydown
  (was the default "api", which skipped the interaction gate and mistracked)
- drop the explanatory comment on the save-as branch
- fold the three save tests into one it.each in the existing container-unbound
  suite, covering both Cmd/Ctrl+S and Cmd/Ctrl+Shift+S and pinning that the
  text is committed before saveAsJSON runs

Claude Opus 5.5 (1M context), xhigh

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-26 22:38:03 +00:00
David Luzar d29d864884 feat(editor): file drop overlay & shift-to-merge imported scene (#12177)
* feat(editor): add file drop overlay and Shift-to-insert support

* fix(editor): report copy drop effect for file drags

Shift defaults a drag to "move" (and a "Move" cursor), but a dropped file
is only ever read in, never moved.

Opus 5.5 (xhigh)

* fix(editor): ignore file drops in view mode and when non-interactive

The dragover/drop cancel listeners were only registered in edit mode, so
elsewhere the browser opened a dropped file natively. Register them always,
show a no-drop cursor where drops are ignored, and don't handle the drop.

Opus 5.5 (xhigh)

* feat(editor): cross out the existing file in the drop overlay when replacing

Opus 5.5 (xhigh)

* fix(editor): note that library files won't replace content in the drop overlay

OS-dragged .excalidraw and .excalidrawlib files both report an empty MIME
type until dropped, so the overlay can't tell them apart. Keep the replace
copy for untyped files and add a secondary note for libraries.

Opus 5.5 (xhigh)

* fix(editor): use --overlay-bg-color for modal and file drop backdrops

Replaces color-mix() in the file drop overlay, which is newer than our
browserslist, with the (previously unused) --overlay-bg-color token, now
hardcoded per theme, and reuses it for the <Modal> backdrop.

Opus 5.5 (xhigh)

* fix(editor): scope image-scene drop try to parsing, drop redundant preventDefault

Only loadFromBlob is wrapped now, so an error while inserting or replacing
isn't reported as an invalid file or followed by a plain image insert.
App's dragover listener is always registered, so the overlay no longer needs
to call preventDefault itself.

Opus 5.5 (xhigh)

* fix(editor): don't let the browser open files dropped on modals

Modal portals live outside the editor container, so its dragover/drop
listeners didn't cover them. Cancel file drags on body-level portal
containers, leaving non-file drags (e.g. text into inputs) native.

Opus 5.5 (xhigh)

* chore(editor): move fileDrop locale strings next to overwriteConfirm

Opus 5.5 (xhigh)

* feat(editor): toast how to undo after a drop replaces the scene

Opus 5.5 (xhigh)
2026-09-27 00:18:02 +02:00
David Luzar f3d99c42e9 fix(editor): pan the canvas to follow the caret instead of shifting the UI (#12175)
* fix(editor): keep the text editor flush with the canvas when a click places the caret in a tall text

Editing a text that reaches past the viewport with the text tool (a click
places the caret) scrolled the editor's content: the textarea is clamped to
the viewport, focus() reveals the caret — which sits at the end of the value
until the initial selection is applied — and the deferred refocus after the
pointerdown's blur did that on an unfocused textarea, so the text moved up by
the overflow while the box stayed put. The selection tool's single-click edit
starts on pointerup, with no blur, so it never refocused and looked fine.

After placing the initial caret — inside the visible box by construction —
the textarea's own scroll goes back to zero.

Verified in the browser on a 49-line text hanging 250px below the viewport at
90% zoom: internal scroll was 278px, now 0, for the text tool with the
mousedown blur; the selection-tool path stays at 0. jsdom textareas don't
scroll, so there is no unit test for it.

Claude Fable 5.1, high effort

* fix(editor): place the editor of a rotated text where the text is

The editor's textarea was scaled and rotated about its own box's center,
while the canvas rotates a text about the text's center. The two only agree
when the box is the text's size — and it isn't once the box is clamped to
the viewport (a tall text reaching past it) or, slightly, through the 5%
height buffer. A rotated text scrolled partly off the viewport got its
editor displaced by the rotated pivot offset (36px for a 49-line text at
-18° and 130% zoom), and the clamp — a cut along the box's own edges —
removed lines that were still on screen on the side the rotation lifted.

Now the transform origin is the text's center whatever the box's size, with
the translate derived from the text's dimensions, so the editor lands on the
text in every case; the viewport clamps (right and bottom) apply only to an
unrotated box, whose edges are the viewport's, and a rotated box is left
whole. Since an unclamped box can reach past the viewport, both focus()
calls pass preventScroll so revealing the caret can never scroll the
container — the app owns scrolling.

Verified in the browser: all four corners of the rotated editor coincide
with the text's (they were off by up to 36px), typing at the off-screen end
leaves the container unscrolled, and the unrotated case is unchanged — still
clamped to the viewport bottom, top-left in place, no internal scroll on a
click caret, select-all on Enter.

Also updates the linear-editor style snapshot for the editor's explicit
transform origin.

Claude Fable 5.1, high effort
Claude Opus 5.5 (1M), xhigh effort (moved the snapshot update in)

* fix(editor): never let the browser scroll the editor container to reveal a caret

The root container is `overflow: hidden`, which still makes it a scroll
container the browser scrolls programmatically. Typing in a text editor
whose box reaches past the viewport — a rotated text's editor cannot be
clamped to the viewport without losing lines still on screen — made the
browser reveal the caret by scrolling the container, pushing the whole UI
out: two new lines below the viewport moved the toolbar up by 416px.

`overflow: clip` clips exactly like `hidden` but is not a scroll container,
so there is nothing for the caret reveal (or a focus) to scroll; `hidden`
stays as the fallback where `clip` is unknown. The editor never scrolled
this box itself.

Verified in the browser with real key input on a rotated 49-line text
hanging below the viewport at 130% zoom: the container stays at 0 and the
toolbar in place through new lines and characters typed past the viewport.

Claude Fable 5.1, high effort

* fix(editor): the canvas follows the caret instead of the page scrolling to reveal it

With the editor root at `overflow: clip` (previous commit), the browser's
caret reveal skips the root, since it is no longer a scroll container. Full
page, nothing moves, but the caret stays out of view. Embedded in a page
that scrolls (directly, or in a same-origin iframe), the reveal scrolls that
host page instead: 434px for a rotated text reaching past the editor's
bottom edge, 345px for a text scrolled below it while edited. Only a
cross-origin iframe stops it.

The root goes back to `overflow: hidden`, so it absorbs the reveal again.
While a text is being edited, the text editor hands any scroll of the root
to the canvas (`viewport.translate` by the same screen distance) and resets
the root. Scroll events fire before the frame is painted, so the root's
shift is never seen. Nothing moves, host page included, and the canvas pans
just far enough to bring the caret into view. The reveal is the browser's,
so this covers everything that moves the caret: typing, Enter, arrow keys,
paste, IME, and drag-selecting past the edge.

Measured with real key input in Chromium, full page and in a same-origin
iframe embed: host page and root unmoved, canvas panned 444px / 330-355px,
caret visible, the editor still on its text. Outside text editing, a root
scroll is left as it was.

Test: a root scroll while editing is reset and pans the canvas (at 200%
zoom, both axes) with the editor on it; with no text being edited, the root
is left alone.

Claude Opus 5.5 (1M), xhigh effort

* fix(editor): never cut the text editor to the viewport; the canvas follows the caret for every text

The editor of an unrotated text was cut to the viewport's right and bottom
edges (the width of unbound text, plus a max-height), so a caret past an
edge was revealed by scrolling the textarea's content inside the cut box.
The canvas stood still, and the editor's text left its place on the canvas
while typing. Rotated texts were already left whole, and their caret reveal
panned the canvas (previous commit).

Now no editor box is cut: rotated or not, it is the text's own box, and a
caret past the viewport's edge is revealed by panning the canvas. Typing
past the bottom edge, or a long line past the right edge, moves the view
along; the editor stays on its text.

What only existed for the cut goes with it: the `maxWidth` bookkeeping, and
the reset of the textarea's internal scroll after placing a click's caret
(a whole box never scrolls inside).

Measured with real input in Chromium: 455px vertical pan for a 30-line text
crossing the bottom edge, 454px horizontal pan for a line crossing the right
edge, root and textarea unscrolled, editor on its text. A text-tool click on
line 3 of a text taller than the viewport places the caret there without any
pan or internal scroll.

Tests: the max-width test now checks the editor isn't cut at either edge
(it fails with the cut in place); the linear-editor style snapshot loses its
max-height.

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): leave room past the caret when the canvas follows it

The browser reveals a caret only as far as it has to, so text typed past
the viewport's edge came into view flush against the edge. The canvas now
pans a further CARET_FOLLOW_PADDING (5 screen px) along the axis it
follows, so the caret keeps a little room from the edge while the view
follows it.

Not done with the root's `scroll-padding`: the browser honors it only as
far as it can scroll, and the editor box ends at the text (5% below it,
nothing past a line's end). Measured in Chromium: `scroll-padding: 80px`
left 39px below the caret and none to its right.

Measured with real input: once the caret reaches the edge, every new line
or character pans the canvas and the caret stays 4-5px from the edge, both
typing down and along a long line.

Test: the pan includes the padding, and a reveal along one axis pans along
that axis only.

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): the canvas follows a caret behind a docked sidebar; the reveal stays in the editor's box

A caret that went behind a docked sidebar stayed hidden: it was still
inside the editor root, so the browser had nothing to reveal and the canvas
never followed.

The text editor's container (`.excalidraw-textEditorContainer`) is now the
box a caret is revealed into: the canvas area, clipped, and inset off a
docked sidebar (measured from `.sidebar--docked` on every editor update, on
the right or the left for RTL; the editor's position is relative to the
box). A caret past its edge, or behind the sidebar, is revealed by
scrolling the box, which the text editor turns into a canvas pan (+
padding) and resets, as it did for the root before.

The reveal now never gets past the box, so the editor root is back to
master's CSS, and what a host does to the root's overflow no longer
matters: with the root forced to `overflow: visible` (as Excalidraw+ does,
inside a wrapper that resets its own scroll), the canvas follows the same
way and the wrapper never scrolls.

Measured with real input in Chromium, full page, with the root forced to
`visible`, in the Excalidraw+ arrangement, and in a same-origin iframe
embed: nothing scrolled (host page, root, box), the canvas panned 449 /
360 / 580px for texts past the bottom edge, and for a line typed in behind
the docked sidebar it panned 120px, leaving the caret 5px left of the
sidebar. Clicks inside the editor still place the caret; clicks on the
canvas still end editing.

Tests: the pan test scrolls the editor's box; a new test covers the insets
for a sidebar docked on either side, and their removal with the editor.

Claude Opus 5.5 (1M), xhigh effort

* refactor(editor): measure the sidebar through the viewport UI markers

The text editor's box was kept off a docked sidebar by querying the
`.sidebar--docked` class and measuring it inline in textWysiwyg. How far
viewport UI reaches into the canvas is AppViewport's concern:
`getOffsets` measures every UI marked `data-viewport-ui`, and the sidebar
is marked (`side` / `sidebar`, except on phones, where it's an overlay).

AppViewport gains `getSidebarInsets()`, which measures just the sidebar
through those markers, with a `measureSide()` that `getOffsets` now shares.
The text editor asks the viewport for the insets. An open sidebar that
isn't docked covers the canvas the same way and now counts too; it closes
when the canvas is clicked to start a text, so this rarely matters.

`getOffsets` isn't used for the box: it gives one band per side for all
marked UI, so the top toolbar and the styles panel (open while a text is
edited) would pan the canvas as soon as one typed below them.

Test: the docked-sidebar test's fake sidebar carries the markers.

Claude Opus 5.5 (1M), xhigh effort

* feat(editor): a text stops growing at the view's width, and is brought into view when it wraps

Closes #8855. Typing a long line grew a free text past the view's edge.
The canvas followed the caret, so the start of the text went out of view
on the other side, and the box had to be resized by hand afterwards.

A free text that grows with its content (`autoResize`) now stops at the
view's width: the canvas minus the sidebar, less TEXT_VIEWPORT_PADDING
(20px, in @excalidraw/common) at each side, in scene units, so it follows
the zoom. On the keystroke that would cross it, the text becomes fixed
width at exactly that width and wraps from then on (`autoResize: false`),
anchored the way it grew, so a right-aligned or centered text keeps its
edge or center. The editor switches to wrapping with it. A text that is
already wider keeps growing; nothing shrinks. The rule is an optional
`maxWidth` on `refreshTextDimensions`, which the editor's per-keystroke
update passes.

When a text starts wrapping in the editor, while typed or pasted into it,
it is scrolled into view with the same room at the edges. Being exactly
the padded view's width, it then fills it. That happens horizontally
always, and vertically only if all of it fits; a taller text leaves the
vertical to the caret-follow, so the view doesn't jump away from the caret.

Pasting onto the canvas keeps its wrap width (half the view, 200-800),
capped by the same limit, so that the 200 minimum can't outgrow a narrow
or zoomed-in view. A paste that wrapped is scrolled fully into view; if
it is taller than the view, its top is brought in.

Scrolling into view is computed by a pure `scrollBoundsIntoView` in
viewport.ts, next to `centerScrollOn` and `constrainScrollState`. It moves
by the least amount, keeps the zoom, takes the usable area as `Offsets`,
and returns null when nothing needs to move. The move itself goes through
`viewport.translate`, as a user pan does: it respects scroll locks, yields
to a host's pending locked navigation, and stops following a
collaborator. No move, no translate.

Measured with real input in Chromium on a 2560px view: typing
character by character at 400% from 100px in, and a sentence inserted at
once, both end as a wrapped text from 20 to 2540; with the docked sidebar
the limit is 2226 (2560 - 294 - 40) and the text sits 20px left of it; a
paste near the bottom-right corner ends 20px inside the right and bottom
edges. Nothing scrolled the page or the editor box.

Tests: the text stops and wraps at the view's width, and keeps that width;
the limit follows the zoom and a docked sidebar; a right-aligned text keeps
its right edge; a text already wider keeps growing (the not-cut-off test,
reworked); starting to wrap scrolls it into view with room at its right
edge, but not vertically when it's taller than the view; a paste isn't
wider than the view at 800% and, wrapped, ends fully in view;
`scrollBoundsIntoView` unit tests.

Claude Opus 5.5 (1M), xhigh effort
2026-09-26 23:00:28 +02:00
c10499eebb feat(editor): text tool edits what you click, shows what a click will do, drags out free text in shapes (#12082)
* fix(editor): do not inherit groupIds/angle from container if not binding the text to it

* fix(editor): text tool edits a label only when clicking the label itself

Clicking elsewhere inside a labeled container creates an independent text at
the clicked position, with no modifier needed (so also on touch/pen), instead
of warping to the label center and editing the existing label.

* feat(editor): highlight what the text tool would act on when hovering

Mirror the click logic on hover: the text element a click would edit gets
a box highlight, and an empty text-bindable container the click would bind
into gets the same binding highlight arrows use (box for arrow containers,
which the binding outline renderer can't draw). Cleared on pointerdown and
managed only while the text tool owns the interaction, so the frame flows
sharing elementsToHighlight are untouched.

Also documents suggestedBinding, isMidpointSnappingEnabled and
elementsToHighlight in AppState.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): hit-test arrow labels at their derived position

An arrow label's stored coords aren't updated when the arrow moves
(dragging deliberately skips them — the position is derived at render
time), which left hitElementItself internally inconsistent: its bounds
pre-check derives the position, but the precise shape test read the stale
stored coords, so a moved arrow's label was only hittable where its old
and new rects overlap. Substitute the derived coords for the precise test,
and skip the version-keyed point cache for container-bound labels (their
position can change without a version bump, mirroring ElementBounds).

Pass the elements map through renderElementsBoxHighlight so the highlight
box is also drawn at the label's derived position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): dashed text box for the text-tool hover, cleared when the tool is left

The text a click would edit gets the same subtle dashed box as a wrapped text
being edited (at the label's derived position for arrows), instead of the
frame-style bounding box. The hover highlights are cleared whichever way the
text tool is left — Esc/finalize paths bypass setActiveTool.

* refactor(editor): dedupe container-bound label position derivation

An arrow label's accurate position is derived from the arrow rather than
stored on the element (dragging deliberately skips updating it), and three
call sites hand-rolled the same clone-with-derived-coords dance. Extract it
into getTextElementWithAccuratePosition next to the existing position
helpers.

Also routes the text-tool dashed hover highlight through the helper, so it
renders at the label's actual position instead of its stale stored coords.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): defer text-tool hover highlights to arrow text anchors

The arrow-endpoint text binding (#11777) added its own text-tool hover
affordance (hoveredArrowTextAnchor) covering arrow endpoints and midpoint
label anchors, and gave endpoints precedence on click. Mirror that in the
text-tool highlight updater: skip positions where a bindable endpoint would
take the click, and leave arrow anchors (endpoint & midpoint label) entirely
to the anchor affordance — the highlights now only cover editing an existing
text and binding into an empty non-arrow container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): make endpoint-vs-text preference z-aware to prevent affordance flicker

The endpoint offer was suppressed by any text element under the cursor,
regardless of stacking order. With an arrow drawn over an existing text,
the text's bbox edge cut into the endpoint's hit circle, and slow pointer
movement flickered between the endpoint anchor and text editing.

Drop the z-blind text check and let the existing z-order occlusion rule
decide: whatever is stacked above owns its hit area. A text above the
arrow keeps its edit behavior; when the arrow is on top, its endpoint has
full preference over the whole hit circle, matching the drawn highlight.
Container-bound labels need no special casing — a labeled container is
interior-hittable, so the occlusion rule already yields to labels whose
container sits above the arrow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(editor): defer a text-tool center click so a drag creates free text

A click on an empty container's center still binds a label, but the decision
now waits for pointerup: dragging horizontally past the autowrap threshold
instead creates an independent fixed-width text at the pointer origin, and
small shapes are no longer provisionally enlarged while dragging. Tool
changes and the missing-pointerup cleanup cancel the pending click.

Shape hover and pointerdown share the empty-container / center-snap / Alt
eligibility via getTextCreationContainerAtPosition, and the hover indicators
are cleared when deferred creation starts (stale outline under a locked tool
at 200% zoom).

Also: a direct collision-cache regression for labels of moved arrows, exact
width/position assertions for drag creation across mouse/pen/touch,
labelPosition coverage at 25/50/75%, and stale AppState comments fixed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): redraw the dragged text box under a locked text tool

Drag-sizing a new text mutates it in place without informing the scene, so
sceneNonce never moves and the interactive canvas — which draws the dashed
text box from the live element but memoizes on canvasNonce — kept the first
size. Unlocked drags only repainted by accident: the tool had already
reverted to selection, whose box-selection branch churns state every move.

Fold the new text's versionNonce into canvasNonce, as is already done for
framed new elements. Four rendering regressions cover successive drag widths
on empty canvas and from a container's center, locked and unlocked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(editor): extract the text tool's pointer interaction into AppTextTool

Pure move of handleTextOnPointerDown, resetTextTool, maybeStartPendingText,
maybeUpdateTextToolHighlightOnPointerMove and getTextCreationContainerAtPosition
into App.textTool.ts — only `this.` became `this.app.` and the names lost
their "Text" prefix. maybeDragNewGenericElement is now public so the pending
center-drag can re-dispatch to it. No behavior change.

Text editing itself (startTextEditing / handleTextWysiwyg and the hit
helpers) is shared with Enter, double-click and the other entry points and
stays in App.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): resolve text-tool targets once; single textToolHover state

What a text-tool click would do at a position was computed in three places
— the text/container highlight, the arrow-anchor hover and the pointerdown —
with different modifier awareness, and shown through three AppState fields
(elementsToHighlight, suggestedBinding, hoveredArrowTextAnchor) cleared from
seven sites. The affordance could promise what the click wouldn't deliver:

- pressing or releasing Alt without moving left the container outline stale
  (and Ctrl/Cmd left the text box stale)
- Alt-hovering an empty arrow's midpoint advertised a label while the click
  created free text
- Ctrl/Cmd hid the container outline (the renderer gates suggestedBinding on
  isBindingEnabled) while the click still bound a label
- over a shape's or arrow's label the cursor was a crosshair while the box
  promised an edit

AppTextTool.getTargetAt now answers the question once — endpoint → text →
empty centered container (unless Alt) → free — and feeds the hover, the
cursor and the pointerdown. The hover lives in one field, textToolHover
(replacing hoveredArrowTextAnchor), drawn by one renderTextToolHover branch;
it is cleared on pointerdown and when the tool is left, nowhere else. Alt and
Ctrl/Cmd keydown/keyup refresh it at the last pointer position. The scene is
hit-tested once per pointermove instead of twice.

Also drops AppArrowText's anchor-state members (the resolver covers them) and
getLabelCenter, dead since the label-center warp was removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(editor): let AppTextTool own the pending center click; explicit textElement

The deferred center click was threaded through PointerDownState.text and a
textCreation parameter that made startTextEditing — the entry point shared
with Enter, double-click, sticky-note drops and autoshape — re-derive what the
text tool's pointerdown had already established, then bail. The helper now
arms `pending` itself (like AppBucketFill), decides it on pointermove/up, and
PointerDownState loses its text slot. The affordance armed at pointerdown now
stays up until the click resolves instead of being cleared and re-shown.

startTextEditing takes an explicit `textElement` instead: the tool passes
the text it resolved, or null to create — so a text selected through the
host API can no longer hijack a text-tool click elsewhere (the Enter and
double-click paths, which leave it undefined, resolve as before). The same
selection shortcut leaves getTextBindableContainerAtPosition, which is now
purely positional; the double-click path keeps it inline.

Also: a pending container deleted mid-press (collab) now still ends the
tool's turn, and the drag threshold's choices are documented in place.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): ctrl/cmd, not alt, opts out of labeling a container with the text tool

Ctrl/cmd already turns off arrow-endpoint binding for the text tool; it now
also keeps a center click from binding a label to an empty container, so one
key means "no binding" throughout the tool, as elsewhere in the editor. It is
read off the event rather than the arrow-binding preference — a label is not
an arrow binding. Alt no longer plays a part in the text tool, so its hover
refresh hooks go (alt keeps its generic drag meaning: resize from center).
The double-click path is unchanged.

Ported from #11210.

Co-authored-by: Loric Brevet <loric.brevet@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): cancel a pending text-tool click on tool exit and pointer cancel; refresh hover and cursor when the viewport moves

Three lifecycle gaps in the text tool's hover and pending center click:

- A pending center click only checked the active tool at the next pointer
  event, so leaving the tool (Esc) and picking it again before releasing
  revived the old press: moving then created free text at the original
  position. And with no pointerup after a pointercancel (scroll, palm
  rejection), the pending click stayed armed for unrelated movement. The
  helper now has an explicit cancel, called when the tool is left and on
  pointercancel next to the bucket fill's.
  The cancel also clears the outline the armed click had set, which no
  hovering pointer would clear otherwise — including when a second finger
  turns the press into a pinch/pan (that discard goes through the same
  cancel).
- The hover was refreshed on pointer moves and modifier keys, but not when
  the viewport scrolled or zoomed under a still pointer, so an outline stayed
  on a shape that had moved away. The refresh now re-derives the scene point
  from the pointer's last viewport position, remembers the last modifiers,
  and runs from the viewport-change lifecycle.
- A modifier press changed the target without updating the cursor (an
  endpoint's pointer cursor survived Ctrl). The refresh sets the cursor from
  the same target, mirroring the pointermove path and leaving a panning or
  scrollbar gesture its own cursor.

Also fixes a stale renderer comment about the container outline's binding
gate.

Tests: tool exit and re-pick mid-press, pointercancel, wheel scroll and zoom
under a still pointer for container and text targets, cursor on modifier
press and release. Verified in Chromium as well.

Claude Fable 5.1, high effort
Claude Opus 5.5 (1M), xhigh effort (moved the snapshot update to the caret-overflow branch)
Claude Opus 5.5 (1M), xhigh effort (the cancel clears the armed outline, from the 2026-09-26 review)

* fix(editor): tear a gesture down on pointercancel; a replayed cleanup no longer counts as a click

A canceled press (the browser taking the pointer for a scroll, a pinch or
palm rejection) left the gesture's window listeners and its missing-pointerup
cleanup subscription in place. The next pointerdown then replayed the old up
handler, whose end-of-gesture revert switched the tool to selection while the
new press was being armed — so after a canceled center press with the text
tool, the next center click switched tools and created nothing.

pointercancel now runs the missing-pointerup cleanup immediately, so nothing
of the canceled gesture lies in wait. And the up handler's tool revert is
gated on a genuine pointerup: a replay by the cleanup — a second finger
landing, a pointercancel, the next press after a lost pointerup — only tears
the gesture down and leaves the tool to the press that follows, instead of
reverting it under that press.

The pointercancel regression no longer sends a synthetic mouse-up that hid
this: after the cancel the tool must still be the text tool and the next
center click must bind a label. It fails without the fix.

Claude Fable 5.1, high effort

* refactor(editor): cursor from the text-tool target alone; a hover refresh needs a hovering pointer

Follow-ups from the whole-branch simplification review (simplification.md),
narrowed with the reviewer:

- The cursor is derived from the resolved target only. The old fallback to
  the plain hit under the pointer kept a text cursor over other texts while
  editing or drag-sizing — where a click has nothing to point at — and forced
  a second hit test and a coordinate recomputation on every modifier press
  and viewport change. Both go. With a locked text tool, editing or dragging
  now shows the crosshair everywhere; hover, endpoint and label cursors are
  unchanged.
- The hover refresh takes the pointer's last viewport position only. The
  fallback to the last *scene* coordinates was reachable when a tap preceded
  any pointer movement, and after a viewport change it resolved at a position
  nothing hovers — a phantom affordance. A tap leaves nothing to refresh,
  pinned by a test that fails with the fallback in place. Nor does a finger
  ever hover: a touch's last move is where it lifted, so touch input doesn't
  count as a hovering pointer (mouse and pen still refresh). The test drives
  real touches — a tap with jitter on a text, a short drag from a center —
  instead of clearing the pointer bookkeeping by hand. The pointercancel
  test now checks the armed outline is gone right after the cancel, and a
  second-finger test covers the missing-pointerup discard.
- The explicit pending-click cancel on pointercancel stays, and its reason is
  written down: the gesture teardown that follows flushes a queued
  pointermove, which would otherwise find the click still pending and resolve
  it as a drag. Verified in Chromium by the reviewer; the test harness runs
  the throttle synchronously, so it cannot be reproduced there.
- Two duplicated test scenarios removed: the arrow-anchor hover test the
  endpoint suite already covers, and a repeated Ctrl assertion.

Claude Fable 5.1, high effort
Claude Opus 5.5 (1M), xhigh effort (touch input never hovers; tests, from the 2026-09-26 review)

* refactor(editor): the text tool refreshes its own hover and cursor; viewport navigation check lives in AppPan

App's `refreshTextToolHover` was text-tool logic kept in App only because
its cursor gate read App-private state. It moves into `AppTextTool` as
`refresh(modifiers?)`, which re-resolves the hover and sets the cursor that
goes with it, taking the place of `refreshHover`.

The gate — space held, a pan in progress, a scrollbar drag, the hand tool —
is the same check the canvas pointermove makes before touching hover or
cursor. It becomes `AppPan.isNavigating()`, used by both. The scrollbar drag
stays App's own and reaches AppPan as a dependency, like the pointer count.

No behavior change.

Claude Opus 5.5 (1M), xhigh effort

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Loric Brevet <loric.brevet@gmail.com>
2026-09-26 12:38:23 +02:00
David Luzar c12a8cd037 fix(editor): reduce cursor hint cooldown to 30s (#12174) 2026-09-26 11:19:00 +02:00
David Luzar 5feb2172d5 feat(editor): better library search + display names (#12173)
* feat(editor): delayed name tooltip on library items

- library items with a name show it in a tooltip above the item after
  the standard tooltip delay (500ms, or right away when moving over from
  another tooltip); hidden on leave and on press
- driven by pointer (not mouse) events so touch taps don't leave the
  tooltip behind, while the mouse-driven hover state (which reveals the
  checkbox on tablets) stays as is
- no per-item hooks: Tooltip exposes `showTooltip()` / `hideTooltip()`
  (plus a `position` option) for elements that can't be wrapped in
  <Tooltip>, and <Tooltip> itself now uses them
- a visible tooltip now hides once its item is removed from the DOM
  (e.g. filtered out by the library search, sidebar closed), which fires
  no pointerleave. A MutationObserver is active only while a tooltip is
  visible; a delayed tooltip whose item unmounted while pending no
  longer shows

Opus 5.5 (1M context), xhigh

* fix(editor): Escape in library search clears it instead of closing sidebar

With a search active in an undocked sidebar, Escape closed the sidebar:
the search relied on the browser's native clearing of search inputs, but
nothing stopped the event from reaching the Sidebar's document Escape
listener. Clear the search ourselves and stop the event on the input.
A second Escape (empty search) still closes the sidebar.

Also adds a test for Escape clearing the item selection without closing
the sidebar (already working).

Opus 5.5 (1M context), xhigh

* feat(editor): larger named items in library search results

- search results render in 2 columns with bigger previews and the item
  name below (ellipsified when it overflows); the whole card, name
  included, is clickable/draggable
- the name tooltip only shows when the name is truncated
- placeholders match the card size, and the grid is full width so it
  doesn't shrink to its content while only placeholders are rendered
- reserve the library scrollbar's space (scrollbar-gutter: stable) so
  the content doesn't shift when results start/stop overflowing

Opus 5.5 (1M context), xhigh
2026-09-26 11:03:47 +02:00
David LuzarandGitUserVivek 5db42c3ddb feat(editor): zen mode polish, hide toolbar hints preference & delayed canvas tooltips (#12165)
* feat(editor): preference to hide toolbar hints

Add a "Show hints" checkbox to the Preferences menu, backed by a new
browser-persisted `appState.showHints` (default true). When off,
HintViewer renders nothing. Also exposed as `Preferences.ToggleShowHints`
for hosts composing their own preferences list.

Opus 5.5 (1M context), low

* fix(editor): keep main menu open when toggling view mode

Entering view mode ran terminateActiveInteraction, which reset
`openMenu` and closed the main menu the user had just toggled view mode
from (Preferences submenu). The main menu stays available in view mode,
so only close it when interaction gets disabled.

Opus 5.5 (1M context), low

* feat(editor): chrome-less canvas controls & in-place fades in zen mode

- UI hidden in zen mode fades out in place (opacity + visibility) instead
  of sliding off the viewport, and is non-interactive from the start of
  the fade
- visible canvas controls (toolbar, main menu trigger, zoom, scroll back
  to content, exit zen mode) drop their background/border/shadow;
  hover/active states still apply, popups & dialogs are unaffected
- only the zoom level stays visible out of the zoom controls
- chrome fades are applied only while toggling (data-zen-mode-transition
  on the container, set before the DOM update) so hover transitions stay
  instant
- single ZEN_MODE_TRANSITION_DURATION, exposed to CSS as
  --zen-mode-transition-duration
- zoom & undo/redo button groups paint a single background (on the
  group) instead of one per button

Opus 5.5 (1M context), low

* feat(editor): shift zoom level into the zoom-out slot in zen mode

With the zoom +/- buttons hidden in zen mode, slide the zoom level over
to the left edge of the group. The shift is applied to the Tooltip
wrapper (new `className` prop) rather than the button, so the tooltip
(positioned off the wrapper) moves along.

Also give the reset-zoom button its own hover background: unlike the
+/- buttons it has no .ToolIcon__icon, and lost its (incidental) hover
once the per-button backgrounds were removed.

Opus 5.5 (1M context), low

* feat(editor): delayed tooltips on canvas buttons

- Tooltip gets a `delay` prop: shows after 500ms, or right away if a
  tooltip was visible within the last 300ms (moving across adjacent
  buttons); passing over a button quickly doesn't flash one
- enable it for the zoom, undo/redo & help buttons
- zoom +/- use our Tooltip instead of the native `title`, and the reset
  zoom button drops its native `title` (it was showing both)
- zen mode now hides the zoom +/- tooltip wrappers rather than the
  buttons, so the invisible wrappers don't show tooltips

Opus 5.5 (1M context), low

Co-authored-by: GitUserVivek <jagtapvs9832@gmail.com>
2026-09-25 00:42:59 +02:00
David Luzar e40d1cba95 feat(editor): compact styles panel (#12163)
* fix(editor): align buttons inflating styles panel intrinsic width

The flex-wrap row break (zero-height flex-basis: 100% spacer) only breaks
the line at layout time; for intrinsic sizing both rows were measured as
a single line. Render the two rows as separate flex rows instead.

Opus 5.5 (1M context), low

* fix(editor): position range slider value label and track via CSS

The label position and filled track were computed in JS from the input's
offsetWidth, and only when the value changed, so they went stale when the
container resized. Drive both from a --range-progress CSS variable, and
drop the UA default inline margin that offset the track from the label.

Opus 5.5 (1M context), low

* feat(editor): compact styles panel

Styles panel buttons are 32px regardless of screen size, rows are closer
together, top-pick swatches are packed tighter, and the panel sizes to its
content (up to the previous fixed width).

Opus 5.5 (1M context), low

* reduce large icon size on 1920px+ screens

* fix color picker border color on mobile
2026-09-24 22:18:52 +02:00
David LuzarandMark Tolmacs 1118751f3e feat(editor): customizable font picker top picks (#12160)
* feat(editor): customizable font picker top picks

Font families can be dragged from the font picker list onto the top-picks
strip to replace a pick, the strip can be reordered by dragging, and
right-clicking it resets to the default picks — same as the color picker
top picks. The drag ghost is only the font's icon (on a strip-button tile).

The color picker's drag & drop is generalized into
components/TopPicksDnD (value-agnostic hook, reorder preview, drop outline,
reset context menu, shared styles), with thin per-picker adapters
(colorTopPicksDnD / fontTopPicksDnD) supplying value equality and the
ghost visual.

Custom picks are kept in `appState.fontTopPicks` (browser-persisted only,
kept on canvas clear); restore drops unknown/internal/fallback families,
dedupes, and caps at the strip's 3 slots.

Claude Opus 5.5 (1M context), reasoning effort: 40

* feat(editor): reset link in top picks tips

The "drag to pin" tip in the font picker and color picker popups now ends
with a plain-text "Reset" link while that picker's top picks are
customized, restoring the defaults without leaving the popup.

Shared as TopPicksTip (tip text styles merged into it). The link never
takes focus on click and hands it back to the picker when activated by
keyboard, since it unmounts on reset; Enter/Space are kept from the
pickers' key handlers.

Claude Opus 5.5 (1M context), reasoning effort: 15

* fix(editor): don't clip or overlap font list item focus/pressed rings

The font list scrolls (clipping outer box-shadows at its edges) and each
item paints over the previous one's bottom edge, so the focus-visible and
:active rings are drawn inset there. Pressing and holding items became
common with dragging fonts onto the top picks.

Claude Opus 5.5 (1M context), reasoning effort: 15

* fix(editor): cancelled top picks drag flying to viewport top-left

The ghost (and its home rect) was built on drag activation, by which time
the source could have been re-rendered away — the font list re-renders
on hover — and a detached element measures as a zero rect. Build and
measure it on pointerdown instead, while the source is mounted.

Claude Opus 5.5 (1M context), reasoning effort: 15

* fix: Always show 3 slots to reorder

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Cleanup

Inconsistent state updates. Font picks go through useExcalidrawSetAppState directly, while color picks go through the action's updateData.

Duplicate icons in the strip. Virgil and Excalifont both use FreedrawIcon, so pinning both shows two identical tiles that can only be told apart by their tooltips.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: remove useMemo

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-09-24 15:06:17 +02:00
David Luzar dc2c16d9e2 fix(editor): Binding follow-ups to #10753 (#12156)
* fix: Midpoint highlight color in dark mode

The highlighted binding midpoint hardcoded rgba(3, 93, 161) for dark mode,
the pre-filter color from when the interactive canvas was inverted by a
CSS filter. The canvas now draws post-filter colors directly, so it
rendered dark blue. Use BINDING_HIGHLIGHT_RGB like the rest of the binding
highlight.

Claude Opus 5.5 (1M context), high reasoning effort

* fix: Show binding midpoints like before the hit test rework

The binding highlight drew all four midpoints for any suggested binding,
including simple arrows with the cursor deep inside the element. Restore
the previous visibility: simple arrows show no midpoints from inside, and
from outside only the one nearest the cursor once it gets near; elbow
arrows show all of them. The highlighted midpoint still comes from the
suggested binding, so it sits where the arrow actually snaps.

Claude Opus 5.5 (1M context), high reasoning effort

* fix: Images and locked opaque elements occlude binding

Only filled shapes hid the elements behind them from binding. Images now
count as opaque, and locked opaque elements hide what is behind them too
(they still can't be bound to). Frames are future-proofed: once they get
a background, an opaque frame hides the non-children behind it without
ending the search, as its children sit just below it in z-order.

Claude Opus 5.5 (1M context), high reasoning effort

* refactor: Add DEFAULT_ZOOM for computations without an editor zoom

Replace the scattered { value: 1 } as Zoom / AppState["zoom"] casts used
as a zoom fallback (elbow arrow routing, transform, element rendering,
binding repair on restore, tests) with a shared frozen constant.

Claude Opus 5.5 (1M context), high reasoning effort
2026-09-24 13:06:56 +02:00
Márk Tolmácsanddwelle 4850bf336f fix(editor): Binding hit test based on distance (#10753)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-23 22:10:02 +02:00
Márk Tolmácsanddwelle 2b9da96100 fix(editor): Freedraw hit test precision (#11550)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-22 22:02:13 +02:00
Márk Tolmács 31df3e6ef2 fix(editor): Tab convert bound arrow update (#12143)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-09-22 10:26:19 +02:00
David Luzar 97c68dd371 refactor(editor): split out duplication logic into App.duplicate.ts (#12125) 2026-09-19 11:53:31 +02:00
David Luzar 14e1c614d4 feat(packages/excalidraw): support onDuplicate replacements, vetoes and lookups (#12124)
feat(packages/excalidraw): make `onDuplicate` changes stick, support veto & lookups

The editor kept working with the duplicates it created after calling
`props.onDuplicate`, so a host returning new objects for them detached
them from the frame assignment and bound text redraw on paste, and from
the alt-drag handover.

- Add `reconcileDuplicatedElements()` and use it in all three duplication
  paths (paste/library insert, duplicate action, alt-drag). Elements the
  host returns for duplicates are shallow-merged into the editor's own
  objects (omitted properties are kept), invalidating what may have been
  cached for them by then (shape, bounds). Other returned elements keep
  the replace semantics.
- Omitting a duplicate (or returning it as deleted) vetoes it. So does
  returning `false` for the duplication as a whole. Bound text goes with
  its vetoed container, and references to vetoed duplicates are cleared
  from the remaining ones (as in `fixDuplicatedBindingsAfterDuplication`).
  If no duplicate remains the duplication is cancelled: paste and the
  action do nothing, alt-drag keeps moving the originals. On a partial
  veto during alt-drag the vetoed originals are left behind.
- On paste, resolve the target frame (and the z-order under it) before the
  callback, so that the host sees the duplicates the way they end up in
  the scene, and a vetoed duplicate isn't brought back.
- Pass lookups covering just the participating elements as a third
  argument: `duplicateElements`, `originalElements`, `origIdToDuplicateId`
  and `duplicateIdToOrigId`.

Claude Fable 5.1
2026-09-19 10:00:47 +02:00
David Luzar c0ad61c674 feat(editor): rightclick to pan canvas (#12110)
* refactor(editor): move the drag-pan out of App.tsx into App.pan.ts

`handleCanvasPanUsingWheelOrSpaceDrag` — gating, the Linux middle-click
paste guard, the throttled move and the teardown — moves as-is into an
`AppPan` helper (`app.pan`), in the shape of `App.cursor` and
`App.viewport`. The session state it ran on was module-level in App.tsx
(`isHoldingSpace`, `isPanning`, `flushPanMove`), shared by every `App`
instance in the page: with two editors mounted, a wheel-button pan in one
made the other ignore wheel input. It is instance state now — `spaceHeld`,
`active`, the pending move flush and the teardown — behind `start`,
`isActive`, `flushMove`, `end` and the space accessors. Only the pointer
count is injected; `gesture` stays where it is.

`AppWheel` asks `app.pan` directly instead of taking those two callbacks
as dependencies, the wysiwyg's wheel-button pan calls `app.pan.start`,
and `maybeCleanupAfterMissingPointerUp` ends the pan explicitly, which
leaves `lastPointerUp` to the scrollbar drag alone. `savePointer` becomes
public for the teardown. No behavior change.

Claude Fable 5.1, high effort

* feat(editor): pan by dragging with the secondary mouse button

A secondary-button press starts a pan session like the wheel button
does, except that it stays a right-click until the pointer travels past
the drag threshold: nothing moves and the cursor stays put before that;
past it the canvas follows the pointer (from that point, the threshold
distance is not caught up) and no menu opens. Released before the
threshold, the session opens the context menu itself at the release
point, through the same `openContextMenu` the `contextmenu` handler now
delegates to.

That handler ignores the platform's `contextmenu` event while a
secondary-button session is active — macOS and Linux fire it on
mousedown, before a drag can be told from a click — and once after the
session ends if it has not fired yet, which is Windows firing it on
mouseup. Touch long-press, pen barrel taps and keyboard-triggered menus
never go through a session, so they open as before. On macOS and Linux
the menu now opens on release rather than on press.

View mode already let any button pan; a right-click there opens the
view-mode menu on release the same way. With interaction disabled the
drag still pans (when navigation is enabled) and no menu opens.

Tests: the threshold, the menu on release, both platform orders of the
native event, no menu after a drag, element selection under the pointer,
and the non-interactive editor.

Claude Fable 5.1, high effort

* fix(editor): let the contextmenu after mouseup open the menu for a right-click, as before

The secondary-button session opened the menu on release everywhere. On
Windows the platform's `contextmenu` fires after mouseup, by which time
our menu was rendered under the pointer; the event then targeted the
popover instead of the canvas, the canvas handler that prevented the
native menu never ran, and the native menu opened on top of ours.

The session now opens the menu on release only where the platform's
`contextmenu` already came with the press (macOS, Linux) and was
swallowed because a drag could not be told from a click yet. Where it
follows the release, it opens the menu itself, exactly as before this
branch, and lands on the canvas as there is nothing under the pointer.
After a drag that event is swallowed once, as it was.

Tests: after mouseup, nothing is open at release and the platform's event
opens the menu; the mousedown-order and drag cases are unchanged.

Claude Fable 5.1, high effort

* reintroduce: ctrl+wheel should always zoom for both input devices

* fix(editor): lower right-button pan threshold to 5px

* fix(editor): broadcast the final pointer position after panning

* fix(editor): suppress cursor broadcasts while panning
2026-09-16 19:50:39 +02:00
David Luzar a918648012 feat(packages/excalidraw): add renderOverrides to allow overriding of element rendering (#12100)
* feat(packages/excalidraw): add renderOverrides to allow overriding rendering of elements

* refactor(renderer): centralize overrides and inherit label offsets

* fix(renderer): preserve frame-name editing during visual overrides

* perf(renderer): limit override culling to translated elements

* fix(renderer): keep frame-title hit bounds at document positions

* fix(renderer): skip clearing an already clear override snapshot

`setElementRenderOverrides(null)` (or an empty map) on an editor whose
snapshot is already empty no longer copies, derives offsets or schedules a
render. Every other submission still repaints, equivalent ones included:
a value comparison would cost about as much as the copy it saves on every
changing frame, so hosts are told to submit only on change and to dedupe
at their own level, where the snapshot's structure is known.

Fable 5.1, medium reasoning

* refactor(renderer): drop two override edge cases

Culling no longer keeps an off-screen container alive because its bound
label alone would be in view: the label follows its container, as it does
for document culling, and the case needed a label wider than its (short
arrow) container at the viewport edge during a translation.

Frame clipping no longer tracks whether an element carries an explicit
offset entry (`hasPositionOverride`); a non-zero offset is what matters.
An explicit zero offset leaves the element at rest, where the ordinary
overlap check already decides correctly.

Fable 5.1, medium reasoning

* perf(renderer): leave bound-label entries out of override culling

A bound label is drawn with its container and its own entry in the visible
list is skipped by the static renderer, so override culling has no reason
to adjust that entry. Doing so made a visible container whose label is
document-off-screen (a long arrow with its label past the viewport) report
the label as newly visible on every translation, which rebuilt the visible
set from the whole scene each frame. Translated elements now update only
their own membership; the label lookup per translated element goes too.

Fable 5.1, medium reasoning
2026-09-15 14:12:53 +02:00
David Luzar 4e758db17e feat(editor): wheel-button zoom and "zoom with scroll wheel" preference (#12099)
* refactor(editor): move wheel handling out of App.tsx into App.wheel.ts

The wheel handler (pan, ctrl/cmd zoom, shift horizontal pan) and the
non-interactive browser-zoom guard move as-is into a new `AppWheel`
helper (`app.wheel`), in the same shape as `App.cursor` / `App.viewport`.
The zoom math becomes a private `zoomBy(deltaY)` so the upcoming wheel
features (wheel-button zoom, zoom-with-scroll-wheel preference) only add
branches to the dispatch instead of another copy of it.

`isPanning` is module state in App.tsx, so it is handed in as a
dependency the way `AppViewport` gets `isGestureActive`; the debounced
`shouldCacheIgnoreZoom` reset is shared with the touch pinch handler and
becomes public. No behavior change.

Claude Fable 5.1, high effort

* feat(editor): zoom by scrolling with the wheel button held down

Scrolling while the wheel (middle) mouse button is held zooms the canvas
around the pointer, exactly like ctrl/cmd+wheel. The button is what
starts a drag-pan and the wheel sits under the same finger, so a pan can
be zoomed one-handed without reaching for a modifier. It wins over the
modifiers (shift included) and over the drag-pan's own wheel gate, which
until now swallowed all wheel input mid-pan. It is read off
`event.buttons`, so no extra state is tracked.

Zooming mid-pan exposed three ways the point grabbed by the pan could
drift from under the cursor, compounding with every tick while the
pointer moves between zooms:

- The drag-pan wrote its scroll as a plain object computed from
  `this.state`. Under React 19 a wheel zoom is flushed asynchronously,
  and the pan's animation frame runs before that flush, so the pan
  overwrote the zoom's anchoring scroll with a pre-zoom value — the zoom
  then happened around the viewport origin instead of the cursor. The pan
  is now a state updater.
- A pointer move still waiting for its frame landed after the zoom, which
  had anchored on a viewport that didn't include it. The wheel handler
  flushes the pending move first.
- The zoom updater read `viewport.lastPosition` when React evaluated it,
  by which time the pointer had moved on; it also started from
  `app.state.zoom`, so ticks queued before a flush collapsed into one.
  Both are now taken at wheel time / from the updater's own state.

Tests: plain wheel pans, shift pans horizontally and ctrl/cmd zooms
(pinning the baseline); with the button held, scrolling zooms in and out
regardless of shift; during a wheel-button drag-pan, plain wheel input is
still swallowed while wheel-button scrolling zooms, and panning is back
after release. Two regression tests pin the grabbed point under the
cursor: one batches the zoom with a pan landing in the same React flush,
one holds the pan's frame back until after the wheel fires (through a
file-level `withBatchedUpdatesThrottled` mock, since the test setup makes
the throttle synchronous). Both fail on the previous code by 11.8 and
2.7 scene units respectively.

Claude Fable 5.1, high effort

* feat(editor): "zoom with scroll wheel" preference

Adds `appState.zoomWithScrollWheel` (default `false`, persisted to
browser storage like the other preferences), for mouse users who would
rather scroll to zoom: a plain wheel zooms around the pointer, and
ctrl/cmd+wheel pans vertically instead — the two roles swap, the way
tldraw's wheel-behavior preference does it. Shift+wheel keeps panning
horizontally, and the wheel-button zoom keeps winning over everything.
One consequence to be aware of: a trackpad pinch arrives as ctrl+wheel,
so with the preference on it pans.

The toggle lives in the main menu's preferences submenu ("Zoom with
scroll wheel", right under "Select on"), exposed as
`MainMenu.DefaultItems.Preferences.ToggleZoomWithScrollWheel` and backed
by the `zoomWithScrollWheel` action, allowed in view mode since it only
concerns navigation.

Tests: the preference is off by default and its action toggles it; with
it on, plain wheel zooms while ctrl and cmd wheel pan vertically, shift
still pans horizontally and the wheel button still zooms. Snapshots pick
up the new default.

Claude Fable 5.1, high effort

* perf(editor): keep the zoom bitmap cache across pan frames during a wheel zoom

`viewport.translate` cleared `shouldCacheIgnoreZoom` on every call. Wheel
zoom sets the flag so elements draw from zoom-scaled cached bitmaps, but
a drag-pan frame landing between two ticks cleared it again, and the
static renderer then re-rasterized every element at a zoom the next tick
was about to change. Wheel-button zoom-while-panning interleaves the two
by design (the old `isPanning` gate made the combination impossible with
a mouse); trackpad pan-plus-pinch has always alternated plain and ctrl
wheel events the same way.

Measured with 1,200 rough shapes on screen, a 12-tick zoom with a small
pointer move on every frame: 10,434 / 8,774 bitmaps regenerated in two
runs, longest frame 242 / 283 ms, gesture 2.9 / 2.3 s. With the pointer
still: 0 bitmaps, 9 ms, 0.39 s.

The reset now happens only when a gesture interrupts an animated
`setViewport`, whose frames set the flag and whose completion handler
would have cleared it. Everywhere else the flag is left to the zoom paths
and their 300 ms debounce. After the change the moving-pointer run
regenerates 0 bitmaps, longest frame 25–33 ms, gesture 0.7–0.8 s. A
plain wheel pan within 300 ms of a zoom now draws from scaled bitmaps
until the debounce fires instead of re-rasterizing on its first frame.

Tests: pan frames between wheel-button zoom ticks keep the flag until the
gesture is idle (fails on the previous code); the interrupted-animation
test also pins the reset, with a 60 s animation and a polling assertion
so a saturated machine cannot finish the animation before the check.

Claude Fable 5.1, high effort

* fix(editor): handle wheel input over a frame label once

The frame label had a React `onWheel` handler while also sitting inside
the editor container, whose native wheel listener already accepts
`.frame-name` targets. Both received the same event, so every wheel tick
over a label panned or zoomed twice (measured: −140 px of scroll for a
tick that moves the canvas −70 px elsewhere). React registers `wheel`
as passive, so the React path could not even preventDefault. Predates
this branch; with the zoom-with-scroll-wheel preference it became a
two-step zoom per tick over labels.

Drop the prop. With no React path left, the wheel handler's event type
narrows to the native `WheelEvent`.

Test: a wheel event on a frame label pans one step; it fails on the
previous code by exactly one extra step.

Claude Fable 5.1, high effort

* refactor(editor): model the wheel preference as an input device

    `appState.zoomWithScrollWheel` becomes `appState.inputDevice:
    "auto" | "mouse" | "trackpad"`.

    Claude Fable 5.1, high effort

* fix(editor): decide whether a translate zoomed against the state it applies to

`viewport.translate` captured `prevZoom` from `app.state` before queuing
its update, then a second updater compared it with the zoom it found in
`prevState` to tell a zoom apart from a pan — a zoom drops the rubberband
overscroll to 0, a pan keeps it. With a zoom queued in the same React
flush, `app.state` still holds the pre-zoom value, so a pan applied on
top of that zoom read as a zoom of its own and hard-clamped the
overscroll away in one frame instead of easing it back. Two wheel events
in one flush could already do this; the wheel-button zoom makes it
routine, as the handler applies the pending pan move on every tick.

The two `setState` calls become one updater: the update is applied to
`prevState`, the zoom comparison is between that state and the result,
and the constrained viewport is merged into it. No captured state, no
ordering to get wrong.

Test: rubberbanded past the box edge, a ctrl+wheel zoom tick and a wheel
pan batched into one flush keep the overscroll; on the previous code it
lands exactly on the edge.

Claude Fable 5.1, high effort

* fix(editor): pan sideways on a horizontal-only wheel; no-op zoom ticks at the limit

In mouse mode a wheel event with only a horizontal delta — a tilt wheel,
a sideways two-finger scroll — took the zoom branch with `deltaY` 0: no
viewport change, but every such event flipped the bitmap-cache flag,
cancelled collab follow and re-armed the cache debounce, and the sideways
scroll itself went nowhere. A wheel with no vertical delta never zooms
now; it falls through to the pan, which moves the canvas by `deltaX`. A
wheel with no delta at all is ignored outright.

The zoom updater also returns early when the tick lands on the zoom it
started from — at MIN or MAX zoom — instead of setting the cache flag and
redrawing the scene on every tick. This predates the branch. To make
that a real no-op, `viewport.translate` treats a `null` update as nothing
to do rather than re-constraining and re-setting the whole state.

Tests: in mouse mode a horizontal-only wheel pans sideways and leaves the
cache flag alone; a zoom-out tick at MIN_ZOOM changes nothing. Both fail
on the previous code.

Claude Fable 5.1, high effort
2026-09-14 16:17:41 +02:00
David LuzarandClaude Fable 5.1 afa3a653fc feat(editor): sticky notes (#12064)
* feat(editor): sticky notes

* clamp sticky note font size

* [dragInput] reject non-finite values

* chore: prettier formatting after rebase

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore(editor): sticky notes cleanup (plan §7.1)

- hide `transparent` in the sticky stroke picker through master's
  `excludedColors` (#11872) instead of the branch's own
  `hiddenPaletteColorNames`; the swatch becomes a grid-preserving
  placeholder, its hotkey stays dead, and arrow-key navigation steps over
  it in the arrow's direction (unit + integration tests)
- move the text pixel-snap change out to `dwelle/text-pixel-snap`
  (705e497e2): it affects every text element at every zoom and is not
  zoom-aware, so it lands separately
- rename `STICKY_NOTE_DEFAULT_FONT_SIZE` → `STICKY_NOTE_FALLBACK_FONT_SIZE`:
  it is only the non-finite fallback; new sticky text seeds its ceiling from
  `currentItemFontSize` (decision D3)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): one sticky note layout core + resize intents (plan §7.2, 1/2)

- `getStickyNoteLayout` is the single fit/grow/position calculation (pure;
  ceiling-anchored size grid, warm-started binary search instead of the
  linear descent); `updateStickyNoteLayout` applies it to a scene and runs
  the bound-arrow pass by default (callers forward `simultaneouslyUpdated`
  or opt out when they own a pass); `relayoutStickyNotes` applies it inside
  immutable element arrays, identity- and version-preserving
- resize intents are absolute values recomputed from the gesture-start
  snapshot on every pointer-move (`getStickyNoteResizeIntent`): width-only
  gestures keep the base height, height gestures set it, proportional
  gestures (Shift on any handle, aspect-locked multi-select, Stats group)
  also scale the font ceiling, flips preserve everything; the content
  correction anchors at the held edge (n* → bottom, Alt → center,
  Shift+E/W → center). The corner pre-fit clamp and the multi-resize
  sentinel/veto are gone: one fit per pointer-move
- arrows bound to a note follow text-driven growth, and multi-resize /
  Stats group update them after the content correction, not before
- `fontSizeMax` is `number | null`; every read/write goes through the
  container-aware `getUserFontSize` / `getUserFontSizeUpdate` (a detached
  label with a stale ceiling neither shows nor keeps writing it); unbind
  clears it and returns an empty note to its base height; restore seeds or
  clears it after binding repair and normalizes a sticky label's stroke
- paste styles lays out a restyled note and its label together in a
  post-pass (no phantom-container mismatch) and reads the source ceiling
  from the copied snapshot rather than the fitted size
- `redrawTextBoundingBox` / `handleBindTextResize` delegate sticky notes up
  front (no wasted generic wrap); the `refreshTextDimensions` sticky arm and
  the wysiwyg duplicate arm are gone; `clampStickyNoteProps` becomes the
  identity-preserving `normalizeStickyNote`

Tests: content-pinned corner drag keeps the requested base, Shift+E scales
note/base/ceiling, width-only drag preserves the base of a grown note,
Shift released mid-gesture restores gesture-start values, flipping a rotated
grown note keeps base/ceiling/label angle, multi-resize syncs an empty
note's base.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): sticky note invariants in changeProperty + regression tests (plan §7.2, 2/2)

- `changeProperty` normalizes sticky notes after any property write
  (identity-preserving `normalizeStickyNote`), replacing the three per-action
  wraps in the background, sloppiness and roundness actions
- tests (new `tests/stickyNotes.test.tsx` + restore): arrows bound to a
  note follow text-driven growth while typing; binding free text seeds the
  ceiling and unbinding clears it (`null`) and returns the note to its base
  height; pasting styles copies the user's ceiling and lays out the note
  together with its label; unselected notes keep object identity through
  property actions; a transparent background write is normalized; restore
  clears a stale ceiling on unbound text and seeds ceiling + visible stroke
  on a sticky label

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): sticky note creation feel + sticky color domain (plan §7.3)

Creation
- a note starts as a 0×0 draft inserted like every other tool and is
  previewed at its true dragged size (no jump to 40px on the first move);
  a gesture under the drag threshold is a click → the default 250×250
  square centered on the pointer; a longer drag snaps to the 40×40 minimum
  only on release, growing away from the origin corner. The preview
  suppression flag and the pointer-move sticky arm are gone
- `normalizeStickyNote` splits into style (constructor + every pass) and
  geometry (finalize, restore, action passes) so a draft is legal

Colors — sticky notes are their own color domain (decision D2)
- new `currentItemStickynoteBackgroundColor` default (yellow) next to the
  stroke one; both persisted/restored non-transparent; new notes and their
  labels seed from the sticky defaults (a label takes the note's stroke)
- own top-pick slots (`colorTopPicks.stickyNoteStroke/Background`) and
  classic note picks; transparent excluded like bucket fill
- one `resolveColorTarget` (regular / sticky / mixed, incl. the text being
  edited) resolved at execution time in the stroke and background actions'
  `perform`; the panel only sends `{ color }`, so the memoized picker's
  stale closure can no longer write the wrong tool's default (A1)
- one `getColorUpdate` policy used by the actions, both eyedroppers (which
  now include bound labels, so a stroke pick colors a note's visible text —
  1.12), paste styles and bind-existing-text (a transparent label bound to
  a note takes the note's text color — A3); the per-surface forks and
  `isStickyNoteStrokeColorElement` are gone

Tests: click under the threshold, 20px drag previews at 20 and snaps to 40
with the origin corner fixed (both directions); closed-popup top pick after
a tool switch writes the sticky default; mixed selection writes both
domains; notes are created from the sticky defaults; bind normalizes a
transparent label; the keyboard eyedropper colors a note's label. App-state
snapshots gain the three new keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): font-aware minimum sticky note size

The 40×40 floor let a freshly placed note grow on the very first keystroke:
one line at the default 20px ceiling plus the 16px padding needs 57px.

- `STICKY_NOTE_MIN_SIZE = 75` replaces the width/height pair as the data
  floor (normalization, layout, restore)
- `getStickyNoteMinSize({ fontSize, fontFamily })` =
  max(75, round(ceiling × lineHeight) + 2 × padding) is the UI floor: the
  creation snap on release uses the current font size/family, the single
  resize floor uses the label's ceiling (constant floor for an empty note).
  Excalifont: 75 up to the L ceiling, 77 at 36px, 92 at 48px

Tests: the 20px drag now snaps to 75; a 48px ceiling yields a 92px note
that keeps its height on the first character.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(editor): sticky note regression matrix (plan §7.4) + two Stats fixes

Tests (27 new): Alt/center resize keeps the center of a content-pinned
note; proportional multi-select scales the label's ceiling, a free
multi-select of an empty note uses the requested height; a proportional
gesture returning to scale 1 restores everything; rotated growth keeps the
top edge; the fit takes ≤ 2 measurements on a warm keystroke and ≤ 12 on a
cold search from a 512 ceiling; a lowered ceiling wins over a still-fitting
old size; odd/fractional ceilings stay reachable on the ceiling-anchored
grid; the font-aware minimum size. Bound arrows follow the note on unbind,
after a content-constrained resize and after a font-size action, and are
not moved twice when resized as part of the same selection. A top pick
while editing a note's label targets the label and the sticky default; the
preview eyedropper colors a selected note's label; paste styles resolves a
since-deleted source's ceiling from the copied snapshot; a detached label
with a stale ceiling neither shows nor writes it; duplication keeps the
ceiling and base height; an empty note escaped from editing stays and is
removed by undo; Stats W keeps the base height and a group W edit scales
the ceiling; restore normalizes both sticky defaults, keeps the new
top-pick slots and refits note + label under `refreshDimensions`; unit
tests for `resolveColorTarget` (regular / sticky / mixed / editing) and the
default normalization.

Source fixes the tests surfaced:
- Stats `DragInput`: the typed-value path handed callbacks the live
  elements map as "original", so gesture-start values (a note's width, its
  label's ceiling) read as the already-applied values; it now snapshots the
  scene like the pointer-drag path does
- Stats group resize (`MultiDimension`): a group unit lists the note's
  label too and scaled it as a standalone element after the note, wiping
  the fitted size; the note's layout owns its label

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): sticky note skeleton API, tool hint, help row, docs (plan §7.5)

- `convertToExcalidrawElements` accepts `{ type: "stickynote", x, y, label?,
  ... }` (new `ValidStickyNote` skeleton member): the note is constructed
  through `newStickyNoteElement` + `normalizeStickyNoteGeometry` (250×250
  default, `baseHeight` finalized, sticky color invariants), and a label is
  bound through the sticky fit so its `fontSize` becomes the ceiling and the
  note grows only if the text overflows at the minimum size (decision D7)
- tool hint for the sticky note tool ("Click to place a note, or drag to
  size it") and the `N` shortcut row in the help dialog
- dev-docs: "Sticky Notes" section in the element-skeleton page; CHANGELOG
  entry (PR number placeholder)

Tests: skeleton defaults, invariant enforcement on given props, label
binding with the ceiling and the note's text color, growth for an
overflowing label.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor: move the seeded PRNG (mulberry32) to @excalidraw/common

Pure utility next to `randomInteger`/`reseed`; the sticky note corner
jitter imports it from there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(editor): drop the dead roughjs Drawable for sticky notes

Nothing ever generated or drew it: both renderers paint a note directly from
`getStickyNoteRenderPoints`, and every `ShapeCache` consumer is type-gated
to the rectangle family or linear elements. It also disagreed with the
rendered outline (generic corner radius, no jitter). Sticky notes now sit
with text/image/frame in the `null`-shape group, which the cache treats
as a valid value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): sticky note creation-date footer

Every note reserves a 32px band under its label body and paints the
note's `created` date there, on canvas and in SVG export: "7 Sep" while
the year is the current one, "7 Sep 2025" otherwise, nothing for an
unknown timestamp (the band stays reserved so geometry never depends on
data availability). The label form is chosen by width bucket, never
measured, so painting stays measurement-free — server-side SVG export has
no text measurer — and an absolute date means exports never go stale.

- `STICKY_NOTE_FOOTER` and `STICKY_NOTE_BODY_INSET_Y` in @excalidraw/common;
  `getStickyNoteDateLabel` / `getStickyNoteFooter` in stickyNote.ts
- the layout, `getBoundTextMaxHeight`, the text editor and both renderers
  share the one inset; a rotated label rotates about the note center, not
  the now-asymmetric body center
- `getStickyNoteMinSize` returns `{ width, height }`; creation and resize
  clamp each axis to its own floor (a Shift drag stays square)
- tests: date label, width buckets, body height, resize floor, SVG + canvas
  export, creation minimums

Plan: sticky_notes_review_final.md §9.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): image tool into "More tools", letter shortcut badges

With the sticky tool on N between text (8) and eraser (0), the numbered
badge row no longer reads as a sequence. On desktop and tablet, move the
image tool into the "More tools" menu (first entry, still `9`,
`UIOptions.tools.image` keeps hiding it) and badge the toolbar tools
with their letter shortcuts (V R D O A L P T N E) instead of numbers.
The number keys keep working; tooltips and the help dialog are unchanged.
The mobile toolbar keeps its image slot.

- Toolbar.tsx: image item in `ExtraToolsDropdown` (trigger highlight and
  icon while active), `UIOptions` passed through for the gate
- Tools.tsx: `keyBindingLabel` prefers the letter
- tests: `UI.clickExtraTool`, image test uses it, toolbar describe in
  tool.test.tsx (menu placement, UIOptions gate, letter badges)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): a sticky note and its label share one ink

The creation-date footer paints from the note's `strokeColor`, the label
from its own, and three writes let them drift: coloring the label while
editing it (the wysiwyg clears the selection, so `changeProperty` targets
only the edited text), binding an already-colored text (only transparent
was rewritten), and paste styles from a source whose container and label
carry two colors. Red text under a black date.

The note's `strokeColor` is its ink — the seed for a new label and what
the footer paints with — and the label always mirrors it:

- `syncStickyNoteInk(elements, prevElementsMap)`: after a write, copies
  the side that changed onto the other; when both changed or the data
  drifted, the label wins (it is the text the user styled); a transparent
  label takes the note's color; identity-preserving when nothing changes
- entrances: the stroke action (after `changeProperty`, which covers edit
  mode), paste styles (before the relayout), bind text (the text's color
  wins, transparent falls back), the skeleton path (a label that sets its
  own color gives it to the note), restore (label wins over a drifted note)
- the stroke picker reads "Text color" for notes (`labels.textColor`)
- tests for the helper and for every entrance

Plan: sticky_notes_review_final.md §12.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* make footer opacity=1

* chore: fix sticky note test import order

* feat(editor): drag a tool out of the toolbar to drop a default-sized element

Press a tool button and drag onto the canvas: a default-sized element
follows the pointer as a translucent preview and is placed where the
pointer is released. Sticky notes opt in; the mechanism is generic.

`App.toolDrag.ts` owns the gesture. `DRAGGABLE_TOOLS` is the registry —
a tool provides `createElement(app, center)` (the element it drops,
centered on the pointer, from the same `currentItem*` defaults a click
uses) and an optional `onDrop` (a sticky note starts editing its label).
Adding a tool is one entry.

The preview exists only on the unit until the drop: not in the scene, the
store or the history, so collaborators never see it and Escape,
pointercancel, window blur or a release outside the canvas leave nothing
behind. It is painted by the existing `NewElementCanvas`, drawn exactly as
it will land, with the canvas itself made translucent (a CSS opacity on
the canvas — per-draw-call alpha would darken where a shadow and a fill
overlap).

The button arms on pointerdown (a new `onPointerDown` hook on the toggle
`IconButton`) and the drag starts once the pointer has moved
`DRAGGING_THRESHOLD`, so a plain click still selects the tool. Listeners
live on the owner window like the other pointer gestures. The drop is
`insertNewElement` + one store capture (one undo entry), selects the
element and switches to the preferred selection tool — whatever tool was
active (an arrow, say) must not stay armed under the dropped element or
its editor. The hit test prefers `elementFromPoint` (sees through a
touch's implicit capture on the button) and falls back to the event
target where it is missing (jsdom).

Made public on App for the unit: `store`, `startTextEditing`,
`getCurrentItemRoundness`, `getCurrentItemStrokeWidth`.

Tests: drag → preview with an untouched scene and history → drop centered
at the pointer with one history entry, the editor open and the selection
tool active, undo removes it; Escape mid-drag leaves nothing and a late
pointerup is inert; a release outside the canvas drops nothing; a plain
click still selects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): default a text element's fontSizeMax to null

`newTextElement` left `fontSizeMax` undefined while restore normalizes it
to `null`. `Delta.distinctKeysIterator` compares with `!==`, so a
restored copy of a locally created text element reported a phantom
`fontSizeMax` change — today only in collab reconciliation deltas, which
are not undoable, but a latent source of spurious version bumps.

Normalize to `null` in the constructor, like `labelPosition`. Snapshots
change accordingly (`undefined` → `null`), including the embedded scene
payload in the SVG export snapshot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore(editor): drop a duplicated scheduleCapture in the sticky note finalize

A paste error: the two calls in one tick set the same flag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): decide history visibility from the applied result

Creating a sticky note and abandoning its editor by clicking out left two
history entries: the note's creation, and one that only deleted the empty
label and dropped the note's reference to it — nothing a user can see, so
the first undo appeared to do nothing. (Escape left the same deletion
pending instead, folded into the next entry.) The text tool's own
click-create-and-abandon had the same invisible entry.

History already skips entries with no visible change, but visibility was
decided per property patch, before binding repair and layout ran: an
`isDeleted` flip or a `boundElements` edit always counted, and anything
layout moved afterwards never did.

`ElementsDelta.applyTo` now decides it from the applied result. After the
deltas, reordering, binding repair and text/arrow layout, the "before"
and "after" of every affected element are compared:

- `idsToCheck` = the changed elements plus what layout can move for them:
  a text's container and everything in `boundElements` (a container's
  label and arrows); the set is walked as it grows, so an arrow brings in
  its own label.
- Unchanged elements are shared between the incoming map and the result
  and layout mutates them in place, so their "before" is a shallow copy;
  changed elements already have a fresh instance. The whole scene is
  never copied.
- An element counts as visible only if it is not deleted and, for text,
  not empty — except an empty label carrying arrow bindings or sitting on
  an arrow, where it cuts the gap. `version`, `versionNonce`, `updated`,
  `created` are metadata and `index` is covered by the z-order check;
  `boundElements` is compared as `type:id` strings with references to
  empty labels dropped, so binding repair that rebuilds equal objects
  reads as unchanged. Everything else counts.

A dev/test-only assertion records every element's version before layout
and throws if layout bumped one outside `idsToCheck`, so a new layout
dependency fails a test instead of letting undo silently skip a real
change. For that to work the `return` had to leave the `finally` block: a
return in `finally` overrides a thrown exception, so the existing
dev/test rethrow for failures in reordering and layout never actually
propagated. Production behavior is unchanged — the catch still returns
with the flag forced to visible.

Cost, production build, median per `applyTo`: sparse changes at parity
through 50k elements; 10k visible bulk updates +13%; 10k metadata-only
updates 44 → 95 ms, because proving "nothing visible" cannot stop at the
first element — the case the old code answered wrongly. `applyTo` runs
on undo/redo and the version slider, both user-paced.

Creation and typing still undo separately, and a drop from the toolbar
still records at the drop. Tests: delta-level (empty-label deletion and
restoration on a rectangle and a sticky note ignored; a non-empty label,
an arrow's label gap, a container resized by a restored label, arrow
bindings on empty text, a label and an arrow repositioned by a container
redraw all visible; excluded or already-applied properties and a
metadata-only update invisible; snapshot restoration visible) and the
sticky note flows (Escape, click-out, font change on an empty label,
typing).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): snap sticky note placement to the grid

* perf(editor): ignore arrow bindings in sticky layout input check

* feat(editor): center a sticky note's label in the whole note, tighten the footer

A middle-aligned label was centered in the body above the creation-date
footer, so it sat visibly high — half a footer above the note's center.
It is now centered in the whole padded note (`height − 2·padding`), the
footer ignored, and only pushed up against the body's bottom once
centering would run it into the footer. The fit and the growth rule are
unchanged: the note still grows at the same label height, no vertical
budget is lost, and top and bottom alignment are as before. Rotation
already turned the label about the note's center.

The footer takes less room: its band shrinks from 32px to 20px and the
date's baseline is defined from the note's bottom edge (14px) rather than
from the padding, so the glyphs sit ~11px from the edge (was 17) with
~13px to the label body above (was 19). The vertical inset follows
(64 → 52), and with it the font-aware minimum note sizes.

Tests: a middle-aligned label is centered on the note's center at three
angles; centering is clamped only once it would overlap the footer;
existing sizes derive from the inset constant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): resize sticky notes proportionally by default

* fix(editor): preserve resize flips and text container anchors

Apply minimum sizes and proportional font scaling to dimension magnitudes, then restore their signs for shared flip geometry.

Share rotation-aware positioning for text-driven height changes and pass vertical flip state through single and multiple-element resizing.

Add regression coverage for mirrored resizing, proportional minimum sizes, and text growth after crossing the resize anchor.

* feat(editor): lift a seeded sticky note corner at high roughness

Curve one corner at roughness 2, chosen from the note seed so it stays consistent through redraws and resizes. Pull its shadow inward by half the bend while retaining the existing down-right offset.

Share the generated paper and shadow paths between Canvas and SVG rendering.

* fix(editor): share the default sticky note color with its quick pick

* refactor(editor): rename a label's `fontSizeMax` to `baseFontSize`

The field holds the font size the user picked, from which the sticky note
layout derives the fitted `fontSize`. "Max" described today's fitter (it
only shrinks); "base" names the intent and pairs with the note's
`baseHeight`, the other user-set value the layout derives from. Renamed
before the field ships, since it is persisted in scene files.

The type is now required (`number | null`): the constructor and restore
already always write it, and history deltas compare with strict
inequality, so an omitted key against `null` would record a spurious
change. Making it required lets tsc catch producers that omit it (one test
fixture did). Helpers follow: `getBaseFontSize`, `getBaseFontSizeUpdate`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): slightly strengthen sticky note edges

* feat(editor): color a sticky note's background while editing its label

While editing a note's label the styles panel targets the label, which has
no fill, so the background picker was gone and the note's color could only
be changed after leaving the editor. The picker now stays available while
editing, shows the note's fill, and a pick colors the note — text color and
background in one editing pass.

One helper, `getColorTargetElement`, decides where a color pick on an
element lands (a background pick on a note's label goes to the note); the
target resolver, the panel predicate, the panel value and the action all
use it, so the picker also gets the sticky domain (sticky picks, no
transparent, the sticky default written).

Also fixes a latent bug in the background action: with nothing selected,
`selectedElements.every(isLine…)` was vacuously true, so the action took
the polygon branch and did nothing. Unreachable before, since no background
picker was available without a selection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix changelog

* fix snaps

* more snap

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:45:37 +02:00
David LuzarandClaude Fable 5.1 6574de60b3 feat(editor): snap element bitmaps, scroll and grid to whole device pixels (#12063)
* feat(editor): blit cached element bitmaps on whole device pixels

Elements are painted from cached bitmaps, blitted 1:1 with smoothing
disabled for unrotated and right-angle elements. Nearest-neighbor at a
fractional offset is then a pixel-exact copy shifted to the nearest
pixel — except at an exact half pixel, where a GPU-accelerated canvas
decides the rounding per scanline by float precision: a few rows sample
the neighboring row and glyph strokes come out doubled or broken, varying
with scroll and position. A centered label lands on a half pixel
routinely (odd free space ÷ 2), which is how it showed up on sticky notes.

Measured in Chrome: a label blitted at y = 639.5 had 3 of 82 scanlines
taken from the adjacent row; rounded to 640 it was clean.

Round the blit position to whole device pixels for those elements. The
context is scaled at the blit (canvas scale × zoom ÷ devicePixelRatio),
so the rounding goes through `context.getTransform()`: rounding the
pre-zoom value only lands on device pixels at 100% zoom. Rotated
elements are resampled regardless and are left alone; export draws
directly and is unaffected.

Tests: the static canvas's `drawImage` events, with their CTM, land on
whole device pixels for text and a rectangle at zoom 1, 1.5, 0.73 and
2.2 under fractional scroll; a rotated element keeps its offset.

Extracted from dwelle/sticky-notes-2 (originally eddb67ca0).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): draw the canvases at a whole-device-pixel scroll

Panning writes a fractional scroll (pointer delta ÷ zoom). With every
bitmap landing on its nearest device pixel, a fractional scroll means
elements with different sub-pixel phases jump on different frames — a
one-pixel wobble between neighbors, a label shifting against its box —
and one scroll value in every pixel puts every integer-positioned
element on the half-pixel boundary at once.

Round the scroll to whole device pixels at render time, in all three
renderers with the same formula so overlays sit on the content — the way
a browser scrolls a page: each element keeps its own constant sub-pixel
phase and the scene moves rigidly. The real scroll in state is untouched,
so hit-testing and DOM overlays are unaffected; the difference stays
under half a device pixel. Export is excluded (it draws vectors).

Tests: the helper rounds to device pixels and is identity when already
whole; a rectangle at integer coordinates blits on whole device pixels at
zoom 1 and 1.5 under fractional scroll, and a scroll a tenth of a pixel
further lands on the same pixels.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(editor): draw grid lines on whole device pixels at every zoom

The grid was nudged onto the pixel grid only at 100% zoom, and only when
the scroll happened to be whole; at any other zoom its one-pixel lines
landed on fractional device pixels and rendered as two lighter pixels.

Draw each line a whole number of device pixels wide, centered to cover
them exactly (on the half pixel when the count is odd), computed from the
zoom and the canvas scale — which also makes "one pixel" one device pixel
group at devicePixelRatio 2 instead of a fractional pair. Lines thinner
than a device pixel (zoomed far out) keep their sub-pixel width: their
lightness is the point. The zoom-1-only nudge is subsumed.

Test: with grid mode on, every grid path start on the static canvas has
its across-axis coordinate on a half device pixel at zoom 1, 1.5 and 2.2
under fractional scroll.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): snap right-angle blits via the transform origin; grid width in device pixels

Two review findings.

Elements rotated by 90° or 270° enter the snapping branch (smoothing is
off for right angles too), but their transform has near-zero `a` and `d`
with the scale in `b`/`c`. Dividing by the diagonal sent the bitmap far
offscreen — a rectangle at (110.37, 120.61) vanished at both angles.
The snap now moves the transform's origin onto the rounded device
position of the blit and draws at (0, 0): rotation, mirroring and scale
stay in `a`–`d`, no inverse is needed, and the origin is an exact integer
even in the canvas's float32 matrix (mapping a rounded position back
through user space left it ~1e-5 off).

The grid's "at least one device pixel wide" test compared
`(1 / zoom) × zoom × scale` against 1 exactly; at 190% that product is
0.9999999999999999, so those lines skipped snapping and stayed blurry
while neighboring zooms were crisp. The width is now computed straight
in device pixels, `min(scale, max × zoom × scale)`, so there is no round
trip to tolerate.

Tests: rectangles at π/2 and 3π/2 blit on whole device pixels and stay
next to the element; the grid test includes 190%. The blit helper in the
test maps positions through the full matrix. The CHANGELOG bullets link
the PR.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(editor): one devicePixelRatio read for the blit, no-op padding scaling dropped

`drawElementFromCanvas` read `window.devicePixelRatio` eleven times; it
reads it once. That read stays the global one on purpose: the cached
bitmap was generated with it (`generateElementCanvas`) and the blit's
size math needs the same value, so switching this path to the owner
window's ratio is a separate change that has to include bitmap
generation. `(padding × scale) / scale` is `padding`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): snap blits only where smoothing is off, through one predicate

The snap ran during zoom gestures too, where the cached bitmaps stay at
the old scale and are resampled with smoothing on: rounding there could
only make elements twitch as the scale changes.

The smoothing switch and the snap now share `canSnapElement` — unrotated
or at a right angle, and not mid-gesture — so the two cannot drift
apart. It is eligibility, not a guarantee of a 1:1 copy: freedraw is
blitted with smoothing on and a size-capped cache is rescaled; snapping
is harmless for both.

Test: with `shouldCacheIgnoreZoom` set, the blit keeps its fractional
position.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(editor): snap a label relative to its container, tie-safe

Each bitmap rounding its own origin lets a label shift by a pixel against
its box as a drag crosses half pixels (nearest-neighbor sampling did the
same implicitly before the explicit snap). A label under an unrotated
container now anchors to the container's rounded origin and rounds its
offset from it separately, so both land on whole pixels and their
distance never changes; every other element anchors to itself with a
zero offset, through the same code path.

The offset is formed from scene coordinates before the scroll is added,
and a tiny bias breaks an exact half-pixel tie the same way every frame.
Ties are common — any odd scene offset at 150% — and the float noise of
a fractional drag otherwise flips `Math.round` between the two neighbors:
a 200-wide box with a centered label went 92 → 91 → 92 device pixels
across drags of 0, 0.3 and 0.4.

Tests: a plain fractional offset and the half-pixel tie keep a constant
device offset across five fractional drags at 150%.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 14:39:59 +02:00
David LuzarandMárk Tolmács 854d00c31b feat(editor): track element creation timestamps (#12060)
feat(editor): element creation timestamp tracking

Co-authored-by: Márk Tolmács <mark@lazycat.hu>
2026-09-08 15:26:14 +02:00
Excalidraw Botanddwelle 0361bdd9cb chore(editor): Update translations from Crowdin (#11942)
* New translations en.json (Italian)

[ci skip]

* New translations en.json (Persian)

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Slovenian)

[ci skip]

* New translations en.json (Slovenian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* Auto commit: Calculate translation coverage

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-07 22:32:05 +02:00
David Luzar 2b1c2a9fbf fix(editor): dismiss dropdown menus on outside clicks (#12053)
fix: dismiss dropdown menus on outside clicks
2026-09-07 17:28:20 +02:00
David LuzarandClaude Fable 5.1 f1a79b73df perf(editor): stop inverting the interactive canvas via CSS filter in dark mode (#12050)
The interactive canvas (selection outlines, transform handles, binding
highlights, collaborator cursors, snap lines, scrollbars) was still
composited through `filter: invert(93%) hue-rotate(180deg)` in dark mode
(#10578 only de-filtered the static canvas). The canvas covers the whole
viewport and repaints on nearly every pointer move, so browsers that
composite in software (Firefox/Zen on software WebRender or with hardware
acceleration disabled) paid for a full-viewport color-matrix pass per frame
and dropped from 120fps to ~40fps during marquee/drag. Light mode was
unaffected.

Colors are now mapped in JS via `applyDarkModeFilter`, like the static
canvas. Hardcoded dark-mode values are the exact post-filter equivalents of
the previous ones, so rendering is unchanged, with two intentional
exceptions:

- collaborator cursors and remote selection outlines render in the
  collaborators' actual colors (matching their avatars) instead of inverted
  ones (per the existing TODO in clients.ts)
- the lasso trail now uses the theme's `--color-selection` like the marquee
  selection, instead of a hardcoded light-theme purple that never matched in
  dark mode

`--color-selection` for the dark theme now holds the actual rendered color
(#b4b0ff); hosts overriding it no longer need to pre-invert.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 11:52:22 +02:00
214cd6e6e8 feat(editor): allow for dragging bound text along an arrow's path (#10947)
Arrow labels can be dragged along their arrow. A label's place is stored on the text element as labelPosition, a normalized arc-length parameter (0–1) over the arrow's whole path, and the label's x/y is derived from it. Because the parameter covers the whole path rather than one segment, the label keeps its place when points are dragged, when midpoints are inserted or removed, and when the arrow is resized. (An earlier iteration anchored labels to a segment; that approach is gone.)

Closes #9338.
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-09-03 17:21:13 +02:00
zsvicziananddwelle e1bb9ff8f8 feat(packages/excalidraw): support rendering into another document (#11974)
* feat: support rendering into another document

* add missed places

* tweak jsdoc

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-08-26 15:33:04 +02:00
David Luzar 4a6f4e7a32 feat(app): d2c streaming (#11960)
* support d2c streaming

* render status spinner while streaming

* enable analytics for ai
2026-08-25 16:08:37 +02:00
Excalidraw Botanddwelle e160ff7ba0 chore(editor): Update translations from Crowdin (#11815)
* New translations en.json (Romanian)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Arabic)

[ci skip]

* New translations en.json (Bulgarian)

[ci skip]

* New translations en.json (Catalan)

[ci skip]

* New translations en.json (Czech)

[ci skip]

* New translations en.json (Danish)

[ci skip]

* New translations en.json (German)

[ci skip]

* New translations en.json (Greek)

[ci skip]

* New translations en.json (Basque)

[ci skip]

* New translations en.json (Finnish)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hungarian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Korean)

[ci skip]

* New translations en.json (Kurdish)

[ci skip]

* New translations en.json (Lithuanian)

[ci skip]

* New translations en.json (Dutch)

[ci skip]

* New translations en.json (Punjabi)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]

* New translations en.json (Slovak)

[ci skip]

* New translations en.json (Slovenian)

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Turkish)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Vietnamese)

[ci skip]

* New translations en.json (Galician)

[ci skip]

* New translations en.json (Portuguese, Brazilian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Persian)

[ci skip]

* New translations en.json (Khmer)

[ci skip]

* New translations en.json (Tamil)

[ci skip]

* New translations en.json (Bengali)

[ci skip]

* New translations en.json (Marathi)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* New translations en.json (Norwegian Nynorsk)

[ci skip]

* New translations en.json (Kazakh)

[ci skip]

* New translations en.json (Latvian)

[ci skip]

* New translations en.json (Azerbaijani)

[ci skip]

* New translations en.json (Hindi)

[ci skip]

* New translations en.json (Burmese)

[ci skip]

* New translations en.json (Chinese Traditional, Hong Kong)

[ci skip]

* New translations en.json (Sinhala)

[ci skip]

* New translations en.json (Uzbek)

[ci skip]

* New translations en.json (Norwegian Bokmal)

[ci skip]

* New translations en.json (Occitan)

[ci skip]

* New translations en.json (German, Switzerland)

[ci skip]

* New translations en.json (Bengali, India)

[ci skip]

* New translations en.json (Kabyle)

[ci skip]

* New translations en.json (Karakalpak)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Arabic)

[ci skip]

* New translations en.json (Bulgarian)

[ci skip]

* New translations en.json (Catalan)

[ci skip]

* New translations en.json (Czech)

[ci skip]

* New translations en.json (Danish)

[ci skip]

* New translations en.json (German)

[ci skip]

* New translations en.json (Greek)

[ci skip]

* New translations en.json (Basque)

[ci skip]

* New translations en.json (Finnish)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hungarian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Korean)

[ci skip]

* New translations en.json (Kurdish)

[ci skip]

* New translations en.json (Lithuanian)

[ci skip]

* New translations en.json (Dutch)

[ci skip]

* New translations en.json (Punjabi)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]

* New translations en.json (Slovak)

[ci skip]

* New translations en.json (Slovenian)

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Turkish)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Vietnamese)

[ci skip]

* New translations en.json (Galician)

[ci skip]

* New translations en.json (Portuguese, Brazilian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Persian)

[ci skip]

* New translations en.json (Khmer)

[ci skip]

* New translations en.json (Tamil)

[ci skip]

* New translations en.json (Bengali)

[ci skip]

* New translations en.json (Marathi)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* New translations en.json (Norwegian Nynorsk)

[ci skip]

* New translations en.json (Kazakh)

[ci skip]

* New translations en.json (Latvian)

[ci skip]

* New translations en.json (Azerbaijani)

[ci skip]

* New translations en.json (Hindi)

[ci skip]

* New translations en.json (Burmese)

[ci skip]

* New translations en.json (Chinese Traditional, Hong Kong)

[ci skip]

* New translations en.json (Sinhala)

[ci skip]

* New translations en.json (Uzbek)

[ci skip]

* New translations en.json (Norwegian Bokmal)

[ci skip]

* New translations en.json (Occitan)

[ci skip]

* New translations en.json (German, Switzerland)

[ci skip]

* New translations en.json (Bengali, India)

[ci skip]

* New translations en.json (Kabyle)

[ci skip]

* New translations en.json (Karakalpak)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Arabic)

[ci skip]

* New translations en.json (Bulgarian)

[ci skip]

* New translations en.json (Catalan)

[ci skip]

* New translations en.json (Czech)

[ci skip]

* New translations en.json (Danish)

[ci skip]

* New translations en.json (German)

[ci skip]

* New translations en.json (Greek)

[ci skip]

* New translations en.json (Basque)

[ci skip]

* New translations en.json (Finnish)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hungarian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Korean)

[ci skip]

* New translations en.json (Kurdish)

[ci skip]

* New translations en.json (Lithuanian)

[ci skip]

* New translations en.json (Dutch)

[ci skip]

* New translations en.json (Punjabi)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]

* New translations en.json (Slovak)

[ci skip]

* New translations en.json (Slovenian)

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Turkish)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Vietnamese)

[ci skip]

* New translations en.json (Galician)

[ci skip]

* New translations en.json (Portuguese, Brazilian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Persian)

[ci skip]

* New translations en.json (Khmer)

[ci skip]

* New translations en.json (Tamil)

[ci skip]

* New translations en.json (Bengali)

[ci skip]

* New translations en.json (Marathi)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* New translations en.json (Norwegian Nynorsk)

[ci skip]

* New translations en.json (Kazakh)

[ci skip]

* New translations en.json (Latvian)

[ci skip]

* New translations en.json (Azerbaijani)

[ci skip]

* New translations en.json (Hindi)

[ci skip]

* New translations en.json (Burmese)

[ci skip]

* New translations en.json (Chinese Traditional, Hong Kong)

[ci skip]

* New translations en.json (Sinhala)

[ci skip]

* New translations en.json (Uzbek)

[ci skip]

* New translations en.json (Norwegian Bokmal)

[ci skip]

* New translations en.json (Occitan)

[ci skip]

* New translations en.json (German, Switzerland)

[ci skip]

* New translations en.json (Bengali, India)

[ci skip]

* New translations en.json (Kabyle)

[ci skip]

* New translations en.json (Karakalpak)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Arabic)

[ci skip]

* New translations en.json (Bulgarian)

[ci skip]

* New translations en.json (Catalan)

[ci skip]

* New translations en.json (Czech)

[ci skip]

* New translations en.json (Danish)

[ci skip]

* New translations en.json (German)

[ci skip]

* New translations en.json (Greek)

[ci skip]

* New translations en.json (Basque)

[ci skip]

* New translations en.json (Finnish)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hungarian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Korean)

[ci skip]

* New translations en.json (Kurdish)

[ci skip]

* New translations en.json (Lithuanian)

[ci skip]

* New translations en.json (Dutch)

[ci skip]

* New translations en.json (Punjabi)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]

* New translations en.json (Slovak)

[ci skip]

* New translations en.json (Slovenian)

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Turkish)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Vietnamese)

[ci skip]

* New translations en.json (Galician)

[ci skip]

* New translations en.json (Portuguese, Brazilian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Persian)

[ci skip]

* New translations en.json (Khmer)

[ci skip]

* New translations en.json (Tamil)

[ci skip]

* New translations en.json (Bengali)

[ci skip]

* New translations en.json (Marathi)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* New translations en.json (Norwegian Nynorsk)

[ci skip]

* New translations en.json (Kazakh)

[ci skip]

* New translations en.json (Latvian)

[ci skip]

* New translations en.json (Azerbaijani)

[ci skip]

* New translations en.json (Hindi)

[ci skip]

* New translations en.json (Burmese)

[ci skip]

* New translations en.json (Chinese Traditional, Hong Kong)

[ci skip]

* New translations en.json (Sinhala)

[ci skip]

* New translations en.json (Uzbek)

[ci skip]

* New translations en.json (Norwegian Bokmal)

[ci skip]

* New translations en.json (Occitan)

[ci skip]

* New translations en.json (German, Switzerland)

[ci skip]

* New translations en.json (Bengali, India)

[ci skip]

* New translations en.json (Kabyle)

[ci skip]

* New translations en.json (Karakalpak)

[ci skip]

* New translations en.json (Romanian)

[ci skip]

* Auto commit: Calculate translation coverage

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-08-16 17:30:01 +02:00
Vishnu RkandVishRk 884225b1c7 fix(app): localize hardcoded "Sign up"/"Sign in" menu labels (#11893)
fix(localization): update signup and signin labels for localization

Co-authored-by: VishRk <VishRk@users.noreply.github.com>
2026-08-16 13:20:15 +00:00
David Luzar abeeaeba21 feat(editor): customizing color top picks (#11872) 2026-08-11 15:59:35 +02:00
JayeshandMark Tolmacs c5a50d223f fix(editor): respect boxSelectionMode ('contain' vs 'overlap') in lasso tool (#11862)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-08-10 13:09:43 +02:00
David Luzarandrafa cf212b2f33 feat(editor): eyedropper fixes and improvements (#11859)
Co-authored-by: rafa <rafaondjango@gmail.com>
2026-08-08 23:17:26 +02:00
David Luzar 4872083c04 feat(editor): bucketfill cursor + eyedropper support (#11849) 2026-08-07 14:07:53 +02:00
David Luzar ea74787447 fix(app): remove ViewportStatusFrame debug (#11848) 2026-08-07 12:53:21 +02:00
David Luzar 219571a718 fix(editor): viewportportStatusFrame UI fixes (#11841) 2026-08-06 22:01:48 +02:00
David Luzar e4ab626739 feat(packages/excalidraw): add props.className (#11839) 2026-08-05 20:10:18 +02:00
David Luzar 1e22618ab2 fix(packages/excalidraw): viewportStatusFrame label offset on mobile (#11840) 2026-08-05 20:00:18 +02:00
David Luzar 74789584be feat(packages/excalidraw): support ViewportStatusFrame label onClick (#11838) 2026-08-05 18:28:52 +02:00
David Luzar e33a1c0e85 feat(packages/excalidraw): do not dedupe collaborators (#11835) 2026-08-05 14:20:18 +02:00
David Luzar 5e375b4331 fix(editor): do not flicker bbox when drag-creating line midpoint (#11834) 2026-08-05 10:52:10 +02:00
David LuzarandTamas Lakatos 85270fcc8d feat(packages/excalidraw): ViewportStatusFrame & factor out user-follow state (#11819)
Co-authored-by: Tamas Lakatos <tamas@excalidraw.com>
2026-08-05 00:01:20 +02:00
yanrin 46c42a6cb0 fix(editor): initialize selectedLinearElement after pasting arrows (#11803) 2026-08-04 23:38:15 +02:00
David Luzar c9c96f619c fix(editor): normalize container + bound text order on restore (#11827) 2026-08-04 23:32:15 +02:00
David Luzar 1da120e914 fix(editor): include custom tools in pointer capture (#11826) 2026-08-04 19:39:21 +02:00
nihaarsirikonda ab0255f21e fix(editor): prevent crash when editing arrow with bound text (#11814) 2026-08-04 16:31:37 +02:00
792ea3a06c feat(editor): bucket fill (#11799)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-08-04 16:25:16 +02:00
David Luzar f1aa8f86a9 feat(editor): disable arrowhead toggle via dblclick (#11822) 2026-08-04 16:25:00 +02:00
Excalidraw Botanddwelle 39103bd33a chore(editor): Update translations from Crowdin (#11813)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-08-03 21:48:06 +02:00
cyforkkandMark Tolmacs 786ab266ff feat(editor): Error message for invalid hex color input (#11782)
---------
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-30 13:30:20 +02:00
Adam 69d4c346cc fix(editor): prevent esc from exiting fullscreen on macos when modal open (#11789) 2026-07-30 07:47:42 +00:00
David Luzar 1acf66edab feat(editor): bind text to hovered arrow endpoint (#11777) 2026-07-28 18:12:33 +02:00
David Luzar 7c0b329576 fix(editor): increase autoresize handle threshold until it hides (#11773) 2026-07-27 23:04:03 +02:00
David Luzar b2e81e38a6 feat(editor): autoshape text + line improvements (#11752) 2026-07-24 16:10:00 +02:00
f179f7ffd2 feat(editor): draw to shape (auto-detection) (#9313)
* Initial commit

* Segments detection

* Remove aspect ratio

* Refactor

* Fix tests

* Toggle shapeSnap

* Fix linting

* debug: live preview

* Rebase fixes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Bind to elements (no hover background)

* Shape closed

* Fix rad calc + rename segments

* Comments

* Fix binding highlighting + lints

* Rename to convert to shape

* fix: Binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Refactor spline simplification

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove unused doBoundsIntersect

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Restore type signature

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Propose new button shapes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove unused types

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Icon shapes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Element cloning

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Element cloning

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Remove unused

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Shape settings in recognition mode

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Actions

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrowheads for freedraw

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Draw shape tool instead of freedraw

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Keyboard shortcut visibility

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Unique cursor for draw shape

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Remove selection

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Shape selection

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Improve shape recognition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Further refine shape recognition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Preview

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Add hint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Refined shape recognition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Less ellipse more rectanguloid

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Refined arrow recognition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Refinerecognition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Lower linear threshold

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Rectangle head arrow

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: More arrowhead sizes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow remains an arrow

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Draw line between elements converts to arrow

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* remove pendingDrawShapeElement

* fix: Arrow start point binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Different arrow endpoint heuristic

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Point type

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Various small fixes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Avoid recompute

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Type issues

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* test(editor): cover the drawShape (snap-to-shape) tool

The drawShape tool had no dedicated tests, which let master's toolbar /
active-tool rewrite silently break it during the merge: actionFinalize
reverted to the selection tool after every sketch, because the drawShape
flow finalizes without a `newElement` and so failed master's new
`&& element` guard.

Covers the behavior the merge had to preserve:
- sketch recognition (rectangle, ellipse) and the too-small no-op
- the tool stays active after finalizing, so shapes can be sketched
  consecutively (regression guard for the above)
- activation via the S shortcut and the extra-tools dropdown
- the tool's own cursor

Points are fed into the trail directly rather than through pointermove,
which is rAF-throttled and would drop all but one point per frame — the
same approach lasso.test.tsx uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: Add test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Breakout from App.tsx

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Shortcut is now Shift+X

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: No selecting arrows for shape recog

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Double history

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: PCA + feature extraction recognition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* reduce and normalize min-size gates

* never select newElement when shape recog tool active

* simplify lifecycle & remove double finalize

* make arrows bindings orbit

* funnel though actionFinalize & fix two unrelated cleanup cases

* use crosshair cursor

* show strokeWidth

* switch icons for drawShape & extraTools

* dedupe getBoundsFromPoints

* remove unused helpers

* jsdoc cleanup

* dynamic trail size

* improve rectangle recognition

* disallow linears to deviate too much from a straight line

* put under freedraw tool on tablet/mobile

* tweak label

* rename `drawShape` -> `autoshape`

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:42:42 +02:00
Márk Tolmács e6ae6bf057 fix(editor): Diamond hit test shortcut (#11631)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-22 22:39:16 +02:00
Márk Tolmács 91b78903ab fix(editor): Dangling deleted bound text (#11733)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-22 22:37:35 +02:00
Márk Tolmács a3b90897b5 fix(editor): Lost focus point on transition to inside-inside (#10964)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-22 13:03:45 +02:00
samirananddwelle 53732f08f4 fix(editor): prevent eyedropper preview from overflowing viewport (#11722)
* fix(editor): prevent eyedropper preview from overflowing viewport

* generalize

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-07-21 22:07:15 +00:00
samiran 5cf547650b fix(editor): make Help button tooltip consistent with toolbar (#11681) 2026-07-18 00:17:39 +02:00
David Luzar a1d9b16b0d fix(editor): show scroll-back-to-content button on mobile (#11680) 2026-07-17 16:22:43 +02:00
David Luzar 65aa577e39 fix(editor): fix AnimationController scheduling race conds (#11678) 2026-07-16 20:52:06 +02:00
David Luzar 93dd51060a feat(packages/excalidraw): add props.ui.enabled.zoom/scrollBackToContent (#11677) 2026-07-16 19:50:19 +02:00
David Luzar 344878b0b5 ci(repo): skip docs & example deployments in CI (#11674) 2026-07-16 14:36:11 +00:00
David Luzar 374716304a fix(editor): improve UX around locked animations (#11671) 2026-07-16 13:30:13 +02:00
David Luzar 5ca083436d feat(packages/excalidraw): add props.activeTool and related (#11665) 2026-07-15 11:46:08 +02:00
David Luzar acb48c3f45 fix(editor): make embeddable ignore higher-z-index non-framelike elements (#11662) 2026-07-14 18:18:21 +02:00
David LuzarandClaude Fable 5 2423819ee0 feat(packages/excalidraw): interaction and UI control (#11605)
* feat(packages/excalidraw): add `props.interaction` and `props.ui`

* fix: prevent browser zoom when interaction disabled

* changelog

* clean up interactive state on disable and consolidate `viewMode` and interaction state updates

* render UI rendered to `children` even if editor-exported

* support `allowed.navigation` and `allowed.embeds/interactiveContent`

* change naming `allowed` -> `enabled`

* changelog: add PR links

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* clear flowchart sessions via consolidated `app.flowchart` after master merge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor getters -> methods

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 16:50:53 +02:00
Márk Tolmács 5776e1cc66 fix(editor): should not select deleted bound text (#11660)
fix: Incorrect selection of deleted bound text

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-14 13:19:19 +02:00
Christopher TangonanandMark Tolmacs e9c856d262 chore(editor): streamline getSelectedElementsByGroup (#11636)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-13 19:38:52 +02:00
Márk Tolmács 082b2eeb1e fix(editor): Hit cache soundness (#11630)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-13 17:53:01 +02:00
minato32andMark Tolmacs d331bb49fc fix(editor): avoid overlap of flowchart children (#11532)
Closes #8518. Fixes the issue when adding a new child to a flowchart node that already has children (after the first creation session was committed), the new child could land on top of, or misaligned with, an existing sibling.
---------
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-13 16:52:19 +02:00
David Luzar 339d3c3733 feat(editor): toolbar / active tool rewrites (#11649) 2026-07-13 00:30:09 +02:00
Márk Tolmácsanddwelle ed7c0c1d7d chore(editor): Refactor Actions for clarity (#11632)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-11 17:22:35 +02:00
Márk Tolmácsanddwelle 3b9e1c07f5 fix(editor): Dragged arrow endpoint ignore grid and angle locks (#10972)
---------
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-07-10 11:19:44 +02:00
Christopher Tangonan adf9631997 fix(editor): keep multi-line bound text and container centered during alt resize (#11480) 2026-07-09 13:02:19 +02:00
David Luzar f790bf7abd feat(editor): tweak sidebar promo style (#11181) 2026-07-09 11:41:13 +02:00
David LuzarandDataVoyager-Aditya 063e0256f5 fix(editor): arrow label rendering fixes and perf improvements (#11637)
Co-authored-by: DataVoyager-Aditya <rajthakuraditya63@gmail.com>
2026-07-08 23:13:01 +02:00
Márk Tolmács fbff832424 fix(editor): Fix binding.test.ts type mismatch (#11633)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-08 20:20:01 +02:00
Márk Tolmács 8462bb81fb fix(editor): Very small bindables can blow up fixedPoint -> position ratio (#11607)
---
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-08 12:20:30 +02:00
Excalidraw Botanddwelle ba0387d186 chore(editor): Update translations from Crowdin (#10731)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-07-07 22:37:30 +02:00
Augusto XavierandMark Tolmacs dd8296af18 fix(editor): narrow NonDeleted type to isDeleted: false (#11470)
---------
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-07 16:22:31 +02:00
David Luzar b2a729c400 feat(editor): dblclick to toggle arrowhead (#11615) 2026-07-07 14:22:21 +02:00
David Luzar aaa14e9df6 feat(editor): show cursor hint when switching arrow types (#11608) 2026-07-07 12:55:32 +02:00
David Luzar 9357f98a9b fix(editor): optimize UI rendering during animation (#11604) 2026-07-05 18:44:26 +02:00
David Luzar 5b0f5a4c2d fix(editor): revert viewport animation back to 500ms (#11600) 2026-07-05 09:45:59 +02:00
51ca8abde4 feat(packages/excalidraw): viewport locking (#11554)
Co-authored-by: barnabasmolnar <barnabas@excalidraw.com>
Co-authored-by: Arnost Pleskot <arnostpleskot@gmail.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-07-03 22:57:47 +02:00
AnviandChristopher Tangonan cce5001aaa fix(editor): keep eraser shortcut consistent with tool switch rules (#11571)
---------
Co-authored-by: Christopher Tangonan <ctangonan123@gmail.com>
2026-06-29 10:04:08 +02:00
Dany Valverde Caldasanddwelle 4ce70b815e fix(editor): ensure canvas is cleared when background color is invalid to prevent ghosting (#11458)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-06-26 22:59:17 +02:00
David Luzar c070c8ffa6 fix(editor): improve scroll animation interpolation (#11562) 2026-06-26 09:56:12 +02:00
Márk Tolmácsanddwelle e4c70cb6c6 feat(editor): AnimationController for scrollToContent (#11553)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-06-26 09:35:32 +02:00
minato32anddwelle 20f694d110 fix(editor): prevent freeze from extremely large line elements (#11556)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-06-25 20:06:02 +02:00
David Luzar 2a82821ec5 fix(editor): tweak freedraw settings and tablet UI/UX (#11551)
* fix(editor): align constant freedraw stroke width more with generic shapes

* reduce streamline for non-mouse

* do not change `currentItemStrokeVariability` unintentionally

* render penMode button under compact styles panel on tablets

* show stroke variability as standalone button in compact actions menu

* improve toolbar clicking UX with pen

* change streamline defaults

* change to `variable` stroke if toggling penMode for the first time
2026-06-24 16:59:10 +02:00
Márk Tolmácsanddwelle 070df27e4d fix(editor): Modern TS require imports from rootDir (#11552)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-06-24 10:52:56 +00:00
Márk Tolmácsanddwelle cd514d72d6 feat(editor): LaserPointer based freedraw (#11507)
Introduces constant width freedraw mode, keeping the original variable mode as default.

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-06-24 09:16:22 +02:00
zsviczian 0642e72cfa fix(editor): Arrows with text are rendered blurry in PNG export with larger scale (#11492) 2026-06-22 18:22:26 +02:00
David Luzar 28a9b1711d test(repo): less noisy test output (#11505) 2026-06-15 18:19:20 +02:00
Márk Tolmácsanddwelle 1cb9fff569 fix(editor): Double history (#11445)
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-06-15 18:19:08 +02:00
David LuzarandDiego Mateos 069982606d fix(editor): update element.frameId on frame change (#11490)
Co-authored-by: Diego Mateos <dimateos@ucm.es>
2026-06-13 22:39:04 +02:00
David Luzar b324a85ab1 fix(editor): elements duplicated when moving frame children (#11485)
* fix(editor): elements duplicated when moving frame children

* fix(editor): accumulate reorders across frames in bring-to-front/back

* add invalid-order tests

* fix: make sure moved indices are within range

* add length/duplicate elements guards
2026-06-13 20:32:45 +02:00
a83ac48853 fix(editor): recalculate roundness type when switching shape types (#11473)
When converting between generic shapes, the roundness type was only
recalculated when the target type was a diamond. Converting a diamond
(which uses the proportional radius algorithm) back to a rectangle kept
the proportional radius, so rectangles ended up rendering with overly
round corners.

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Maruthan G <maruthang4@gmail.com>
Co-authored-by: Sivram <withsivram@gmail.com>
Co-authored-by: Jai Kumar Dewani <jai.dewani.99@gmail.com>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-06-11 19:17:15 +02:00
KrishhnaT 0cf56b19c7 test(editor): add unit tests for BinaryHeap (#11419) 2026-06-10 17:12:42 +02:00
KrishhnaT 61fe15a51d fix(editor): cardinal direction arrows with label are invisible in exported SVG (#11441)
fix: arrows with bound text labels missing from SVG export

Axis-aligned (horizontal/vertical) arrows with a bound label vanished from SVG exports. The label-gap mask defaulted to objectBoundingBox units, whose region collapses to zero area for a zero-size bounding box, masking out the whole line. Pin the mask to userSpaceOnUse with an explicit user-space region (the coords already used by the visible rect).

Fixes #11439
2026-06-07 19:19:44 +02:00
David Luzar 647a264a48 feat(packages/excalidraw): consolidate theme state handling (#11453) 2026-06-06 18:18:06 +02:00
Márk Tolmács b6d80e4256 fix(packages/excalidraw): consolidate bounds checks (#11275)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-06-04 19:27:25 +02:00
David Luzar 3372149277 feat(packages/excalidraw): export applyDarkModeFilter and simplify (#11429) 2026-06-01 15:43:45 +02:00
David Luzar c08be69618 ci(docker): fix docker dep bundling and pin remaining actions (#11398)
* docker: use slim alpine image to remove bundling deps in Docker image

* pin remaining yml actions

* use lockfile

* remove pulling
2026-05-25 14:39:21 +02:00
Márk Tolmácsanddwelle b42b1a193d fix(editor): excessive battery usage (#11377)
* fix: Excessive battery usage

* chore: Refactor Eraser, Lasso and Laser pointer to use AnimationController

* fix: Last laser trail element is not removed from SVG
---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-05-24 16:12:17 +02:00
David Luzar f6d85bc80f feat(packages/excalidraw): expose image size config and optimize resizing (#11332) 2026-05-14 22:45:03 +02:00
David Luzar 0457ac9063 fix(editor): handle invalid points on restore (#11321)
* fix: handle invalid points on restore

* move isValidPoint to @excalidraw/math
2026-05-12 18:44:49 +02:00
Praneeth Kodumagulla b2b2815954 fix(editor): prevent duplicate lasso toolbar item (#11286) 2026-05-06 23:10:50 +02:00
d992c10bc1 fix(app): resolve app-jotai import path in LocalData (#11290)
Co-authored-by: chulkin-mdb <oleksandr.chulkin@mongodb.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-06 23:03:10 +02:00
melvinandDavid Luzar 091b9053a3 fix(editor): enabled ctrl+y redo shortcut on linux and mac (#11179)
* enabled ctrl+y redo shortcut on linux and mac

* Apply suggestion from @dwelle

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2026-05-06 22:59:28 +02:00
David Luzar 97274a74b2 ci(repo): enforce scopes (#11292) 2026-05-06 19:54:30 +02:00
Márk Tolmácsanddwelle c59fb8dcbc fix: LocalStorage is empty object on node@25 which breaks tests (#11240)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-05-06 17:42:36 +02:00
Márk Tolmácsanddwelle 7f56cc0cf3 fix: Speculative fixes for arrow invariant failures (#11241)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-05-06 14:39:45 +00:00
974b338b7e fix: Group selection (#11234)
* fix: Group selection

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Frames and overlap

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Remove unnecessary crust

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* revert unused Set signature

* skip ignored elements from group condition

when wrap-mode selecting grouped elements, we should not require to select those we ignore (bound elements or locked ones), else it's impossible to select grouped text containers

unclear whether locked elements should also be excluded - but it feels like a good heuristic on the whole

* apply exclusion

* simplify

* feat: return all elements in group for overlap selection

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-05-06 15:27:34 +02:00
David Luzar d2557474e2 fix(editor): fix target element index when creating/adding elements to frames (#11257) 2026-05-05 21:35:32 +02:00
Márk Tolmácsanddwelle 3004c642da fix: Fractional index validation (#11258)
- Vendored fractional-indexing and converted to TypeScript
- Stricter index format validation in fractional-indexing
- Added format validation to fractional index validation

---
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-05-04 11:37:17 +02:00
Márk Tolmács 2dfcc6f0ce chore: Remove startBoundElement from state (#11264) 2026-05-02 16:36:42 +02:00
David Luzar 3f5fdec04e fix: group defragmenting (#11269) 2026-05-02 15:50:58 +02:00
David Luzar 278cd35772 feat(editor): scale video embeddables based on zoom (#11251) 2026-04-28 21:49:40 +02:00
David Luzar 43fa4b5602 fix: frame selection and membership (#11250)
fix: element fully overlapping frame
2026-04-28 18:23:10 +02:00
2e1a529c67 fix(editor): remove extremely large arrows on restore (#11235)
* fix: Temp fix for elbow arrow at restore

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Speculative fixes to avoid Infinity

Co-authored-by: Copilot <copilot@github.com>
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* validate/remove arrow size after point normalization & move binding repairs back

* validate even simple arrows

* remove x/y check

* remove duplicate constant

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-04-25 12:03:50 +02:00
David Luzar b1c6bfcf40 chore(docker): bump node (#11208) 2026-04-20 22:07:00 +02:00
Tom LouveauandDavid Luzar 1caec99b29 docs: change twitter label by X (#11158)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2026-04-13 10:30:58 +02:00
Nand Gopal Sharma e18c1dd213 Fix typo in Discord badge URL parameter (#11096) 2026-04-02 10:37:02 +02:00
David LuzarandMark Tolmacs d9e8a33aa4 feat(editor): implement overlap box selection (#11053)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-04-01 18:41:11 +02:00
dagecko 4be4cc0ed0 fix: pin 9 actions to commit SHA (#11075) 2026-03-30 16:49:27 +02:00
David Luzar 4a5c9e990c fix(editor): ensure font picker font names are not quoted (#11036) 2026-03-25 17:56:48 +01:00
c09e170bdd feat(editor): deselect on esc (#11035)
Co-authored-by: Jawahar <jawahars_16@live.in>
Co-authored-by: Andrew Aquino <dawneraq@gmail.com>
2026-03-25 17:14:24 +01:00
David Luzar c1082923ee feat(editor): support mermaid staate diagrams (#11031) 2026-03-24 20:20:28 +01:00
KundanandMark Tolmacs 1c292e4936 fix(math): correctly validate second point in isLineSegment (#11007)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2026-03-24 19:01:08 +01:00
Márk Tolmács d6f0f34fe9 fix: Rotated rounded arrow center point (#10962) 2026-03-23 15:54:59 +01:00
Márk Tolmácsanddwelle 75789f620d fix: Other endpoint is not immediately updated on midpoint snap (#10933)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-03-23 14:54:44 +00:00
David Luzar a9ca16eb42 chore(packages/excalidraw): export Fonts helper class (#11008) 2026-03-21 22:44:27 +01:00
Márk Tolmács 987173b52f fix: Arrow point index Out-of-Bounds (#10922)
* fix: Make OOB not fatal

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: More conservative temp arrow state update

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Capture condition variables in binding restoration failure

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-03-21 19:26:47 +01:00
David Luzar 81ab857a6f feat(editor): various text related improvements (#10979) 2026-03-19 16:00:58 +01:00
David Luzar e8b4620a96 feat(editor): put caret at pointer coords when clicking on selected text element (#10970) 2026-03-18 19:14:44 +01:00
David Luzar 2b0e4c9623 fix(editor): remove leftover debug code path (#10954) 2026-03-14 13:12:48 +01:00
David Luzar c9ba7f839c chore(editor): bump @excalidraw/mermaid-to-excalidraw@2.1.1 (#10944) 2026-03-12 17:32:13 +01:00
David Luzar b4ce7c713b fix(editor): arrowhead picker overflowing viewport (#10943) 2026-03-12 16:08:46 +01:00
David Luzar 816c81c12e feat(editor): ERD arrowheads and diagrams (#10940) 2026-03-11 22:00:31 +01:00
David Luzar 92d25446d6 feat(packages/excalidraw): tweak and expose more API around state and lifecycle (#10939) 2026-03-11 16:51:03 +01:00
David Luzar e73a5b0116 docs(packages/excalidraw): improve readme (#10932) 2026-03-11 09:49:12 +01:00
David Luzar 21dd1cfacc feat(packages/excalidraw): state tracking, api hook, and others (#10870) 2026-03-08 23:15:18 +01:00
David Luzar fa1f7d9f22 feat(packages/excalidraw): export throttleRAF (#10912) 2026-03-07 12:05:33 +01:00
David Luzar 3d8c12fba4 fix(editor): do not conditionally disable midpoint snapping menu preference (#10906) 2026-03-06 20:44:57 +01:00
757dfeb6ad fix(editor): call throttleRAF with lastArgs and remove trailing (#10905)
Co-authored-by: Varun Chawla <varun_6april@hotmail.com>
Co-authored-by: aziamimoh <aziamimoh@users.noreply.github.com>
Co-authored-by: pgzcoa <pgzcoa@users.noreply.github.com>
Co-authored-by: TinaZhang24 <TinaZhang24@users.noreply.github.com>
2026-03-06 20:40:36 +01:00
David Luzar a0e93b6040 feat(editor): sync export theme with ui theme (#10903) 2026-03-06 18:37:28 +01:00
Hendrik Horstmann 499e9d64a5 fix: dropdownMenu item badge position (#10895) 2026-03-06 08:41:49 +00:00
David Luzar c1dbbdf678 feat(editor): mermaid code editor & improve parsing (#10897) 2026-03-05 18:52:41 +01:00
David Luzar 47c254216b fix(editor): disable snap-to-midpoint menu item when arrow-binding disabled (#10885) 2026-03-04 16:48:33 +01:00
Hendrik Horstmann d1cff91b75 fix: spacing in the left menu (#10880) 2026-03-03 22:11:30 +00:00
Márk Tolmácsanddwelle 437595fa65 feat: Arrow binding is a preference (#10839)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-03-03 21:55:40 +00:00
David Luzar 60b275880d feat(editor): support radar chart and multiple series for other chart types (#10824) 2026-02-26 16:13:15 +01:00
zsviczian cae9d2bcbd fix: "hand" tool active after exiting view mode if laser point was used (#10841) 2026-02-26 12:55:13 +01:00
David Luzar 2874f9e48c fix(editor): simplify and fix midpoint highlighting (#10832) 2026-02-24 21:11:46 +01:00
Márk Tolmács 0b3a5e7cc4 fix: Multi-point arrow bound point update (#10831)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-02-24 13:32:44 +01:00
Márk Tolmácsanddwelle 7ea3229e17 fix(editor): Hardened fixed point and bound element parsing in restore (#10816)
* fix: Reinforce fixedPoint restore

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Even more hardened boundElement in restore

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Extract constant

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Remove superfluous check from restore

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove non-needed code path

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: More robust number test for fixedPoint parsing

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Validate bindings for element being parsed

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* unrelated type safety

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-02-23 19:22:27 +00:00
David Luzar b0404b10b6 chore(debug): add debug.logChanged() and make easy to import (#10828) 2026-02-23 20:20:37 +01:00
David Luzar eb959128ac feat(editor): allow laser-pointing in view mode (#10802)
* feat(editor): allow laser pointing in view mode

* feat: allow switching between laser/hand in view mode

* fix lint

* factor out to utils

* fix: only handle primary clicks with the selection/laser tools
2026-02-20 22:49:46 +01:00
4c3d037f9c feat(editor): allow clicking on links and embeds with laser tool (#10797)
Co-authored-by: Anvi <anvikudaraya417@gmail.com>
Co-authored-by: Chris Tangonan <ctangonan123@gmail.com>
2026-02-19 11:45:01 +01:00
Márk Tolmácsanddwelle 5852d0d410 fix: Arrow overlap arrow behavior (#10732)
* fix(arrow): Overlap arrow behavior

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat(editor): reduce binding gap (#10739)

* feat(editor): reduce binding gap to 7px

* feat(editor): reduce binding gap to 5px

* feat(editor): reduce binding gap to 3px

* go back to 5px

* update tests

* feat: Simplified update bind points

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Remove non-needed export

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix. Possessed arrows #1

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Focus point projection stabilization

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Remove arrow stability hack

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Unbound other endpoint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat(editor): visualize binding midpoints + support for simple arrows (#10611)

* feat: Force exact center focus point

When the projected point is close to center snap it to the exact center.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Snap to center around side mid point.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Trigger CI

* fix: Midpoint outline focus point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Dragging existing arrow reset focus point on outline

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Midpoint indicator

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Rotated mid points

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: No hole

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Cache hits and scene lookups

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Consider hit threshold and inside override too

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Increase outline midpoint sticky distance

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Don't show midpoint indicator when no snapping is possible

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Indicate lock-in

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove Map caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: incorrect threshold

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: threshold setting

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hit caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Simple arrow mid point selection inconsistency

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: cache override

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Precise know dragging with midpoint refactor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fear: Frame support

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Crossing arrow won't trigger mid point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow creation point highlight

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Restore types & tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Restore restore.ts

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: restore.ts

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Elbow arrows reliably highlight center point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Highlight point ordering

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Bind with focus point across shape

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

* fix: Midpoint and binding alignment

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Indicator color

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: More knob tuning

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Radius

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* simplify point indicators

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Snapshots

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Target point selection

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove non-needed change

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Try again removing non-needed modification

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Inside-inside binding arrow endpoint drag trigger focus point editor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Area based edge case

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Overlapping new arrow jump

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2026-02-17 14:10:05 +01:00
Christopher Tangonan c1e00c44f5 fix: convert ArrowheadNoneIcon to component matching arrowhead icon pattern (#10789) 2026-02-17 07:57:09 +01:00
Márk Tolmács ffcb67b21f fix: Inside-inside bound arrow endpoint drag trigger focus point editor (#10771)
fix: Inside-inside binding arrow endpoint drag trigger focus point editor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-02-16 22:25:28 +01:00
David Luzar 46ddd60948 feat(editor): support embedding google drive videos (#10788) 2026-02-16 22:19:11 +01:00
Christopher Tangonan 89a9badc27 fix: update arrowhead property defaultValue handling (#10778) 2026-02-13 22:33:52 +01:00
zsviczian 8b3e149db6 fix: hex8 regression #10578 (#10773) 2026-02-12 22:03:13 +01:00
Márk Tolmácsanddwelle a70417f23f feat(editor): visualize binding midpoints + support for simple arrows (#10611)
* feat: Force exact center focus point

When the projected point is close to center snap it to the exact center.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Snap to center around side mid point.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Trigger CI

* fix: Midpoint outline focus point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Dragging existing arrow reset focus point on outline

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Midpoint indicator

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Rotated mid points

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: No hole

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Cache hits and scene lookups

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Consider hit threshold and inside override too

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Increase outline midpoint sticky distance

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Don't show midpoint indicator when no snapping is possible

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Indicate lock-in

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove Map caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: incorrect threshold

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: threshold setting

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hit caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Simple arrow mid point selection inconsistency

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: cache override

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Precise know dragging with midpoint refactor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fear: Frame support

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Crossing arrow won't trigger mid point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow creation point highlight

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Restore types & tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Restore restore.ts

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: restore.ts

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Elbow arrows reliably highlight center point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Highlight point ordering

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Bind with focus point across shape

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

* fix: Midpoint and binding alignment

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Indicator color

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: More knob tuning

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Radius

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* simplify point indicators

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-02-11 11:06:27 +01:00
Márk TolmácsandDavid Luzar 1c8e8bb0f3 fix: Arrow update when cloned (#10747)
* fix: Arrow update when cloned

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat(editor): reduce binding gap (#10739)

* feat(editor): reduce binding gap to 7px

* feat(editor): reduce binding gap to 5px

* feat(editor): reduce binding gap to 3px

* go back to 5px

* update tests

* chore: Refactor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Align focus points

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2026-02-09 13:03:05 +01:00
David Luzar 3a5ef4020d feat(app): add preferences to main menu (#10760)
feat(editor): add preferences to main menu
2026-02-08 23:30:45 +01:00
David LuzarandBarnabás Molnár 063533aede feat(packages/excalidraw): support nested dropdown menu (#10749)
Co-authored-by: Barnabás Molnár <38168628+barnabasmolnar@users.noreply.github.com>
2026-02-08 22:27:34 +01:00
Márk Tolmács b43260d97b fix: Other binding converted from fixed to orbit unconditionally (#10748)
* fix: Other binding converted from fixed to orbit unconditionally

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: New arrow creation

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt point setting on inside binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Initial arrow creation with Alt

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-02-06 15:01:07 +01:00
David Luzar 83d3943cd0 feat(editor): reduce binding gap (#10739)
* feat(editor): reduce binding gap to 7px

* feat(editor): reduce binding gap to 5px

* feat(editor): reduce binding gap to 3px

* go back to 5px

* update tests
2026-02-05 12:00:56 +01:00
Márk Tolmács f39ac4a653 fix(editor): On focus drag only update other binding if it's orbit (#10730)
fix(focus): Only update other binding if it's orbit

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-02-02 20:12:56 +01:00
David Luzar 54a9826817 fix(editor): copying to clipboard with no ClipboardEvent (#10729)
* fix(editor): copying to clipboard with no ClipboardEvent

* fix(editor): use green for `success` state of `FilledButton`
2026-02-01 11:06:37 +01:00
David Luzar d29fd62e41 fix(editor): crop editor cursor drift (#10727)
* fix(editor): do not scale cropping editor pointer offsets

* fix lint

* fix more lint

* fix drift related to image canvas scale
2026-02-01 10:45:04 +01:00
David Luzar b57f3e0096 fix(editor): image positioning in crop editor (#10726) 2026-02-01 09:21:30 +01:00
Excalidraw Bot f12ae80ba1 chore: Update translations from Crowdin (#10598)
* New translations en.json (Russian)

* New translations en.json (Vietnamese)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Hungarian)

* New translations en.json (Hungarian)

* New translations en.json (Hindi)

* New translations en.json (Dutch)

* New translations en.json (Dutch)

* New translations en.json (Vietnamese)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Romanian)

* New translations en.json (Vietnamese)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Romanian)

* New translations en.json (Italian)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Vietnamese)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage
2026-01-31 22:12:24 +01:00
David Luzar f7b537a8b1 feat(packages/excalidraw): export CommandPalette (#10724)
feat: export CommandPalette
2026-01-31 20:24:20 +01:00
Yashanddwelle 94364af68f fix: Clarify welcome screen message about browser storage limitations (#10721)
* fix: Clarify welcome screen message about browser storage limitations

* css tweaks

* update snaps

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-01-31 17:15:14 +01:00
zsvicziananddwelle dfa1ce572b fix: SVG Inversion on Safari (#10712)
* invert image on safari

* lint

* Inversion to match theme filter

* cleanup

* Adjust canvas dimensions for device pixel ratio when inverting on Safari

* revert inversion algo & handle darkMode placeholder

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-01-31 16:28:21 +01:00
Márk Tolmácsanddwelle b552c60714 feat: Focus indicator (#10613)
* feat: Focus indicator

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Snapshot update

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Move visualdebug to utils and introduce volume bindable volume visualization

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Move visualdebug to elements

Due to dep circles

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Possible test timeout

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Incorrect hit test point input

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Add fallback when dragged outside of allowed area

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Elbow arrows don't need focus point mgmt

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: End bound indirect fix

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Show indicator when arrow endpoint dragging

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Update bound arrow endpoint at mid-point drag

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Refactor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Curve endpoint intersection

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Outline focus point is reset on existing arrow drag

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Fix lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Dragging focus point off

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Don't show the focus indicator when arrow endpoint is dragged

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Drag area for focus handles

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Focus point size unified

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Size bump for focus knob

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Cache hits and scene lookups

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Consider hit threshold and inside override too

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Other shape switching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* perf: Update tolerance params

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Focus know line width

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: knob offset

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Full overlap

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove Map caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: incorrect threshold

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: threshold setting

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hit caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: cache override

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Snapshots

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Redesigned focus point handling

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Inside-inside mode

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove comment

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Allow focus knob outside the shape

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow endpoint offset

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Focus knob element distance

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Increase iteration on curve intersection calc

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Handle disabled binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt mode

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Nested shape focus rewrite

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt + Ctrl + arrow endpoitn

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hit ordering for focus points

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Focus point visibility

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* dry out renderFocusPointIndicator

* do not higlight point when dragging & make focus point smaller

* optimize retrieval of selectedLinearElement

* move focus highlighting into renderFocusPointIndicator to DRY out and colocate

* remove `disabled` state from focus highlight

* make focus point stroke color less prominent

* fix: No focus point for multi-point arrows

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow edit mode drag focus point release

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* DRY out arrow point-like drag

* move `focus.ts` to `arrows/focus.ts`

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-01-31 15:08:28 +01:00
Márk Tolmács 216afc3625 fix: Coherent stats binding (#10718)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-30 22:20:46 +01:00
David Luzar 802cde3501 feat: support customizing TTD welcome screen (#10719)
* feat: support customizing TTD welcome screen

* remove debug
2026-01-30 16:28:36 +01:00
David LuzarandAshutosh Kumar f5cf81ce42 feat: prevent pasting excalidraw into textarea & paste element text if avail (#10710)
* feat: prevent pasting excalidraw json into textarea & paste element text if avail

Co-authored-by: Ashutosh Kumar <130897584+codeaashu@users.noreply.github.com>

* fix FF

---------

Co-authored-by: Ashutosh Kumar <130897584+codeaashu@users.noreply.github.com>
2026-01-28 22:35:39 +01:00
Márk Tolmács 6a891365b9 fix: Arrow endpoint offset (#10706)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-26 20:55:08 +01:00
Márk Tolmács 54fa0c9089 fix: Increase iteration on curve intersection calc (#10707)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-26 20:52:13 +01:00
Márk Tolmács dfdd994dbb perf: Cache hits in collision detection (#10648)
* feat: Cache hits and scene lookups

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Consider hit threshold and inside override too

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove Map caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: incorrect threshold

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: threshold setting

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hit caching

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: cache override

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-26 20:50:44 +01:00
Christopher TangonanandAnvi Kudaraya 28691e14b1 fix: Bound arrow elements for distribute and wyswig updates (#10702)
* fix: update distributeElements to updateBoundElements

Co-authored-by: Anvi Kudaraya <anvikudaraya417@gmail.com>

* fix: apply updateBoundElements when bound text extends past container height

Co-authored-by: Anvi Kudaraya <anvikudaraya417@gmail.com>

---------

Co-authored-by: Anvi Kudaraya <anvikudaraya417@gmail.com>
2026-01-26 18:52:13 +01:00
zsviczian 60759d314d fix: Regression - invert SVGs in Dark Mode (#10695)
* initial implementation

* lint

* removed separate getThemeFilterValue function from renderElement

* removed BinaryFileData changes

* filter instead of css filter
2026-01-24 13:00:14 +01:00
zsviczian d5e37cda81 fix: set link icon opacity based on element opacity (#10693)
* Set global alpha for drawing elements

* Adjust opacity calculation for canvas rendering

* lint
2026-01-24 12:16:48 +01:00
David Luzar 6135548534 fix: rerender TTD preview on message remove (#10681) 2026-01-21 14:07:48 +01:00
David Luzar acf54c6f38 fix: dropdownMenu item badge position (#10682) 2026-01-21 14:07:36 +01:00
David Luzar 84a309d669 fix: ttd 429 error handling (#10680) 2026-01-21 14:07:23 +01:00
David Luzar 3c8e893cab fix: keep input focus during generation (#10679) 2026-01-20 20:14:28 +01:00
Márk Tolmács 9ba0f5dbc9 fix: Arrow drag start in bindable area jumps across bindable (#10676)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-20 12:38:40 +01:00
David Luzar 60ab14c2f6 fix: fail gracefully during restore (#10673)
* fix: fail gracefully during restore

* tests
2026-01-20 12:33:22 +01:00
Márk Tolmács 0988ecfef4 fix: Angle-locked line history (#10677)
* fix: Line history

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: More conservative condition

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-20 12:11:58 +01:00
David Luzar 1f47d61e8c chore: bump node in tests.yml (#10670) 2026-01-19 09:23:50 +01:00
David Luzar 9d760336d1 feat: reduce max tablet MQ size (#10669)
* feat: reduce max tablet MQ size

* replace UIOptions.formFactor with getFormFactor
2026-01-18 21:55:14 +01:00
David Luzar 0443511954 fix: tweak error display in ttd (#10668)
fix: better error handling for ttd
2026-01-17 18:17:04 +01:00
David Luzar 5a73b9a363 refactor: change TTD persistence to iDB (#10662)
refactor: change ttd persistence to iDB
2026-01-16 15:10:00 +01:00
David Luzar 24a6941861 fix: retries and related UX fixes (#10657)
* fix: retries and related UX fixes

* bump

* fix package version

* yarn.lock

* naming and clearer type

* ignore test flake
2026-01-16 09:52:18 +01:00
Tamas Landdwelle a0b98a944f feat: TextToDiagram v2 (#10530)
* feat: introducing TextToDiagram v2 feature

* fix: eslint issue

* debug mermaid bundle size

* tests: covering the utils

* fix: import mock chunks dynamically to shrink the bundle size

* fix: removing replay feature

* fix: removing unused prop

* fix: bumping workbox cache limit

* snapshots + yarn.lock

* bump mermaid-to-excalidraw@2 and split into its own chunk

* bump node@20

* css tweaks

* move files around & rewrite stream chunk schema

* random naming & file structure refactor + some tweaks

* fix preview theme

* support custom warning renderer

* label and css fix

* fix and rwrite 429 handling

* fix label

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-01-15 19:15:41 +01:00
Márk Tolmács 6ebf52279d fix: Elbow end point disconnect (#10646)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-15 15:27:00 +01:00
Márk Tolmács 3b97f5a10c feat: move visualdebug to utils and introduce volume bindable visualization (#10617)
* feat: Move visualdebug to utils and introduce volume bindable volume visualization

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Move visualdebug to elements

Due to dep circles

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2026-01-12 15:24:50 +01:00
Márk Tolmácsanddwelle da59205846 fix: Curve endpoint intersection (#10640)
* fix: Curve endpoint intersection

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix debug

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-01-12 15:02:56 +01:00
David Luzar b9a255407f refactor: update SCSS syntax & remove open-color dep (#10633) 2026-01-10 18:15:14 +01:00
David Luzar cc6c29c0b9 fix: update wysiwyg color on theme change (#10618) 2026-01-07 12:55:33 +01:00
David Luzar 87faa5d3da fix: sentry CI worfklow (#10610) 2026-01-06 11:39:45 +01:00
David Luzar c158187f20 fix: grid color in dark mode (#10600) 2026-01-04 17:51:53 +01:00
David Luzar 63e1148280 feat: stop using CSS filters for dark mode (static canvas) (#10578)
* feat: stop using CSS filters for dark mode (static canvas)

* fix comment

* remove conditional dark mode export

* make shape cache theme-aware

* refactor

* refactor

* fixes and notes
2026-01-04 15:16:35 +01:00
Excalidraw Bot b5fc873323 chore: Update translations from Crowdin (#10453)
* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Dutch)

* Auto commit: Calculate translation coverage

* New translations en.json (Dutch)

* Auto commit: Calculate translation coverage

* New translations en.json (Dutch)

* New translations en.json (Dutch)

* New translations en.json (Spanish)

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovak)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovak)

* Auto commit: Calculate translation coverage

* New translations en.json (Vietnamese)

* New translations en.json (Vietnamese)
2026-01-04 15:15:46 +01:00
David Luzar 6c908553a9 fix: reconciliation of server updates & refactor restore (#10597) 2026-01-04 15:13:38 +01:00
kish dizonanddwelle 0586fc138c feat: add qr code to live session share dialog. (#10588)
* add qr code to live session share dialog

* use uqr

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2026-01-02 11:08:27 +01:00
Ryan Dianddwelle e95222ed32 fix: add constants and side methods to packages (#10418)
* fix: add constants and side methods to packages

* add transform to the element package

* lint

* remove dead code

* put transform types back to transform.ts

* fix imports

* fix imports in test

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-12-28 21:53:25 +01:00
Márk Tolmács d87620b239 fix: Circular reference (#10544)
* fix: Circular reference

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Trigger CI

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-12-21 22:14:21 +01:00
Viczián András 7cc31ac64a fix: Context menu paste adding image twice #10542 (#10543)
removed line that was adding image file twice to paste
2025-12-20 06:31:02 +01:00
zsviczian 071b17a217 fix: Embeddables lost stroke color option in element properties after #9996 (#10541)
Add 'embeddable' type to comparisons
2025-12-19 18:23:09 +01:00
Márk Tolmácsanddwelle 859207b8bc fix: Broken bindings during collab (#10537)
* fix: Broken bindings during collab

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* move repair of non-legacy binding outside the migration branch

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-12-17 16:22:24 +01:00
David Luzar becaabfa0f chore: bump node@20 in ci workflows (#10531) 2025-12-16 19:08:42 +01:00
Márk Tolmácsandzsviczian f06484c6ab fix: Angle snapping around bindable objects incorrectly resolves (#10501)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: zsviczian <viczian.zsolt@gmail.com>
2025-12-15 09:49:46 +00:00
Márk Tolmács bf4c65f483 fix: Turn into inside bind when angle locked (#10479)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-12-09 19:36:28 +01:00
Márk Tolmács 8d18078f5c fix: Box selection of arrows (#10451) 2025-12-03 12:47:30 +01:00
David Luzar d080833f4d chore: bump typescript@5.9.3 (#10431) 2025-12-01 22:37:42 +01:00
Márk Tolmácsanddwelle 451bcac0b7 fix: Ctrl/Alt elbow arrow jumps (#10432)
* fix: Ctrl/Alt elbow arrow jumps

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Trigger build

* style

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-12-01 17:06:08 +00:00
Ethan Olesinski 06f01e11f8 style: remove blue lines (#10425)
remove blue lines
2025-12-01 11:15:29 +00:00
Excalidraw Bot 51ad8951d4 chore: Update translations from Crowdin (#10316)
* New translations en.json (Swedish)

* New translations en.json (Japanese)

* Auto commit: Calculate translation coverage

* New translations en.json (Occitan)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Occitan)

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* New translations en.json (Japanese)

* New translations en.json (Occitan)

* New translations en.json (Russian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Italian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage
2025-12-01 12:10:13 +01:00
zsviczian 7497a08270 fix: Frame and Frame Element binding highlight offset (#10423)
fix binding highlight offset
2025-12-01 12:08:55 +01:00
David Luzar 210dc85c8c fix: do not finalize multi-point lines if binding not enabled (#10410)
* fix: do not finalize multi-point lines if binding not enabled

* refactor
2025-11-26 21:02:36 +01:00
Márk Tolmácsanddwelle 019ce4c52c fix: Corner jumping & hints (#10403)
* fix: Corner jumping

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hints

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: No corner avoidance for simple arrows

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* show alt/cmd hint when creating/moving arrow point any time

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-26 11:00:33 +00:00
Márk Tolmácsanddwelle c141960ada feat: Non-elbow arrow snapping and behavior changes (#9670)
* Fixed point binding for simple arrows

Tests added

Fix binding

Remove unneeded params

Unfinished simple arrow avoidance

Fix newly created jumping arrow when gets outside

Do not apply the jumping logic to elbow arrows for new elements

Existing arrows now jump out

Type updates to support fixed binding for simple arrows

Fix crash for elbow arrws in mutateElement()

Refactored simple arrow creation

Updating tests

No confirm threshold when inside biding range

Fix multi-point arrow grid off

Make elbow arrows respect grids

Unbind arrow if bound and moved at shaft of arrow key

Fix binding test

Fix drag unbind when the bound element is in the selection

Do not move mid point for simple arrows bound on both ends

Add test for mobing mid points for simple arrows when bound on the same element on both ends

Fix linear editor bug when both midpoint and endpoint is moved

Fix all point multipoint arrow highlight and binding

Arrow dragging gets a little drag to avoid accidental unbinding

Fixed point binding for simple arrows when the arrow doesn't point to the element

Fix binding disabled use-case triggering arrow editor

Timed binding mode change for simple arrows

Apply fixes

Remove code to unbind on drag

Update simple arrow fixed point when arrow is dragged or moved by arrow keys

Binding highlight fixes

Change bind mode timeout logic

Fix tests

Add Alt bindMode switch

 No dragging of arrows when bound, similar to elbow

Fix timeout not taking effect immediately

Bumop z-index for arrows when dragged

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Only transparent bindables allow binding fallthrough

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix point click array creation interaction with fixed point binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Restrict new behavior to arrows only

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Allow binding inside images

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix already existing fixed binding retention

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Refactor and implement fixed point binding for unfilled elements

Restore drag

Removed point binding

Binding code refactor

Added centered focus point

Binding & focus point debug

Add invariants to check binding integrity in elements

Binding fixes

Small refactors

Completely rewritten binding

Include point updates after binding update

Fix point updates when endpoint dragged and opposite endpoint orbits

centered focus point only for new arrows

Make z-index arrow reorder on bind

Turn off inside binding mode after leaving a shape

Remove invariants from debug

feat: expose `applyTo` options, don't commit empty text element (#9744)

* Expose applyTo options, skip re-draw for empty text

* Don't commit empty text elements

test: added test file for distribute (#9754)

z-index update

Bind mode on precise binding

Fix binding to inside element

Fix initial arrow not following cursor (white dot)

Fix elbow arrow

Fix z-index so it works on hover

Fix fixed angle orbiting

Move point click arrow creation over to common strategy

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Add binding strategy for drag arrow creation

Fix elbow arrow

Fix point handles

Snap to center

Fix transparent shape binding

Internal arrow creation fix

Fix point binding

Fix selection bug

Fix new arrow focus point

Images now always bind inside

Flashing arrow creation on binding band

Add watchState debug method to window.h

Fix debug canvas crash

Remove non-needed bind mode

Fix restore

No keyboard movement when bound

Add actionFinalize when arrow in edit mode

Add drag to the Stats panel when bound arrow is moved

Further simplify curve tracking

Add typing to action register()

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix point at finalize

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix type errors

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

New arrow binding rules

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix cyclical dep

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix jiggly arrows

Fix jiggly arrow x2

Long inside-other binding

Click-click binding

Fix arrows

Performance

[PERF] Replace in-place Jacobian derivation with analytical version

Different approach to inside binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fixes

Fix inconsistent arrow start jump out

Change how images are bound to on new arrow creation

Lower timeout

Small insurance fix

Fix curve test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

No center focus point

90% inside center binding

Fixing tests

fix: Elbow arrow fixes

fix: More arrow fixes

Do not trigger arrow binding for linear elements

fix: Linear elements

fix: Refactor actionFinalize for linear

Binding tests updated

fix: Jump when cursor not moved

fix: history tests

Fix history snapshot

Fix undo issue

fix(eraser): Remove binding from the other element

fix(tests): Update tests

chore: Attempt filtering new set state

Fix excessive history recording

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix all tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

fix(transform): Fix group resize and rotate

fix(binding): Harmonize binding param usage

fix: Center focus point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

chore: Trigger build

Remove binding gap

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Binding highlight refactor

fix: Refactored timeout bind mode handling

fix: Center when orbiting

feat: Color change on highlight

Fix orbit binding highlight

fix: hiding arrow

Fix arrow binding

Fix arrow drag selection logic

Binding highlight is now hot pink

Change inside binding logic for start point

Render focus point in debug mode

Fix snap to center

Fix actionFinalize for new arrow creation

fix: snapToCenter()

80% by length

fix: attempt at fixing the dancing arrows

feat: No center snap when start is not bound

Fix centering for existing arrows

tweak binding highlight color

change `appState.suggestedBindings` -> `suggestedBinding` & remove unused code

Refactor delayed bind mode change

Binding highlight rotation support + image support

fix(highlight): Overdraw fixes

feat: Do not allow drag bound arrow closer to the shape than dragging distance

feat: Stroke width adaptive fixed binding distance

chore: More point dragging centralization

New element behavior

Refactor dragging

Fix incorrect highlight sizing

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix delayed bind mode for multiElement arrows

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix multi-point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fix elbow arrows

Simplify state

Small positional fixes

Fix jiggly arrows

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Fixes for arrow dragging

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

Elbow arrow fixes

Highlight fixes

Fix elbow arrow binding

Frame highlight

Fix elbow mid-point binding

Fix binding suggestion for disabled binding state

Implement Alt

Remove debug

* CHange new arrow creation

* fix: allow inside binding via timeout if arrow has no startBinding

* fix: Delete invariant violation with arrows

* fix: Deleted arrow causes problems

* fix: Dragging issues

* fix: Dragging fix 2

* fix: Disable drag drag when arrow is bound

* fix: Multipoint arrow opposite point movement

* fix: Ctrl+Alt precedence

* feat: Alt inside start binding mode change

* Fix multipoint arrow orbit

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow start inside binding switch

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: New arrow never binds inside

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Small refactor

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Multi-point arrows and linears

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Nested shapes handling

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Overlap behavior

* Alt unbind fix

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Existing arrow nested bindable

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Binding suggestions

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Circular dep

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: snapshots

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt immediate update

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Laxing on invariants

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: New highlight overdraws arrow

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Image binding rule changed

* Trigger Rebuild

* fix:Highlight flicker

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Fully nested shapes

* fix: Tune nested shape binding

* fix: Size-based orbit jump-in

* fix: Binding highlight stroke on sharp bindables

* fix: Nested shape binding

* fix: history

* fix:More precise element nesting check

* feat:Add tolerance to shape nesting detection

* fix: Reverse

* fix:Change center binding to circular

* ignore invisible elements when binding

* feat: Center point with more precise highlight outlines

* fix:Arrow tool hover stuck highlight

* fix:More stroke width for highlight

* POC: highlight center on hover

* tweak binding highlight width

* render highlight on the outside

* fix: Locked elbow arrow creation

* update hints

* reduce timeout

* handle overlap when both elements the same size

* tweak highlight stroke width some more

* fix:Add intersection padding

* fix: New arrow start orbit when nested binds on the end

* fix: Update history snapshot

* feat: Allow inside binding for new arrows in nested cases

* chore: Logic for measurement

* fix: Locked tool + arrow

* feat: Remove center binding

* fix: Jump arrow inside it gets visially too short

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore:Basic interactive canvas animation re-render trigger for highlights

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat:Highlight animations

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix:Refactored and fixed highlight animation

* fix:Poisoned arrow

* fix Arrow edit mode selection

* fix:Tool lock binding behavior restored

* fix:Overlap inside binding

* fix:Animated binding highlight

* alt anims + increase timeout to 700

* tweak animation some more + remove countdown

* fix: False bind timeout indicator

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: better file normalization (#10024)

* feat: better file normalization

* fix lint

* fix png detection

* optimize

* fix type

* fix: increase rejection delay for opening files with legacy api (#8961)

* Increased input change interval to 1000 ms to fix IOS 18 file opening issue

* increase more

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* feat: library search (#9903)

* feat(utils): add support for search input type in isWritableElement

* feat(i18n): add search text

* feat(cmdp+lib): add search functionality for command pallete and lib menu items

* chore: fix formats, and whitespaces

* fix: opt to optimal code changes

* chore: fix for linting

* focus input on mount

* tweak placeholder

* design and UX changes

* tweak item hover/active/seletected states

* unrelated: move publish button above delete/clear to keep it more stable

* esc to clear search input / close sidebar

* refactor command pallete library stuff

* make library commands bigger

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* fix: Allow already inside bound arrows to continue inside binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: No angle lock over bindable elements

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Center binding on SHIFT key

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Fix ghost start binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* FEAT: No binding to frame cutout

* feat: Bind to frame when frame-bound object hidden part is approached

* fix: revert preferred selection to box once you switch to `full` UI (#10160)

* fix: mobile UI and other fixes (#10177)

* remove legacy openMenu=shape state and unused actions

* close menus/popups in applicable cases when opening a different one

* split ui z-indexes to account prefer different overlap

* make top canvas area clickable on mobile

* make mobile main menu closable by clicking outside and reduce width

* offset picker popups from viewport border on mobile

* reduce items gap in mobile main menu

* show top picks for canvas bg colors in all ui modes

* fix menu separator visibility on mobile

* fix command palette items not being filtered

* fix: Increase transform handle offset (#10180)

* fix: Increase transform handle offset

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Temporarily disable transform handles for linear elements on mobile and tablets

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Linear hidden resize

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* disable mobielOrTablet linear element bbox completely

* fix: Test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* fix: context menu getting covered (#10199)

* do not show z-index actions on mobile or tablet

* fix: context menu getting covered

* fix lint

* fix style popup getting covered

* put contextmenu z-index above sidebar

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* feat: More prominent keyboard shortcuts in hints (#10057)

* Initial

* Memoize

* Styling

* Use double angle brackets for keyboard shortcuts

* Use rem in gap

* Use an existing function for substituting tags in a string

* Revert styling

* Avoid unique key warnings

* Styling

* getTransChildren -> nodesFromTextWithTags

* Use height and padding instead of padding only

* Initial new idea

* WIP shortcut substitutions

* Use simple regex for parsing shortcuts

* Use single shortcut for combos

* Use kbd instead of span

* shortcutFromKeyString -> getTaggedShortcutKey

* Bug fix

* FlowChart -> Flowchart

* memo is useless here

* Trigger CI

* Translate in getShortcutKey

* More normalized shortcuts

* improve shortcut normalization and replacement & support multi-key tagged shortcuts

* fix regex

* tweak css

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* fix: small tweaks to shortcut hints (#10214)

* fix: Test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Bind mode

* feat: Support special key shortcut highlight

* fix: Lint

* fix: Remove non-needed function

* fix: Skip frame cutout for hover, but keep shape for binding

* fix: Lint

* fix: Restore removal of deleted elements on restore

* fix: Inside-inside during drag

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Arrow vanishes when orbit binding to the same element

* feat: Feature flag support

* Simplified binding

* fix: Diamond corner binding

* feat: Binding highlight band re-added

* feat: Settings menu

* fix: Same shape binding

* fix: set radix PropertiesPopover collision boundary (#10221)

* Set collision boundary

* Calculate collisionPadding dynamically based on container

* Add appState offsetTop and offsetLeft to padding calculation.

Refactor collisionPadding calculation to use app state offsets.

* Update PropertiesPopover.tsx

* popover positioning relative to container

* fix: prevent wrap text in a container to only text that are not bound to a container (#10250)

* fix: only enable wrap text in a container when at least one text element selected is unbound

* Trigger Rebuild

---------

Co-authored-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Uncap the nodejs version requirement (#10238)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* refactor: single source of truths with editor interface (#10178)

* refactor device to editor interface and derive styles panel

* allow host app to control form factor and ui mode

* add editor interface event listener

* put new props inside UIOptions

* refactor: move related apis into one file

* expose getFormFactor

* privatize the setting of desktop mode and fix snapshots

* refactor and fix test

* remove unimplemented code

* export getFormFactor()

* replace `getFormFactor` with `getEditorInterface`

* remove dead & useless

* comment

* fix ts

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* chore: Update translations from Crowdin (#7429)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* New translations en.json (German)

* New translations en.json (Chinese Simplified)

* New translations en.json (Polish)

* New translations en.json (Romanian)

* New translations en.json (Korean)

* New translations en.json (Chinese Traditional)

* New translations en.json (Hebrew)

* New translations en.json (Hebrew)

* New translations en.json (Slovak)

* New translations en.json (Slovak)

* New translations en.json (Hungarian)

* New translations en.json (Hungarian)

* New translations en.json (Slovak)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Korean)

* New translations en.json (Chinese Traditional)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Romanian)

* New translations en.json (German)

* New translations en.json (Slovenian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Spanish)

* New translations en.json (Russian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Turkish)

* New translations en.json (Slovak)

* New translations en.json (Slovak)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Korean)

* New translations en.json (Chinese Traditional)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (German)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Chinese Simplified)

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* New translations en.json (Slovak)

* New translations en.json (German)

* New translations en.json (Portuguese)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* Auto commit: Calculate translation coverage

* New translations en.json (Turkish)

* Auto commit: Calculate translation coverage

* New translations en.json (Turkish)

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* fix: mobile view ui issues (#10284)

* hide zen mode when formFactor = phone

* tool bar fixes: icon and width

* view mode

* fix lint

* add exit-view-mode button

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>

* chore: Update snaps

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Blue highlight

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Diagonal binding point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove settings

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Jump other binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Hovered arrow mode highlight

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* feat: Alt does not snap

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Check debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt precise positioning

* fix: Jump out to orbit for new arrows when dragged outside

* fix: New arrow preserved projection

* chore: Remove debug

* chore: Introduce different debug color for orbit and other binding modes

* fix: Restore arrow start point when self binding

* fix: Turn of start jump-out

* fix: Tests

* fix: Select the first possible altBindPoint

* fix: Random projection

* fix: Use last point for existing arrows

* fix: Preserve alternate orbit focus point during drag

* fix: Lint

* fix: Update tests

* fix: Elbow arrow direction at binding

* binding gap and distance and binding highlight tweaks

* chore: Naming refactors

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt-duplication copied elements placement (#10152)

* feat: Animation support (#10042)

* fix: banner url (#10315)

* feat: Animation support (#10042)

* fix: Merge discrepancy

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* chore: Remove non-needed code

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Trigger build

* chore: Remove hint for V1

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* shorten focus point diagonal helpers to fix corner binding cases

* fix: Tests

* fix: Multi-point arrow closeness fallback

* fix: Finalize multipoint arrow on binding area click

* fix: Finalize arrow now truly finalzes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Point click arrow creation jumping to orbit

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Alt+drag movement block

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Trigger build

* feat: hide point highlight when dragging

* feat: hide bbox when dragging points

* revert binding gap increase for elbow arrows

* reset selectionLinearElement on tool change

* chore: Remove debug

* feat: Better restore for bindings

* use elementsMap instead of array when passing to restoreElement

* fix: Arrow angle reset

* fix: Arrow angle

* Arrow angle support

* fix trashing cached canvases in `LinearElementEditor.getElementAbsoluteCoords`

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-25 15:46:02 +01:00
zsviczian d7e63e66a7 fix: rounded left and top clipped image export to SVG (#10387)
Remove clipPathUnits attribute from clipPath

Removed setting of clipPathUnits attribute for clipPath element.
2025-11-24 23:34:42 +01:00
David Luzar b660478164 fix: prevent translation of excalidraw container (#10389) 2025-11-22 16:16:30 +01:00
gothamsiddanddwelle 37882c66cb fix: canvas panning stops when hovering over frame title (#10340) (#10351)
* fix: canvas panning stops when hovering over frame title (#10340)

* improve

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-18 09:24:02 +00:00
David Luzar 7f66e1fe89 fix: banner url (#10315) 2025-11-11 11:29:44 +01:00
Márk Tolmács 2b4540225d feat: Animation support (#10042) 2025-11-10 22:31:23 +01:00
Márk Tolmács dc2f25c14a fix: Alt-duplication copied elements placement (#10152) 2025-11-10 22:31:08 +01:00
Márk Tolmács 8fb16669ab feat: Add binding visual debug (#10222) 2025-11-10 12:08:57 +01:00
David Luzar f2600fe3e8 fix: close floating sidebar on main menu open (#10295) 2025-11-06 22:39:39 +01:00
zsvicziananddwelle 95ddc66339 fix: add toggle pen-mode to MobileMenu (#10293)
* add toggle pen mode to MobileMenu

* swap buttons

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-06 21:33:11 +00:00
David Luzar 5bcd8280c9 feat: add comments/presi eplus promos for discoveribility (#10294) 2025-11-06 21:35:14 +01:00
Ryan Dianddwelle c99e81678b fix: mobile view ui issues (#10284)
* hide zen mode when formFactor = phone

* tool bar fixes: icon and width

* view mode

* fix lint

* add exit-view-mode button

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-04 11:20:55 +00:00
Excalidraw Botanddwelle d1f39823f1 chore: Update translations from Crowdin (#7429)
* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* New translations en.json (German)

* New translations en.json (Chinese Simplified)

* New translations en.json (Polish)

* New translations en.json (Romanian)

* New translations en.json (Korean)

* New translations en.json (Chinese Traditional)

* New translations en.json (Hebrew)

* New translations en.json (Hebrew)

* New translations en.json (Slovak)

* New translations en.json (Slovak)

* New translations en.json (Hungarian)

* New translations en.json (Hungarian)

* New translations en.json (Slovak)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Korean)

* New translations en.json (Chinese Traditional)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Romanian)

* New translations en.json (German)

* New translations en.json (Slovenian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Spanish)

* New translations en.json (Russian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Turkish)

* New translations en.json (Slovak)

* New translations en.json (Slovak)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Korean)

* New translations en.json (Chinese Traditional)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (German)

* New translations en.json (Russian)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Chinese Simplified)

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* New translations en.json (Slovak)

* New translations en.json (German)

* New translations en.json (Portuguese)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Uzbek)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (German, Switzerland)

* New translations en.json (Bengali, India)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* Auto commit: Calculate translation coverage

* New translations en.json (Turkish)

* Auto commit: Calculate translation coverage

* New translations en.json (Turkish)

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-03 23:36:08 +01:00
Ryan Dianddwelle 47cbb5b6fb refactor: single source of truths with editor interface (#10178)
* refactor device to editor interface and derive styles panel

* allow host app to control form factor and ui mode

* add editor interface event listener

* put new props inside UIOptions

* refactor: move related apis into one file

* expose getFormFactor

* privatize the setting of desktop mode and fix snapshots

* refactor and fix test

* remove unimplemented code

* export getFormFactor()

* replace `getFormFactor` with `getEditorInterface`

* remove dead & useless

* comment

* fix ts

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-03 23:34:17 +01:00
Márk Tolmácsanddwelle 8fd970320e chore: Uncap the nodejs version requirement (#10238)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-11-03 17:30:35 +01:00
Christopher TangonanandMark Tolmacs 8d8f696628 fix: prevent wrap text in a container to only text that are not bound to a container (#10250)
* fix: only enable wrap text in a container when at least one text element selected is unbound

* Trigger Rebuild

---------

Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-10-26 23:00:17 +01:00
zsviczian 19b3dc658a fix: set radix PropertiesPopover collision boundary (#10221)
* Set collision boundary

* Calculate collisionPadding dynamically based on container

* Add appState offsetTop and offsetLeft to padding calculation.

Refactor collisionPadding calculation to use app state offsets.

* Update PropertiesPopover.tsx

* popover positioning relative to container
2025-10-22 23:29:39 +02:00
David Luzar 4e0441eeb4 fix: small tweaks to shortcut hints (#10214) 2025-10-20 16:57:40 +02:00
Omar Brikaaanddwelle 8013eb5e16 feat: More prominent keyboard shortcuts in hints (#10057)
* Initial

* Memoize

* Styling

* Use double angle brackets for keyboard shortcuts

* Use rem in gap

* Use an existing function for substituting tags in a string

* Revert styling

* Avoid unique key warnings

* Styling

* getTransChildren -> nodesFromTextWithTags

* Use height and padding instead of padding only

* Initial new idea

* WIP shortcut substitutions

* Use simple regex for parsing shortcuts

* Use single shortcut for combos

* Use kbd instead of span

* shortcutFromKeyString -> getTaggedShortcutKey

* Bug fix

* FlowChart -> Flowchart

* memo is useless here

* Trigger CI

* Translate in getShortcutKey

* More normalized shortcuts

* improve shortcut normalization and replacement & support multi-key tagged shortcuts

* fix regex

* tweak css

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-10-20 16:09:20 +02:00
Ryan Dianddwelle 725412ebd3 fix: context menu getting covered (#10199)
* do not show z-index actions on mobile or tablet

* fix: context menu getting covered

* fix lint

* fix style popup getting covered

* put contextmenu z-index above sidebar

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-10-20 11:56:55 +02:00
Márk Tolmácsanddwelle 7da176ff7d fix: Increase transform handle offset (#10180)
* fix: Increase transform handle offset

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Temporarily disable transform handles for linear elements on mobile and tablets

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Linear hidden resize

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* disable mobielOrTablet linear element bbox completely

* fix: Test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* fix: Lint

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-10-15 21:16:20 +02:00
David Luzar 5fffc4743f fix: mobile UI and other fixes (#10177)
* remove legacy openMenu=shape state and unused actions

* close menus/popups in applicable cases when opening a different one

* split ui z-indexes to account prefer different overlap

* make top canvas area clickable on mobile

* make mobile main menu closable by clicking outside and reduce width

* offset picker popups from viewport border on mobile

* reduce items gap in mobile main menu

* show top picks for canvas bg colors in all ui modes

* fix menu separator visibility on mobile

* fix command palette items not being filtered
2025-10-14 16:34:49 +02:00
David Luzar 8608d7b2e0 fix: revert preferred selection to box once you switch to full UI (#10160) 2025-10-12 23:33:02 +02:00
Omar Brikaa 19b03b4ca9 fix: remove redundant selectionStart/End resetting that causes scroll-reset bug on firefox (#8263)
Remove redundant selectionStart/End resetting that causes scroll-reset bug on firefox
2025-10-10 18:12:08 +02:00
416e8b3e42 feat: new mobile layout (#9996)
* compact bottom toolbar

* put menu trigger to top left

* add popup to switch between grouped tool types

* add a dedicated mobile toolbar

* update position for mobile

* fix active tool type

* add mobile mode as well

* mobile actions

* remove refactored popups

* excali logo mobile

* include mobile

* update mobile menu layout

* move selection and deletion back to right

* do not fill eraser

* fix styling

* fix active styling

* bigger buttons, smaller gaps

* fix other tools not opened

* fix: Style panel persistence and restore

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* move hidden action btns to extra popover

* fix dropdown overlapping with welcome screen

* replace custom popup with popover

* improve button styles

* swapping redo and delete

* always show undo & redo and improve styling

* change background

* toolbar styles

* no any

* persist perferred selection tool and align tablet as well

* add a renderTopLeftUI to props

* tweak border and bg

* show combined properties only when using suitable tools

* fix preferred tool

* new stroke icon

* hide color picker hot keys

* init preferred tool based on device

* fix main menu sizing

* fix welcome screen offset

* put text before image

* disable call highlight on buttons

* fix renderTopLeftUI

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-10-09 23:48:31 +02:00
David Espinoza 98e0cd9078 build: Docker compose version removed (#10074) 2025-10-05 14:48:54 +02:00
Akibur Rahman f3c16a600d fix: text to diagram translation update issue on language update (#10016) 2025-10-02 16:47:26 +02:00
Emilanddwelle 835eb8d2fd fix: display error message when local storage quota is exceeded (#9961)
* fix: display error message when local storage quota is exceeded

* add danger alert instead of toast

* tweak text

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-30 23:54:43 +02:00
zsvicziananddwelle fde796a7a0 feat: Make naming of library items discoverable (#10041)
* updated library relevant strings

* fix: detect name changes

* clarify hashing function

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-30 18:38:10 +00:00
Yago Dórea 7c41944856 fix: small improvement on binary heap implementation (#9992) 2025-09-30 17:09:20 +02:00
Omar Eltomyanddwelle f1b097ad06 fix: support bidirectional shift+click selection in library items (#10034)
* fix: support bidirectional shift+click selection in library items

- Enable bottom-up multi-selection (previously only top-down worked)
- Use Math.min/max to handle selection range in both directions
- Maintains existing behavior for preserving non-contiguous selections
- Fixes issue where shift+clicking items above last selected item failed

* improve deselection behavior

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-29 11:46:42 +00:00
David Luzar 9fcbbe0d27 fix: library search UI fixes/tweaks (#10032)
* fix library icon height in command palette

* add clear button when no results
2025-09-29 12:06:17 +02:00
Archie Senguptaanddwelle ec070911b8 feat: library search (#9903)
* feat(utils): add support for search input type in isWritableElement

* feat(i18n): add search text

* feat(cmdp+lib): add search functionality for command pallete and lib menu items

* chore: fix formats, and whitespaces

* fix: opt to optimal code changes

* chore: fix for linting

* focus input on mount

* tweak placeholder

* design and UX changes

* tweak item hover/active/seletected states

* unrelated: move publish button above delete/clear to keep it more stable

* esc to clear search input / close sidebar

* refactor command pallete library stuff

* make library commands bigger

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-28 22:16:28 +02:00
Davide Wietlisbachanddwelle dcdeb2be57 fix: increase rejection delay for opening files with legacy api (#8961)
* Increased input change interval to 1000 ms to fix IOS 18 file opening issue

* increase more

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-26 16:30:23 +02:00
David Luzar a8acc8212d feat: better file normalization (#10024)
* feat: better file normalization

* fix lint

* fix png detection

* optimize

* fix type
2025-09-25 22:26:58 +02:00
Márk Tolmács a89a03c66c fix: Arrow eraser precision arrow selection (#10006) 2025-09-24 20:28:41 +02:00
Márk Tolmács e32836f799 fix: Use analytical Jacobian for curve intersection testing (#10007) 2025-09-24 19:33:20 +02:00
Márk Tolmács 06c40006db fix: Elbow arrow routing issue with diamonds and ellipses (#10021) 2025-09-24 19:22:32 +02:00
91c7748c3d fix: added normalization to images added with the image tool to prevent MIME-mismatches (#10018)
* fix: fixed a bug where a MIME-mismatch in an image would cause an error to update cache

* fix: fixed a bug where a MIME-mismatch in an image would cause an error to update cache

* normalize inside insertImages()

---------

Co-authored-by: Mårten Mossberg <marmo607@student.liu.se>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-24 16:30:50 +00:00
David Luzar f738b74791 fix: reintroduce height-based mobile query detection (#10020) 2025-09-24 18:17:39 +02:00
Omar BrikaaandDavid Luzar 00ae455873 fix: Remove local elements when there is room data during startCollaboration (#9786)
* Remove local elements when there is room data

* Update excalidraw-app/collab/Collab.tsx

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-09-23 22:18:41 +00:00
ericvannunenanddwelle 06c5ea94d3 fix: Race conditions when adding many library items (#10013)
* Fix for race condition when adding many library items

* Remove unused import

* Replace any with LibraryItem type

* Fix comments on pr

* Fix build errors

* Fix hoisted variable

* new mime type

* duplicate before passing down to be sure

* lint

* fix tests

* Remove unused import

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-23 23:47:03 +02:00
Márk Tolmács f55ecb96cc fix: Mobile arrow point drag broken (#9998)
* fix: Mobile bound arrow point drag broken

* fix:Check real point
2025-09-19 19:41:03 +02:00
David Luzar a6a32b9b29 fix: align MQ breakpoints and always use editor dimensions (#9991)
* fix: align MQ breakpoints and always use editor dimensions

* naming

* update snapshots
2025-09-17 07:57:10 +00:00
Márk Tolmács ac0d3059dc fix: Use the right polygon enclosure test (#9979) 2025-09-15 10:07:37 +02:00
Christopher TangonanandMárk Tolmács 1161f1b8ba fix: eraser can handle dots without regressing prior performance improvements (#9946)
Co-authored-by: Márk Tolmács <mark@lazycat.hu>
2025-09-14 11:33:43 +00:00
Ryan Dianddwelle 204e06b77b feat: compact layout for tablets (#9910)
* feat: allow the hiding of top picks

* feat: allow the hiding of default fonts

* refactor: rename to compactMode

* feat: introduce layout (incomplete)

* tweak icons

* do not show border

* lint

* add isTouchMobile to device

* add isTouchMobile to device

* refactor to use showCompactSidebar instead

* hide library label in compact

* fix icon color in dark theme

* fix library and share btns getting hidden in smaller tablet widths

* update tests

* use a smaller gap between shapes

* proper fix of range

* quicker switching between different popovers

* to not show properties panel at all when editing text

* fix switching between different popovers for texts

* fix popover not closable and font search auto focus

* change properties for a new or editing text

* change icon for more style settings

* use bolt icon for extra actions

* fix breakpoints

* use rem for icon sizes

* fix tests

* improve switching between triggers (incomplete)

* improve trigger switching (complete)

* clean up code

* put compact into app state

* fix button size

* remove redundant PanelComponentProps["compactMode"]

* move fontSize UI on top

* mobile detection (breakpoints incomplete)

* tweak compact mode detection

* rename appState prop & values

* update snapshots

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-12 10:18:31 +10:00
David Luzar 414182f599 fix: normalize file on paste/drop (#9959) 2025-09-10 17:59:02 +02:00
David Luzar b9d27d308e fix: pasting not working in firefox (#9947) 2025-09-06 22:51:23 +02:00
Omar Brikaaanddwelle 3bdaafe4b5 feat: [cont.] support inserting multiple images (#9875)
* feat: support inserting multiple images

* Initial

* handleAppOnDrop, onImageToolbarButtonClick, pasteFromClipboard

* Initial get history working

* insertMultipleImages -> insertImages

* Bug fixes, improvements

* Remove redundant branch

* Refactor addElementsFromMixedContentPaste

* History, drag & drop bug fixes

* Update snapshots

* Remove redundant try-catch

* Refactor pasteFromClipboard

* Plain paste check in mermaid paste

* Move comment

* processClipboardData -> insertClipboardContent

* Redundant variable

* Redundant variable

* Refactor insertImages

* createImagePlaceholder -> newImagePlaceholder

* Get rid of unneeded NEVER schedule, filter out failed images

* Trigger CI

* Position placeholders before initializing

* Don't mutate scene with positionElementsOnGrid, captureUpdate: CaptureUpdateAction.IMMEDIATELY

* Comment

* Move positionOnGrid out of file

* Rename file

* Get rid of generic

* Initial tests

* More asserts, test paste

* Test image tool

* De-duplicate

* Stricter assert, move rest of logic outside of waitFor

* Modify history tests

* De-duplicate update snapshots

* Trigger CI

* Fix package build

* Make setupImageTest more explicit

* Re-introduce generic to use latest placeholder versions

* newElementWith instead of mutateElement to delete failed placeholder

* Insert failed images separately with CaptureUpdateAction.NEVER

* Refactor

* Don't re-order elements

* WIP

* Get rid of 'never' for failed

* refactor type check

* align max file size constant

* make grid padding scale to zoom

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-01 17:31:24 +02:00
Christopher TangonanandA-Mundanilkunathil ae89608985 fix: bound text rotation across alignments (#9914)
Co-authored-by: A-Mundanilkunathil <aaronchackom2002@gmail.com>
2025-08-29 12:31:23 +02:00
Ryan Di 3085f4af81 fix: tighten distance for double tap text creation (#9889) 2025-08-22 18:12:51 +02:00
David LuzarandMark Tolmacs 531f3e5524 fix: restore from invalid fixedSegments & type-safer point updates (#9899)
* fix: restore from invalid fixedSegments & type-safer point updates

* fix: Type updates and throw for invalid point states

---------

Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-08-22 15:45:58 +00:00
David Luzar 90ec2739ae fix: calling toLowerCase on potentially undefined navigator.* values (#9901) 2025-08-22 17:37:16 +02:00
David Luzar f29e9df72d chore: bump mermaid-to-excalidraw to 1.1.3 (#9898) 2025-08-21 20:58:04 +02:00
Marcel Mraz b5ad7ae4e3 fix: even deltas with version & version nonce are valid (#9897) 2025-08-21 16:09:19 +02:00
David Luzar c78e4aab7f chore: tweak title & remove timeout (#9883) 2025-08-20 14:09:20 +02:00
Ryan Dianddwelle b4903a7eab feat: drag, resize, and rotate after selecting in lasso (#9732)
* feat: drag, resize, and rotate after selecting in lasso

* alternative ux: drag with lasso right away

* fix: lasso dragging should snap too

* fix: alt+cmd getting stuck

* test: snapshots

* alternatvie: keep lasso drag to only mobile

* alternative: drag after selection on PCs

* improve mobile dection

* add mobile lasso icon

* add default selection tool

* render according to default selection tool

* return to default selection tool after deletion

* reset to default tool after clearing out the canvas

* return to default tool after eraser toggle

* if default lasso, close lasso toggle

* finalize to default selection tool

* toggle between laser and default selection

* return to default selection tool after creation

* double click to add text when using default selection tool

* set to default selection tool after unlocking tool

* paste to center on touch screen

* switch to default selection tool after pasting

* lint

* fix tests

* show welcome screen when using default selection tool

* fix tests

* fix snapshots

* fix context menu not opening

* prevent potential displacement issue

* prevent element jumping during lasso selection

* fix dragging on mobile

* use same selection icon

* fix alt+cmd lasso getting cut off

* fix: shortcut handling

* lint

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-08-20 00:03:02 +02:00
zsviczian c6f8ef9ad2 fix: Scene deleted after pica image resize failure (#9879)
Revert change in private updateImageCache
2025-08-18 11:45:05 +02:00
Marcel Mraz 2535d73054 feat: apply deltas API (#9869) 2025-08-15 15:25:56 +02:00
David Luzar dda3affcb0 fix: do not strip invisible elements from array (#9844) 2025-08-12 11:56:11 +02:00
Marcel Mraz 54c148f390 fix: text restore & deletion issues (#9853) 2025-08-12 09:27:04 +02:00
zsviczian cc8e490c75 fix: do not auto-add elements to locked frame (#9851)
* Do not return locked frames when filtering for top level frame

* lint

* lint

* lint
2025-08-11 11:52:44 +02:00
Marcel Mraz 9036812b6d fix: editing linear element (#9839) 2025-08-08 09:30:11 +02:00
Marcel Mraz df25de7e68 feat: fix delta apply to issues (#9830) 2025-08-07 15:38:58 +02:00
David Luzar a3763648fe chore: update title (#9814)
* chore: update title

* update meta tag

* lint
2025-08-01 17:17:42 +02:00
Ryan Dianddwelle 178eca5828 fix: add frame clipping to new element canvas (#9794)
* fix: add frame clipping to new element canvas

* cleanup save/restore

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-07-31 12:10:59 +00:00
Ryan Di cb33de25f4 feat: allow a frame to snap to its children (#9795) 2025-07-31 13:58:29 +02:00
Omar Brikaa 37ad85cbaf fix: Fix the root cause of flushSync flickering (#9791)
* More reliable width and height change detection

* Put the dimensions useEffect before the scene render one, just in case
2025-07-27 23:52:07 +02:00
Márk Tolmácsanddwelle d6a934ed19 chore: Remove editingLinearElement (#9771)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-07-24 17:02:21 +02:00
Omar BrikaaandMark Tolmacs 416da62138 fix: multiple line editor bugs (#9760)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-07-24 09:11:04 +02:00
Omar Brikaa f38f381989 fix: Remove flushSync from alt-lasso and elbow dragging (#9734)
* Remove lasso flushSync

* Remove selectedLinearElement flushSync

* Early return
2025-07-23 23:39:16 +02:00
Ryan Dianddwelle e5e07260c6 fix: improve line creation ux on touch screens (#9740)
* fix: awkward point adding and removing on touch device

* feat: move finalize to next to last point

* feat: on touch screen, click would create a default line/arrow

* fix: make default adaptive to zoom

* fix: increase padding to avoid cutoffs

* refactor: simplify

* fix: only use bigger padding when needed

* center arrow horizontally on pointer

* increase min drag distance before we start 2-point-arrow-drag-creating

* do not render 0-width arrow while creating

* dead code

* fix tests

* fix: remove redundant code

* do not enter line editor on creation

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-07-23 18:49:56 +10:00
Christopher Tangonan 8492b144b0 test: added test file for distribute (#9754) 2025-07-17 19:52:16 +02:00
Marcel Mraz e46f038132 feat: expose applyTo options, don't commit empty text element (#9744)
* Expose applyTo options, skip re-draw for empty text

* Don't commit empty text elements
2025-07-17 15:22:32 +02:00
David Luzar 678dff25ed fix: ellipsify MainMenu and CommandPalette items (#9743)
* fix: ellipsify MainMenu and CommandPalette items

* fix lint
2025-07-15 12:59:55 +02:00
Christopher Tangonan 0cfa53b764 fix: aligning and distributing elements and nested groups while editing a group (#9721) 2025-07-15 12:43:42 +02:00
David Luzar cde46793f8 feat: support timestamps for youtube video emebds (#9737) 2025-07-13 19:19:10 +02:00
Aakansha Doshi 2d127f8c22 docs: fix broken update scene button example in docs (#9726)
fix: update scene example in docs
2025-07-08 19:29:44 +05:30
Soham KulkarniandRyan Di 4eadb891f8 fix(toast): prevent toast from re-rendering and resetting timeout Fixes #9714 (#9715)
* Update App.tsx

* fix: lint

---------

Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
2025-07-03 17:07:26 +10:00
Marcel Mraz 258605d1d5 chore: release multiple packages (#9698) 2025-06-30 12:19:15 +02:00
Márk Tolmács c141500400 chore: Relocate visualdebug so ESLint doesn't complain (#9668) 2025-06-18 14:45:51 +02:00
Márk Tolmács 8e27de2cdc fix: Frame dimensions change by stats don't include new elements (#9568) 2025-06-16 14:07:03 +02:00
Márk Tolmács 0a19c93509 fix: Bindings at partially overlapping binding areas (#9536) 2025-06-16 12:30:59 +02:00
Márk Tolmács 958597dfaa chore: Refactor doBoundsIntersect (#9657) 2025-06-16 12:30:42 +02:00
Marcel Mrazanddwelle 058918f8e5 feat: capture images after they initialize (#9643)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-06-15 23:43:14 +02:00
Spawn 3f194918e6 feat: add mulitplatform Docker image support (#9594) 2025-06-15 20:11:37 +02:00
Ryan Di 93c92d13e9 feat: wrap texts from stats panel (#9552) 2025-06-14 13:05:24 +02:00
zsvicziananddwelle 84e96e9393 fix: move doBoundsIntersect from element/src/bounds.ts to common/math/src/utils.ts (#9650)
move doBoundsIntersect to math/utils

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-06-14 11:01:30 +00:00
zsviczian 320af405e9 fix: move elementCenterPoint from common/src/utils.ts to element/src/bounds.ts (#9647)
move elementCenterPoint from utils to bounds.ts
2025-06-14 12:49:22 +02:00
Marcel Mraz 60512f13d5 Fix broken history when eleemnt in update scene are optional 2025-06-14 12:29:58 +02:00
Márk Tolmács f0458cc216 fix: Mid-point for rounded linears are not precisely centered (#9544) 2025-06-12 21:08:37 +02:00
Márk Tolmács 9f3fdf5505 fix: Test hook usage in production code (#9645) 2025-06-12 10:39:50 +02:00
Márk Tolmács f42e1ab64e perf: Improve elbow arrow indirect binding logic (#9624) 2025-06-11 19:15:48 +02:00
Ashwin TemkarandMark Tolmacs 18808481fd fix: set cursor to auto when not hovering a point on linear element (#9642)
* fix: set cursor to auto when not hovering a point on linear element #9628

* Simplify hover test for cursor

* Add back comment

* Fix test for hit testing

---------

Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-06-11 16:52:02 +02:00
Marcel Mrazanddwelle a7b64f02b3 fix: remove image preview on image insertion (#9626)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-06-10 21:31:11 +02:00
Marcel Mraz 0d4abd1ddc fix: add history capture for paste and drop of images and embeds (#9605) 2025-06-10 14:28:16 +02:00
Sachintha Lakmin 9e77373c81 fix: add generic font family fallbacks before Segoe UI Emoji to fix glyph rendering on windows (#9425) 2025-06-10 13:43:39 +02:00
Marcel Mraz d108053351 feat: various delta improvements (#9571) 2025-06-09 09:55:35 +02:00
David Luzar d4e85a9480 feat: use enter to edit line points & update hints (#9630)
feat: use enter to edit line points & update hints
2025-06-07 18:05:20 +02:00
David Luzar 08cd4c4f9a test: improve getTextEditor test helper (#9629)
* test: improve getTextEditor test helper

* fix test
2025-06-07 17:45:37 +02:00
cheapsteranddwelle 469caadb87 fix: prevent double-click to edit/create text scenarios on line (#9597)
* fix : double click on line enables line editor

* fix : prevent double-click to edit/create text
when inside line editor

* refactor: use lineCheck instead of arrowCheck in
doubleClick handler to align with updated logic

* fix: replace negative arrowCheck with lineCheck in
dbl click handler and fix double-click bind text
test in linearElementEditor tests

* clean up test

* simplify check

* add tests

* prevent text editing on dblclick when inside arrow editor

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-06-07 17:08:35 +02:00
Márk Tolmács ca1a4f25e7 feat: Precise hit testing (#9488) 2025-06-07 12:56:32 +02:00
Sujal Gupta 56c05b3099 fix: prevent search menu from opening when dialog is open (#9279) 2025-06-03 15:53:00 +02:00
Aarav Dayal 6c0ff7fc5c docs: added the correct CSS import for nextjs dynamic first import integration example (#9584)
Added the correct CSS import for nextjs dynamic first import integration example

This is with reference to [this](https://github.com/excalidraw/excalidraw/issues/9562)
2025-05-29 22:03:20 +02:00
Muhammad Khuzaima UmairandMark Tolmacs 7cad3645a0 perf: Simplify normalizeRadians function (#9572)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-05-28 15:58:42 +02:00
Márk Tolmács 5921ebc416 fix: Regression in long press context menu closure (#9588) 2025-05-28 13:38:47 +02:00
Márk Tolmács 864353be5f feat: Try to preserve line angle on SHIFT+drag (#9570) 2025-05-27 12:39:45 +02:00
cheapster db2911c6c4 fix: ghost point issue when moving a shape after dragging a point in the line editor (#9530)
fix: ghost point issue when moving a shape after
dragging a point in the line editor
2025-05-26 21:34:41 +02:00
David Luzar fc3e062074 feat: do not break polygon on point delete inside line editor (#9580)
* feat: do not break polygon on point delete inside line editor

* fix: polygon point highlighting when selected point == 0
2025-05-26 16:51:47 +02:00
zsvicziananddwelle 87c87a9fb1 feat: line polygons (#9477)
* Loop Lock/Unlock

* fixed condition. 4 line points are required for the action to be available

* extracted updateLoopLock to improve readability. Removed unnecessary SVG attributes

* lint + added loopLock to restore.ts

* added  loopLock to newElement, updated test snapshots

* lint

* dislocate enpoint when breaking the loop.

* change icon & turn into a state style button

* POC: auto-transform to polygon on bg set

* keep polygon icon constant

* do not split points on de-polygonizing & highlight overlapping points

* rewrite color picker to support no (mixed) colors & fix focus handling

* refactor

* tweak point rendering inside line editor

* do not disable polygon when creating new points via alt

* auto-enable polygon when aligning start/end points

* TBD: remove bg color when disabling polygon

* TBD: only show polygon button for enabled polygons

* fix polygon behavior when adding/removing/moving points within line editor

* convert to polygon when creating line

* labels tweak

* add to command palette

* loopLock -> polygon

* restore `polygon` state on type conversions

* update snapshots

* naming

* break polygon on restore/finalize if invalid & prevent creation

* snapshots

* fix: merge issue and forgotten debug

* snaps

* do not merge points for 3-point lines

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-26 11:14:55 +02:00
Márk Tolmácsanddwelle 4dc205537c feat: Call actionFinalize at the end of arrow creation and drag (#9453)
* First iter

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Restore binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* More actionFinalize

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Additional fixes

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* New elbow arrow is removed if  too small

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Remove very small arrows

* Still allow loops

* Restore tests

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Update history snapshot

* More history snapshot updates

* keep invisible 2-point lines/freedraw elements

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-25 22:28:24 +02:00
David LuzarandMark Tolmacs cc571c4681 chore: init CLAUDE.md (#9563)
* chore: init CLAUDE.md

* Add Copilot instructions

* update gitignore

* simplify

---------

Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-05-25 21:23:40 +02:00
Marcel Mraz 14d512f321 Fix import.meta.env.MODE being undefined in host apps 2025-05-22 15:25:48 +02:00
Marcel Mraz 41c036e1a5 chore: Add DeepWiki badge (#9559) 2025-05-22 13:05:56 +02:00
Márk Tolmács 91d36e9b81 fix: Linear to elbow conversion crash (#9556)
* Fix linear to elbow conversion

* Add invariant check

* Add dev invariant fix

* Add arrowhead
2025-05-22 12:34:15 +02:00
Kamil Wąż 27522110df fix: fix keybindings for arrowheads (#9557) 2025-05-22 09:47:41 +02:00
Ryan Dianddwelle 712f267519 feat: better unlock (#9546)
* change lock label

* feat: add unlock logic for single units on pointer up

* feat: add unlock popup

* fix: linting errors

* style: padding tweaks

* style: remove redundant line

* feat: lock multiple units together

* style: tweak color & position

* feat: add highlight for locked elements

* feat: select groups correctly after unlocking

* test: update snapshots

* fix: lasso from selecting locked elements

* fix: should prevent selecting unlocked elements and setting locked id at the same time

* fix: reset hit locked id immediately when appropriate

* feat: capture locked units in delta

* test: update locking test

* feat: show lock highlight when locking (including undo/redo)

* feat: make locked highlighting consistent

* feat: show correct cursor type when moving over locked elements

* fix history

* remove `lockedUnits.singleUnits`

* tweak button

* do not render UnlockPopup if not locked element selected

* tweak actions

* refactor: simplify type

* refactor: rename type

* refactor: simplify hit element setting & checking

* fix: prefer locked over link

* rename to `activeLockedId`

* refactor: getElementAtPosition takes an optional hitelments array

* fix: avoid setting active locked id after resizing

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-21 21:57:12 +10:00
Márk Tolmács 41a7613dff fix: Elbow arrow conversion labels mixed up (#9547) 2025-05-19 20:35:48 +02:00
David Luzar 95d89a751a refactor: decouple radio button selection from .buttonList wrapper (#9528)
* refactor: decouple radio button selection from `.buttonList`

* fix
2025-05-15 13:22:26 +02:00
Marcel Mraz 6b5fb30d69 fix: unify line height across default fonts (#9513) 2025-05-14 16:02:01 +02:00
Marcel Mraz d92a849038 fix: issues when importing package outside of browser (#9525) 2025-05-14 16:01:43 +02:00
David Luzar 0a534f1bc6 fix: never show snap lines when lasso tool active (#9523) 2025-05-14 22:04:40 +10:00
Ryan DiandDavid Luzar 4ca5f53b1f fix: alt + ctrl lasso selected elements not always kept (#9522)
* fix: alt + ctrl lasso selected elements not always kept

* Update packages/excalidraw/components/App.tsx

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-05-14 22:04:03 +10:00
zsvicziananddwelle f7dcc893ea feat: transparent link background, scale link icon when zooming to below 100% (#9520)
* Do not set link background color, dynamically scale down link icon size with zoom.

* removed unnecessary change

* use canvas bg color & reduce size and stroke width

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-14 13:38:18 +02:00
zsviczian 4dfb8a3f8e feat: allow forms.microsoft.com domain for embeddables (#9519)
* Update embeddable.ts

* no need for same origin

* The form does not load without allow same origin

* automatically add embed=true to link if not present

* fix link check
2025-05-13 19:48:26 +02:00
David Luzar 298812e1d0 fix: improve ctrl+alt lasso selecting (#9514) 2025-05-12 18:09:37 +02:00
Ryan Di 35bb449a4b fix: update cached segments when visible area changes (#9512) 2025-05-12 15:55:36 +02:00
David Luzar c4c064982f feat: show empty active color if no common color (#9506) 2025-05-11 15:07:57 +02:00
David Luzar 51dbd4831b refactor: make element type conversion more generic (#9504)
* feat: add `reduceToCommonValue()` & improve opacity slider

* feat: generalize and simplify type conversion cache

* refactor: change cache from atoms to Map

* feat: always attempt to reuse original fontSize when converting generic types
2025-05-10 20:06:16 +02:00
Marcel Mraz 7e41026812 refactor: export everything from @excalidraw/element, don't import from subpaths (#9466)
* Don't import from subpaths

* Fix tests, move related tests to element
2025-05-09 23:01:33 +02:00
shindi-renuo a8ebe514da Replace tongue emoji with globe emoji (#9489) 2025-05-09 16:59:06 +00:00
Ryan Dianddwelle a30e1b25c6 feat: include frame names in canvas searches (#9484)
* fix frame name clipping on zooming

* include assistant font

* default frame name

* extend search to frame names

* add a simple test

* collpase search match items

* id check out of loop

* fix frame name check

* include focusedId for small perf improvement

* optionally show and hide collapse icon

* update section title

* fix tests

* rename `serverSide` -> `private`

* revert: do not reset zoom on zoom change

* feat: do not close menu on repeated ctrl+f

* remove collapsible

* tweak results CSS

* remove redundant check

* set `appState.searchMatches` to null if empty

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-05-09 18:32:16 +02:00
David Luzar ff2ed5d26a refactor: change movePoints pointUpdates type (#9499) 2025-05-08 16:47:13 +02:00
Narek Malkhasyan e058a08b33 fix: use rimraf instead of rm -rf (#9460) 2025-05-07 14:13:27 +02:00
Narek Malkhasyan a306a909a0 fix: don't scroll page when TTDDialog is opened (#9455) 2025-05-07 13:33:18 +02:00
Marcel Mraz 3dc54a724a feat: add onIncrement API (#9450) 2025-05-06 19:23:02 +02:00
David Luzar a7c61319dd fix: do not translate bound elements twice (#9486) 2025-05-06 13:09:00 +02:00
Narek Malkhasyan cec5232a7a fix: when resizing element, update bound elements after final size of element is determined (#9475) 2025-05-05 12:15:42 +02:00
Márk Tolmács d4f70e9f31 feat: Quarter snap points for diamonds (#9387) 2025-05-05 11:34:40 +02:00
Márk Tolmács e19fd1332a feat: Precise highlights for bindings (#9472) 2025-05-05 09:51:20 +02:00
Hazem KrimiandMark Tolmacs 6e655cdb24 fix: When moving a frame through the stats inputs or drags move along its children (#9433)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-05-02 17:07:17 +02:00
Gowtham SelvarajandDavid Luzar 192c4e7658 docs: added shape cycling shortcut in helper dialog (#9465)
* docs: added shape cycling shortcut in helper dialog

- Document Tab and Shift+Tab usage for shape cycling

* docs: added shape cycling shortcut in helper dialog

* Update packages/excalidraw/components/HelpDialog.tsx

* Update packages/excalidraw/locales/en.json

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-05-01 12:12:45 +02:00
Ryan Dianddwelle 195a743874 feat: switch between basic shapes (#9270)
* feat: switch between basic shapes

* add tab for testing

* style tweaks

* only show hint when a new node is created

* fix panel state

* refactor

* combine captures into one

* keep original font size

* switch multi

* switch different types altogether

* use tab only

* fix font size atom

* do not switch from active tool change

* prefer generic when mixed

* provide an optional direction when shape switching

* adjust panel bg & shadow

* redraw to correctly position text

* remove redundant code

* only tab to switch if focusing on app container

* limit which linear elements can be switched

* add shape switch to command palette

* remove hint

* cache initial panel position

* bend line to elbow if needed

* remove debug logic

* clean switch of arrows using app state

* safe conversion between line, sharp, curved, and elbow

* cache linear when panel shows up

* type safe element conversion

* rename type

* respect initial type when switching between linears

* fix elbow segment indexing

* use latest linear

* merge converted elbow points if too close

* focus on panel after click

* set roudness to null to fix drag points offset for elbows

* remove Mutable

* add arrowBoundToElement check

* make it dependent on one signle state

* unmount when not showing

* simpler types, tidy up code

* can change linear when it's linear + non-generic

* fix popup component lifecycle

* move constant to CLASSES

* DRY out type detection

* file & variable renaming

* refactor

* throw in not-prod instead

* simplify

* semi-fix bindings on `generic` type conversion

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-30 18:07:31 +02:00
David Luzar 4a60fe3d22 fix: remove noreferrer on internal links (#9452)
* fix: remove `noreferrer` on internal links

* fix snaps

* fix lint
2025-04-29 18:45:17 +02:00
Narek Malkhasyan 2a0d15799c fix: when dragging arrow endpoint, update binding only on the dragged side (#9367) 2025-04-25 10:46:58 +02:00
CharitSinghChauhan a18b139a60 fix: laser pointer trail disappearing on pointerup (#9413) (#9427)
* Fix laser pointer trail disappearing on pointerup (#9413)

Previously, the laser pointer trail would disappear as soon as the pointerup event was triggered. This fix delays the trail removal to ensure it persists for a smoother visual experience.

Fixes #9413.

* Remove extra blank lines

Minor formatting cleanup. No functional changes.
2025-04-24 10:05:08 +10:00
Marcel Mraz 1913599594 refactor: remove dependency on the (static) Scene (#9389) 2025-04-23 13:45:08 +02:00
Vedant Mishra debf2ad608 docs: Fix missing verb in Footer component documentation (#9393) 2025-04-20 12:35:38 +02:00
David Luzar 8fb2f70414 fix: scrollbar rendering and improve dragging (#9417)
* fix: scrollbar rendering and improve dragging

* tweak offsets
2025-04-20 12:28:41 +02:00
Jack Walshanddwelle 5fc13e4309 feat: add props.renderScrollbars (#9399)
* Expose renderScrollbars to AppState

* fix: scrollbar rendering should use al renderable elements

* remove `appState.renderScrollbars`

* clean unused

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-19 21:50:44 +00:00
David Luzar b5d60973b7 fix: duplication tests pointer state leaking between tests (#9414)
* fix: duplication tests pointer state leaking between tests

* fix snapshots
2025-04-18 11:11:12 +02:00
David Luzar a5d6939826 fix: keep orig elem in place on alt-duplication (#9403)
* fix: keep orig elem in place on alt-duplication

* clarify comment

* fix: incorrect selection on duplicating labeled containers

* fix: duplicating within group outside frame should remove from group
2025-04-17 16:08:07 +02:00
David Luzar 0cf36d6b30 fix: erasing locked elements (#9400)
* fix: erasing locked elements

* signature tweaks
2025-04-16 10:28:56 +02:00
Ryan Di 58f7d33d80 perf: make eraser great again (#9352)
* perf: make eraser great again

* lint

* refactor and improve perf

* lint
2025-04-15 16:58:45 +02:00
Rubén NorteandDavid Luzar 6fe7de8020 fix: Add DOCTYPE and XML preamble in exported SVG documents (#9386)
* Add DOCTYPE and XML preamble in exported SVG documents

* Update packages/excalidraw/data/index.ts

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-04-14 21:25:18 +02:00
Márk Tolmácsanddwelle 01304aac49 feat: Keep text label horizontal (#9364)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-13 21:21:49 +02:00
jhanma17dev dff69e9191 chore: Element center point util (#9298) 2025-04-09 17:04:51 +02:00
Ryan Di 6fc85022ae fix: lasso selection issues (#9353)
* revert stroke slicing hack for knot

* fix incorrect closing of path

* nonzero enclosure

* lint
2025-04-08 00:50:52 +10:00
Márk Tolmács e48b63a0ae fix: Rounded diamond edge elbow arrow U route (#9349) 2025-04-07 10:43:07 +02:00
David LuzarandMark Tolmacs c2caf78e95 fix: deselected hit element being duplicated + incorrect re-seeding (#9333)
* fix: deselected hit element being duplicated + incorrect re-seeding

* snapshots

* Fix alt-drag binding

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* Add alt-drag bound arrow test

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-04-07 10:41:31 +02:00
ce267aa0d3 feat: lasso selection (#9169)
* lasso without 'real' shape detection

* select a single linear el

* improve ux

* feed segments to worker

* simplify path threshold adaptive to zoom

* add a tiny threshold for checks

* refactor code

* lasso tests

* fix: ts

* do not capture lasso tool

* try worker-loader in next config

* update config

* refactor

* lint

* feat: show active tool when using "more tools"

* keep lasso if selected from toolbar

* fix incorrect checks for resetting to selection

* shift for additive selection

* bound text related fixes

* lint

* keep alt toggled lasso selection if shift pressed

* fix regression

* fix 'dead' lassos

* lint

* use workerpool and polyfill

* fix worker bundled with window related code

* refactor

* add file extension for worker constructor error

* another attempt at constructor error

* attempt at build issue

* attempt with dynamic import

* test not importing from math

* narrow down imports

* Reusing existing workers infrastructure (fallback to the main thread, type-safety)

* Points on curve inside the shared chunk

* Give up on experimental code splitting

* Remove potentially unnecessary optimisation

* Removing workers as the complexit is much worse, while perf. does not seem to be much better

* fix selecting text containers and containing frames together

* render fill directly from animated trail

* do not re-render static when setting selected element ids in lasso

* remove unnecessary property

* tweak trail animation

* slice points to remove notch

* always start alt-lasso from initial point

* revert build & worker changes (unused)

* remove `lasso` from `hasStrokeColor`

* label change

* remove unused props

* remove unsafe optimization

* snaps

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
2025-04-07 16:44:25 +10:00
Narek Malkhasyan 6e47fadb59 feat: add container to multiple text elements (#9348) 2025-04-07 00:57:27 +02:00
Márk Tolmács b3d5ba0567 fix: Linear element is not normalized (#9347)
* Fix #9292
2025-04-06 13:41:11 +02:00
Panagiotis Papadopoulosanddwelle c79e892e55 chore: bump @radix-ui/react-tabs version to 1.1.3 (#9329)
* chore: bump @radix-ui/react-tabs version to 1.1.3

bumped the version to latest stable that includes
react ^19 as peerDepenecy.
This fixes peerDependency issues, as reported in #9253

* redeploy

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-04-02 16:23:15 +02:00
David Luzar 57a9e301d4 feat: tweak color swatch, and button bgs (#9330)
* feat: tweak color swatch, and button bgs

* snapshots
2025-04-02 14:36:13 +02:00
David Luzar 7c58477382 feat: tweak properties panel styling (#9322) 2025-03-30 19:20:13 +02:00
David Luzar 83fac6d0db feat: tweak stats panel input styles (#9321) 2025-03-30 19:00:31 +02:00
David Luzar f2e8404c7b feat: allow to disable preventUnload in dev (#9319)
* feat: allow to disable preventUnload in dev

* add template
2025-03-29 19:42:33 +01:00
David Luzar d797c2e210 fix: strip legacy attrs on element restore (#9318) 2025-03-29 19:31:16 +01:00
Marcel Mraz 0cd5a259ae fix: incorrect type imports (#9308) 2025-03-27 12:00:12 +01:00
Marcel Mraz 432a46ef9e refactor: separate elements logic into a standalone package (#9285) 2025-03-26 15:24:59 +01:00
Márk Tolmács a18f059188 fix: Reduce allocations in collision detection (#9299)
Reduce allocations
2025-03-26 15:10:43 +01:00
KODIFYandAviral Sharma ab89d4c16f feat: add keyboard shortcut to save file in text (#9295)
Co-authored-by: Aviral Sharma <aviralsharma954@gmail.com>
2025-03-25 22:18:55 +01:00
Mubaraq WahabandMarcel Mraz 6c3a434f2a docs: Fix table rendering and broken links in Props page (#9293)
* Fix table rendering and broken links

---------

Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
2025-03-25 14:32:15 +01:00
Mursaleen NisarandMark Tolmacs e1bb59fb8f chore: Use isDevEnv() and isTestEnv() (#9264)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2025-03-24 19:44:00 +01:00
Márk Tolmács 77aca48c84 fix: Refactor and merge duplication and binding (#9246) 2025-03-23 18:39:33 +01:00
WalterMitty 58990b41ae fix: 'Rotate' spell error (#9288) 2025-03-22 09:06:23 +00:00
David Luzar 99d8bff175 fix: elements offset incorrectly when action-duplicated during drag (#9275)
* fix: elements offset incorrectly when action-duplicated during drag

* prevent duplicate action during drag altogether
2025-03-15 20:05:42 +01:00
Márk Tolmács 30983d801a fix: Arrow conversion regression (#9241)
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-03-15 12:31:25 +01:00
Marcel Mraz 21ffaf4d76 refactor: auto ordered imports (#9163) 2025-03-12 15:23:31 +01:00
Marcel Mraz 82b9a6b464 docs: CHANGELOG typos 🙏 (#9250) 2025-03-11 23:18:15 +01:00
Marcel Mraz 817d8c553c docs: update CHANGELOG (#9243) 2025-03-11 13:44:10 +01:00
Marcel Mraz 69bc5bdaab chore: post publish docs & examples changes (#9217) 2025-03-11 13:02:59 +01:00
Márk Tolmács d587b8a3de fix: Do not rebind undragged elbow arrow endpoint (#9191) 2025-03-10 16:25:33 +01:00
Márk Tolmács 4ec812bc18 fix: Bound elbow arrow on duplication does not route correctly (#9236) 2025-03-08 12:39:54 +01:00
Márk Tolmács a9e2d2348b chore: Logging and fixing extremely large scenes (#9225) 2025-03-05 23:06:01 +01:00
David Luzar 70c3e921bb fix: package env vars (#9221) 2025-03-04 21:45:48 +01:00
David Luzar d92384b77d revert: vite@6 -> vite@5 (#9220) 2025-03-04 17:45:52 +00:00
Ritobroto Kalita c5d3bb0b6a fix: #8475 Arrow updated on both sides (#8593) 2025-03-04 17:24:39 +01:00
David Luzar d21c6a1bc6 chore: bump vite@6.x (#9210) 2025-03-04 14:00:13 +01:00
David Luzar d1112bbf4f fix: docked sidebar width (#9213) 2025-03-02 18:24:20 +01:00
Márk Tolmács 2523624f15 fix: React 18 element.ref was accessed error (#9208)
Bump @radix-ui/react-popover to fix element.ref React 19 error

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2025-03-02 14:24:50 +01:00
David Luzar 68578556ff fix: cleanup legacy element.rawText (obsidian) (#9203) 2025-03-01 11:03:02 +01:00
Marcel Mraz ecef5d12f4 chore: release @excalidraw/excalidraw@18.0.0 🎉 (#9127) 2025-02-28 16:49:09 +01:00
Márk Tolmács 392118bf26 fix: Fix elbow arrow fixed binding on restore (#9197) 2025-02-28 15:36:41 +01:00
Márk Tolmácsanddwelle 0ffeaeaecf feat: Remove GA code from binding (#9042)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-02-25 22:52:06 +01:00
David Luzar 31e8476c78 chore: upgrade to react@19 (#9182) 2025-02-25 19:18:42 +01:00
Abhinav Pant 9ee0b8ffcb Enhancement: grouped together Undo and Redo buttons on mobile (#9109)
* bugfix: put the redo and undo button under the same div so that they look grouped together

* fixed the position of the redo and undo buttons to the right
2025-02-13 13:07:44 +00:00
David Luzar 16b86d7d16 chore: update firebase@8 to @11 (#9136) 2025-02-13 13:57:14 +01:00
Márk Tolmácsanddwelle f12b92ce9d chore: Upgrade Sentry to latest and update debug messages (#9134)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-02-13 12:47:27 +01:00
Márk Tolmács 77dc055d81 chore: Revert aspect ratio fix with element size limits and chk (#9131) 2025-02-12 15:02:35 +01:00
David Luzar 26f02bebea fix: stop using structuredClone (#9128)
fix: stop using `structuredClone`
2025-02-12 13:02:53 +01:00
Marcel Mraz e3060dfb8f feat: custom text metrics provider (#9121) 2025-02-11 14:23:08 +01:00
Kyosuke Fujimoto c329470b73 fix: Fix inconsistency in resizing while maintaining aspect ratio (#9116) 2025-02-10 15:24:08 +01:00
David Luzar c8f4a4cb41 feat: add props.onDuplicate (#9117)
* feat: add `props.onDuplicate`

* docs

* clarify docs

* fix docs
2025-02-10 14:20:18 +00:00
Márk Tolmács 9e49c9254b fix: IFrame and elbow arrow interaction fix (#9101) 2025-02-06 14:45:49 +01:00
David Luzar b0c8c5f7a7 feat: change empty arrowhead icon (#9100) 2025-02-06 10:52:03 +01:00
tothatt81anddwelle 4f64372506 perf: Improved pointer events related performance when the sidebar is docked with a large library open (#9086)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-02-04 22:05:56 +01:00
David Luzar 424e94a403 fix: duplicating/removing frame while children selected (#9079) 2025-02-04 19:23:47 +01:00
Márk Tolmács 302664e500 fix: Elbow arrow z-index binding (#9067) 2025-02-01 19:21:03 +01:00
David Luzar 86c67bd37f fix: library item checkbox style regression (#9080) 2025-02-01 12:27:41 +01:00
David Luzar 511433988c feat: tweak slider colors to be more muted (#9076) 2025-01-31 16:52:50 +01:00
Márk Tolmács 9b6edc767a fix: Elbow arrow orthogonality (#9073) 2025-01-31 14:19:07 +01:00
David Luzar 6cdb683410 fix: button bg CSS variable leaking into other styles (#9075) 2025-01-31 12:33:54 +01:00
ShaliniandDavid Luzar 84bab403ff Fix: issue #8818 Xiaolai font has been set as a fallback for Excalifont (#9055)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-01-30 13:41:41 +00:00
Areanddwelle 61e0bb83d0 feat: improve library sidebar performance (#9060)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-01-30 14:41:08 +01:00
Saikat Dasanddwelle bd1590fc74 feat: implement custom Range component for opacity control (#9009)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-01-29 21:46:40 +00:00
Marcel Mraz d29c3db7f6 fix: fonts not loading on export (again) (#9064) 2025-01-29 22:24:26 +01:00
Marcel Mraz a58822c1c1 fix: merge server-side fonts with liberation sans (#9052) 2025-01-29 22:04:49 +01:00
David Luzar a3e1619635 fix: hyperlinks html entities (#9063) 2025-01-29 19:02:54 +01:00
Ryan Dianddwelle 52eaf64591 feat: box select frame & children to allow resizing at the same time (#9031)
* box select frame & children

* avoid selecting children twice to avoid double their moving

* do not show ele stats if frame and children selected together

* do not update frame membership if selected together

* do not group frame and its children

* comment and refactor code

* hide align altogether

* include frame children when selecting all

* simplify

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-01-28 22:10:16 +01:00
David Luzar 7028daa44a fix: remove flushSync to fix flickering (#9057) 2025-01-28 19:23:35 +01:00
Ashwin Temkar 65f218b100 fix: excalidraw issue #9045 flowcharts: align attributes of new node (#9047)
* fix: excalidraw#9045 by modifying the stroke style, opacity, and fill style for the new node and next nodes.

* fix: added roughness and opacity to the arrowbindings
2025-01-25 17:05:50 +01:00
Alplune 807b3c59f2 fix: align arrows bound to elements excalidraw#8833 (#8998) 2025-01-25 17:00:39 +01:00
Alplune b8da5065fd fix: update elbow arrow on font size change #8798 (#9002) 2025-01-25 17:00:26 +01:00
Márk Tolmács 49f1276ef2 fix: Undo for elbow arrows create incorrect routing (#9046) 2025-01-24 20:18:08 +01:00
Ashwin Temkar 8f20b29b73 fix: #8575 , Flowchart clones the current arrowhead (#8581)
* fix: #8575, Flowchart clones the current arrowhead

* fix: #8575, changed stroke color, style and width to startBindingElement
2025-01-24 16:50:07 +01:00
David Luzar f87c2cde09 feat: allow installing libs from excal github (#9041) 2025-01-23 16:50:47 +01:00
Ryan Di 0bf234fcc9 fix: adding partial group to frame (#9014)
* prevent new frame from including partial groups

* separate wrapped partial group
2025-01-23 07:26:12 +08:00
Ryan Di dd1b45a25a perf: reduce unnecessary frame clippings (#8980)
* reduce unnecessary frame clippings

* further optim
2025-01-23 07:25:46 +08:00
David Luzar ec06fbc1fc fix: do not refocus element link input on unrelated updates (#9037) 2025-01-22 21:30:15 +01:00
David Luzar fa05ae1230 refactor: remove defaultProps (#9035) 2025-01-22 12:43:02 +01:00
Márk Tolmácsanddwelle 91ebf8b0ea feat: Elbow arrow segment fixing & positioning (#8952)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2025-01-17 18:07:03 +01:00
Arnost Pleskotanddwelle 8551823da9 feat: update jotai (#9015)
* feat: update jotai in excalidraw package

* feat: update jotai in excalidraw-app

* fix: exports from excalidraw/jotai

* fix: use isolated react hooks

* test: use jotai provider in <Trans /> test

* remove unused package

* refactor & make safer

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-01-16 16:59:11 +01:00
David Luzar ae6bee3403 feat: do not delete frame children on frame delete (#9011) 2025-01-14 21:08:25 +01:00
David Luzar 46f42ef8d7 fix: arrow binding behaving unexpectedly on pointerup (#9010)
* fix: arrow binding behaving unexpectedly on pointerup

* update snaps
2025-01-14 19:36:47 +01:00
Ryan Dianddwelle 00b5b0a0ca feat: add action to wrap selected items in a frame (#9005)
* feat: add action to wrap selected items in a frame

* fix type

* select frame on wrap & refactor

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-01-13 15:03:56 +00:00
YongJoon Kim c92f3bebf5 fix: change cursor by tool change immediately (#8212) 2025-01-09 14:26:12 +01:00
Marcel Mraz 2ac55067cd fix: package build fails on worker chunks (#8990) 2025-01-07 11:22:36 +00:00
David Luzar 78ab12c7e6 fix: z-index clash in mobile UI (#8985) 2025-01-06 21:21:11 +01:00
David Luzar f2f8219917 feat: reintroduce .excalidraw.png default when embedding scene (#8979) 2025-01-05 22:21:39 +01:00
한별 12c39d1034 feat: add mimeTypes on file save (#8946) 2025-01-05 21:12:07 +00:00
d33e42e3a1 feat: add crowfoot to arrowheads (#8942)
* crowfoot many

* crowfoot one

* one or many

* add icons for crowfoot

* add crowfoot icons

* adjust arrowhead selection popover

* make options collapsible

* swap triangle and bar

* switch to radix popover

* put triangle outline in the first row

* align shadow with new design spec

* remove unused flag

* swap order

* tweak labels

* handle shift+tab

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: Jakub Królak <108676707+j-krolak@users.noreply.github.com>
2025-01-05 21:50:24 +01:00
zsviczian 3b9ffd9586 fix: elbow arrows do not work within frames (issue: #8964) (#8969)
check for !isFrameLikeElement
2025-01-05 21:47:20 +01:00
David Luzar b63689c230 feat: make HTML attribute sanitization stricter (#8977)
* feat: make HTML attribute sanitization stricter

* fix double escape
2025-01-05 21:45:04 +01:00
David Luzar c84babf574 feat: validate library install urls (#8976) 2025-01-05 17:10:55 +01:00
David Luzar 36274f1f3e feat: cleanup svg export and move payload to <metadata> (#8975) 2025-01-05 16:53:05 +01:00
Aakansha Doshi 798c795405 docs: add demo link for browser integration (#8956) 2024-12-27 14:39:08 +09:00
Ryan Dianddwelle 107eae3916 refactor: separate resizing logic from pointer (#8155)
* separate resizing logic for a single element

* replace resize logic in stats

* do not recompute width and height from points when they're already given

* correctly update linear elements' position when resized

* update snapshots

* lint

* simplify linear resizing logic

* fix initial scale for aspect ratio

* update tests for linear elements

* test typo

* separate pointer from resizing for multiple elements

* lint and simplify

* fix tests

* lint

* provide scene in param instead

* type

* refactor code

* fix floating in tests

* remove restrictions/checks on width & height

* update pointer to dimension to prevent regression

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-12-23 11:10:35 +01:00
zsviczian 56fca30bd0 fix: normalizeSVG width and height from viewbox when size includes decimal points (#8939)
Update image.ts
2024-12-22 23:10:11 +01:00
Ryan Di 1e3399eac8 fix: make arrow binding area adapt to zoom levels (#8927)
* make binding area adapt to zoom

* revert stroke color

* normalize binding gap

* reduce normalized gap
2024-12-22 22:55:50 +01:00
David Luzar 873698a1a2 fix: robust state.editingFrame teardown (#8941) 2024-12-22 22:47:39 +01:00
Ryan Di 606ac6c743 fix: regression on dragging a selected frame by its name (#8924)
fix hit element check for a selected frame's name
2024-12-22 22:47:21 +01:00
Ryan Dianddwelle d99e4a23ca feat: use stats panel to crop (#8848)
* feat: use stats panel to crop

* fix: test flake

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-12-17 13:15:30 +01:00
Ryan Di 551bae07a7 feat: snap when cropping as well (#8831)
* crop with snap

* make crop snap work with cmd as well

* turn off grid with cmd as well in crop
2024-12-16 18:31:33 +08:00
Shreyansh Jainanddwelle 2af3221974 fix: right-click paste for images in clipboard (Issue #8826) (#8845)
* Fix right-click paste command for images (Issue #8826)

* Fix clipboard logic for multiple paste types

* fix: remove unused code

* refactor & robustness

* fix: creating paste event with image files

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-12-10 20:10:34 +00:00
Antonio Della Fortuna 9b401f6ea3 fix: fixed image transparency by adding alpha option to preserve image alpha channel (#8895)
added alpha option to preserve image alpha channel
2024-12-10 13:41:10 +01:00
Marcel Mraz 8a1152ce36 fix: Flush pending DOM updates before .focus() (#8901) 2024-12-09 21:57:37 +01:00
Ryan Di b5652b8e36 fix: normalize svg using only absolute sizing (#8854) 2024-11-27 13:09:44 +01:00
David Luzar 31e2a0cb4a fix: element link selector dialog z-index & positioning (#8853) 2024-11-26 23:18:20 +01:00
Ryan Dianddwelle c0b80a03bd feat: in canvas links between shapes (#8812)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-11-26 18:53:25 +01:00
David Luzar a758aaf8f6 fix: update old blog links & add canonical url (#8846) 2024-11-26 17:42:25 +01:00
Márk Tolmács b2a6a87b10 chore: Remove @tldraw/vec (#8762)
Not needed.
2024-11-21 15:19:20 +01:00
Márk Tolmács ab8b3537b3 fix: Optimize frameToHighlight state change and snapLines state change (#8763)
Fix case when frame interactions recursively call setState() without any change.
2024-11-21 15:19:00 +01:00
Márk Tolmács d21e0008dd fix: Make some events expllicitly active to avoid console warnings (#8757)
Avoid chrome/edge reporting of by-default blocking event handlers
2024-11-21 15:18:18 +01:00
David Luzar 840f1428c4 chore: bump @excalidraw/mermaid-to-excalidraw@1.1.2 (#8830) 2024-11-20 13:10:07 +01:00
Márk Tolmács 2db5bbcb29 fix: Unify binding update options for updateBoundElements() (#8832)
Fix insonsistent naming for option newSize/oldSize for updateBoundElements()
2024-11-20 11:46:45 +01:00
David Luzar 0927431d0d chore: bump @excalidraw/mermaid-to-excalidraw (#8829) 2024-11-19 20:46:55 +01:00
dependabot[bot] 98c0a67333 build(deps): bump cross-spawn from 7.0.3 to 7.0.6 (#8824)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 20:34:35 +01:00
Aakansha Doshi 57cf577376 fix: cleanup scripts and support upto node 22 (#8794) 2024-11-11 23:56:00 +05:30
Hamir Mahal 6e0ee89ee4 fix: usage of node12 which is deprecated (#8791) 2024-11-11 12:05:55 +01:00
David Luzar 35f778a734 build: set PWA flag in dev to false (#8788) 2024-11-09 21:04:50 +01:00
Aakansha Doshi ee091d0dbd build: add a flag VITE_APP_ENABLE_PWA for enabling pwa in dev environment (#8784)
* build: add a flag VITE_APP_ENABLE_PWA for enabling pwa in dev environment

* fix

* set VITE_ENABLE_PWA to false in .env.development
2024-11-09 21:45:37 +05:30
Aakansha Doshi ef9ea14a75 fix: remove manifest.json (#8783)
* fix: remove manifest.json

* disable pwa in dev
2024-11-09 13:42:11 +05:30
Aakansha Doshianddwelle df168a6883 fix: load env vars correctly and set debug and linter flags to false explicitly in prod mode (#8770)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-11-07 21:14:49 +00:00
David Luzar 798f5f4dfb feat: update blog url (#8767) 2024-11-06 16:41:41 +00:00
Barnabás Molnáranddwelle d9ad7c039b feat: export scene to e+ on workspace creation/redemption (#8514)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-11-04 22:35:45 +00:00
Márk Tolmács 7c0239e693 fix: Console error in dev mode due to missing font path in non-prod (#8756)
Fix console error due to missing font path in dev mode reported by Firefox.
2024-11-04 17:54:00 +01:00
Aakansha Doshi da33481fa3 chore: support upto node 22.x.x (#8755) 2024-11-04 18:57:40 +05:30
Marcel Mraz 70e0e8dc29 fix: text pushes UI due to padding (#8745) 2024-11-01 23:43:48 +02:00
Marcel Mraz 2734e646ca chore: simplify line-break regexes, separate text wrapping (#8715) 2024-10-30 14:24:12 +01:00
Marcel Mraz dfaaff4432 fix: fix trailing line whitespaces layout shift (#8714) 2024-10-30 14:11:13 +01:00
Marcel Mraz 03028eaa8c fix: load font faces in Safari manually (#8693) 2024-10-30 12:40:24 +01:00
David Luzar 79b181bcdc fix: restore svg image DataURL dimensions (#8730) 2024-10-29 22:40:24 +01:00
David LuzarandRyan Di f9815b8b4f fix: image cropping svg + compat mode (#8710)
Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
2024-10-28 10:08:05 +01:00
codemanandDavid Luzar 96ed8a4331 chore: remove duplicated meta tag (#8718)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2024-10-25 16:29:48 +00:00
Hamir Mahal 33b01d4e80 fix: usage of node12 which is deprecated (#8709) 2024-10-25 10:39:44 +05:30
Milos Vetesnik 7d52176fea feat: added sitemap & fixed robot txt (#8699) 2024-10-24 12:49:33 +02:00
David Luzar 958e03fcc6 fix: image render perf (#8697) 2024-10-23 11:58:43 +02:00
David Luzar 4cedf3d966 feat: do not strip unknown element properties on restore (#8682) 2024-10-21 22:56:22 +02:00
Ryan Dianddwelle e957c8e9ee feat: image cropping (#8613)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-10-21 22:26:52 +02:00
Denis MishankovandMarcel Mraz eb09b48ae6 fix: undo/redo action for international keyboard layouts (#8649)
Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
2024-10-21 17:08:39 +02:00
Marcel Mraz 61623bbeba fix: Comic Shanns issues, new fonts structure (#8641) 2024-10-21 00:11:53 +02:00
David Luzar 15ca182333 fix: remove export-to-clip-as-svg shortcut for now (#8660) 2024-10-17 20:47:05 +02:00
Marcel Mraz b479f3bd65 feat: add first-class support for CJK (#8530) 2024-10-17 20:14:17 +02:00
Mathis Beauville 21815fb930 fix: text disappearing on edit (#8558) (#8624) 2024-10-17 13:11:48 +02:00
David Luzar 47ee8a0094 refactor: point() -> pointFrom() to fix compiler issue (#8578) 2024-10-01 21:27:17 +02:00
Subhadeep SenguptaandAakansha Doshi a977dd1bf5 feat: Added reddit links as embeddable (#8099)
feat: #8063 Added reddit links as embeddable

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2024-09-28 11:49:18 +05:30
Aakansha Doshi 3fe1883f3f feat: prefer user defined coords and dimensions over calculated for for frame (#8517)
* feat: prefer user defined coords and dimensions over calculated for frame

* update changelog

* lint

* show the info only in dev mode and when children present
2024-09-24 21:09:15 +05:30
Marcel Mraz a80cb5896a feat: self-hosting existing google fonts (#8540) 2024-09-24 17:30:21 +02:00
David Luzar 6dfa18414a test: decrease min coverage thresholds (#8541) 2024-09-24 12:01:28 +00:00
David LuzarandMark Tolmacs 8ca4cf3260 feat: flip arrowheads if only arrow(s) selected (#8525)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
2024-09-19 15:46:36 +02:00
Márk Tolmács f3f0ab7c83 fix: Elbow arrow fixedpoint flipping now properly flips on inverted resize and flip action (#8324)
* Flipping action now properly mirrors selections with elbow arrows
* Flipping action now re-centers the selection to the original center to avoid "walking" selections on repeated flipping
2024-09-19 08:47:23 +02:00
David Luzar 44a1c8d857 fix: svg and png frame clipping cases (#8515) 2024-09-18 00:20:22 +02:00
Márk Tolmács e0a22edfbd fix: Re-route elbow arrows when pasted (#8448)
Re-route elbow arrows when pasted
2024-09-17 12:20:40 +02:00
Márk Tolmács c07f5a0c80 feat: Common elbow mid segments (#8440)
Common start or end segment length for elbow arrows regardless of arrowhead is present
2024-09-17 10:11:07 +02:00
David Luzar 508f16dc04 refactor: rename example App.tsx -> ExampleApp.tsx (#8501) 2024-09-13 16:56:32 +02:00
zsvicziananddwelle c1b310c56b fix: Buffer dependency (#8474)
* fix Buffer dependency

* moved to encode.ts

* move base64 parsing out

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-09-12 15:48:47 +02:00
zsviczian d4900e8f19 fix: Linear element complete button disabled (#8492) 2024-09-12 14:59:38 +02:00
zsviczian caf2db934c fix: aspect ratio of distorted images are not preserved in SVG exports (#8061) 2024-09-12 14:11:08 +02:00
zsviczian 60e3801691 fix: WYSIWYG editor padding is not normalized with zoom.value (#8481) 2024-09-12 13:42:39 +02:00
David Luzar 80f3b75d42 chore: revert vite 5.4.2 -> 5.0.12 (#8499) 2024-09-12 12:56:45 +02:00
hocino dc812bee19 docs: replace dead link (#8494)
* docs update dead link on main-menu page

* doc: fix dead link
2024-09-12 13:20:18 +05:30
David Luzar 01e83cc9a5 fix: default sidebar triggers & behavior (#8498) 2024-09-11 20:19:14 +02:00
David Luzar 813f9b702e feat: merge search sidebar back to default sidebar (#8497) 2024-09-11 19:26:01 +02:00
David Luzar fd39712ba6 fix: improve canvas search scroll behavior further (#8491) 2024-09-11 18:01:18 +02:00
zsviczian b46ca0192b fix: addFiles clears the whole image cache when each file is added - regression from #8471 (#8490)
Update App.tsx
2024-09-11 07:57:41 +02:00
David Luzar 72b7c937b1 feat: smarter zooming when scrolling to match & only match on search/switch (#8488) 2024-09-10 21:26:01 +02:00
David Luzar d107215564 fix: select instead of focus search input (#8483) 2024-09-09 19:57:22 +02:00
Ryan Dianddwelle 6959a363f0 feat: canvas search (#8438)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-09-09 17:12:07 +02:00
Abhishek Mehandiratta 5a11c70714 fix: image rendering issue when passed in initialData (#8471) 2024-09-08 23:56:00 +02:00
Aakansha Doshi 6ff56c36e3 fix: add partial mocking (#8473)
* fix: add partial mocking

* lint

* Update packages/utils/export.test.ts
2024-09-06 16:41:37 +05:30
Aakansha Doshi 51ea184938 build: upgrade vite to 5.4.x, vitest to 2.x and related vite packages (#8459)
* build: upgrade vite to 5.x, vitest to 2.x and related vite packages

* upgrade vitest-ui and coverage

* pass empty set to fix type error and update snap

* set ignoreEmptyLines to false

* update threshold

* update coverage threshold

* downgrade vite-plugin-pwa as its better to push separately with testing

* add package resolutions for strip-ansi, string-width and wrap-ansi

* disable pwa

* only add resolution for strip-ansi
2024-09-05 18:35:36 +05:30
dependabot[bot] 21fff26d31 build(deps): bump micromatch from 4.0.5 to 4.0.8 in /dev-docs (#8458)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-04 15:46:37 +05:30
Márk Tolmácsanddwelle f4dd23fc31 chore: Unify math types, utils and functions (#8389)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-09-02 22:23:38 +00:00
dependabot[bot] e3d1dee9d0 build(deps): bump micromatch from 4.0.7 to 4.0.8 (#8450)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 00:37:49 +02:00
hocino e3f31df747 docs : update dead link on main-menu page (#8454) 2024-09-01 22:31:13 +00:00
David Luzar 60e75406e0 refactor: remove unused env variable (#8457) 2024-09-02 00:25:12 +02:00
zsviczian b396e07b90 fix: PropertiesPopover maxWidth changing fixed units to relative units (#8456) 2024-09-01 18:18:37 +02:00
zsvicziananddwelle 2d1d84a47b fix: View mode wheel zooming does not work (#8452)
* Update App.tsx

* fix: prevent zooming browser over DOM

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-31 12:06:38 +02:00
Marcel Mrazanddwelle ee30225062 feat: subset font glyphs for SVG export (#8384)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-30 22:26:06 +02:00
David Luzar 16cae4fc07 feat: reset copyStatus on export dialog settings change (#8443) 2024-08-29 15:10:08 +02:00
David Luzar 576bc0dbe5 feat: tweak copy button success animation (#8441) 2024-08-29 00:43:01 +02:00
David Luzar 00af35c692 feat: enable panning/zoom while in wysiwyg (#8437) 2024-08-29 00:42:46 +02:00
Márk Tolmács ea7c702cfc feat: Visual debugger (#8344)
Add visual debugger to the Excalidraw app (only).
2024-08-27 19:46:00 +02:00
spc-28anddwelle 26d2296578 fix: fixed copy to clipboard button (#8426)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-27 00:27:44 +02:00
zsviczian afb68a6467 feat: improve elbow arrow keyboard move (#8392) 2024-08-26 15:58:54 +02:00
zsvicziananddwelle b459e5cfd2 fix: context menu does not work after after dragging on StatsDragInput (#8386)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-26 15:58:46 +02:00
David Luzar 5facc0d6da fix: perf regression in getCommonBounds (#8429) 2024-08-26 15:57:28 +02:00
Paul Tune 824ad603e1 docs: Update function call in @excalidraw/mermaid-to-excalidraw (#8420) 2024-08-26 13:20:29 +02:00
Ryan Dianddwelle 5e1ff7cafe perf: improve new element drawing (#8340)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-23 20:27:57 +02:00
David Luzar b5d7f5b4ba feat: rewrite d2c to not require token (#8269) 2024-08-20 18:06:22 +02:00
David Luzar fb4bb29aa5 fix: object snapping not working (#8381) 2024-08-15 18:48:25 +02:00
David Luzar 3cfcc7b489 feat: split gridSize from enabled state & support custom gridStep (#8364) 2024-08-14 14:59:14 +02:00
David Luzar 4320a3cf41 feat: improve zoom-to-content when creating flowchart (#8368) 2024-08-12 20:42:08 +02:00
Márk Tolmács 8420e1aa13 fix: Reimplement rectangle intersection (#8367) 2024-08-12 19:19:16 +02:00
Márk Tolmács 5daf1a1b4e fix: Round coordinates and sizes for rectangle intersection (#8366)
Round coordinates and sizes for rectangle intersection
2024-08-12 11:07:58 +02:00
David Luzar 97981804d7 feat: Stats popup style tweaks (#8361) 2024-08-11 19:33:44 +02:00
Clarence Chananddwelle f7b3befd0a fix: text content with tab characters act different in view/edit (#8336)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-09 20:20:36 +00:00
DDDDD12138 7b2bee9746 chore: remove unused parameter (#8355) 2024-08-09 21:39:40 +02:00
David Luzar 88014ace4a fix: drawing from 0-dimension canvas (#8356) 2024-08-09 21:36:04 +02:00
David Luzar 87a9430809 fix: disable flowchart keybindings inside inputs (#8353) 2024-08-09 18:44:17 +02:00
Márk Tolmács 99b91c46f7 fix: Yet more patching of intersect code (#8352)
* Yet more patching of intersect code
2024-08-09 17:33:12 +02:00
David Luzar 1ea5b26f25 fix: missing act() in flowchart tests (#8354) 2024-08-09 17:27:02 +02:00
Clarence Chan d5f4ee7b3f fix: z-index change by one causes app to freeze (#8314) 2024-08-09 15:26:08 +02:00
Márk Tolmács 261304c1a4 fix: Patch over intersection calculation issue (#8350)
* Patch over intersection calculation issue
2024-08-09 14:40:57 +02:00
Márk Tolmács 84398a7e5c fix: Point duplication in LEE on ALT+click (#8347) 2024-08-09 12:01:32 +02:00
54491d13d4 feat: create flowcharts from a generic element using elbow arrows (#8329)
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-08 21:43:15 +02:00
Márk Tolmács dd1370381d chore: Refactor and remove scene from elbow arrow generation (#8342)
* Refactor and remove scene from elbow arrow generation
2024-08-08 14:06:26 +02:00
Márk Tolmács 72d6ee48fc fix: Do not allow resizing unbound elbow arrows either (#8333)
* Fix resizing of unbound elbow arrows
2024-08-06 17:33:34 +02:00
David Luzar 232242d2e9 test: skip test.yml in PRs (#8330) 2024-08-06 16:04:04 +02:00
David Luzar f19ce30dfe chore: bump @testing-library/react 12.1.5 -> 16.0.0 (#8322) 2024-08-06 15:17:42 +02:00
Ryan Dianddwelle 3cf14c73a3 refactor: rename draggingElement -> newElement (#8294)
* add newElement to appState

* freedraw should not be an editing element

* do not set editing element for freedraw and generic

* remove ununsed `appState.draggingElement`

* remove setting dragged for new linear element

* decouple selection element from new element

* fix hint for text bindables

* update snapshot

* fixes

* fix frame regressions

* add comments to types

* document `editingElement`

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-06 19:26:06 +08:00
Márk Tolmács 8d530cf102 fix: Docker build in CI (#8312)
* Fix Docker build CI
* Bump nginx-alpine version to 1.27
2024-08-06 13:21:20 +02:00
David Luzar b87925d253 build: add example apps public and vite dev-dist to eslintignore (#8326) 2024-08-05 23:43:48 +02:00
b1sar a6684b09f2 docs: fix typos in props.mdx (#8325) 2024-08-05 21:41:19 +00:00
David Luzar b427617f66 build: add rm:build, rm:node_modules & clean-install scripts (#8323) 2024-08-05 23:37:33 +02:00
dependabot[bot] 2907fbe34b build(deps): bump @excalidraw/excalidraw from 0.17.0 to 0.17.6 in /dev-docs (#7908)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 09:49:57 +02:00
zsviczianandDavid Luzar c67815f7b0 fix: Duplicating arrow without bound elements throws error (#8316)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2024-08-03 16:37:36 +00:00
Márk Tolmács c641860cb1 fix: CVE-2023-45133 (#7988)
* Upgrade @babel/* versions to 7.24 to ensure non-vulnerable Babel versions
* Pinning React version to 18.2.0 exactly, avoiding test-utils type version clashes
* Fix warning message on yarn start
* Moving react to peer dependencies
* Moving app dependencies from workspace into app
* Bump vitest to 1.6.0 to fix history.test.tsx breaking

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-08-02 14:44:38 +02:00
Marcel Mraz 84d89b9a8a fix: throttle fractional indices validation (#8306) 2024-08-02 11:55:15 +02:00
David Luzar e63dd025c9 fix: allow binding elbow arrows to frame children (#8309) 2024-08-01 19:40:54 +02:00
Márk Tolmácsanddwelle 15e019706d feat: Orthogonal (elbow) arrows for diagramming (#8299)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-08-01 18:39:03 +02:00
Aakansha Doshi a133a70e87 build: update release script to build esm (#8308) 2024-08-01 20:19:37 +05:30
Marcel Mraz 80ea7ca23f fix: skip registering font faces for local fonts (#8303) 2024-08-01 11:32:16 +02:00
David Luzar e844580b14 feat: remove automatic frame naming (#8302) 2024-07-31 13:56:11 +02:00
Marcel Mraz 5a0771ad9c fix: load fonts for exportToCanvas (#8298) 2024-07-30 17:23:35 +02:00
Marcel Mraz adcdbe2907 fix: re-add Cascadia Code with ligatures (#8291) 2024-07-30 11:15:11 +02:00
Marcel Mraz 230d0edc44 feat: multiple fonts fallbacks (#8286) 2024-07-30 10:34:40 +02:00
Marcel Mraz d0a380758e feat: ability to debug the state of fractional indices (#8235) 2024-07-30 10:03:27 +02:00
Ryan Dianddwelle 7b36de0476 fix: linear elements not selected on pointer up from hitting its bound text (#8285)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-07-27 13:02:00 +00:00
David Luzar 2427e622b0 feat: improve mermaid detection on paste (#8287) 2024-07-27 12:36:54 +02:00
Marcel Mrazanddwelle 62228e0bbb feat: introduce font picker (#8012)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-07-25 18:55:55 +02:00
DDDDD12138andwuzhiqing 4c5408263c chore: Correct Typos in Code Comments (#8268)
chore: correct typos

Co-authored-by: wuzhiqing <wuzhiqing@linklogis.com>
2024-07-23 14:26:55 +05:30
Aakansha Doshianddwelle bd7b778f41 perf: cache the temp canvas created for labeled arrows (#8267)
* perf: cache the temp canvas created for labeled arrows

* use allEleemntsMap so bound text element can be retrieved when editing

* remove logs

* fix rotation

* pass isRotating

* feat: cache `element.angle` instead of relying on `appState.isRotating`

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-07-23 11:17:32 +05:30
David Luzar 43b2476dfe fix: revert default element canvas padding change (#8266) 2024-07-22 11:47:16 +02:00
BlueGreenMagick df8875a497 fix: freedraw jittering (#8238) 2024-07-14 08:44:47 +00:00
David Luzar 6fbc44fd1f fix: messed up env variable (#8231) 2024-07-11 14:33:35 +02:00
Aakansha Doshi d25a7d365b feat: upgrade mermaid-to-excalidraw to v1.1.0 (#8226)
* feat: upgrade mermaid-to-excalidraw to v1.1.0

* fixes

* upgrade and remove config as its redundant

* lint

* upgrade to v1.1.0
2024-07-10 20:57:43 +05:30
David Luzar e52c2cd0b6 fix: log allowed events (#8224) 2024-07-09 12:16:14 +02:00
David Luzar 96eeec5119 feat: bump max file size (#8220) 2024-07-08 18:35:13 +02:00
Hamir Mahal f5221d521b ci: upgrade gh actions checkout and setup-node to v4 (#8168)
fix: usage of `node12 which is deprecated`
2024-07-08 14:26:25 +05:30
Alexandre Lemoine db2c235cd4 Fix : exportToCanvas() doc example (#8127) 2024-07-08 08:52:05 +00:00
David Luzar 148b895f46 feat: smarter preferred lang detection (#8205) 2024-07-04 17:55:35 +02:00
DDDDD12138 d9258a736b chore: Consolidate i18n import in LanguageList component (#8201) 2024-07-04 17:34:16 +02:00
zsviczian 2e1f08c796 fix: memory leak - scene.destroy() and window.launchQueue (#8198) 2024-07-02 22:08:02 +02:00
David Luzar 1d5b41dabb fix: stop updating text versions on init (#8191) 2024-07-01 14:04:58 +02:00
Márk Tolmács 66a2f24296 fix: Add binding update to manual stat changes (#8183)
Manual stats changes now respect previous element bindings.
2024-07-01 09:45:31 +02:00
Márk Tolmács 04668d8263 fix: Binding after duplicating is now applied for both the old and duplicate shapes (#8185)
Using ALT/OPT + drag to clone does not transfer the bindings (or leaves the duplicates in place of the old one , which are also not bound).

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-06-28 15:28:48 +02:00
David Luzar abbeed3d5f feat: support Stats bound text fontSize editing (#8187) 2024-06-28 13:52:29 +02:00
Márk Tolmács ba8c09d529 fix: Incorrect point offsetting in LinearElementEditor.movePoints() (#8145)
The LinearElementEditor.movePoints() function incorrectly calculates the offset for local linear element points when multiple targetPoints are provided, one of those target points is index === 0 AND the other points are moved in the negative direction, and ending up with negative local coordinates.

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-06-28 12:23:10 +02:00
David Luzar 744b3e5d09 fix: stats state leaking & race conds (#8177) 2024-06-26 23:31:08 +02:00
Esteban Romo 6ba9bd60e8 feat: allow props.initialData to be a function (#8135) 2024-06-24 11:36:49 +02:00
zsvicziananddwelle a1ffa064df fix: only bind arrow (#8152)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-06-19 10:55:35 +02:00
David Luzar 4dc4590f24 fix: repair invalid binding on restore & fix type check (#8133) 2024-06-13 19:42:08 +02:00
Ryan Dianddwelle d2f67e619f feat: editable element stats (#6382)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-06-12 19:49:46 +02:00
David Luzar 22b39277f5 feat: paste as mermaid if applicable (#8116) 2024-06-11 19:19:22 +02:00
Sunil 63dee03ef0 docs: remove extra braces in callback JSX (#8087)
Fix: Syantax error
2024-05-31 10:48:31 +00:00
David Luzar 08b13f971d fix: wysiwyg blur-submit on mobile (#8075) 2024-05-28 16:18:02 +02:00
David Luzar 69f4cc70cb feat: stop autoselecting text on text edit on mobile (#8076) 2024-05-28 16:17:52 +02:00
Ryan Dianddwelle 860308eb27 feat: create new text with width (#8038)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-28 15:53:52 +02:00
David Luzar 4eb9463f26 fix: restore linear dimensions from points (#8062) 2024-05-27 10:36:58 +02:00
Aakansha Doshi 6ed6131169 build: run tests on master branch (#8072)
* build: run tests on master branch

* lint
2024-05-27 12:44:20 +05:30
David Luzar 1ed98f9c93 fix: lp plus url (#8056) 2024-05-24 09:10:14 +00:00
David Luzar a71bb63d1f fix: fix twitter og image (#8050) 2024-05-23 11:52:37 +02:00
Marcel Mraz 661d6a4a75 fix: flaky snapshot tests with floating point precision issues (#8049) 2024-05-23 11:51:01 +02:00
David Luzar defd34923a docs: fix updateScene storeAction default tsdoc & document types (#8048) 2024-05-22 13:40:23 +02:00
Ryan Dianddwelle c540bd68aa feat: wrap long text when pasting (#8026)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-21 16:56:09 +02:00
Marcel Mraz eddbe55f50 fix: always re-generate index of defined moved elements (#8040) 2024-05-20 23:23:42 +02:00
Aakansha Doshi 2f9526da24 feat: upgrade to mermaid-to-excalidraw v1 🚀 (#8022)
* feat: upgrade to mermaid-to-excalidraw v1 🚀

* upgrade to v1
2024-05-20 11:19:38 +05:30
David Luzar 1b6e3fe05b feat: rerender canvas on focus (#8035) 2024-05-19 22:20:40 +02:00
VatsalSoni_13anddwelle afe52c89a7 fix: undo/redo when exiting view mode (#8024)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-19 15:54:52 +02:00
zsvicziananddwelle be4e127f6c fix: Two finger panning is slow (#7849)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-19 14:23:43 +02:00
Karthik Nishanth ff0b4394b1 feat: add missing type="button" (#8030) 2024-05-18 08:36:08 +00:00
7d8b7fc14d fix: compatible safari layers button svg (#8020)
Co-authored-by: ysen <ysen.ge@hairobotics.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-15 15:22:05 +02:00
Ryan Dianddwelle 971b4d4ae6 feat: text wrapping (#7999)
* resize single elements from the side

* fix lint

* do not resize texts from the sides (for we want to wrap/unwrap)

* omit side handles for frames too

* upgrade types

* enable resizing from the sides for multiple elements as well

* fix lint

* maintain aspect ratio when elements are not of the same angle

* lint

* always resize proportionally for multiple elements

* increase side resizing padding

* code cleanup

* adaptive handles

* do not resize for linear elements with only two points

* prioritize point dragging over edge resizing

* lint

* allow free resizing for multiple elements at degree 0

* always resize from the sides

* reduce hit threshold

* make small multiple elements movable

* lint

* show side handles on touch screen and mobile devices

* differentiate touchscreens

* keep proportional with text in multi-element resizing

* update snapshot

* update multi elements resizing logic

* lint

* reduce side resizing padding

* bound texts do not scale in normal cases

* lint

* test sides for texts

* wrap text

* do not update text size when changing its alignment

* keep text wrapped/unwrapped when editing

* change wrapped size to auto size from context menu

* fix test

* lint

* increase min width for wrapped texts

* wrap wrapped text in container

* unwrap when binding text to container

* rename `wrapped` to `autoResize`

* fix lint

* revert: use `center` align when wrapping text in container

* update snaps

* fix lint

* simplify logic on autoResize

* lint and test

* snapshots

* remove unnecessary code

* snapshots

* fix: defaults not set correctly

* tests for wrapping texts when resized

* tests for text wrapping when edited

* fix autoResize refactor

* include autoResize flag check

* refactor

* feat: rename action label & change contextmenu position

* fix: update version on `autoResize` action

* fix infinite loop when editing text in a container

* simplify

* always maintain `width` if `!autoResize`

* maintain `x` if `!autoResize`

* maintain `y` pos after fontSize change if `!autoResize`

* refactor

* when editing, do not wrap text in textWysiwyg

* simplify text editor

* make test more readable

* comment

* rename action to match file name

* revert function signature change

* only update  in app

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-05-15 21:04:53 +08:00
David Luzar cc4c51996c build: specify packageManager field (#8010) 2024-05-14 10:45:27 +02:00
Guillaume Grossetie 79257a1923 fix: correctly resolve the package version (#8016)
The property name is `VITE_PKG_VERSION` (not `PKG_VERSION`)

Resolves #7984
2024-05-14 13:31:02 +05:30
Marcel Mraz dc66261c19 fix: re-introduce wysiwyg width offset (#8014) 2024-05-13 17:38:21 +02:00
David Luzar 273ba803d9 fix: font not rendered correctly on init (#8002) 2024-05-10 16:37:46 +02:00
David Luzar 301e83805d feat: add install-PWA to command palette (#7935) 2024-05-08 22:02:28 +02:00
David Luzar ed5ce8d3de fix: command palette filter (#7981) 2024-05-08 17:56:05 +02:00
Aakansha Doshi 1ed53b153c build: enable consistent type imports eslint rule (#7992)
* build: enable consistent type imports eslint rule

* change to warn

* fix the warning in example and excalidraw-app

* fix packages

* enable type annotations and throw error for the rule
2024-05-08 14:21:50 +05:30
Aakansha Doshi c1926f33bb fix: remove unused param from drawImagePlaceholder (#7991) 2024-05-07 20:22:51 +05:30
Andreas GebhardtandAakansha Doshi 6539029d2a fix: docker build of Excalidraw app (#7430)
* fix: docker build of Excalidraw app

Fixes #7403.

* deps: update (container) Nginx to 1.24

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2024-05-07 18:00:58 +05:30
David Luzar d1f37cc64f feat: tweak a few icons & add line editor button to side panel (#7990) 2024-05-07 13:18:39 +02:00
Márk Tolmács f0d25e34c3 chore: Add lcov coverage output to vitest (#7987)
chore: Add lcov coverage output to vitest so VSCode coverage gutters extension works

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-05-06 17:35:23 +02:00
d9bbf1eda6 feat: Allow binding only via linear element ends (#7946)
Arrows now only bind to new shapes if their start or end point is dragged close to them. Arrows previously bound to shapes remain bound on move and drag if at the end of the drag/move the points remain in the original shapes' binding area.

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
Co-authored-by: Sammy Lee <sammy.joe.lee@gmail.com>
2024-05-02 08:32:12 +02:00
Márk Tolmács f79fb9aae2 chore: Bump vitest to 1.5.3 to support VSCode vitest Extension (#7968)
Bump vitest to 1.5.3 to support VSCode vitest Extension

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
2024-05-01 20:47:52 +02:00
Mritunjay Goutam 275f6fbe24 fix: typo in doc api (#7466) 2024-04-30 16:52:42 +00:00
Ryan Dianddwelle 88812e0386 feat: resize elements from the sides (#7855)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-30 18:05:00 +02:00
Marcel Mraz 6e5aeb112d feat: record freedraw tool selection to history (#7949) 2024-04-25 17:24:05 +00:00
Marcel Mraz 4d83d1c91e fix: use Reflect API instead of Object.hasOwn (#7958) 2024-04-25 15:36:26 +02:00
Márk Tolmács a04676d423 fix: CTRL/CMD & arrow point drag unbinds both sides (#6459) (#7877) 2024-04-23 00:01:05 +02:00
Milos Vetesnik c851aaaf7b fix: z-index for laser pointer to be able to draw on embeds and such (#7918) 2024-04-22 23:53:55 +02:00
Marcel Mraz 1bd2b1fe55 feat: export reconciliation (#7917) 2024-04-22 17:27:57 +02:00
Marcel Mraz 015b46ab23 feat: expose StoreAction in relation to multiplayer history (#7898)
Improved Store API and improved handling of actions to eliminate potential concurrency issues
2024-04-22 09:22:25 +00:00
Marcel Mraz 530617be90 feat: multiplayer undo / redo (#7348) 2024-04-17 14:01:24 +02:00
David Luzar 5211b003b8 fix: double text rendering on edit (#7904) 2024-04-17 13:48:04 +02:00
Ryan Di bbcca06b94 fix: collision regressions from vector geometry rewrite (#7902) 2024-04-17 13:31:12 +02:00
johnd99 f92f04c13c fix: Correct unit from 'eg' to 'deg' (#7891) 2024-04-15 11:11:27 +02:00
David Luzar 890ed9f31f feat: add "toggle grid" to command palette (#7887) 2024-04-13 19:12:29 +02:00
David Luzar da2e507298 fix: allow same origin for all necessary domains (#7889) 2024-04-13 18:51:30 +02:00
David Luzar f59b4f6af4 fix: always make sure we render bound text above containers (#7880) 2024-04-12 21:50:02 +02:00
David Luzar afcde542f9 fix: parse embeddable srcdoc urls strictly (#7884) 2024-04-12 20:51:17 +02:00
Ryan Di 4689a6b300 fix: hit test for closed sharp curves (#7881) 2024-04-12 12:58:51 +02:00
David Luzar 0ae9b383d6 fix: Gist embed allowing unsafe html (#7883) 2024-04-12 12:57:43 +02:00
David Luzar f597bd3e01 fix: command palette tweaks and fixes (#7876) 2024-04-11 11:39:19 +02:00
Ryan Di 4987cc53d0 fix: include borders when testing insides of a shape (#7865) 2024-04-09 16:07:36 +02:00
Rinku Chaudhari d917db438e fix: external link not opening (#7859) 2024-04-09 16:06:49 +02:00
Aakansha Doshi a33a400f01 fix: add safe check for arrow points length in tranformToExcalidrawElements (#7863)
* fix: add safe check for arrow points length in tranformToExcalidrawElements

* add spec

* throw error only for dev mode

* fix lint
2024-04-09 09:56:21 +05:30
David Luzar 8a162a4cb4 fix: import (#7869) 2024-04-08 16:59:03 +02:00
David Luzar c6a045d092 fix: theme toggle shortcut event.code (#7868) 2024-04-08 16:55:33 +02:00
Arnost Pleskotanddwelle cd50aa719f feat: add system mode to the theme selector (#7853)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-08 16:46:24 +02:00
David Luzar 92bc08207c fix: remove incorrect check from index.html (#7867) 2024-04-08 16:42:00 +02:00
32df5502ae feat: fractional indexing (#7359)
* Introducing fractional indices as part of `element.index`

* Ensuring invalid fractional indices are always synchronized with the array order

* Simplifying reconciliation based on the fractional indices

* Moving reconciliation inside the `@excalidraw/excalidraw` package

---------

Co-authored-by: Marcel Mraz <marcel@excalidraw.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-04 13:51:11 +01:00
Ryan Dianddwelle bbdcd30a73 refactor: update collision from ga to vector geometry (#7636)
* new collision api

* isPointOnShape

* removed redundant code

* new collision methods in app

* curve shape takes starting point

* clean up geometry

* curve rotation

* freedraw

* inside curve

* improve ellipse inside check

* ellipse distance func

* curve inside

* include frame name bounds

* replace previous private methods for getting elements at x,y

* arrow bound text hit detection

* keep iframes on top

* remove dependence on old collision methods from app

* remove old collision functions

* move some hit functions outside of app

* code refactor

* type

* text collision from inside

* fix context menu test

* highest z-index collision

* fix 1px away binding test

* strictly less

* remove unused imports

* lint

* 'ignore' resize flipping test

* more lint fix

* skip 'flips while resizing' test

* more test

* fix merge errors

* fix selection in resize test

* added a bit more comment

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-04-04 16:31:23 +08:00
David Luzar 3e334a67ed feat: show firefox-compatible command palette shortcut alias (#7825) 2024-03-28 18:12:54 +01:00
David Luzar 1d71f84515 fix: stop using lookbehind for backwards compat (#7824) 2024-03-28 17:32:38 +01:00
Ryan Dianddwelle 550a388b2b feat: command palette (#7804)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-03-28 16:16:32 +00:00
David Luzar 6b523563d8 fix: ejs support in html files (#7822) 2024-03-28 14:58:47 +01:00
David Luzar 65bc500598 fix: excalidrawAPI.toggleSidebar not switching between tabs correctly (#7821) 2024-03-28 14:52:23 +01:00
Aakansha Doshi 7949aa1f1c feat: upgrade mermaid-to-excalidraw to 0.3.0 (#7819) 2024-03-28 16:44:29 +05:30
David Luzar 15bfa626b4 feat: support to not render remote cursor & username (#7130) 2024-03-18 10:41:06 +01:00
David Luzar 068895db0e feat: expose more collaborator status icons (#7777) 2024-03-18 10:20:07 +01:00
dwelle b7babe554b feat: load old library if migration fails 2024-03-11 09:57:01 +01:00
dwelle 6a385d6663 feat: change LibraryPersistenceAdapter load() source -> priority
to clarify the semantics
2024-03-11 09:40:51 +01:00
David Luzar 2382fad4f6 feat: store library to IndexedDB & support storage adapters (#7655) 2024-03-08 22:29:19 +01:00
Marcel Mraz 480572f893 fix: correcting Assistant metrics (#7758)
* Changed Assistant metrics to the corrrect ones from OS/2 table

* Adding more information about font metrics

* Adding branded types to avoid future mistakes
2024-03-07 16:54:36 +01:00
David Luzar 68b1fdb20e fix: add missing font metrics for Assistant (#7752) 2024-03-06 10:53:37 +01:00
David Luzar a38e82f999 feat: close dropdown on escape (#7750) 2024-03-05 23:22:34 +01:00
David Luzar a07f6e9e3a feat: show ai badge for discovery (#7749) 2024-03-05 23:22:25 +01:00
Marcel Mraz 7e471b55eb feat: text measurements based on font metrics (#7693)
* Introduced vertical offset based on harcoded font metrics 

* Unified usage of alphabetic baseline for both canvas & svg export

* Removed baseline property

* Removed font-size rounding on Safari

* Removed artificial width offset
2024-03-05 19:33:27 +00:00
Ryan Dianddwelle 160440b860 feat: improve collab error notification (#7741)
* identify cause

* toast after dialog for error messages in collab

* remove comment

* shake tooltip instead for repeating collab errors

* clear collab error

* empty commit

* simplify & fix reset race condition

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-03-04 20:43:44 +08:00
Aakansha Doshi f207bd0a1c build: export types for @excalidraw/utils (#7736)
* build: export types for @excalidraw/utils

* fix

* add types
2024-02-29 15:43:04 +05:30
Aakansha Doshi 99601baffc build: create ESM build for utils package 🥳 (#7500)
* build: create ESM build for utils package

* add deps, exports and import.meta
2024-02-28 19:33:47 +05:30
Aakansha Doshi af1a3d5b76 fix: export utils from excalidraw package in excalidraw library (#7731)
* fix: export utils from excalidraw package in excalidraw library

* don't export utils utilities

* fix import path

* fix export

* don't export export utilites

* fix export paths

* reexport utils from excalidraw package

* add exports from withinBounds

* fix path
2024-02-28 11:14:57 +05:30
Wabweni Brian 36e56267c9 docs: add missing closing angle bracket in integration.mdx (#7729)
Update integration.mdx: Fix missing closing angle bracket in code sample

 A closing angle bracket was missing in a code sample.

Original code:
<div style={{height:"500px", width:"500px"}}
    <Excalidraw />
</div>

Changes:

<div style={{height:"500px", width:"500px"}}>
    <Excalidraw />
</div>
2024-02-27 07:19:20 +00:00
Aakansha Doshi b09b5cb5f4 fix: split renderScene so that locales aren't imported unnecessarily (#7718)
* fix: split renderScene so that locales aren't imported unnecessarily

* lint

* split export code

* rename renderScene to helpers.ts

* add helpers

* fix typo

* fixes

* move renderElementToSvg to export

* lint

* rename export to staticSvgScene

* fix
2024-02-27 10:37:44 +05:30
Aashman Verma dd8529743a docs: type mistake in integration of excalidraw (#7723) 2024-02-26 10:24:27 +01:00
Aakansha Doshi f639d44a95 fix: remove dependency of t in blob.ts (#7717)
* remove dependency of t in blob.ts

* fix
2024-02-23 15:05:46 +05:30
Aakansha Doshi f5ab3e4e12 fix: remove dependency of t from clipboard and image (#7712)
* fix: remove dependency of t from clipboard and image

* pass errorMessage to copyTextToSystemClipboard where needed

* wrap copyTextToSystemClipboard and rethrow translated error in caller

* review fix

* typo
2024-02-21 19:45:33 +05:30
Aakansha Doshianddwelle 361a9449bb fix: remove scene hack from export.ts & remove pass elementsMap to getContainingFrame (#7713)
* fix: remove scene hack from export.ts as its not needed anymore

* remove

* pass elementsMap to getContainingFrame

* remove unused `mapElementIds` param

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-02-21 16:34:20 +05:30
Aakansha Doshi 2e719ff671 fix: decouple pure functions from hyperlink to prevent mermaid bundling (#7710)
* move hyperlink code into its folder

* move pure js functions to hyperlink/helpers and move actionLink to actions

* fix tests

* fix
2024-02-20 20:59:01 +05:30
Aakansha Doshi 79d9dc2f8f fix: make bounds independent of scene (#7679)
* fix: make bounds independent of scene

* pass only elements to getCommonBounds

* lint

* pass elementsMap to getVisibleAndNonSelectedElements
2024-02-19 19:39:14 +05:30
Aakansha Doshianddwelle 9013c84524 fix: make LinearElementEditor independent of scene (#7670)
* fix: make LinearElementEditor independent of scene

* more fixes

* pass elements and elementsMap to maybeBindBindableElement,getHoveredElementForBinding,bindingBorderTest,getElligibleElementsForBindableElementAndWhere,isLinearElementEligibleForNewBindingByBindable

* replace `ElementsMap` with `NonDeletedSceneElementsMap` & remove unused params

* fix lint

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-02-19 11:49:01 +05:30
Aakansha Doshi 47f87f4ecb fix: remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap (#7663)
* fix: remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap

* lint

* fix

* use non deleted elements where possible

* use non deleted elements map in actions

* pass elementsMap instead of array to elementOverlapsWithFrame

* lint

* fix

* pass elementsMap to getElementsCorners

* pass elementsMap to getEligibleElementsForBinding

* pass elementsMap in bindOrUnbindSelectedElements and unbindLinearElements

* pass elementsMap in elementsAreInFrameBounds,elementOverlapsWithFrame,isCursorInFrame,getElementsInResizingFrame

* pass elementsMap in getElementsWithinSelection, getElementsCompletelyInFrame, isElementContainingFrame, getElementsInNewFrame

* pass elementsMap to getElementWithTransformHandleType

* pass elementsMap to getVisibleGaps, getMaximumGroups,getReferenceSnapPoints,snapDraggedElements

* lint

* pass elementsMap to bindTextToShapeAfterDuplication,bindLinearElementToElement,getTextBindableContainerAtPosition

* revert changes for bindTextToShapeAfterDuplication
2024-02-16 11:35:01 +05:30
Aakansha Doshianddwelle 73bf50e8a8 fix: remove t from getDefaultAppState and allow name to be nullable (#7666)
* fix: remove t and allow name to be nullable

* pass name as required prop

* remove Unnamed

* pass name to excalidrawPlus as well for better type safe

* render once we have excalidrawAPI to avoid defaulting

* rename `getAppName` -> `getName` (temporary)

* stop preventing editing filenames when `props.name` supplied

* keep `name` as optional param for export functions

* keep `appState.name` on `props.name` state separate

* fix lint

* assertive first

* fix lint

* Add TODO

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-02-15 11:11:18 +05:30
Aakansha Doshi 48c3465b19 docs: release patch v0.17.3 (#7673)
* docs: release patch v0.17.3

* update cl
2024-02-09 19:29:50 +05:30
David Luzar adc4c9f484 fix: prevent panning to trigger history on macos chrome (#7671) 2024-02-08 19:50:50 +01:00
YuBin, Hsu def1df2c68 fix: keep customData when converting to ExcalidrawElement (#7656)
* feat: keep customData when converting to ExcalidrawElement (#7654)

* docs: add changelog for keeping customData when converting to ExcalidrawElement
2024-02-08 17:23:10 +05:30
David Luzar 0513b647ec feat: change collab trigger & add share dialog (#7647) 2024-02-03 14:04:23 +00:00
David Luzar a289c42830 feat: add loading state to FilledButton (#7650) 2024-02-03 14:53:31 +01:00
David Luzar d67eaa8710 fix: file save timing out with big file sizes (#7649) 2024-02-03 11:53:35 +01:00
Aakansha Doshi 0c3dffb082 fix: make getEmbedLink independent of t function (#7643)
* fix: make getEmbedLink independent of t function

* rename warning to error and make it type safe
2024-02-01 21:12:10 +05:30
Milos Vetesnik 0e0f34edd8 fix: follow mode border for hosts apps (#7642) 2024-02-01 15:03:15 +01:00
David Luzar 4888d9d355 chore: change default port of collab server (#7641) 2024-02-01 14:41:38 +01:00
Aakansha Doshi 1c39bd5781 fix: don't bundle react and jotai when importing from scene (#7640)
* don't bundle react and jotai when importing from scene

* fix
2024-02-01 18:24:17 +05:30
Aakansha DoshiandDavid Luzar 90ad885446 feat: support onPointerUp prop (#7638)
* feat: support onPointerUp prop

* update changelog

* Update packages/excalidraw/CHANGELOG.md

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>

---------

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2024-02-01 12:26:55 +00:00
Aakansha Doshi 1741c234a6 fix: decouple container cache logic to containerCache. (#7637) 2024-01-31 21:17:41 +05:30
Aakansha Doshi 63b50b3586 fix: don't bundle react-dom when importing from transformHandles (#7634)
* fix: don't bundle react when importing from transfromHandles

* rename to DEFAULT_TRANSFORM_HANDLE_SPACING
2024-01-31 16:50:35 +05:30
Aakansha Doshi e0fefa8025 fix: don't bundle react-dom when importing from element (#7635) 2024-01-31 16:43:37 +05:30
Milos Vetesnikanddwelle d426cc968d refactor: remove portal as it is no longer needed (#7623)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-29 16:37:09 +01:00
Aashir Israranddwelle 2409c091ff feat: support roundness for images (#7558)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-29 15:27:07 +01:00
Andran1kanddwelle 626fe252ab fix: frame name field (#7457)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-29 10:57:22 +01:00
Aakansha Doshianddwelle 10bd08ef19 fix: make getBoundTextElement and related helpers pure (#7601)
* fix: make getBoundTextElement pure

* updating args

* fix

* pass boundTextElement to getBoundTextMaxWidth

* fix labelled arrows

* lint

* pass elementsMap to removeElementsFromFrame

* pass elementsMap to getMaximumGroups, alignElements and distributeElements

* lint

* pass allElementsMap to renderElement

* lint

* feat: make more typesafe

* fix: remove unnecessary assertion

* fix: remove unused params

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-26 11:29:07 +05:30
Aakansha Doshi 2789d08154 docs: update the docs for next js integration (#7605)
* docs: update the docs for next js integration

* update

* update

* update docs with tabbed examples

* fix
2024-01-25 20:26:48 +05:30
dependabot[bot] 678bb2b819 build(deps-dev): bump vite from 5.0.6 to 5.0.12 (#7586)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.6 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 19:29:50 +05:30
dependabot[bot] 966f9aead9 build(deps-dev): bump vite from 5.0.6 to 5.0.12 in /examples/excalidraw/with-script-in-browser (#7603)
build(deps-dev): bump vite

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.6 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 19:28:11 +05:30
Aakansha Doshi 4f0a2a9593 docs: add next js with app router example (#7552)
* move the existing example to with-script-in-browser

* Add example with next js app router

* disable ssr for excalidraw client comp

* typo

* update output dir

* don't include nextjs example in tsconfig

* remove meta.json

* lint

* remove example.ts

* port

* move the examples outside packages and use the deps as workspaces in examples

* update gitignore

* fix example

* update path of build dir

* fix

* fix scripts

* try local path

* fix

* update commands

* fix

* fix

* fix script

* skip ts

* disable ts

* add vercel.json

* install

* update tsconfig

* fix lint

* remove console.log

* lets see if this works

* revert

* remove ts nocheck

* add types and some utils in nextjs example

* fix types

* updatw example and remove nextjs dynamic syntax so we don't import excal twice

* move both examples to workspaces and create generic example to be used by browser and next js both

* copy the static assets to nextjs

* fix ts config

* render custom menu items

* fix custom footer

* fix types in browser example

* use regular imports for importing excal and import it using dynamic next js in app router instead

* Add example for pages router

* fix css discrepancies

* fix css

* configure output dir

* fix

* fix css

* rename to with-nextjs

* move components to examples/excalidraw/components
2024-01-24 17:07:54 +05:30
halocean96 f3f8217125 docs: toggleSidebar api fix (#7575) 2024-01-23 14:50:51 +00:00
David Luzar 89bd6181f2 fix: revert mapElementIds flag removal (#7594) 2024-01-22 17:23:00 +01:00
Aakansha Doshi c6fdac131b ci: add the workspace ignore check to install actions as dependency for auto release (#7593) 2024-01-22 17:01:00 +05:30
David Luzar 0415c616b1 refactor: decoupling global Scene state part-1 (#7577) 2024-01-22 00:23:02 +01:00
David Luzar 740a165452 fix: filter out elements not overlapping frame on paste (#7591) 2024-01-21 20:55:57 +01:00
Ryan Di 4997624a3a fix: frame name editing inconvenience (#7437) 2024-01-21 20:55:28 +01:00
Barnabás Molnár b66daae1f3 fix: Truncate collaborator name in dropdown. (#7576) 2024-01-21 20:36:09 +01:00
David Luzar 1e7df58b5b feat: add pasted elements to frame under cursor (#7590) 2024-01-21 14:01:43 +01:00
David Luzar 46da032626 fix: exporting frame-overlapping elements belonging to other frames (#7584) 2024-01-19 14:41:22 +01:00
みけCAT 3b0593baa7 fix: Prevent the library label from being collapsed (#7579) 2024-01-19 14:41:08 +01:00
みけCAT dd530737a2 docs: fix "canvas actions" link in Props page (#7536)
fix "canvas actions" link in Props page
2024-01-17 16:19:42 +05:30
Aakansha Doshi a4e5e46dd1 fix: move default to last so its compatible with nextjs (#7561) 2024-01-15 14:52:04 +05:30
David Luzar 0fa5f5de4c fix: translating frames containing grouped text containers (#7557) 2024-01-13 21:28:54 +01:00
David Luzar 41cc746885 fix: host font assets from root (#7548) 2024-01-11 21:29:29 +01:00
David Luzar 8ead8559e0 feat: redirect font requests to cdn (#7549) 2024-01-11 21:08:17 +01:00
David Luzar 5245276409 feat: erase groups atomically (#7545) 2024-01-11 17:43:04 +01:00
David Luzar 0c24a7042f feat: remove ExcalidrawEmbeddableElement.validated flag (#7539) 2024-01-11 17:42:51 +01:00
Areanddwelle 86cfeb714c feat: add eraser tool trail (#7511)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2024-01-11 16:10:15 +00:00
David Luzar 872973f145 fix: do not modify elements while erasing (#7531) 2024-01-11 16:00:07 +01:00
Aakansha Doshi 3ecf72a507 docs: add changelog for ESM build (#7542)
* docs: add changelog for ESM build

* move to breaking change
2024-01-11 16:40:45 +05:30
みけCAT 1aaa400876 docs: fix extra space in UIOptions/tools (#7537)
fix typo in UIOptions/tools
2024-01-11 11:09:33 +00:00
Aakansha Doshi 65047cc2cb fix: decouple react and react-dom imports from utils and make it treeshakeable (#7527)
fix: decouple react and react-dom imports from utils and make it tree-shakeable
2024-01-08 21:01:47 +05:30
David Luzar 8b993d409e feat: render embeds lazily (#7519) 2024-01-04 19:03:04 +01:00
David Luzar 1cb350b2aa feat: update X brand logo & tweak labels (#7518) 2024-01-04 14:57:31 +01:00
David Luzar 43ccc875fb feat: support multi-embed pasting & x.com domain (#7516) 2024-01-04 13:27:52 +01:00
Aakansha Doshi 4249b7dec8 chore: add version for excalidraw-app workspace (#7514) 2024-01-04 13:53:19 +05:30
Aakansha Doshi 49f15c736b chore: remove unused files (#7509)
chore remove unused files
2024-01-03 16:25:36 +05:30
Aakansha Doshi a8064ba3ee build: Welcome ESM and Bye Bye UMD (#7441)
* build: Welcome ESM and Bye Bye UMD

* remove package

* create unbundled esm build

* update script for example

* fix typo

* dummy commit

* update autorelease script to build esm

* revert dummy commit

* move react, react-dom and testing library to dev dependencies

* remove entry.js, publicPath and yarn install:deps script

* fix

* upgrade esbuild to fix glob import error for locales

* remove webpack chunk names as thats not needed anymore

* marking the code sideeffects free

* make the library tree-shakeable and move fonts to fonts directory

* allow side effects for css, scss files

* remove tree-shaking

* comment code for tree shaking

* move to vite for example

* bye bye webpack

* ignore ts

* separate build and output dir

* use esbuild for creating bundle for example

* update output dir

* lint

* create browser dev build with source maps and prod with minification

* add dev and prod builds for bundler

* lint

* update script

* remove await

* load prod build

* create minified build in dist

* prod and dev builds using export field

* remove import.meta

* dummy

* define import.meta prod and dev

* fix

* export types

* add types field

* typo

* lint

* Update scripts/buildPackage.js

* move types inside export

* newline
2024-01-01 20:18:44 +05:30
David Luzar e6c3c06c2e feat: support pen erasing (#7496) 2024-01-01 13:27:03 +01:00
David Luzar d19b51d4f8 fix: drawing-tablet stylus touch events being prevented (#7494) 2023-12-30 15:00:12 +01:00
David Luzar c72e853c85 refactor: editor events sub/unsub refactor (#7483) 2023-12-30 11:12:38 +01:00
zsviczian 5f40a4cad4 fix: missing cross-env from build:umd in package.json (#7460) 2023-12-19 00:02:03 +01:00
David Luzar d91c98b82e fix: incorrect types in ActionNavigate (#7462) 2023-12-18 21:14:30 +01:00
David Luzar 57ea4e61d1 fix: mixing clientId & socketId in UserList (#7461) 2023-12-18 18:21:57 +01:00
David Luzar 0808532b49 fix: follow mode collaborator status indicator (#7459) 2023-12-18 16:14:25 +01:00
2a0fe2584e fix: empty snapLines arrays would cause re-render (#7454)
Co-authored-by: Lynda Lin <lynda.lin@optoma.com>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-12-18 12:42:17 +00:00
Adithyan 7bd6496854 refactor: Fix Typo (#7445) 2023-12-16 18:23:11 +00:00
Jason Prafulanddwelle 537f6e7f68 docs: add steps for local development (#7449)
* docs: add steps for local development #7434

* docs: minor tweaks

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-12-16 19:18:35 +01:00
David Luzar 6dfa89e846 fix: emitted visible scene bounds not accounting for offsets (#7450) 2023-12-16 17:32:54 +01:00
David Luzar 561e919a2e fix: import Socket as type (#7446) 2023-12-16 11:15:04 +01:00
David Luzar 20e3acf7a6 feat: bump socket.io-client & collab tweaks (#7444) 2023-12-16 00:23:59 +01:00
David Luzar 2c0929e537 fix: follow-mode tweaks (#7443) 2023-12-15 15:16:25 +01:00
Barnabás Molnáranddwelle aad8ab0123 feat: follow mode (#6848)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-12-15 00:07:11 +01:00
Aakansha Doshi 88a2b286c7 feat: move utils to utils package and make @excalidraw/utils a workspace (#7432)
* feat: move utils to utils package and make @excalidraw/utils a workspace

* remove esm and update types path

* remove esm script

* fix package.json and yarn.lock

* update path

* fix

* fix lint and test
2023-12-13 21:51:27 +05:30
Excalidraw BotandAakansha Doshi b635b10b59 chore: Update translations from Crowdin (#7176)
* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (Marathi)

* New translations en.json (Hindi)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Korean)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* Auto commit: Calculate translation coverage

* New translations en.json (Arabic)

* New translations en.json (Thai)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Russian)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* New translations en.json (Korean)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* New translations en.json (Catalan)

* Auto commit: Calculate translation coverage

* New translations en.json (Basque)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* Auto commit: Calculate translation coverage

* New translations en.json (Spanish)

* Auto commit: Calculate translation coverage

* New translations en.json (Arabic)

* New translations en.json (Thai)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* Auto commit: Calculate translation coverage

* New translations en.json (Slovenian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Traditional)

* Auto commit: Calculate translation coverage

* New translations en.json (Swedish)

* Auto commit: Calculate translation coverage

* New translations en.json (French)

* Auto commit: Calculate translation coverage

* New translations en.json (German)

* Auto commit: Calculate translation coverage

* New translations en.json (Danish)

* Auto commit: Calculate translation coverage

* New translations en.json (Italian)

* Auto commit: Calculate translation coverage

* New translations en.json (Chinese Simplified)

* Auto commit: Calculate translation coverage

* New translations en.json (Korean)

* Auto commit: Calculate translation coverage

* New translations en.json (Polish)

* New translations en.json (Slovak)

* Auto commit: Calculate translation coverage

* New translations en.json (Karakalpak)

* Auto commit: Calculate translation coverage

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.json (Kurdish)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Khmer)

* New translations en.json (Tamil)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Latvian)

* New translations en.json (Azerbaijani)

* New translations en.json (Hindi)

* New translations en.json (Burmese)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Sinhala)

* New translations en.json (Norwegian Bokmal)

* New translations en.json (Occitan)

* New translations en.json (Kabyle)

* New translations en.json (Karakalpak)

* New translations en.json (Swedish)

* New translations en.json (Romanian)

* remove packages

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-12-12 16:56:22 +05:30
Aakansha Doshi 7ebda02b81 fix: update path of files in docs (#7428)
* fix: update path of files in docs

* remove root yarn install in size limit

* add vercel.json in dev-docs

* update config

* fix lint

* indent

* revert changelog and readme
2023-12-12 14:28:34 +05:30
Aakansha Doshi d6cd8b78f1 build: decouple package deps and introduce yarn workspaces (#7415)
* feat: decouple package deps and introduce yarn workspaces

* update root directory

* fix

* fix scripts

* fix lint

* update path in scripts

* remove yarn.lock files from packages

* ignore workspace

* dummy

* dummy

* remove comment check

* revert workflow changes

* ignore ws when installing gh actions

* remove log

* update path

* fix

* fix typo
2023-12-12 11:32:51 +05:30
Aakansha Doshi b7d7ccc929 fix: env variable for text-to-diagram and use frozen lock file when install deps in excalidraw-app (#7409) 2023-12-07 19:32:19 +05:30
Aakansha Doshi f14ad61bd0 build: move build process and excalidraw-app dependencies in its own package.json (#7021)
* build: move build process and excalidraw-app dependencies in its own package.json

* fix

* fix public path

* move bug-issue-template to excalidraw-app

* make env vars accessible in excalidraw app

* update build script

* install when building

* add ts ignore

* fix build-version script

* update config in vercel.json

* add vercel config for example

* fix vercel config

* update install script in vercel

* update install script in lint.yml

* update install script in test workflows

* push locales to locales folder pwa

* add favicons to manifest

* move react to peer deps in editor

* fix ts

* Enable vite intellisense

* add global.d.ts for excalidraw-app

* remove console.log

* remove react, react-dom and vite from excalidraw-app deps

* increase size limit
2023-12-07 16:39:11 +05:30
Aakansha Doshi 8963baf5ad chore: upgrade to vite 5.x and vitest 1.x (#7407)
* chore: upgrade to vite 5.x and vitest 1.x

* fix coverage

* move to ESM for vite config

* use ESM for vitest
2023-12-07 15:30:08 +05:30
Aakansha Doshi 557add5bf7 feat: Support Mermaid Class diagrams 🥳 (#7381)
* feat: support mermaid class diagrams

* upgrade mermaid-to-excalidraw

* upgrade mermaid-to-excalidraw

* add sequence diagrams in supported chart types

* upgrade mermaid-to-excalidraw

* update i18n
2023-12-06 21:31:54 +05:30
David Luzar b9cfbc2077 feat: add support for more UML arrowheads (#7391) 2023-12-06 16:00:00 +01:00
dependabot[bot] a04cc707c3 build(deps-dev): bump vite from 4.4.2 to 4.4.12 (#7393)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.2 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 15:31:44 +05:30
Vaibhav Shukla 72ea8022bf docs: changelog instruction removed from docs (#7395)
changelog instruction removed from docs
2023-12-06 11:36:39 +05:30
David Luzar 4bdeaf999b feat: TTD dialog UI tweaks (#7384) 2023-12-04 17:50:30 +01:00
Aakansha Doshi 42d8c5a040 chore: update changelog and package.json for v0.17.1 (#7351) 2023-11-28 19:12:39 +05:30
Aakansha Doshi f299514e44 fix: umd build so it can be used in browser (#7349)
* fix: umd build so it can be used in browser

* fix lint

* increase size limit

* update changelog

* use json.stringify for env preact variable so its accessible as string

* update changelog
2023-11-28 18:11:16 +05:30
David Luzar dd220bcaea feat: TTD dialog tweaks (#7346)
* tweaks to TTD dialog ~ prepping for settings dialog

* tweaks to ttd parsing & error logging
2023-11-27 16:03:03 +01:00
David Luzar fe75f29c15 fix: disable caching bounds for arrow labels (#7343) 2023-11-25 23:32:05 +01:00
Barnabás Molnáranddwelle 14845a343b feat: text-to-diagram (#7325)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-25 21:11:03 +00:00
David Luzar dd8a7d41e2 fix: bounds cached prematurely resulting in incorrectly rendered labels (#7339) 2023-11-24 19:55:56 +01:00
David Luzar fda5c6fdf7 fix: t2c settings dialog spacing for apps that use CSS resets (#7337) 2023-11-24 16:32:00 +01:00
David Luzar 3d1631f375 feat: d2c tweaks (#7336) 2023-11-24 14:02:11 +01:00
David Luzar c7ee46e7f8 feat: wireframe-to-code (#7334) 2023-11-23 23:07:53 +01:00
DanielJGeiger d1e4421823 feat: Expose ActionManager.registerAction through ExcalidrawImperativeAPI (#6995)
* feat: Expose `ActionManager` through `ExcalidrawImperativeAPI`

* Only expose `registerAction` instead of `ActionManager`
2023-11-22 15:22:49 -06:00
Barnabás Molnár 7c9cf30909 fix: make zoomToFit fitToViewport account for sidebar (#7298) 2023-11-17 15:56:19 +01:00
David Luzar 1e37dbd60e feat: change frame resizing behavior (#7307) 2023-11-17 14:37:43 +01:00
David Luzar f8d5c2a1b6 build: allow a range of major node versions (#7306) 2023-11-17 14:23:19 +01:00
Aakansha Doshi 23b24ea5c3 build: use caret for specifying node version to avoid major upgrades automatically (#7297) 2023-11-16 16:18:38 +05:30
Aakansha Doshi a528769b68 docs: upgrade to @excalidraw/excalidraw@0.17.0 (#7285) 2023-11-14 20:10:19 +05:30
Aakansha DoshiandDavid Luzar ddb7585057 docs: Docs for v0.17.0 🚀 (#7248)
* feat: add docs for getCommonBounds

* docs: add docs for frames api support

* docs: update docs for regenerateIds opts in convertToExcalidrawElements

* add docs for ref removal

* add docs for lock support and insertOnCanvasDirectly in setActiveTool

* fix broken links

* update docs for next js support

* update docs for Preact

* add faq

* docs: add `onChange`, `onPointerDown`, `onPointerUp` docs

* docs: update `useDevice` docs

* update docs for disabling image tool

* add docs for withinBounds helpers

* fix lint

* upgrade excal

* add docusaurus2-dotenv for expose env vars

* fix env variable and upgrade excal

* Update dev-docs/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton.mdx

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>

* update docs

Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>

* update docs for process.env

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-14 19:54:34 +05:30
Aakansha Doshi 111a48ffb1 docs: release @excalidraw/excalidraw@0.17.0 🎉 (#7284) 2023-11-14 19:53:59 +05:30
Aakansha Doshi 54153629c0 chore: update release scripts (#7282)
* chore: update release scripts

* update docs
2023-11-14 16:37:57 +05:30
David LuzarandAakansha Doshi 9c425224c7 feat: support disabling image tool (#6320)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-11-14 10:25:41 +01:00
Aakansha Doshi 9d1d45a8ea chore: update changelog (#7279)
* chore: update changelog

* fix

* Update CHANGELOG.md
2023-11-14 13:11:05 +05:30
David Luzar 029c3c48ba fix: image insertion bugs (#7278) 2023-11-13 15:34:59 +01:00
Aakansha Doshi adfd95be33 build: support preact 🥳 (#7255)
* build: support preact

* add log

* Simplify the config and generate prod and dev builds for preact

* update changelog

* remove logs

* use env variable so its available during build time

* update cl

* fix
2023-11-13 16:18:36 +05:30
zsvicziananddwelle ceb255e8ee fix: exportToSvg to honor frameRendering also for name not only for frame itself (#7270)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-12 22:34:05 +00:00
David Luzar ae5b9a4ffd fix: not cloning elements on export polluting Scene mapping (#7276) 2023-11-12 23:32:12 +01:00
zsvicziananddwelle 3d4ff59f40 fix: Can't toggle penMode off due to missing typecheck in togglePenMode (#7273)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-12 13:24:13 +01:00
David Luzar 7b00089314 chore: bump @excalidraw/random-username (#7272) 2023-11-11 19:23:22 +01:00
zsviczian af6b81df40 fix: Replace hard coded font family with const value in addFrameLabelsAsTextElements (#7269) 2023-11-11 10:04:02 +01:00
FilBot3andDavid Luzar 02cc8440c4 feat: allow D&D dice app domain for embeds (#7263)
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
2023-11-10 15:29:19 +00:00
David LuzarandRyan Di 6363492cee fix: perf issue when ungrouping elements within frame (#7265)
Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
2023-11-10 16:13:08 +01:00
Sahil Nagpure 900b317bf3 feat: remove full screen shortcut (#7222) 2023-11-10 14:44:02 +00:00
Gabriel Lalondeanddwelle 68179356e6 fix: Fixes the shortcut collision between "toggleHandTool" and "distributeHorizontally" (#7189)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2023-11-10 15:33:02 +01:00
Jan-Peter Dhallé 3ed15e95da Small typo fix frames.mdx (#7216) 2023-11-10 15:23:43 +01:00
zsviczian 798e1fd858 fix: allow pointer events when editing a linear element (#7238) 2023-11-10 15:21:59 +01:00
David Luzar f66c93633c feat: make adaptive-roughness less aggressive (#7250) 2023-11-10 13:32:34 +01:00
Aakansha Doshi cee00767df feat: support excalidrawAPI and remove refs support (#7251)
* feat: support excalidrawAPI and remove refs support

* update changelog

* remove ready and readyPromise

* update changelog

* update changelog
2023-11-10 15:33:43 +05:30
David Luzar 864c0b3ea8 feat: render frames on export (#7210) 2023-11-09 17:00:21 +01:00
Aakansha Doshi a9a6f8eafb docs: update the docs with next js dynamic import support (#7252) 2023-11-09 16:03:35 +05:30
David Luzar 3c96943db3 test: fix mermaid test flake (#7249) 2023-11-07 18:06:15 +01:00
David Luzar 9006caff39 fix: make modal use viewport breakpoints (#7246) 2023-11-07 10:10:12 +01:00
Aakansha Doshi ce7a847668 feat: export getCommonBounds util (#7247)
* feat: export getCommonBounds util

* add pr link

* fix
2023-11-07 14:19:13 +05:30
David Luzar b1037b342d feat: make device breakpoints more specific (#7243) 2023-11-06 16:29:00 +01:00
Dante Calderon 18a7b97515 chore: Fix typo in comment in LocalData file (#7235) 2023-11-04 18:15:09 +01:00
Aakansha Doshianddwelle e8def8da8d feat: Support mermaid flowchart and sequence diagrams to excalidraw diagrams 🥳 (#6920)
* feat: integrate mermaidToExcalidraw

* create mermaid to excal dialog

* allow mermaid syntax and export in preview

* fix

* fix webpack config

* fix markdown error by using named export

* center preview

* set elements as selected when inserted onto canvas

* persist mermaid data to storage

* store canvas data in refs

* load mermaid lazily

* tweak design

* compute width, height correctly for arrows

* fix undefined vertex issue

* add mermaid icon in dropdown

* add a note in dialog

* reset preview when error

* show error in preview when error

* show mermaid error messgae react way

* design tweaks

* add example and docs link

* fix

* tweak design to remove scroll bar

* show a spinner unless mermaid loaded

* regenerate ids when needed via programmatic api, this makes sure for mermaid diagrams ids are regenerated

* tweak

* add option to transform viewport to scene coords in transform api

* make opts optional and use 100% zoom when inserting to canvas

* fix arrow bindings in safari and firefox

* fix elements insert position and viewport centering

* fix: Update start/end points by 0.5 so bindings don't overlap with start/end bound element coordinates.

* defer rendering the preview

* tweak text

* fix tests

* remove only

* make design responsive

* fix: show extra tools dropdown in mobile

* fix mobile css

* width auto

* upgrade mermaid-to-excalidraw

* don't pass appState in deps as its not used

* upgrade mermaid-to-excalidraw to fix firefox issue

* use types from mermaid-to-excalidraw

* upgrade mermaid-to-excalidraw

* use stable version of mermaid-to-excalidraw

* upgrade mermaid-to-excalidraw

* fix width of shapes toolbar for smaller screen size and also fix regression of mobile menu

* use i18n

* better api

* enable test coverage in ui

* Add tests

* use common utils to update and get text editor

* updgrade mermaid-to-excalidraw to support sequence diagrams

* fix test

* don't update arrow container height anytime in when redrawing text bounding box

* increase size limit

* increase size limit of vendor to 900kb

* use openDialog for mermaid

* upgrade mermaid-to-excalidraw

* update frame id post generation

* upgrade mermaid-to-excalidraw to add entity codes support

* update size limit

* upgrade mermaid-to-excalidraw package with frame api changes

* upgrade mermaid-to-excalidraw to remove directive and use config

* don't highlight mermaid tool and remove unused api setSelection

* stop using loading state to update text area

* move some styling to scss

* review fixes

* use modifiedTableIcon props and remove stale snap

* css

* dialog css

* fix snap

* use dialog border

* change mermaidToExcalidrawLib to state

* better styling of errors

* make modal bigger

* fix mobile

* update snaps

* fix icon color

* fix dark mode insert button color

* horizontally center spinner

* render canvas conditionally on loaded state

* rd tweaks

* tweak class names

* remove max height

* typo in example

* upgrade mermaid-to-excalidraw

* simplify error state

* fix height & overflow on vertical breakpoint

* fix lint

* show errors in overlay

* set textarea font family

* reduce opacity

* update snap

* upgrade to mermaid  0.1.2

---------

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-11-03 17:41:34 +05:30
David Luzar a7db41c5ba fix: align input :hover/:focus with spec (#7225) 2023-11-02 16:06:26 +01:00
David Luzar d8166d9e1d fix: dialog remounting on className updates (#7224) 2023-11-02 16:06:15 +01:00
Farzaneh Sefidabi 81c0259041 docs: add npm downloads rate badge to README.md file (#7127) 2023-11-01 13:27:57 +01:00
Aakansha Doshi f5c91c3a0f feat: support frames via programmatic API (#7205)
* update frame id post generation

* support frames via programmatic API

* fix types

* add test for frames

* throw error when element doesn't exist

* naming tweaks

* update the api to use children

* consider max of frame dimensions and calculated bounds of elements

* consider bound elements in frame api
2023-11-01 17:14:04 +05:30
David Luzar 9b8de8a12e test: disable flaky test (#7213) 2023-10-31 12:05:08 +01:00
David Luzar ea677d4581 feat: make clipboard more robust and reintroduce contextmenu actions (#7198) 2023-10-28 19:29:28 +00:00
Aakansha Doshi ec2de7205f fix: don't update label position when dragging labelled arrows (#6891)
* fix: don't update label position when dragging labelled arrows

* lint

* add test

* don't update coords for label when labelled arrow inside frame

* increase locales bundle size limit
2023-10-27 12:06:11 +05:30
Areanddwelle d5e3f436dc feat: add approximate elements in bbox detection (#6727)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-26 23:33:00 +02:00
Aakansha Doshi dcf4592e79 feat: regenerate ids by default when using transform api and also update bindings by 0.5px to avoid possible overlapping (#7195)
* feat: regenerate ids by default when using transform api and also update bindings by 0.5px to avoid possible overlapping

* type

* increase limit as some past PR(s) increased the bundle size

* review fixes

* update changelog
2023-10-27 00:43:48 +05:30
David Luzar d1f8eec174 feat: support giphy.com embed domain (#7192) 2023-10-26 00:00:50 +02:00
David Luzar 0f81c30276 fix: frame add/remove/z-index ordering changes (#7194) 2023-10-25 23:16:02 +02:00
zsvicziananddwelle f098789d16 fix: element relative position when dragging multiple elements on grid (#7107)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-25 22:48:03 +02:00
David Luzar f794b0bb90 fix: freedraw non-solid bg hitbox not working (#7193) 2023-10-25 17:21:01 +02:00
David Luzar 104f64f1dc revert: remove bound-arrows from frames (#7190) 2023-10-25 10:39:19 +02:00
Viczián Andrásanddwelle 71ad3c5356 fix: Actions panel ux improvement (#6850)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-24 18:36:13 +00:00
David Luzar afea0df141 feat: renderer tweaks (#6698) 2023-10-20 17:45:37 +02:00
Preetanddwelle d2a508104e fix: Better fill rendering with latest RoughJS (#7031)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-20 15:08:24 +02:00
David LuzarandAakansha Doshi 3697618266 feat: support props.locked for setActiveTool (#7153)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-10-20 13:16:23 +02:00
David Luzar e7cc2337ea feat: add onChange, onPointerDown, onPointerUp api subs (#7154) 2023-10-20 13:08:22 +02:00
dependabot[bot] 9eb89f9960 build(deps): bump @babel/traverse from 7.18.9 to 7.23.2 in /dev-docs (#7165)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 11:31:27 +02:00
Excalidraw Bot ab1bcc7615 chore: Update translations from Crowdin (#6695) 2023-10-20 11:29:28 +02:00
Vaibhav Shuklaanddwelle b1cac35269 feat: Closing of "Save to.." Dialog on Save To Disk (#7168)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-19 17:51:50 +00:00
Vaibhav Shuklaanddwelle 83f86e2b86 fix: Fix for Strange Symbol Appearing on Canvas after Deleting Grouped Graphics (Issue #7116) (#7170)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-19 12:59:39 +02:00
dependabot[bot] 7e38cab76e build(deps): bump @babel/traverse from 7.21.4 to 7.23.2 (#7171)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 12:33:16 +02:00
David Luzar 2cabb1f1f4 fix: attempt to fix flake in wysiwyg tests (#7173) 2023-10-19 12:32:31 +02:00
Lakshya Satpalanddwelle 63650f82d1 feat: Added Copy/Paste from Google Docs (#7136)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-19 12:14:23 +02:00
David Luzar dde3dac931 feat: remove bound-arrows from frames (#7157) 2023-10-17 18:18:20 +02:00
David Luzar 5b94cffc74 fix: ensure ClipboardItem created in the same tick to fix safari (#7066) 2023-10-16 11:38:57 +02:00
David Luzar aaf73c8ff3 fix: double image dialog shown on insert (#7152) 2023-10-16 00:19:46 +02:00
mazijian-pp 44d9d5fcac fix: wysiwyg left in undefined state on reload (#7123) 2023-10-13 14:29:54 +02:00
Alex Kimanddwelle 89a3bbddb7 test: add more resizing tests (#7028)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-12 20:59:02 +02:00
David Luzar b86184a849 fix: ensure relative z-index of elements added to frame is retained (#7134) 2023-10-12 15:00:23 +02:00
Barnabás Molnáranddwelle b552166924 feat: new dark mode theme & light theme tweaks (#7104)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-12 14:58:33 +02:00
David Luzar 26ff3993bb feat: better laser cursor for dark mode (#7132) 2023-10-11 11:17:27 +02:00
David Luzar 7ad02c359a fix: memoize static canvas on props.renderConfig (#7131) 2023-10-10 23:31:23 +02:00
David Luzar 2523fe82e3 feat: laser pointer improvements (#7128) 2023-10-10 13:55:55 +02:00
zsvicziananddwelle 4ea079eb85 fix: regression from #6739 preventing redirect link in view mode (#7120)
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-09 12:26:49 +02:00
Ryan Di f20ba90ffa perf: improve element in frame check (#7124) 2023-10-09 16:32:27 +08:00
Emmanuel Ferdman 03da9112cf fix: update links to excalidraw-app (#7072) 2023-10-08 19:37:17 -05:00
David Luzar a249f332a2 fix: ensure we do not stop laser update prematurely (#7100) 2023-10-06 12:00:35 +02:00
Areanddwelle 2e61926a6b feat: initial Laser Pointer MVP (#6739)
* feat: initial Laser pointer mvp

* feat: add laser-pointer package and integrate it with collab

* chore: fix yarn.lock

* feat: update laser-pointer package, prevent panning from showing

* feat: add laser pointer tool button when collaborating, migrate to official package

* feat: reduce laser tool button size

* update icon

* fix icon & rotate

* fix: lock zoom level

* fix icon

* add `selected` state, simplify and reduce api

* set up pointer callbacks in viewMode if laser tool active

* highlight extra-tools button if one of the nested tools active

* add shortcut to laser pointer

* feat: don't update paths if nothing changed

* ensure we reset flag if no rAF scheduled

* move `lastUpdate` to instance to optimize

* return early

* factor out into constants and add doc

* skip iteration instead of exit

* fix naming

* feat: remove testing variable on window

* destroy on editor unmount

* fix incorrectly resetting `lastUpdate` in `stop()`

---------

Co-authored-by: dwelle <luzar.david@gmail.com>
2023-10-05 17:05:16 +02:00
DanielJGeiger e921bfb1ae feat: Export iconFillColor() (#6996) 2023-10-04 18:17:22 -05:00
David Luzar e6f74350ac refactor: DRY out tool typing (#7086) 2023-10-04 23:39:00 +02:00
David Luzar fa33aa08ab refactor: refactor event globals to differentiate from lastPointerUp (#7084) 2023-10-04 16:18:22 +02:00
David Luzar 8b838049df fix: remove invisible elements safely (#7083) 2023-10-04 16:09:59 +02:00
David Luzar 1f4f5e11ae refactor: DRY out and simplify setting active tool from toolbar (#7079) 2023-10-04 00:16:54 +02:00
David Luzar 12420592ef feat: support menu / dropdown items to have selected state (#7078) 2023-10-03 23:35:47 +02:00
DanielJGeiger bfd318e765 docs: Update the excalidraw-app source-code link in README.md (#7035)
chore: Update the `excalidraw-app` source-code link in README.md
2023-10-03 08:41:13 -05:00
Thomas Steiner 6a821f3b76 fix: Icon size in manifest (#7073) 2023-10-03 11:07:02 +02:00
Tanmoy 84fd13e872 docs: fix minor grammar and spellings (#7039) 2023-10-02 10:11:02 +02:00
Alberto Torrigiotti 7d2b6f3374 docs: fix typo on homepage of developer docs (#7047) 2023-09-29 20:52:53 -05:00
David Luzar ceb637f5ea fix: elements being dropped/duplicated when added to frame (#7057) 2023-09-29 15:40:14 +02:00
4c35eba72d feat: element alignments - snapping (#6256)
Co-authored-by: Ryan Di <ryan.weihao.di@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
2023-09-28 16:28:08 +02:00
David Luzar 4765f5536e fix: frame name not editable on dbl-click (#7037) 2023-09-25 16:54:23 +02:00
David Luzar 556175558a fix: polyfill Element.replaceChildren (#7034) 2023-09-24 19:07:35 +02:00
Aakansha Doshi 4db73a7f95 docs: release @excalidraw/excalidraw@0.16.1 🎉 (#7020) 2023-09-21 10:28:21 +05:30
1457 changed files with 307444 additions and 102816 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:18-bullseye
FROM node:24-bullseye
# Vite wants to open the browser using `open`, so we
# need to install those utils.
+9 -1
View File
@@ -4,8 +4,16 @@
!.eslintrc.json
!.npmrc
!.prettierrc
!excalidraw-app/
!package.json
!public/
!src/
!packages/
!scripts/
!tsconfig.json
!yarn.lock
# keep (sub)sub directories at the end to exclude from explicit included
# e.g. ./packages/excalidraw/{dist,node_modules}
**/build
**/dist
**/node_modules
+21 -8
View File
@@ -1,3 +1,5 @@
MODE="development"
VITE_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/
VITE_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/
@@ -7,28 +9,25 @@ VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfu
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
VITE_APP_WS_SERVER_URL=http://localhost:3002
# set this only if using the collaboration workflow we use on excalidraw.com
VITE_APP_PORTAL_URL=
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=https://app.excalidraw.com
VITE_APP_PLUS_APP=http://localhost:3000
VITE_APP_AI_BACKEND=http://localhost:3016
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
# put these in your .env.local, or make sure you don't commit!
# must be lowercase `true` when turned on
#
# whether to enable Service Workers in development
VITE_APP_DEV_ENABLE_SW=
# whether to disable live reload / HMR. Usuaully what you want to do when
# debugging Service Workers.
VITE_APP_DEV_DISABLE_LIVE_RELOAD=
VITE_APP_DISABLE_TRACKING=true
VITE_APP_ENABLE_TRACKING=true
FAST_REFRESH=false
# The port the run the dev server
VITE_APP_PORT=3000
VITE_APP_PORT=3001
#Debug flags
@@ -40,3 +39,17 @@ VITE_APP_COLLAPSE_OVERLAY=true
# Set this flag to false to disable eslint
VITE_APP_ENABLE_ESLINT=true
# Enable PWA in dev server
VITE_APP_ENABLE_PWA=false
VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2g5T+Rub6Kbf1Mf57t0
7r2zeHuVg4dla3r5ryXMswtzz6x767octl6oLThn33mQsPSy3GKglFZoCTXJR4ij
ba8SxB04sL/N8eRrKja7TFWjCVtRwTTfyy771NYYNFVJclkxHyE5qw4m27crHF1y
UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
HQIDAQAB'
# set to true in .env.development.local to disable the prevent unload dialog
VITE_APP_DISABLE_PREVENT_UNLOAD=
+22 -6
View File
@@ -1,18 +1,34 @@
MODE="production"
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
VITE_APP_PORTAL_URL=https://portal.excalidraw.com
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=https://app.excalidraw.com
# Fill to set socket server URL used for collaboration.
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
VITE_APP_WS_SERVER_URL=
VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
# socket server URL used for collaboration
VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
VITE_APP_DISABLE_TRACKING=
VITE_APP_ENABLE_TRACKING=false
VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApQ0jM9Qz8TdFLzcuAZZX
/WvuKSOJxiw6AR/ZcE3eFQWM/mbFdhQgyK8eHGkKQifKzH1xUZjCxyXcxW6ZO02t
kPOPxhz+nxUrIoWCD/V4NGmUA1lxwHuO21HN1gzKrN3xHg5EGjyouR9vibT9VDGF
gq6+4Ic/kJX+AD2MM7Yre2+FsOdysrmuW2Fu3ahuC1uQE7pOe1j0k7auNP0y1q53
PrB8Ts2LUpepWC1l7zIXFm4ViDULuyWXTEpUcHSsEH8vpd1tckjypxCwkipfZsXx
iPszy0o0Dx2iArPfWMXlFAI9mvyFCyFC3+nSvfyAUb2C4uZgCwAuyFh/ydPF4DEE
PQIDAQAB'
# Set the below flags explicitly to false in production mode since vite loads and merges .env.local vars when running the build command
VITE_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=false
VITE_APP_COLLAPSE_OVERLAY=false
# Enable eslint in dev server
VITE_APP_ENABLE_ESLINT=false
+7
View File
@@ -0,0 +1,7 @@
# VITE_DEBUG_DOM
# When "true", testing-library failures (waitFor / getBy*) include the full
# serialized DOM in the error message. It's off by default because it's noisy.
#
# Flip it to "true" (or use `VITE_DEBUG_DOM=true yarn test`) when you need to
# inspect the DOM of a failing test.
VITE_DEBUG_DOM=false
+4 -1
View File
@@ -5,4 +5,7 @@ package-lock.json
firebase/
dist/
public/workbox
src/packages/excalidraw/types
packages/excalidraw/types
examples/**/public
dev-dist
coverage
+59 -2
View File
@@ -1,7 +1,64 @@
{
"extends": ["@excalidraw/eslint-config", "react-app"],
"rules": {
"import/order": [
"warn",
{
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"],
"pathGroups": [
{
"pattern": "@excalidraw/**",
"group": "external",
"position": "after"
}
],
"newlines-between": "always-and-inside-groups",
"warnOnUnassignedImports": true
}
],
"import/no-anonymous-default-export": "off",
"no-restricted-globals": "off"
}
"no-restricted-globals": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"disallowTypeAnnotations": false,
"fixStyle": "separate-type-imports"
}
],
"no-restricted-imports": [
"error",
{
"name": "jotai",
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
}
],
"react/jsx-no-target-blank": [
"error",
{
"allowReferrer": true
}
]
},
"overrides": [
{
"files": ["packages/excalidraw/**/*.{ts,tsx}"],
"excludedFiles": ["packages/excalidraw/**/*.test.{ts,tsx}", "packages/excalidraw/**/*.test.*.{ts,tsx}"],
"rules": {
"@typescript-eslint/no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["@excalidraw/excalidraw"],
"message": "Do not import from the barrel 'index.tsx' files. Use direct relative imports to the specific module instead.",
"allowTypeImports": true
}
],
"paths": [".", "..", "../..", "../../..", "../../../..", "../../../../..", "../index", "../../index", "../../../index", "../../../../index"]
}
]
}
}
]
}
+45
View File
@@ -0,0 +1,45 @@
# Project coding standards
## Generic Communication Guidelines
- Be succint and be aware that expansive generative AI answers are costly and slow
- Avoid providing explanations, trying to teach unless asked for, your chat partner is an expert
- Stop apologising if corrected, just provide the correct information or code
- Prefer code unless asked for explanation
- Stop summarizing what you've changed after modifications unless asked for
## TypeScript Guidelines
- Use TypeScript for all new code
- Where possible, prefer implementations without allocation
- When there is an option, opt for more performant solutions and trade RAM usage for less CPU cycles
- Prefer immutable data (const, readonly)
- Use optional chaining (?.) and nullish coalescing (??) operators
## React Guidelines
- Use functional components with hooks
- Follow the React hooks rules (no conditional hooks)
- Keep components small and focused
- Use CSS modules for component styling
## Naming Conventions
- Use PascalCase for component names, interfaces, and type aliases
- Use camelCase for variables, functions, and methods
- Use ALL_CAPS for constants
## Error Handling
- Use try/catch blocks for async operations
- Implement proper error boundaries in React components
- Always log errors with contextual information
## Testing
- Always attempt to fix #problems
- Always offer to run `yarn test:app` in the project root after modifications are complete and attempt fixing the issues reported
## Types
- Always include `packages/math/src/types.ts` in the context when your write math related code and always use the Point type instead of { x, y}
+6 -6
View File
@@ -9,13 +9,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 2
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 18.x
node-version: 20.x
- name: Set up publish access
run: |
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
@@ -23,5 +23,5 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Auto release
run: |
yarn add @actions/core
yarn autorelease
yarn add @actions/core -W
yarn release --tag=next --non-interactive
-55
View File
@@ -1,55 +0,0 @@
name: Auto release excalidraw preview
on:
issue_comment:
types: [created, edited]
jobs:
Auto-release-excalidraw-preview:
name: Auto release preview
if: github.event.comment.body == '@excalibot trigger release' && github.event.issue.pull_request
runs-on: ubuntu-latest
steps:
- name: React to release comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
comment-id: ${{ github.event.comment.id }}
reactions: "+1"
- name: Get PR SHA
id: sha
uses: actions/github-script@v4
with:
result-encoding: string
script: |
const { owner, repo, number } = context.issue;
const pr = await github.pulls.get({
owner,
repo,
pull_number: number,
});
return pr.data.head.sha
- uses: actions/checkout@v2
with:
ref: ${{ steps.sha.outputs.result }}
fetch-depth: 2
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
node-version: 18.x
- name: Set up publish access
run: |
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Auto release preview
id: "autorelease"
run: |
yarn add @actions/core
yarn autorelease preview ${{ github.event.issue.number }}
- name: Post comment post release
if: always()
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
issue-number: ${{ github.event.issue.number }}
body: "@${{ github.event.comment.user.login }} ${{ steps.autorelease.outputs.result }}"
+1 -1
View File
@@ -9,5 +9,5 @@ jobs:
build-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- run: docker build -t excalidraw .
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: styfle/cancel-workflow-action@0.6.0
- uses: styfle/cancel-workflow-action@ce177499ccf9fd2aded3b0426c97e5434c2e8a73 # 0.6.0
with:
workflow_id: 400555, 400556, 905313, 1451724, 1710116, 3185001, 3438604
access_token: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -7,16 +7,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 18.x
node-version: 20.x
- name: Install and lint
run: |
yarn --frozen-lockfile
yarn install
yarn test:other
yarn test:code
yarn test:typecheck
+8 -8
View File
@@ -10,23 +10,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 18.x
node-version: 20.x
- name: Create report file
run: |
yarn locales-coverage
FILE_CHANGED=$(git diff src/locales/percentages.json)
FILE_CHANGED=$(git diff packages/excalidraw/locales/percentages.json)
if [ ! -z "${FILE_CHANGED}" ]; then
git config --global user.name 'Excalidraw Bot'
git config --global user.email 'bot@excalidraw.com'
git add src/locales/percentages.json
git add packages/excalidraw/locales/percentages.json
git commit -am "Auto commit: Calculate translation coverage"
git push
fi
@@ -40,8 +40,8 @@ jobs:
echo ::set-output name=body::$body
- name: Update description with coverage
uses: kt3k/update-pr-description@v1.0.1
uses: kt3k/update-pr-description@1b35a6dcd84d81aa0bc1889610efdcde7f37b0c0 # v1.0.1
with:
pr_body: ${{ steps.getCommentBody.outputs.body }}
pr_title: "chore: Update translations from Crowdin"
pr_title: "chore(editor): Update translations from Crowdin"
github_token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
+8 -3
View File
@@ -11,15 +11,20 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
push: true
tags: excalidraw/excalidraw:latest
platforms: linux/amd64, linux/arm64, linux/arm/v7
+99 -1
View File
@@ -6,11 +6,109 @@ on:
- opened
- edited
- synchronize
- labeled
- unlabeled
jobs:
semantic:
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: amannn/action-semantic-pull-request@v3.0.0
- uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: true
scopes: |
app
editor
packages/excalidraw
packages/utils
docker
repo
ignoreLabels: |
skip-semantic-title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
label-scope:
needs: semantic
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Label scoped PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
scope_labels=(s-app s-editor s-package)
readarray -t desired_labels < <(
node <<'NODE'
const title = process.env.PR_TITLE;
const match = title.match(/^[a-z]+(?:\(([^)]+)\))?!?:/i);
const scopes = match?.[1]?.split(",").map((scope) => scope.trim()) ?? [];
const labels = new Set();
for (const scope of scopes) {
if (scope === "app") {
labels.add("s-app");
} else if (scope === "editor") {
labels.add("s-editor");
} else if (scope.startsWith("packages/")) {
labels.add("s-package");
}
}
process.stdout.write([...labels].join("\n"));
NODE
)
should_apply_label() {
local label="$1"
for desired_label in "${desired_labels[@]}"; do
if [[ "$desired_label" == "$label" ]]; then
return 0
fi
done
return 1
}
for label in "${scope_labels[@]}"; do
if ! should_apply_label "$label"; then
gh api \
--method DELETE \
"repos/${REPOSITORY}/issues/${PR_NUMBER}/labels/${label}" \
--silent 2>/dev/null || true
fi
done
for label in "${desired_labels[@]}"; do
if ! gh api \
--method POST \
"repos/${REPOSITORY}/issues/${PR_NUMBER}/labels" \
--field "labels[]=${label}" \
--silent; then
echo "::warning::Could not apply ${label}. The workflow token likely does not have issues:write permission for this PR."
fi
done
+5 -5
View File
@@ -9,11 +9,11 @@ jobs:
sentry:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 18.x
node-version: 20.x
- name: Install and build
run: |
yarn --frozen-lockfile
@@ -28,7 +28,7 @@ jobs:
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
sentry-cli releases new $SENTRY_RELEASE --project $SENTRY_PROJECT
sentry-cli releases set-commits --auto $SENTRY_RELEASE
sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps --no-rewrite ./build/static/js/ --url-prefix "~/static/js"
sentry-cli sourcemaps upload --release $SENTRY_RELEASE --no-rewrite ./build/static/js/ --url-prefix "~/static/js"
sentry-cli releases finalize $SENTRY_RELEASE
sentry-cli releases deploys $SENTRY_RELEASE new -e production
env:
+10 -12
View File
@@ -10,21 +10,19 @@ jobs:
CI_JOB_NUMBER: 1
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js 18.x
uses: actions/setup-node@v3
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 18.x
- name: Install
run: yarn --frozen-lockfile
- name: Install in src/packages/excalidraw
run: yarn --frozen-lockfile
working-directory: src/packages/excalidraw
node-version: 20.x
- name: Install in packages/excalidraw
run: yarn
working-directory: packages/excalidraw
env:
CI: true
- uses: andresz1/size-limit-action@v1
- uses: andresz1/size-limit-action@e7493a72a44b113341c0cf6186ab49c17c4b65c1 # v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build:umd
build_script: build:esm
skip_step: install
directory: src/packages/excalidraw
directory: packages/excalidraw
+5 -5
View File
@@ -10,17 +10,17 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: "Install Node"
uses: actions/setup-node@v2
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18.x"
node-version: "20.x"
- name: "Install Deps"
run: yarn --frozen-lockfile
run: yarn install
- name: "Test Coverage"
run: yarn test:coverage
- name: "Report Coverage"
if: always() # Also generate the report if tests are failing
uses: davelosert/vitest-coverage-report-action@v2
uses: davelosert/vitest-coverage-report-action@2500dafcee7dd64f85ab689c0b83798a8359770e # v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+8 -6
View File
@@ -1,17 +1,19 @@
name: Tests
on: pull_request
on:
push:
branches: master
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 18.x
node-version: 20.x
- name: Install and test
run: |
yarn --frozen-lockfile
yarn install
yarn test:app
+3 -4
View File
@@ -21,10 +21,9 @@ npm-debug.log*
package-lock.json
yarn-debug.log*
yarn-error.log*
src/packages/excalidraw/types
src/packages/excalidraw/example/public/bundle.js
src/packages/excalidraw/example/public/excalidraw-assets-dev
src/packages/excalidraw/example/public/excalidraw.development.js
packages/excalidraw/types
coverage
dev-dist
html
meta*.json
.claude
-1
View File
@@ -1 +0,0 @@
18
+4
View File
@@ -0,0 +1,4 @@
# Guidelines
- For new DOM/browser API usage, use `app.ownerDocument` and `app.ownerWindow` instead of globals; without `app`, derive them from the mounted node's `ownerDocument` and its `defaultView`.
- When overriding properties of an existing type, prefer `Merge<Base, Overrides>` from `@excalidraw/common/utility-types` over `Omit<Base, keyof Overrides> & Overrides`.
-3
View File
@@ -1,3 +0,0 @@
## 2020-10-13
- Added ability to embed scene source into exported PNG/SVG files so you can import the scene from them (open via `Load` button or drag & drop). #2219
+34
View File
@@ -0,0 +1,34 @@
# CLAUDE.md
## Project Structure
Excalidraw is a **monorepo** with a clear separation between the core library and the application:
- **`packages/excalidraw/`** - Main React component library published to npm as `@excalidraw/excalidraw`
- **`excalidraw-app/`** - Full-featured web application (excalidraw.com) that uses the library
- **`packages/`** - Core packages: `@excalidraw/common`, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils`
- **`examples/`** - Integration examples (NextJS, browser script)
## Development Workflow
1. **Package Development**: Work in `packages/*` for editor features
2. **App Development**: Work in `excalidraw-app/` for app-specific features
3. **Testing**: Always run `yarn test:update` before committing
4. **Type Safety**: Use `yarn test:typecheck` to verify TypeScript
## Development Commands
```bash
yarn test:typecheck # TypeScript type checking
yarn test:update # Run all tests (with snapshot updates)
yarn fix # Auto-fix formatting and linting issues
```
## Architecture Notes
### Package System
- Uses Yarn workspaces for monorepo management
- Internal packages use path aliases (see `vitest.config.mts`)
- Build system uses esbuild for packages, Vite for the app
- TypeScript throughout with strict configuration
+10 -7
View File
@@ -1,17 +1,20 @@
FROM node:18 AS build
FROM --platform=${BUILDPLATFORM} node:24@sha256:8530f76a96d88820d288761f022e318970dda93d01536919fbc16076b7983e63 AS build
WORKDIR /opt/node_app
COPY package.json yarn.lock ./
RUN yarn --ignore-optional --network-timeout 600000
COPY . .
# do not ignore optional dependencies:
# Error: Cannot find module @rollup/rollup-linux-x64-gnu
RUN --mount=type=cache,target=/root/.cache/yarn \
npm_config_target_arch=${TARGETARCH} yarn --frozen-lockfile --network-timeout 600000
ARG NODE_ENV=production
COPY . .
RUN yarn build:app:docker
RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker
FROM nginx:1.21-alpine
FROM nginx:stable-alpine-slim@sha256:2c605dbeab79a6b2a63340474fe58119d0ef95bdc4b1f41df0aa689659b3d13b
COPY --from=build /opt/node_app/build /usr/share/nginx/html
COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1
+17 -19
View File
@@ -7,7 +7,7 @@
<h4 align="center">
<a href="https://excalidraw.com">Excalidraw Editor</a> |
<a href="https://blog.excalidraw.com">Blog</a> |
<a href="https://plus.excalidraw.com/blog">Blog</a> |
<a href="https://docs.excalidraw.com">Documentation</a> |
<a href="https://plus.excalidraw.com">Excalidraw+</a>
</h4>
@@ -23,17 +23,17 @@
<br />
<p align="center">
<a href="https://github.com/excalidraw/excalidraw/blob/master/LICENSE">
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" />
</a>
<img alt="Excalidraw is released under the MIT license." src="https://img.shields.io/badge/license-MIT-blue.svg" /></a>
<a href="https://www.npmjs.com/package/@excalidraw/excalidraw">
<img alt="npm downloads/month" src="https://img.shields.io/npm/dm/@excalidraw/excalidraw" /></a>
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" />
</a>
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
<a href="https://discord.gg/UexuTaE">
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/>
</a>
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widget=false"/></a>
<a href="https://deepwiki.com/excalidraw/excalidraw">
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" /></a>
<a href="https://twitter.com/excalidraw">
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/>
</a>
<img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+@excalidraw&style=social&logo=twitter"/></a>
</p>
<div align="center">
@@ -60,7 +60,7 @@ The Excalidraw editor (npm package) supports:
- 🏗️&nbsp;Customizable.
- 📷&nbsp;Image support.
- 😀&nbsp;Shape libraries support.
- 👅&nbsp;Localization (i18n) support.
- 🌐&nbsp;Localization (i18n) support.
- 🖼️&nbsp;Export to PNG, SVG & clipboard.
- 💾&nbsp;Open format - export drawings as an `.excalidraw` json file.
- ⚒️&nbsp;Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
@@ -70,7 +70,7 @@ The Excalidraw editor (npm package) supports:
## Excalidraw.com
The app hosted at [excalidraw.com](https://excalidraw.com) is a minimal showcase of what you can build with Excalidraw. Its [source code](https://github.com/excalidraw/excalidraw/tree/master/src/excalidraw-app) is part of this repository as well, and the app features:
The app hosted at [excalidraw.com](https://excalidraw.com) is a minimal showcase of what you can build with Excalidraw. Its [source code](https://github.com/excalidraw/excalidraw/tree/master/excalidraw-app) is part of this repository as well, and the app features:
- 📡&nbsp;PWA support (works offline).
- 🤼&nbsp;Real-time collaboration.
@@ -82,19 +82,17 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
## Quick start
Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
```
Use `npm` or `yarn` to install the package.
```bash
npm install react react-dom @excalidraw/excalidraw
```
or via yarn
```
# or
yarn add react react-dom @excalidraw/excalidraw
```
Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
Check out our [documentation](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation) for more details!
## Contributing
+2 -2
View File
@@ -1,3 +1,3 @@
files:
- source: /src/locales/en.json
translation: /src/locales/%locale%.json
- source: /packages/excalidraw/locales/en.json
translation: /packages/excalidraw/locales/%locale%.json
@@ -2,14 +2,14 @@
Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer.
You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should a valid React Node.
You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should be a valid React Node.
**Usage**
```jsx live
function App() {
return (
<div style={{ height: "500px"}}>
<div style={{ height: "500px" }}>
<Excalidraw>
<Footer>
<button
@@ -25,21 +25,21 @@ function App() {
}
```
This will only for `Desktop` devices.
This will only work for `Desktop` devices.
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useEditorInterface`](#useEditorInterface) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
Open the `Menu` in the below playground and you will see the `custom footer` rendered.
```jsx live noInline
const MobileFooter = ({}) => {
const device = useDevice();
if (device.isMobile) {
const editorInterface = useEditorInterface();
if (editorInterface.formFactor === "phone") {
return (
<Footer>
<button
className="custom-footer"
style= {{ marginLeft: '20px', height: '2rem'}}
style={{ marginLeft: "20px", height: "2rem" }}
onClick={() => alert("This is custom footer in mobile menu")}
>
custom footer
@@ -65,4 +65,4 @@ const App = () => (
// Need to render when code is span across multiple components
// in Live Code blocks editor
render(<App />);
```
```
@@ -38,6 +38,7 @@ To render an item, its recommended to use `MainMenu.Item`.
| Prop | Type | Required | Default | Description |
| --- | --- | :-: | :-: | --- |
| `onSelect` | `function` | Yes | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
| `selected` | `boolean` | No | `false` | Whether item is active |
| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
| `icon` | `JSX.Element` | No | - | The icon used in the menu item |
| `shortcut` | `string` | No | - | The shortcut to be shown for the menu item |
@@ -70,6 +71,7 @@ function App() {
| Prop | Type | Required | Default | Description |
| --- | --- | :-: | :-: | --- |
| `onSelect` | `function` | No | - | Triggered when selected (via mouse). Calling `event.preventDefault()` will stop menu from closing. |
| `selected` | `boolean` | No | `false` | Whether item is active |
| `href` | `string` | Yes | - | The `href` attribute to be added to the `anchor` element. |
| `children` | `React.ReactNode` | Yes | - | The content of the menu item |
| `icon` | `JSX.Element` | No | - | The icon used in the menu item |
@@ -131,7 +133,7 @@ function App() {
}
```
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/src/components/mainMenu/DefaultItems.tsx) of the default items.
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/components/main-menu/DefaultItems.tsx) of the default items.
### MainMenu.Group
@@ -80,7 +80,7 @@ A given tab trigger button that switches to a given sidebar tab. It must be rend
| `className` | `string` | No | |
| `style` | `React.CSSProperties` | No | |
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/ref#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
You can use the [`ref.toggleSidebar({ name: "custom" })`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#toggleSidebar) api to control the sidebar, but we export a trigger button to make UI use cases easier.
## Example
@@ -31,7 +31,7 @@ The welcome screen consists of two main groups of subcomponents:
<img
src={require("@site/static/img/welcome-screen-overview.png").default}
alt="Excalidraw logo: Sketch handrawn like diagrams."
alt="Excalidraw logo: Sketch hand-drawn like diagrams."
/>
### Center
@@ -8,15 +8,15 @@
import { FONT_FAMILY } from "@excalidraw/excalidraw";
```
`FONT_FAMILY` contains all the font families used in `Excalidraw` as explained below
`FONT_FAMILY` contains all the font families used in `Excalidraw`. The default families are the following:
| Font Family | Description |
| ----------- | ---------------------- |
| `Virgil` | The `handwritten` font |
| `Helvetica` | The `Normal` Font |
| `Cascadia` | The `Code` Font |
| `Excalifont` | The `Hand-drawn` font |
| `Nunito` | The `Normal` Font |
| `Comic Shanns` | The `Code` Font |
Defaults to `FONT_FAMILY.Virgil` unless passed in `initialData.appState.currentItemFontFamily`.
Pre-selected family is `FONT_FAMILY.Excalifont`, unless it's overriden with `initialData.appState.currentItemFontFamily`.
### THEME
@@ -37,7 +37,7 @@ Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
### MIME_TYPES
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
**How to use **
@@ -2,21 +2,25 @@
We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
## convertToExcalidrawElements
**_Signature_**
<pre>
convertToExcalidrawElements(elements:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133">
ExcalidrawElementSkeleton
</a>
)
</pre>
```ts
convertToExcalidrawElements(
elements: ExcalidrawElementSkeleton,
opts?: { regenerateIds: boolean }
): ExcalidrawElement[]
```
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `elements` | [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L137) | | The Excalidraw element Skeleton which needs to be converted to Excalidraw elements. |
| `opts` | `{ regenerateIds: boolean }` | ` {regenerateIds: true}` | By default `id` will be regenerated for all the elements irrespective of whether you pass the `id` so if you don't want the ids to regenerated, you can set this attribute to `false`. |
**_How to use_**
@@ -24,13 +28,13 @@ The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/
import { convertToExcalidrawElements } from "@excalidraw/excalidraw";
```
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#updatescene) if you are using the Skeleton API
This function converts the Excalidraw Element Skeleton to excalidraw elements which could be then rendered on the canvas. Hence calling this function is necessary before passing it to APIs like [`initialData`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/initialdata), [`updateScene`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) if you are using the Skeleton API
## Supported Features
### Rectangle, Ellipse, and Diamond
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional_.
To create these shapes you need to pass its `type` and `x` and `y` coordinates for position. The rest of the attributes are optional\_.
For the Skeleton API to work, `convertToExcalidrawElements` needs to be called before passing it to Excalidraw Component via initialData, updateScene or any such API.
@@ -67,7 +71,7 @@ function App() {
}
```
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L27) as well to decorate the shapes.
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L27) as well to decorate the shapes.
:::info
@@ -168,7 +172,7 @@ convertToExcalidrawElements([
type: "arrow",
x: 450,
y: 20,
startArrowhead: "dot",
startArrowhead: "circle",
endArrowhead: "triangle",
strokeColor: "#1971c2",
strokeWidth: 2,
@@ -188,7 +192,7 @@ convertToExcalidrawElements([
### Text Containers
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
If you don't provide the dimensions of container, we calculate it based of the label dimensions.
@@ -270,6 +274,35 @@ convertToExcalidrawElements([
![image](https://github.com/excalidraw/excalidraw/assets/11256141/f8123cd1-c9aa-452d-b96b-05c846c5030d)
### Sticky Notes
A sticky note is an always-filled note whose label auto-fits: the font shrinks from the label's `fontSize` (the note's font ceiling) down to a minimum, and only then does the note grow in height. Only `type`, `x` and `y` are required; `width`/`height` default to a 250×250 square and `label` is optional. The note's `strokeColor` is its ink — its text and creation-date footer are always one color; a label that sets its own `strokeColor` gives it to the note. A footer band below the label shows the note's creation date (`created`); pass `created: null` for a note without one (the band stays reserved, but empty).
```js
convertToExcalidrawElements([
{
type: "stickynote",
x: 100,
y: 100,
label: {
text: "Remember to ship it",
fontSize: 28,
},
},
{
type: "stickynote",
x: 400,
y: 100,
width: 200,
height: 200,
backgroundColor: "#fcc2d7",
strokeColor: "#c2255c",
},
]);
```
Notes keep their invariants regardless of the properties you pass: the background is never transparent (it falls back to the default yellow), the fill is solid, and the size is at least 75×75. The height you pass is kept as the note's `baseHeight`; if a label needs more room at the minimum font size, the created note is taller.
### Labelled Arrows
Similar to Text Containers, you can create labelled arrows as well.
@@ -322,7 +355,7 @@ convertToExcalidrawElements([
### Arrow bindings
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
```js
convertToExcalidrawElements([
@@ -427,3 +460,45 @@ convertToExcalidrawElements([
```
![image](https://github.com/excalidraw/excalidraw/assets/11256141/a8b047c8-2eed-4aea-82a2-e1e6bbddb8d4)
### Frames
To create a frame, you need to pass `type`, `children` (list of Excalidraw element ids). The rest of the attributes are optional.
```ts
{
type: "frame";
children: readonly ExcalidrawElement["id"][];
name?: string;
} & Partial<ExcalidrawFrameElement>);
```
```ts
convertToExcalidrawElements([
{
"type": "rectangle",
"x": 10,
"y": 10,
"strokeWidth": 2,
"id": "1"
},
{
"type": "diamond",
"x": 120,
"y": 20,
"backgroundColor": "#fff3bf",
"strokeWidth": 2,
"label": {
"text": "HELLO EXCALIDRAW",
"strokeColor": "#099268",
"fontSize": 30
},
"id": "2"
},
{
"type": "frame",
"children": ["1", "2"],
"name": "My frame"
}]
}
```
@@ -1,29 +1,28 @@
# ref
# excalidrawAPI
<pre>
<a href="https://reactjs.org/docs/refs-and-the-dom.html#creating-refs">
createRef
</a>{" "}
&#124;{" "}
<a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>{" "}
&#124;{" "}
<a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">
callbackRef
</a>{" "}
&#124; <br />
&#123; current: &#123; readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
resolvablePromise
</a> } }
(api:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616">
ExcalidrawAPI
</a>
) => void;
</pre>
You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
Once the callback is triggered, you will need to store the api in state to access it later.
```jsx showLineNumbers
export default function App() {
const [excalidrawAPI, setExcalidrawAPI] = useState(null);
return <Excalidraw excalidrawAPI={(api)=> setExcalidrawAPI(api)} />;
}
```
You can use this prop when you want to access some [Excalidraw APIs](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616). We expose the below APIs :point_down:
| API | Signature | Usage |
| --- | --- | --- |
| ready | `boolean` | This is set to true once Excalidraw is rendered |
| [readyPromise](#readypromise) | `function` | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readypromise) |
| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
| [updateLibrary](#updatelibrary) | `function` | updates the scene with the sceneData |
| [updateLibrary](#updatelibrary) | `function` | updates the library |
| [addFiles](#addfiles) | `function` | add files data to the appState |
| [resetScene](#resetscene) | `function` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
| [getSceneElementsIncludingDeleted](#getsceneelementsincludingdeleted) | `function` | Returns all the elements including the deleted in the scene |
@@ -38,61 +37,22 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
| [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool |
| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
| [toggleSidebar](#toggleSidebar) | `function` | Toggles specific sidebar on/off |
| [onChange](#onChange) | `function` | Subscribes to change events |
| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
## readyPromise
:::info The `Ref` support has been removed in v0.17.0 so if you are using refs, please update the integration to use the `excalidrawAPI`.
<pre>
const excalidrawRef = &#123; current:&#123; readyPromise:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
&nbsp;resolvablePromise
</a>
&nbsp;&#125; &#125;
</pre>
Additionally `ready` and `readyPromise` from the API have been discontinued. These APIs were found to be superfluous, and as part of the effort to streamline the APIs and maintain simplicity, they were removed in version v0.17.0.
Since plain object is passed as a `ref`, the `readyPromise` is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the `ref` in the react way and want to do some action on the host when this promise resolves.
```jsx showLineNumbers
const resolvablePromise = () => {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
};
const App = () => {
const excalidrawRef = useMemo(
() => ({
current: {
readyPromise: resolvablePromise(),
},
}),
[],
);
useEffect(() => {
excalidrawRef.current.readyPromise.then((api) => {
console.log("loaded", api);
});
}, [excalidrawRef]);
return (
<div style={{ height: "500px" }}>
<Excalidraw ref={excalidrawRef} />
</div>
);
};
```
:::
## updateScene
<pre>
(scene:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L339">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L339">
sceneData
</a>
) => void
@@ -102,10 +62,10 @@ You can use this function to update the scene with the sceneData. It accepts the
| Name | Type | Description |
| --- | --- | --- |
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L38) | The `elements` to be updated in the scene |
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L39) | The `appState` to be updated in the scene. |
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L38) | The `elements` to be updated in the scene |
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L39) | The `appState` to be updated in the scene. |
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
| `captureUpdate` | [`CaptureUpdateAction`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/store.ts#L40) | Controls which updates should be captured by the `Store`. Captured updates are emmitted and listened to by other components, such as `History` for undo / redo purposes. |
```jsx live
function App() {
@@ -145,6 +105,7 @@ function App() {
appState: {
viewBackgroundColor: "#edf2ff",
},
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
excalidrawAPI.updateScene(sceneData);
};
@@ -155,23 +116,35 @@ function App() {
<button className="custom-button" onClick={updateScene}>
Update Scene
</button>
<Excalidraw ref={(api) => setExcalidrawAPI(api)} />
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
</div>
);
}
```
#### captureUpdate
You can use the `captureUpdate` to influence undo / redo behaviour.
> **NOTE**: Some updates are not observed by the store / history - i.e. updates to `collaborators` object or parts of `AppState` which are not observed (not `ObservedAppState`). Such updates will never make it to the undo / redo stacks, regardless of the passed `captureUpdate` value.
| | `captureUpdate` value | Notes |
| --- | --- | --- |
| _Immediately undoable_ | `CaptureUpdateAction.IMMEDIATELY` | Use for updates which should be captured. Should be used for most of the local updates. These updates will _immediately_ make it to the local undo / redo stacks. |
| _Eventually undoable_ | `CaptureUpdateAction.EVENTUALLY` | Use for updates which should not be captured immediately - likely exceptions which are part of some async multi-step process. Otherwise, all such updates would end up being captured with the next `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene` or internally by the editor. These updates will _eventually_ make it to the local undo / redo stacks. |
| _Never undoable_ | `CaptureUpdateAction.NEVER` | Use for updates which should never be recorded, such as remote updates or scene initialization. These updates will _never_ make it to the local undo / redo stacks. |
### updateLibrary
<pre>
(opts: &#123; <br /> libraryItems:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249">
LibraryItemsSource
</a>
;<br /> merge?: boolean; <br /> prompt?: boolean;
<br /> openLibraryMenu?: boolean;
<br /> defaultStatus?: "unpublished" | "published"; <br /> &#125;) => Promise&lt;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L246">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L246">
LibraryItems
</a>
&gt;
@@ -181,7 +154,7 @@ You can use this function to update the library. It accepts the below attributes
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
| `merge` | boolean | `false` | Whether to merge with existing library items. |
| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
@@ -228,8 +201,8 @@ function App() {
Update Library
</button>
<Excalidraw
ref={(api) => setExcalidrawAPI(api)}
// initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/src/initialData.js
excalidrawAPI={(api) => setExcalidrawAPI(api)}
// initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/packages/excalidraw/initialData.js
initialData={{
libraryItems: initialData.libraryItems,
appState: { openSidebar: "library" },
@@ -244,7 +217,7 @@ function App() {
<pre>
(files:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
BinaryFileData
</a>
) => void
@@ -264,7 +237,7 @@ Resets the scene. If `resetLoadingState` is passed as true then it will also for
<pre>
() =>{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
ExcalidrawElement[]
</a>
</pre>
@@ -275,7 +248,7 @@ Returns all the elements including the deleted in the scene.
<pre>
() => NonDeleted&#60;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
ExcalidrawElement
</a>
[]&#62;
@@ -287,7 +260,7 @@ Returns all the elements excluding the deleted in the scene
<pre>
() =>{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
AppState
</a>
</pre>
@@ -328,7 +301,7 @@ Scroll the nearest element out of the elements supplied to the center of the vie
| Attribute | type | default | Description |
| --- | --- | --- | --- |
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) &#124; [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) &#124; [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
@@ -376,7 +349,7 @@ The unique id of the excalidraw component. This can be used to identify the exca
<pre>
() =>{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82">
files
</a>
</pre>
@@ -387,14 +360,19 @@ This API can be used to get the files present in the scene. It may contain files
This API has the below signature. It sets the `tool` passed in param as the active tool.
<pre>
(tool: <br /> &#123; type:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L15">
SHAPES
</a>
[number]["value"]&#124; "eraser" &#125; &#124;
<br /> &#123; type: "custom"; customType: string &#125;) => void
</pre>
```ts
(
tool: (
| { type: ToolType }
| { type: "custom"; customType: string }
) & { locked?: boolean },
) => {};
```
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool |
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
## setCursor
@@ -421,3 +399,51 @@ This API is especially useful when you render a custom [`<Sidebar/>`](/docs/@exc
```
This API can be used to reset to default mouse cursor.
## onChange
```tsx
(
callback: (
elements: readonly ExcalidrawElement[],
appState: AppState,
files: BinaryFiles,
) => void
) => () => void
```
Subscribes to change events, similar to [`props.onChange`](/docs/@excalidraw/excalidraw/api/props#onchange).
Returns an unsubscribe function.
## onPointerDown
```tsx
(
callback: (
activeTool: AppState["activeTool"],
pointerDownState: PointerDownState,
event: React.PointerEvent<HTMLElement>,
) => void,
) => () => void
```
Subscribes to canvas `pointerdown` events.
Returns an unsubscribe function.
## onPointerUp
```tsx
(
callback: (
activeTool: AppState["activeTool"],
pointerDownState: PointerDownState,
event: PointerEvent,
) => void,
) => () => void
```
Subscribes to canvas `pointerup` events.
Returns an unsubscribe function.
@@ -1,18 +1,18 @@
# initialData
<pre>
&#123; elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> &#125;
&#123; elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> &#125;
</pre>
This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
| Name | Type | Description |
| --- | --- | --- |
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
| `scrollToContent` | `boolean` | This attribute indicates whether to `scroll` to the nearest element to center once `Excalidraw` is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L247) &#124; Promise&lt;[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)&gt; | This library items with which `Excalidraw` should be mounted. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82) | The `files` added to the scene. |
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L247) &#124; Promise&lt;[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200)&gt; | This library items with which `Excalidraw` should be mounted. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82) | The `files` added to the scene. |
You might want to use this when you want to load excalidraw with some initial elements and app state.
@@ -1,43 +1,45 @@
# Props
All `props` are *optional*.
All `props` are _optional_.
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` &#124; `null` &#124; <code>Promise<object &#124; null></code> | `null` | The initial data with which app loads. |
| [`ref`](/docs/@excalidraw/excalidraw/api/props/ref) | `object` | _ | `Ref` to be passed to Excalidraw |
| [`isCollaborating`](#iscollaborating) | `boolean` | _ | This indicates if the app is in `collaboration` mode |
| [`onChange`](#onchange) | `function` | _ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
| [`onPointerUpdate`](#onpointerupdate) | `function` | _ | Callback triggered when mouse pointer is updated. |
| [`onPointerDown`](#onpointerdown) | `function` | _ | This prop if passed gets triggered on pointer down evenets |
| [`onScrollChange`](#onscrollchange) | `function` | _ | This prop if passed gets triggered when scrolling the canvas. |
| [`onPaste`](#onpaste) | `function` | _ | Callback to be triggered if passed when the something is pasted in to the scene |
| [`onLibraryChange`](#onlibrarychange) | `function` | _ | The callback if supplied is triggered when the library is updated and receives the library items. |
| [`onLinkOpen`](#onlinkopen) | `function` | _ | The callback if supplied is triggered when any link is opened. |
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` &#124; `null` &#124; <code>Promise<object &#124; null></code> | `null` | The initial data with which app loads. |
| [`excalidrawAPI`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api) | `function` | \_ | Callback triggered with the excalidraw api once rendered |
| [`isCollaborating`](#iscollaborating) | `boolean` | \_ | This indicates if the app is in `collaboration` mode |
| [`onChange`](#onchange) | `function` | \_ | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw `elements` and the current `app state`. |
| [`onPointerUpdate`](#onpointerupdate) | `function` | \_ | Callback triggered when mouse pointer is updated. |
| [`onPointerDown`](#onpointerdown) | `function` | \_ | This prop if passed gets triggered on pointer down events |
| [`onScrollChange`](#onscrollchange) | `function` | \_ | This prop if passed gets triggered when scrolling the canvas. |
| [`onPaste`](#onpaste) | `function` | \_ | Callback to be triggered if passed when something is pasted into the scene |
| [`onLibraryChange`](#onlibrarychange) | `function` | \_ | The callback if supplied is triggered when the library is updated and receives the library items. |
| [`generateLinkForSelection`](#generatelinkforselection) | `function` | \_ | Allows you to override `url` generation when linking to Excalidraw elements. |
| [`onLinkOpen`](#onlinkopen) | `function` | \_ | The callback if supplied is triggered when any link is opened. |
| [`langCode`](#langcode) | `string` | `en` | Language code string to be used in Excalidraw |
| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | _ | Render function that renders custom UI in top right corner |
| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | _ | Render function that can be used to render custom stats on the stats dialog. |
| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | _ | This indicates if the app is in `view` mode. |
| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | _ | This indicates if the `zen` mode is enabled |
| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | _ | This indicates if the `grid` mode is enabled |
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
| [`renderTopRightUI`](/docs/@excalidraw/excalidraw/api/props/render-props#rendertoprightui) | `function` | \_ | Render function that renders custom UI in top right corner |
| [`renderCustomStats`](/docs/@excalidraw/excalidraw/api/props/render-props#rendercustomstats) | `function` | \_ | Render function that can be used to render custom stats on the stats dialog. |
| [`viewModeEnabled`](#viewmodeenabled) | `boolean` | \_ | This indicates if the app is in `view` mode. |
| [`zenModeEnabled`](#zenmodeenabled) | `boolean` | \_ | This indicates if the `zen` mode is enabled |
| [`gridModeEnabled`](#gridmodeenabled) | `boolean` | \_ | This indicates if the `grid` mode is enabled |
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | \_ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
| [`theme`](#theme) | `"light"` &#124; `"dark"` | `"light"` | The theme of the Excalidraw component |
| [`name`](#name) | `string` | | Name of the drawing |
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
| [`generateIdForFile`](#generateidforfile) | `function` | _ | Allows you to override `id` generation for files added on canvas |
| [`validateEmbeddable`](#validateEmbeddable) | string[] | `boolean | RegExp | RegExp[] | ((link: string) => boolean | undefined)` | \_ | use for custom src url validation |
| [`autoFocus`](#autofocus) | `boolean` | `false` | Indicates whether to focus the Excalidraw component on page load |
| [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas |
| [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| <code>((link: string) => boolean &#124; undefined)</code> | \_ | use for custom src url validation |
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `<iframe>` |
| [`renderScrollbars`] | `boolean`| | `false` | Indicates whether scrollbars will be shown
### Storing custom data on Excalidraw elements
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L66) and is optional.
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L66) and is optional.
You can use this to add any extra information you need to keep track of.
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/ref#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/ref#updatelibrary) afterwards.
You can add `customData` to elements when passing them as [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata), or using [`updateScene`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatescene) / [`updateLibrary`](/docs/@excalidraw/excalidraw/api/props/excalidraw-api#updatelibrary) afterwards.
```js showLineNumbers
{
@@ -59,11 +61,11 @@ Every time component updates, this callback if passed will get triggered and has
(excalidrawElements, appState, files) => void;
```
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) in the scene.
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) in the scene.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) which are added to the scene.
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) which are added to the scene.
Here you can try saving the data to your backend or local storage for example.
@@ -79,14 +81,14 @@ This callback is triggered when mouse pointer is updated.
2.`button`: The position of the button. This will be one of `["down", "up"]`
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L131) map of the scene
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L131) map of the scene
```js
(exportedElements, appState, canvas) => void
```
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L87) which needs to be exported.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L87) which needs to be exported.
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
3. `canvas`: The `HTMLCanvasElement` of the scene.
### onPointerDown
@@ -94,7 +96,14 @@ This callback is triggered when mouse pointer is updated.
This prop if passed will be triggered on pointer down events and has the below signature.
<pre>
(activeTool: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L115"> AppState["activeTool"]</a>, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L424">PointerDownState</a>) => void
(activeTool:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
{" "}
AppState["activeTool"]
</a>
, pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L424">
PointerDownState
</a>) => void
</pre>
### onScrollChange
@@ -110,7 +119,11 @@ This prop if passed will be triggered when canvas is scrolled and has the below
This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.
<pre>
(data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L18">ClipboardData</a>, event: ClipboardEvent &#124; null) => boolean
(data:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/clipboard.ts#L18">
ClipboardData
</a>
, event: ClipboardEvent &#124; null) => boolean
</pre>
This callback must return a `boolean` value or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to a boolean value.
@@ -123,7 +136,7 @@ This callback if supplied will get triggered when the library is updated and has
<pre>
(items:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">
LibraryItems
</a>
) => void | Promise&lt;any&gt;
@@ -131,13 +144,24 @@ This callback if supplied will get triggered when the library is updated and has
It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.
### generateLinkForSelection
This prop if passed will be used to replace the default link generation function. The idea is that the host app can take over the creation of element links, which can be used to navigate to a particular element or a group. If the host app chooses a different key for element link id, then the host app should also take care of the handling and the navigation in `onLinkOpen`.
```tsx
(id: string, type: "element" | "group") => string;
```
### onLinkOpen
This prop if passed will be triggered when clicked on `link`. To handle the redirect yourself (such as when using your own router for internal links), you must call `event.preventDefault()`.
<pre>
(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>,
event: CustomEvent&lt;&#123; nativeEvent: MouseEvent }&gt;) => void
(element:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
ExcalidrawElement
</a>
, event: CustomEvent&lt;&#123; nativeEvent: MouseEvent }&gt;) => void
</pre>
Example:
@@ -167,7 +191,7 @@ const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
### langCode
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
```js
import { defaultLang, languages } from "@excalidraw/excalidraw";
@@ -176,35 +200,33 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
| name | type |
| --- | --- |
| `defaultLang` | `string` |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
### viewModeEnabled
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over *intialData.appState.viewModeEnabled*, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over _intialData.appState.viewModeEnabled_, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
### zenModeEnabled
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over *intialData.appState.zenModeEnabled*, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over _intialData.appState.zenModeEnabled_, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
### gridModeEnabled
This prop indicates whether the shows the grid. When supplied, the value takes precedence over *intialData.appState.gridModeEnabled*, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates whether the shows the grid. When supplied, the value takes precedence over _intialData.appState.gridModeEnabled_, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
### libraryReturnUrl
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com).
Defaults to *window.location.origin + window.location.pathname*. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
If supplied, this URL will be used when user tries to install a library from [libraries.excalidraw.com](https://libraries.excalidraw.com). Defaults to _window.location.origin + window.location.pathname_. To install the libraries in the same tab from which it was opened, you need to set `window.name` (to any alphanumeric string) — if it's not set it will open in a new tab.
### theme
This prop controls Excalidraw's theme. When supplied, the value takes precedence over *intialData.appState.theme*, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless *UIOptions.canvasActions.toggleTheme* is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
This prop controls Excalidraw's theme. When supplied, the value takes precedence over _intialData.appState.theme_, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless _UIOptions.canvasActions.toggleTheme_ is set to `true`, in which case the `theme` prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the `theme` prop when you detect a change to `appState.theme` from the [onChange](#onchange) callback).
You can use [`THEME`](/docs/@excalidraw/excalidraw/api/utils#theme) to specify the theme.
### name
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over *intialData.appState.name*, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
This prop sets the `name` of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over _intialData.appState.name_, the `name` will be fully controlled by host app and the users won't be able to edit from within Excalidraw.
### detectScroll
@@ -236,4 +258,4 @@ validateEmbeddable?: boolean | string[] | RegExp | RegExp[] | ((link: string) =>
This is an optional property. By default we support a handful of well-known sites. You may allow additional sites or disallow the default ones by supplying a custom validator. If you pass `true`, all URLs will be allowed. You can also supply a list of hostnames, RegExp (or list of RegExp objects), or a function. If the function returns `undefined`, the built-in validator will be used.
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
Supplying a list of hostnames (with or without `www.`) is the preferred way to allow a specific list of domains.
@@ -4,7 +4,7 @@
<pre>
(isMobile: boolean, appState:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
AppState
</a>) => JSX | null
</pre>
@@ -66,7 +66,7 @@ function App() {
<pre>
(element: NonDeleted&lt;ExcalidrawEmbeddableElement&gt;, appState:{" "}
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
AppState
</a>
) => JSX.Element | null
@@ -1,10 +1,10 @@
# UIOptions
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) and [`welcomeScreen`](#welcmescreen).
This prop can be used to customise UI of Excalidraw. Currently we support customising [`canvasActions`](#canvasactions), [`dockedSidebarBreakpoint`](#dockedsidebarbreakpoint) [`welcomeScreen`](#welcmescreen) and [`tools`](#tools).
<pre>
&#123;
<br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L372">
<br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L372">
CanvasActions
</a>, <br /> dockedSidebarBreakpoint?: number, <br /> welcomeScreen?: boolean <br />
@@ -55,7 +55,7 @@ If `UIOptions.canvasActions.export` is `false` the export button will not be ren
## dockedSidebarBreakpoint
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L161).
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L161).
If the _width_ of the _excalidraw_ container exceeds _dockedSidebarBreakpoint_, the sidebar will be `dockable` and the button to `dock` the sidebar will be shown
If user choses to `dock` the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
@@ -70,3 +70,12 @@ function App() {
);
}
```
## tools
This `prop` controls the visibility of the tools in the editor.
Currently you can control the visibility of `image` tool via this prop.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| image | boolean | true | Decides whether `image` tool should be visible.
@@ -20,16 +20,16 @@ exportToCanvas(&#123;<br/>&nbsp;
getDimensions,<br/>&nbsp;
files,<br/>&nbsp;
exportPadding?: number;<br/>
&#125;: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a>
&#125;: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/utils/export.ts#L24">ExportOpts</a>
</pre>
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | | The elements to be exported to canvas. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L17) | The app state of the scene. |
| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | | The elements to be exported to canvas. |
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L17) | The app state of the scene. |
| [`getDimensions`](#getdimensions) | `function` | _ | A function which returns the `width`, `height`, and optionally `scale` (defaults to `1`), with which canvas is to be exported. |
| `maxWidthOrHeight` | `number` | _ | The maximum `width` or `height` of the exported image. If provided, `getDimensions` is ignored. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59) | _ | The files added to the scene. |
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59) | _ | The files added to the scene. |
| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
@@ -90,7 +90,7 @@ function App() {
<img src={canvasUrl} alt="" />
</div>
<div style={{ height: "400px" }}>
<Excalidraw ref={(api) => setExcalidrawAPI(api)}
<Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)}
/>
</div>
</>
@@ -105,7 +105,7 @@ function App() {
<pre>
exportToBlob(<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L14">ExportOpts</a> & &#123;<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L14">ExportOpts</a> & &#123;<br/>&nbsp;
mimeType?: string,<br/>&nbsp;
quality?: number,<br/>&nbsp;
exportPadding?: number;<br/>
@@ -134,16 +134,16 @@ Returns a promise which resolves with a [blob](https://developer.mozilla.org/en-
<pre>
exportToSvg(&#123;<br/>&nbsp;
elements:&nbsp;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
ExcalidrawElement[]
</a>,<br/>&nbsp;
appState:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95"> AppState
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95"> AppState
</a>,<br/>&nbsp;
exportPadding: number,<br/>&nbsp;
metadata: string,<br/>&nbsp;
files:&nbsp;
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
BinaryFiles
</a>,<br/>
&#125;);
@@ -151,10 +151,10 @@ exportToSvg(&#123;<br/>&nbsp;
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | | The elements to exported as `svg `|
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The `appState` of the scene |
| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | | The elements to exported as `svg `|
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L11) | The `appState` of the scene |
| exportPadding | number | 10 | The `padding` to be added on canvas |
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | undefined | The `files` added to the scene. |
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) | undefined | The `files` added to the scene. |
This function returns a promise which resolves to `svg` of the exported drawing.
@@ -164,7 +164,7 @@ This function returns a promise which resolves to `svg` of the exported drawing.
<pre>
exportToClipboard(<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a> & &#123;<br/>&nbsp;
opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L21">ExportOpts</a> & &#123;<br/>&nbsp;
mimeType?: string,<br/>&nbsp;
quality?: number;<br/>&nbsp;
type: 'png' | 'svg' |'json'<br/>
@@ -8,7 +8,7 @@ id: "restore"
**_Signature_**
<pre>
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/>&nbsp; localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/>&nbsp; localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>
</pre>
**_How to use_**
@@ -17,7 +17,7 @@ restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob
import { restoreAppState } from "@excalidraw/excalidraw";
```
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) and if any key is missing, it will be set to its `default` value.
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) and if any key is missing, it will be set to its `default` value.
When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of the defaults.
Use this as a way to not override user's defaults if you persist them.
@@ -29,16 +29,16 @@ You can pass `null` / `undefined` if not applicable.
<pre>
restoreElements(
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean }<br/>
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean, normalizeIndices?: boolean }<br/>
)
</pre>
| Prop | Type | Description |
| ---- | ---- | ---- |
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> &#124; null &#124; undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> &#124; null &#124; undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
#### localElements
@@ -51,8 +51,9 @@ The extra optional parameter to configure restored elements. It has the followin
| Prop | Type | Description|
| --- | --- | ------|
| `refreshDimensions` | `boolean` | Indicates whether we should also `recalculate` text element dimensions. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration. |
| `repairBindings` |`boolean` | Indicates whether the `bindings` for the elements should be repaired. This is to make sure there are no containers with non existent bound text element id and no bound text elements with non existent container id. |
| `refreshDimensions` | `boolean` | Indicates whether we should also _recalculate_ text element dimensions. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration. |
| `repairBindings` |`boolean` | Indicates whether the _bindings_ for the elements should be repaired. This is to make sure there are no containers with non existent bound text element id and no bound text elements with non existent container id. |
| `normalizeIndices` |`boolean` | Indicates whether _fractional indices_ for the elements should be normalized. This is to prevent possible issues caused by using stale (too old, too long) indices. |
**_How to use_**
@@ -70,15 +71,15 @@ Parameter `refreshDimensions` indicates whether we should also `recalculate` tex
<pre>
restore(
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/>&nbsp;
localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a><br/>
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean }<br/>
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState</a>,<br/>&nbsp;
localAppState: Partial&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null | undefined,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L4">DataState</a><br/>
opts: &#123; refreshDimensions?: boolean, repairBindings?: boolean, normalizeIndices?: boolean }<br/>
)
</pre>
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreElements) about `localElements`.
**_How to use_**
@@ -93,7 +94,7 @@ This function makes sure elements and state is set to appropriate values and set
**_Signature_**
<pre>
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/>&nbsp;
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/>&nbsp;
defaultStatus: "published" | "unpublished")
</pre>
@@ -8,7 +8,7 @@ These are pure Javascript functions exported from the @excalidraw/excalidraw [`@
### serializeAsJSON
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L42) source for details).
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/json.ts#L42) source for details).
If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
@@ -16,8 +16,8 @@ If you want to overwrite the `source` field in the `JSON` string, you can set `w
<pre>
serializeAsJSON(&#123;<br/>&nbsp;
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>,<br/>
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>,<br/>
}): string
</pre>
@@ -37,7 +37,7 @@ If you want to overwrite the source field in the JSON string, you can set `windo
<pre>
serializeLibraryAsJSON(
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems[]</a>)
libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems[]</a>)
</pre>
**How to use**
@@ -53,7 +53,7 @@ Returns `true` if element is invisibly small (e.g. width & height are zero).
**_Signature_**
<pre>
isInvisiblySmallElement(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>): boolean
isInvisiblySmallElement(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement</a>): boolean
</pre>
**How to use**
@@ -80,10 +80,10 @@ excalidrawAPI.updateScene(scene);
<pre>
loadFromBlob(<br/>&nbsp;
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>&nbsp;
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
fileHandle?: FileSystemHandle | null <br/>
) => Promise&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L61">RestoredDataState</a>>
) => Promise&lt;<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L61">RestoredDataState</a>>
</pre>
### loadLibraryFromBlob
@@ -129,11 +129,11 @@ if (contents.type === MIME_TYPES.excalidraw) {
<pre>
loadSceneOrLibraryFromBlob(<br/>&nbsp;
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/>&nbsp;
localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/>&nbsp;
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/>&nbsp;
fileHandle?: FileSystemHandle | null<br/>
) => Promise&lt;&#123; type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L33">ImportedLibraryState</a>}>
) => Promise&lt;&#123; type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L33">ImportedLibraryState</a>}>
</pre>
### getFreeDrawSvgPath
@@ -149,7 +149,7 @@ import { getFreeDrawSvgPath } from "@excalidraw/excalidraw";
**Signature**
<pre>
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
</pre>
### isLinearElement
@@ -164,9 +164,9 @@ import { isLinearElement } from "@excalidraw/excalidraw";
**Signature**
```tsx
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L80">ExcalidrawElement</a>): boolean
```
<pre>
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L80">ExcalidrawElement</a>): boolean
</pre>
### getNonDeletedElements
@@ -181,7 +181,7 @@ import { getNonDeletedElements } from "@excalidraw/excalidraw";
**Signature**
<pre>
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
</pre>
### mergeLibraryItems
@@ -195,8 +195,10 @@ import { mergeLibraryItems } from "@excalidraw/excalidraw";
**_Signature_**
<pre>
mergeLibraryItems(localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>,<br/>&nbsp;
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a>) => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>
mergeLibraryItems(<br/>&nbsp;
localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>,<br/>&nbsp;
otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems</a><br/>
): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>
</pre>
### parseLibraryTokensFromUrl
@@ -237,8 +239,8 @@ export const App = () => {
<pre>
useHandleLibrary(opts: &#123;<br/>&nbsp;
excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L494">ExcalidrawAPI</a>,<br/>&nbsp;
getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L253">LibraryItemsSource</a><br/>
excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L494">ExcalidrawAPI</a>,<br/>&nbsp;
getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L253">LibraryItemsSource</a><br/>
});
</pre>
@@ -251,7 +253,7 @@ This function returns the current `scene` version.
**_Signature_**
<pre>
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>)
getSceneVersion(elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>)
</pre>
**How to use**
@@ -272,7 +274,7 @@ import { sceneCoordsToViewportCoords } from "@excalidraw/excalidraw";
<pre>
sceneCoordsToViewportCoords(&#123; sceneX: number, sceneY: number },<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): &#123; x: number, y: number }
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): &#123; x: number, y: number }
</pre>
### viewportCoordsToSceneCoords
@@ -287,10 +289,10 @@ import { viewportCoordsToSceneCoords } from "@excalidraw/excalidraw";
<pre>
viewportCoordsToSceneCoords(&#123; clientX: number, clientY: number },<br/>&nbsp;
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): &#123;x: number, y: number}
appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): &#123;x: number, y: number}
</pre>
### useDevice
### useEditorInterface
This hook can be used to check the type of device which is being used. It can only be used inside the `children` of `Excalidraw` component.
@@ -298,8 +300,8 @@ Open the `main menu` in the below example to view the footer.
```jsx live noInline
const MobileFooter = ({}) => {
const device = useDevice();
if (device.isMobile) {
const editorInterface = useEditorInterface();
if (editorInterface.formFactor === "phone") {
return (
<Footer>
<button
@@ -331,14 +333,23 @@ const App = () => (
render(<App />);
```
The `device` has the following `attributes`
The `device` has the following `attributes`, some grouped into `viewport` and `editor` objects, per context.
| Name | Type | Description |
| --- | --- | --- |
| `isSmScreen` | `boolean` | Set to `true` when the device small screen is small (Width < `640px` ) |
| `isMobile` | `boolean` | Set to `true` when the device is `mobile` |
| `isTouchScreen` | `boolean` | Set to `true` for `touch` devices |
| `canDeviceFitSidebar` | `boolean` | Implies whether there is enough space to fit the `sidebar` |
| ---- | ---- | ----------- |
The `EditorInterface` object has the following properties:
| Name | Type | Description |
| --- | --- | --- | --- | --- | --- |
| `formFactor` | `'phone' | 'tablet' | 'desktop'` | Indicates the device type based on screen size |
| `desktopUIMode` | `'compact' | 'full'` | UI mode for desktop form factor |
| `userAgent.raw` | `string` | Raw user agent string |
| `userAgent.isMobileDevice` | `boolean` | True if device is mobile |
| `userAgent.platform` | `'ios' | 'android' | 'other' | 'unknown'` | Device platform |
| `isTouchScreen` | `boolean` | True if touch events are detected |
| `canFitSidebar` | `boolean` | True if sidebar can fit in the viewport |
| `isLandscape` | `boolean` | True if viewport is in landscape mode |
### i18n
@@ -347,8 +358,8 @@ To help with localization, we export the following.
| name | type |
| --- | --- |
| `defaultLang` | `string` |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
```js
import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
@@ -383,3 +394,94 @@ function App() {
);
}
```
### getCommonBounds
This util can be used to get the common bounds of the passed elements.
**_Signature_**
```ts
getCommonBounds(
elements: readonly ExcalidrawElement[]
): readonly [
minX: number,
minY: number,
maxX: number,
maxY: number,
]
```
**_How to use_**
```js
import { getCommonBounds } from "@excalidraw/excalidraw";
```
### elementsOverlappingBBox
To filter `elements` that are inside, overlap, or contain the `bounds` rectangle.
The bounds check is approximate and does not precisely follow the element's shape. You can also supply `errorMargin` which effectively makes the `bounds` larger by that amount.
This API has 3 `type`s of operation: `overlap`, `contain`, and `inside`:
- `overlap` - filters elements that are overlapping or inside bounds.
- `contain` - filters elements that are inside bounds or bounds inside elements.
- `inside` - filters elements that are inside bounds.
**_Signature_**
<pre>
elementsOverlappingBBox(<br/>&nbsp;
elements: readonly NonDeletedExcalidrawElement[];<br/>&nbsp;
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a> | ExcalidrawElement;<br/>&nbsp;
errorMargin?: number;<br/>&nbsp;
type: "overlap" | "contain" | "inside";<br/>
): NonDeletedExcalidrawElement[];
</pre>
**_How to use_**
```js
import { elementsOverlappingBBox } from "@excalidraw/excalidraw";
```
### isElementInsideBBox
Lower-level API than `elementsOverlappingBBox` to check if a single `element` is inside `bounds`. If `eitherDirection=true`, returns `true` if `element` is fully inside `bounds` rectangle, or vice versa. When `false`, it returns `true` only for the former case.
**_Signature_**
<pre>
isElementInsideBBox(<br/>&nbsp;
element: NonDeletedExcalidrawElement,<br/>&nbsp;
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>&nbsp;
eitherDirection = false,<br/>
): boolean
</pre>
**_How to use_**
```js
import { isElementInsideBBox } from "@excalidraw/excalidraw";
```
### elementPartiallyOverlapsWithOrContainsBBox
Checks if `element` is overlapping the `bounds` rectangle, or is fully inside.
**_Signature_**
<pre>
elementPartiallyOverlapsWithOrContainsBBox(<br/>&nbsp;
element: NonDeletedExcalidrawElement,<br/>&nbsp;
bounds: <a href="https://github.com/excalidraw/excalidraw/blob/9c425224c789d083bf16e0597ce4a429b9ee008e/src/element/bounds.ts#L37-L42">Bounds</a>,<br/>
): boolean
</pre>
**_How to use_**
```js
import { elementPartiallyOverlapsWithOrContainsBBox } from "@excalidraw/excalidraw";
```
@@ -1,6 +1,6 @@
# Customizing Styles
Excalidraw is using CSS variables to style certain components. To override them, you should set your own on the `.excalidraw` and `.excalidraw.theme--dark` (for dark mode variables) selectors.
Excalidraw uses CSS variables to style certain components. To override them, you should set your own on the `.excalidraw` and `.excalidraw.theme--dark` (for dark mode variables) selectors.
Make sure the selector has higher specificity, e.g. by prefixing it with your app's selector:
@@ -21,7 +21,7 @@ Most notably, you can customize the primary colors, by overriding these variable
- `--color-primary-light`
- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/css/theme.scss), though most of them will not make sense to override.
```css showLineNumbers
.custom-styles .excalidraw {
@@ -13,47 +13,27 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
1. Install the dependencies
```bash
cd src/packages/excalidraw && yarn
yarn
```
2. Start the example app
```bash
yarn start
yarn start:example
```
[http://localhost:3001](http://localhost:3001) will open in your default browser.
The example is same as the [codesandbox example](https://ehlz3.csb.app/)
This is the same example as the [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
## Releasing
### Create a test release
You can create a test release by posting the below comment in your pull request:
```bash
@excalibot trigger release
```
Once the version is released `@excalibot` will post a comment with the release version.
### Creating a production release
To release the next stable version follow the below steps:
```bash
yarn prerelease version
yarn release --tag=latest --version=0.19.0
```
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
The next step is to run the `release` script:
```bash
yarn release
```
This will publish the package.
Right now there are two steps to create a production release but once this works fine these scripts will be combined and more automation will be done.
You will need to pass the `latest` tag with `version` for which you want to create the release. This will make the changes needed before publishing the packages into NPM, like updating dependencies of all `@excalidraw/*` packages, generating new entries in `CHANGELOG.md` and more.
+13 -4
View File
@@ -2,7 +2,7 @@
### Does this package support collaboration ?
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/src/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation [here](https://github.com/excalidraw/excalidraw/blob/master/excalidraw-app/index.tsx). Here is a [detailed answer](https://github.com/excalidraw/excalidraw/discussions/3879#discussioncomment-1110524) on how you can achieve the same.
### Turning off Aggressive Anti-Fingerprinting in Brave browser
@@ -17,11 +17,9 @@ We strongly recommend turning it off. You can follow the steps below on how to d
<div style={{width:'30rem'}}>
2. Once opened, look for **Aggressively Block Fingerprinting**
![Aggresive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
![Aggressive block fingerprinting](../../assets/aggressive-block-fingerprint.png)
3. Switch to **Block Fingerprinting**
![Block filtering](../../assets/block-fingerprint.png)
4. Thats all. All text elements should be fixed now 🎉
@@ -31,6 +29,17 @@ We strongly recommend turning it off. You can follow the steps below on how to d
If disabling this setting doesn't fix the display of text elements, please consider opening an [issue](https://github.com/excalidraw/excalidraw/issues/new) on our GitHub, or message us on [Discord](https://discord.gg/UexuTaE).
### ReferenceError: process is not defined
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
```
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
},
```
## Need help?
@@ -1,16 +1,12 @@
# Installation
**Excalidraw** is published to npm as a component you can directly embed in your projects.
**Excalidraw** is exported as a component to be directly embedded in your project.
Using `npm`:
Use `npm` or `yarn` to install the package.
```bash
npm install react react-dom @excalidraw/excalidraw
```
or `yarn`:
```bash
# or
yarn add react react-dom @excalidraw/excalidraw
```
@@ -20,24 +16,40 @@ yarn add react react-dom @excalidraw/excalidraw
:::
### Static assets
### Self-hosting fonts
Excalidraw depends on assets such as localization files (if you opt to use them), fonts, and others.
By default, Excalidraw will try to download all the used fonts from the [CDN](https://esm.run/@excalidraw/excalidraw/dist/prod).
By default these assets are loaded from a public CDN [`https://unpkg.com/@excalidraw/excalidraw/dist/`](https://unpkg.com/@excalidraw/excalidraw/dist), so you don't need to do anything on your end.
However, if you want to host these files yourself, you can find them in your `node_modules/@excalidraw/excalidraw/dist` directory, in folders `excalidraw-assets` (for production) and `excalidraw-assets-dev` (for development).
Copy these folders to your static assets directory, and add a `window.EXCALIDRAW_ASSET_PATH` variable in your `index.html` or `index.js` entry file pointing to your public assets path (relative). For example, if you serve your assets from the root of your hostname, you would do:
For self-hosting purposes, you'll have to copy the content of the folder `node_modules/@excalidraw/excalidraw/dist/prod/fonts` to the path where your assets should be served from (i.e. `public/` directory in your project). In that case, you should also set `window.EXCALIDRAW_ASSET_PATH` to the very same path, i.e. `/` in case it's in the root:
```js
window.EXCALIDRAW_ASSET_PATH = "/";
```
or, if you serve your assets from the root of your CDN, you would do:
```js
// Vanilla
<head>
<script>
window.EXCALIDRAW_ASSET_PATH = "https://my.cdn.com/assets/";
</script>
</head>
```
or, if you prefer the path to be dynamicly set based on the `location.origin`, you could do the following:
```jsx
// Next.js
<Script id="load-env-variables" strategy="beforeInteractive" >
{ `window["EXCALIDRAW_ASSET_PATH"] = location.origin;` } // or use just "/"!
</Script>
```
### Dimensions of Excalidraw
Excalidraw takes _100%_ of `width` and `height` of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.
### Demo
## Demo
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example.
@@ -12,7 +12,7 @@ import { Excalidraw } from "@excalidraw/excalidraw";
Throughout the documentation we use live, editable Excalidraw examples like the one shown below.
While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
While we aim for the examples to closely reflect what you'd get if you rendered it yourself, we actually initialize it with some props behind the scenes.
For example, we're passing a `theme` prop to it based on the current color theme of the docs you're just reading.
:::
@@ -30,48 +30,137 @@ function App() {
}
```
### Rendering Excalidraw only on client
### Next.js
Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
Since Excalidraw doesn't support `server side rendering` so it should be rendered only on `client`. The way to achieve this in next.js is using `next.js dynamic import`.
The following worfklow shows one way how to render Excalidraw on Next.js. We'll add more detailed and alternative Next.js examples, soon.
If you want to only import `Excalidraw` component you can do :point_down:
```jsx showLineNumbers
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import "@excalidraw/excalidraw/index.css";
const Excalidraw = dynamic(
async () => (await import("@excalidraw/excalidraw")).Excalidraw,
{
ssr: false,
},
);
export default function App() {
const [Excalidraw, setExcalidraw] = useState(null);
useEffect(() => {
import("@excalidraw/excalidraw").then((comp) => setExcalidraw(comp.Excalidraw));
}, []);
return <>{Excalidraw && <Excalidraw />}</>;
return <Excalidraw />;
}
```
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
However the above component only works for named component exports. If you want to import some util / constant or something else apart from Excalidraw, then this approach will not work. Instead you can write a wrapper over Excalidraw and import the wrapper dynamically.
If you are using `pages router` then importing the wrapper dynamically would work, where as if you are using `app router` then you will have to also add `useClient` directive on top of the file in addition to dynamically importing the wrapper as shown :point_down:
<Tabs>
<TabItem value="Excalidraw Wrapper" label="Excalidraw Wrapper" >
```jsx showLineNumbers
"use client";
import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
const ExcalidrawWrapper: React.FC = () => {
console.info(convertToExcalidrawElements([{
type: "rectangle",
id: "rect-1",
width: 186.47265625,
height: 141.9765625,
},]));
return (
<div style={{height:"500px", width:"500px"}}>
<Excalidraw />
</div>
);
};
export default ExcalidrawWrapper;
```
</TabItem>
<TabItem value="pages" label="Pages router">
```jsx showLineNumbers
import dynamic from "next/dynamic";
// Since client components get prerenderd on server as well hence importing
// the excalidraw stuff dynamically with ssr false
const ExcalidrawWrapper = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<ExcalidrawWrapper />
);
}
```
</TabItem>
<TabItem value="app" label="App router">
```jsx showLineNumbers
import dynamic from "next/dynamic";
// Since client components get prerenderd on server as well hence importing
// the excalidraw stuff dynamically with ssr false
const ExcalidrawWrapper = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<ExcalidrawWrapper />
);
}
```
</TabItem>
</Tabs>
{/* Link should be updated to point to the latest! */}
Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs.vercel.app/).
The `types` are available at `@excalidraw/excalidraw/types`, check [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/master/examples/with-script-in-browser) example for details.
### Preact
Since we support `umd` build ships with `react/jsx-runtime` and `react-dom/client` inlined with the package. This conflicts with `Preact` and hence the build doesn't work directly with `Preact`.
However we have shipped a separate build for `Preact` so if you are using `Preact` you need to set `process.env.IS_PREACT` to `true` to use the `Preact` build.
Once the above `env` variable is set, you will be able to use the package in `Preact` as well.
:::info
When using `vite` or any build tools, you will have to make sure the `process` is accessible as we are accessing `process.env.IS_PREACT` to decide whether to use `preact` build.
Since Vite removes env variables by default, you can update the vite config to ensure its available :point_down:
```
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
},
```
:::
## Browser
To use it in a browser directly:
To use it Excalidraw in a browser directly, use the following setup :point_down:
For development use :point_down:
```js
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>
```
For production use :point_down:
```js
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"
></script>
```
You will need to make sure `react`, `react-dom` is available as shown in the below example. For prod please use the production versions of `react`, `react-dom`.
> **Note**: We rely on import maps to de-duplicate `react`, `react-dom` and `react/jsx-runtime` versions.
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
@@ -85,13 +174,23 @@ import TabItem from "@theme/TabItem";
<head>
<title>Excalidraw in browser</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
<script
type="text/javascript"
src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>
<link
rel="stylesheet"
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
/>
<link rel="stylesheet" href="./index.css" />
<script>
window.EXCALIDRAW_ASSET_PATH = "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
</script>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@19.0.0",
"react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime",
"react-dom": "https://esm.sh/react-dom@19.0.0"
}
}
</script>
</head>
<body>
@@ -99,7 +198,7 @@ import TabItem from "@theme/TabItem";
<h1>Excalidraw Embed Example</h1>
<div id="app"></div>
</div>
<script type="text/javascript" src="src/index.js"></script>
<script type="text/javascript" src="packages/excalidraw/index.js"></script>
</body>
</html>
```
@@ -108,6 +207,14 @@ import TabItem from "@theme/TabItem";
<TabItem value="js" label="Javascript">
```js showLineNumbers
// See https://www.npmjs.com/package/@excalidraw/excalidraw documentation.
import * as ExcalidrawLib from 'https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.js?external=react,react-dom';
import React from "https://esm.sh/react@19.0.0";
import ReactDOM from "https://esm.sh/react-dom@19.0.0"
window.ExcalidrawLib = ExcalidrawLib;
console.log("Excalidraw library", ExcalidrawLib);
const App = () => {
return React.createElement(
React.Fragment,
@@ -129,3 +236,5 @@ root.render(React.createElement(App));
</TabItem>
</Tabs>
You can try it out [here](https://jsfiddle.net/vfn6dm14/3/).
@@ -14,7 +14,7 @@ This API receives the mermaid syntax as the input, and resolves to skeleton Exca
import { parseMermaidToExcalidraw } from "@excalidraw/mermaid-to-excalidraw";
import { convertToExcalidrawElements} from "@excalidraw/excalidraw"
try {
const { elements, files } = await parseMermaid(mermaidSyntax: string, {
const { elements, files } = await parseMermaidToExcalidraw(mermaidSyntax: string, {
fontSize: number,
});
const excalidrawElements = convertToExcalidrawElements(elements);
@@ -38,9 +38,9 @@ Add the diagram type in switch case in [`parseMermaid`](https://github.com/excal
## Writing the Excalidraw Skeleton Convertor
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) format.
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) format.
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133).
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133).
Thats it, you have added the new diagram type 🥳, now lets test it out!
@@ -6,7 +6,7 @@ In this section we will be diving into how the [flowchart parser](https://github
![image](https://github.com/excalidraw/excalidraw/assets/11256141/2a097bbb-64bf-49d6-bf7f-21172bdb538d)
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
For computing `vertices` and `edge`s lets consider the below svg generated by mermaid
@@ -42,7 +42,7 @@ Considering the same example this is the response from the API
}
}
```
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
The final output from `parseVertex` looks like :point_down:
@@ -55,11 +55,11 @@ If you want to understand how flowchart parser works, you can navigate to [Flowc
## Converting to ExcalidrawElementSkeleton
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton.
For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton.
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
![image](https://github.com/excalidraw/excalidraw/assets/11256141/00226e9d-043d-4a08-989a-3ad9d2a574f1)
+22
View File
@@ -0,0 +1,22 @@
# Frames
## Ordering
Frames should be ordered where frame children come first, followed by the frame element itself:
```
[
other_element,
frame1_child1,
frame1_child2,
frame1,
other_element,
frame2_child1,
frame2_child2,
frame2,
other_element,
...
]
```
If not ordered correctly, the editor will still function, but the elements may not be rendered and clipped correctly. Further, the renderer relies on this ordering for performance optimizations.
+13 -1
View File
@@ -43,7 +43,7 @@ When saving an Excalidraw scene locally to a file, the JSON file (`.excalidraw`)
// editor state (canvas config, preferences, ...)
"appState": {
"gridSize": null,
"gridSize": 20,
"viewBackgroundColor": "#ffffff"
},
@@ -62,6 +62,18 @@ When saving an Excalidraw scene locally to a file, the JSON file (`.excalidraw`)
}
```
### Element creation time
Every normalized element has a `created: number | null` field. A number is the client's wall-clock time in epoch milliseconds when this element instance was created. `null` means the creation time is unknown; restoring an older file does not substitute its load time or last-update time.
| Operation | `created` |
| --- | --- |
| Construct a new element | Current time, unless explicitly supplied (including `null`) |
| Duplicate, paste, or insert from a library | New time for the new instance |
| Convert element skeletons with ID regeneration | New time for each resulting instance |
| Restore/open/import an existing scene | Preserve the carried value; missing values become `null` |
| Edit, change type while retaining the ID, capture, undo, or redo | Preserve the value |
## Excalidraw clipboard format
When copying selected excalidraw elements to clipboard, the JSON schema is similar to `.excalidraw` format, except it differs in attributes.
+1 -21
View File
@@ -9,13 +9,11 @@ In case you want to pick up something from the roadmap, comment on that issue an
## Setup
### Option 1 - Manual
1. Fork and clone the repo
1. Run `yarn` to install dependencies
1. Create a branch for your PR with `git checkout -b your-branch-name`
> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork. To do this, run:
> To keep `master` branch pointing to remote repository and make pull requests from branches on your fork, run:
>
> ```bash
> git remote add upstream https://github.com/excalidraw/excalidraw.git
@@ -23,15 +21,6 @@ In case you want to pick up something from the roadmap, comment on that issue an
> git branch --set-upstream-to=upstream/master master
> ```
### Option 2 - CodeSandbox
1. Go to https://codesandbox.io/p/github/excalidraw/excalidraw
1. Connect your GitHub account
1. Go to Git tab on left side
1. Tap on `Fork Sandbox`
1. Write your code
1. Commit and PR automatically
## Pull Request Guidelines
Don't worry if you get any of the below wrong, or if you don't know how. We'll gladly help out.
@@ -52,15 +41,6 @@ Make sure the title starts with a semantic prefix:
- **chore**: Other changes that don't modify src or test files
- **revert**: Reverts a previous commit
### Changelog
Add a brief description of your pull request to the changelog located here: [changelog](https://github.com/excalidraw/excalidraw/blob/master/CHANGELOG.md)
Notes:
- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title
- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request
### Testing
Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.
@@ -1,11 +1,5 @@
# Development
## Code Sandbox
- Go to https://codesandbox.io/p/github/excalidraw/excalidraw
- You may need to sign in with GitHub and reload the page
- You can start coding instantly, and even send PRs from there!
## Local Installation
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
+42 -9
View File
@@ -1,6 +1,11 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
// Set the env variable to false so the excalidraw npm package doesn't throw
// process undefined as docusaurus doesn't expose env variables by default
process.env.IS_PREACT = "false";
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Excalidraw developer docs",
@@ -36,10 +41,7 @@ const config = {
showLastUpdateTime: true,
},
theme: {
customCss: [
require.resolve("./src/css/custom.scss"),
require.resolve("../src/packages/excalidraw/example/App.scss"),
],
customCss: [require.resolve("./src/css/custom.scss")],
},
}),
],
@@ -64,7 +66,7 @@ const config = {
label: "Docs",
},
{
to: "https://blog.excalidraw.com",
to: "https://plus.excalidraw.com/blog",
label: "Blog",
position: "left",
},
@@ -95,8 +97,8 @@ const config = {
href: "https://discord.gg/UexuTaE",
},
{
label: "Twitter",
href: "https://twitter.com/excalidraw",
label: "𝕏",
href: "https://x.com/excalidraw",
},
{
label: "Linkedin",
@@ -109,7 +111,7 @@ const config = {
items: [
{
label: "Blog",
to: "https://blog.excalidraw.com",
to: "https://plus.excalidraw.com/blog",
},
{
label: "GitHub",
@@ -139,7 +141,38 @@ const config = {
},
}),
themes: ["@docusaurus/theme-live-codeblock"],
plugins: ["docusaurus-plugin-sass"],
plugins: [
"docusaurus-plugin-sass",
[
"docusaurus2-dotenv",
{
systemvars: true,
},
],
function () {
return {
name: "disable-fully-specified-error",
configureWebpack() {
return {
module: {
rules: [
{
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
},
],
},
optimization: {
// disable terser minification
minimize: false,
},
};
},
};
},
],
};
module.exports = config;
+4 -3
View File
@@ -18,18 +18,19 @@
"@docusaurus/core": "2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@docusaurus/theme-live-codeblock": "2.2.0",
"@excalidraw/excalidraw": "0.15.2-eb020d0",
"@excalidraw/excalidraw": "0.18.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"docusaurus-plugin-sass": "0.2.3",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"sass": "1.57.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-rc.1",
"@tsconfig/docusaurus": "^1.0.5",
"docusaurus2-dotenv": "1.4.0",
"typescript": "^4.7.4"
},
"browserslist": {
+6 -2
View File
@@ -23,7 +23,11 @@ const sidebars = {
},
items: ["introduction/development", "introduction/contributing"],
},
{ type: "category", label: "Codebase", items: ["codebase/json-schema"] },
{
type: "category",
label: "Codebase",
items: ["codebase/json-schema", "codebase/frames"],
},
{
type: "category",
label: "@excalidraw/excalidraw",
@@ -49,7 +53,7 @@ const sidebars = {
},
items: [
"@excalidraw/excalidraw/api/props/initialdata",
"@excalidraw/excalidraw/api/props/ref",
"@excalidraw/excalidraw/api/props/excalidraw-api",
"@excalidraw/excalidraw/api/props/render-props",
"@excalidraw/excalidraw/api/props/ui-options",
],
+3 -2
View File
@@ -1,5 +1,6 @@
import React from "react";
import clsx from "clsx";
import React from "react";
import styles from "./styles.module.css";
const FeatureList = [
@@ -15,7 +16,7 @@ const FeatureList = [
Svg: require("@site/static/img/undraw_blank_canvas.svg").default,
description: (
<>
Want to build your own app powered by Excalidraw by don't know where to
Want to build your own app powered by Excalidraw but don't know where to
start?
</>
),
+2 -1
View File
@@ -1,5 +1,6 @@
import React from "react";
import clsx from "clsx";
import React from "react";
import styles from "./styles.module.css";
type FeatureItem = {
+1 -1
View File
@@ -59,7 +59,7 @@ pre a {
padding: 5px;
background: #70b1ec;
color: white;
font-weight: bold;
font-weight: 700;
border: none;
}
+5 -4
View File
@@ -1,10 +1,11 @@
import React from "react";
import clsx from "clsx";
import Layout from "@theme/Layout";
import Link from "@docusaurus/Link";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import styles from "./index.module.css";
import HomepageFeatures from "@site/src/components/Homepage";
import Layout from "@theme/Layout";
import clsx from "clsx";
import React from "react";
import styles from "./index.module.css";
function HomepageHeader() {
const { siteConfig } = useDocusaurusContext();
+1 -1
View File
@@ -1,6 +1,6 @@
// Import the original mapper
import MDXComponents from "@theme-original/MDXComponents";
import Highlight from "@site/src/components/Highlight";
import MDXComponents from "@theme-original/MDXComponents";
export default {
// Re-use the default mapping
@@ -3,11 +3,18 @@ import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment";
import initialData from "@site/src/initialData";
import { useColorMode } from "@docusaurus/theme-common";
import "@excalidraw/excalidraw/index.css";
let ExcalidrawComp = {};
if (ExecutionEnvironment.canUseDOM) {
ExcalidrawComp = require("@excalidraw/excalidraw");
}
const Excalidraw = React.forwardRef((props, ref) => {
if (!window.EXCALIDRAW_ASSET_PATH) {
window.EXCALIDRAW_ASSET_PATH =
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
}
const { colorMode } = useColorMode();
return <ExcalidrawComp.Excalidraw theme={colorMode} {...props} ref={ref} />;
});
@@ -26,6 +33,7 @@ const ExcalidrawScope = {
initialData,
useI18n: ExcalidrawComp.useI18n,
convertToExcalidrawElements: ExcalidrawComp.convertToExcalidrawElements,
CaptureUpdateAction: ExcalidrawComp.CaptureUpdateAction,
};
export default ExcalidrawScope;
+10
View File
@@ -0,0 +1,10 @@
{
"git": {
"deploymentEnabled": {
"master": true,
"**": false
}
},
"outputDirectory": "build",
"installCommand": "yarn install"
}
+1609 -47
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,5 +1,3 @@
version: "3.8"
services:
excalidraw:
build:
+39
View File
@@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# copied assets
public/**/*.woff2
+36
View File
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3005) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+12
View File
@@ -0,0 +1,12 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
distDir: "build",
typescript: {
// The ts config doesn't work with `jsx: preserve" and if updated to `react-jsx` it gets ovewritten by next js throwing ts errors hence I am ignoring build errors until this is fixed.
ignoreBuildErrors: true,
},
// This is needed as in pages router the code for importing types throws error as its outside next js app
transpilePackages: ["../"],
};
module.exports = nextConfig;
+26
View File
@@ -0,0 +1,26 @@
{
"name": "with-nextjs",
"version": "0.1.0",
"private": true,
"scripts": {
"build:packages": "yarn --cwd ../../ build:packages",
"build:workspace": "yarn build:packages && yarn copy:assets",
"copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
"dev": "yarn build:workspace && next dev -p 3005",
"build": "yarn build:workspace && next build",
"start": "next start -p 3006",
"lint": "next lint"
},
"dependencies": {
"next": "14.1",
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"path2d-polyfill": "2.0.1",
"typescript": "^5"
}
}

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+11
View File
@@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
+27
View File
@@ -0,0 +1,27 @@
import dynamic from "next/dynamic";
import Script from "next/script";
import "../common.scss";
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
// with ssr false
const ExcalidrawWithClientOnly = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<>
<a href="/excalidraw-in-pages">Switch to Pages router</a>
<h1 className="page-title">App Router</h1>
<Script id="load-env-variables" strategy="beforeInteractive">
{`window["EXCALIDRAW_ASSET_PATH"] = window.origin;`}
</Script>
{/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
<ExcalidrawWithClientOnly />
</>
);
}
+15
View File
@@ -0,0 +1,15 @@
* {
box-sizing: border-box;
font-family: sans-serif;
}
a {
color: #1c7ed6;
font-size: 20px;
text-decoration: none;
font-weight: 500;
}
.page-title {
text-align: center;
}
@@ -0,0 +1,23 @@
"use client";
import * as excalidrawLib from "@excalidraw/excalidraw";
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
import App from "../../with-script-in-browser/components/ExampleApp";
const ExcalidrawWrapper: React.FC = () => {
return (
<>
<App
appTitle={"Excalidraw with Nextjs Example"}
useCustom={(api: any, args?: any[]) => {}}
excalidrawLib={excalidrawLib}
>
<Excalidraw />
</App>
</>
);
};
export default ExcalidrawWrapper;
@@ -0,0 +1,23 @@
import dynamic from "next/dynamic";
import "../common.scss";
// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
// with ssr false
const Excalidraw = dynamic(
async () => (await import("../excalidrawWrapper")).default,
{
ssr: false,
},
);
export default function Page() {
return (
<>
<a href="/">Switch to App router</a>
<h1 className="page-title">Pages Router</h1>
{/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
<Excalidraw />
</>
);
}
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
},
"forceConsistentCasingInFileNames": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "build/types/**/*.ts"],
"exclude": ["node_modules"]
}
+9
View File
@@ -0,0 +1,9 @@
{
"git": {
"deploymentEnabled": {
"master": true,
"**": false
}
},
"outputDirectory": "build"
}
+252
View File
@@ -0,0 +1,252 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@excalidraw/excalidraw@workspace:^":
version "0.17.2"
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.2.tgz#9a636a1e6bb3c88c5883347d3a7e75e9cce8ab96"
integrity sha512-7pqUWD8+mPjDhF4XxG3gw4rvE2JGaLW3Vss5UZfTbITPxAtFaGEc1K081bncitnaYhUwN9ENJE0i87QB3poDwQ==
"@next/env@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a"
integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==
"@next/swc-darwin-arm64@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618"
integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==
"@next/swc-darwin-x64@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b"
integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==
"@next/swc-linux-arm64-gnu@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21"
integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==
"@next/swc-linux-arm64-musl@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd"
integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==
"@next/swc-linux-x64-gnu@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32"
integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==
"@next/swc-linux-x64-musl@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247"
integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==
"@next/swc-win32-arm64-msvc@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3"
integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==
"@next/swc-win32-ia32-msvc@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600"
integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==
"@next/swc-win32-x64-msvc@14.0.4":
version "14.0.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1"
integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==
"@swc/helpers@0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
dependencies:
tslib "^2.4.0"
"@types/node@^20":
version "20.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
dependencies:
undici-types "~5.26.4"
"@types/prop-types@*":
version "15.7.11"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
"@types/react-dom@^18":
version "18.2.18"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18":
version "18.2.47"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40"
integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.8"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff"
integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==
busboy@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
dependencies:
streamsearch "^1.1.0"
caniuse-lite@^1.0.30001406:
version "1.0.30001576"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4"
integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==
client-only@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
graceful-fs@^4.1.2, graceful-fs@^4.2.11:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
nanoid@^3.3.6:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
next@14.0.4:
version "14.0.4"
resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc"
integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==
dependencies:
"@next/env" "14.0.4"
"@swc/helpers" "0.5.2"
busboy "1.6.0"
caniuse-lite "^1.0.30001406"
graceful-fs "^4.2.11"
postcss "8.4.31"
styled-jsx "5.1.1"
watchpack "2.4.0"
optionalDependencies:
"@next/swc-darwin-arm64" "14.0.4"
"@next/swc-darwin-x64" "14.0.4"
"@next/swc-linux-arm64-gnu" "14.0.4"
"@next/swc-linux-arm64-musl" "14.0.4"
"@next/swc-linux-x64-gnu" "14.0.4"
"@next/swc-linux-x64-musl" "14.0.4"
"@next/swc-win32-arm64-msvc" "14.0.4"
"@next/swc-win32-ia32-msvc" "14.0.4"
"@next/swc-win32-x64-msvc" "14.0.4"
path2d-polyfill@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391"
integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@8.4.31:
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
react-dom@^18:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react@^18:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
streamsearch@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
styled-jsx@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
dependencies:
client-only "0.0.1"
tslib@^2.4.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
typescript@^5:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
watchpack@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
dependencies:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
@@ -0,0 +1,5 @@
FROM node:18-bullseye
# Vite wants to open the browser using `open`, so we
# need to install those utils.
RUN apt update -y && apt install -y xdg-utils
@@ -0,0 +1,35 @@
{
// These tasks will run in order when initializing your CodeSandbox project.
"setupTasks": [
{
"name": "Install Dependencies",
"command": "yarn install"
}
],
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
"tasks": {
"build": {
"name": "Build",
"command": "yarn build",
"runAtStart": false
},
"start": {
"name": "Start Example",
"command": "yarn start",
"runAtStart": true,
"preview": {
"port": 3001
}
},
"install-deps": {
"name": "Install Dependencies",
"command": "yarn install",
"restartOn": {
"files": ["yarn.lock"],
"branch": false,
"resume": false
}
}
}
}
@@ -0,0 +1,2 @@
# copied assets
public/**/*.woff2
@@ -1,6 +1,7 @@
import { ExcalidrawImperativeAPI } from "../../../types";
import { MIME_TYPES } from "../entry";
import { Button } from "../../../components/Button";
import React from "react";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
const COMMENT_SVG = (
<svg
@@ -18,24 +19,28 @@ const COMMENT_SVG = (
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
</svg>
);
const CustomFooter = ({
excalidrawAPI,
excalidrawLib,
}: {
excalidrawAPI: ExcalidrawImperativeAPI;
excalidrawLib: typeof TExcalidraw;
}) => {
const { Button, MIME_TYPES } = excalidrawLib;
return (
<>
<Button
onSelect={() => alert("General Kenobi!")}
className="you are a bold one"
style={{ marginLeft: "1rem" }}
style={{ marginLeft: "1rem", width: "auto" }}
title="Hello there!"
>
{COMMENT_SVG}
Hit me
</Button>
<button
<Button
className="custom-element"
onClick={() => {
onSelect={() => {
excalidrawAPI?.setActiveTool({
type: "custom",
customType: "comment",
@@ -58,15 +63,10 @@ const CustomFooter = ({
)}`;
excalidrawAPI?.setCursor(`url(${url}), auto`);
}}
title="Comments!"
>
{COMMENT_SVG}
</button>
<button
className="custom-footer"
onClick={() => alert("This is dummy footer")}
>
custom footer
</button>
</Button>
</>
);
};
@@ -15,14 +15,23 @@
border-radius: 50%;
}
}
.app-title {
margin-block-start: 0.83em;
margin-block-end: 0.83em;
}
}
.button-wrapper button {
z-index: 1;
height: 40px;
max-width: 200px;
margin: 10px;
padding: 5px;
.button-wrapper {
input[type="checkbox"] {
margin: 5px;
}
button {
z-index: 1;
height: 40px;
max-width: 200px;
margin: 10px;
padding: 5px;
}
}
.excalidraw .App-menu_top .buttonList {
@@ -43,7 +52,7 @@
transform: none;
}
.excalidraw .panelColumn {
.excalidraw .selected-shape-actions {
text-align: left;
}
@@ -1,23 +1,20 @@
import { useEffect, useState, useRef, useCallback } from "react";
import ExampleSidebar from "./sidebar/ExampleSidebar";
import type * as TExcalidraw from "../index";
import "./App.scss";
import initialData from "./initialData";
import { nanoid } from "nanoid";
import {
resolvablePromise,
ResolvablePromise,
withBatchedUpdates,
withBatchedUpdatesThrottled,
} from "../../../utils";
import { EVENT, ROUNDNESS } from "../../../constants";
import { distance2d } from "../../../math";
import { fileOpen } from "../../../data/filesystem";
import { loadSceneOrLibraryFromBlob } from "../../utils";
import {
import React, {
useEffect,
useState,
useRef,
useCallback,
Children,
cloneElement,
} from "react";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
import type {
NonDeletedExcalidrawElement,
Theme,
} from "@excalidraw/excalidraw/element/types";
import type {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
@@ -25,18 +22,24 @@ import {
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
} from "../../../types";
import { NonDeletedExcalidrawElement, Theme } from "../../../element/types";
import { ImportedLibraryData } from "../../../data/types";
} from "@excalidraw/excalidraw/types";
import initialData from "../initialData";
import {
resolvablePromise,
distance2d,
fileOpen,
withBatchedUpdates,
withBatchedUpdatesThrottled,
} from "../utils";
import CustomFooter from "./CustomFooter";
import MobileFooter from "./MobileFooter";
import { KEYS } from "../../../keys";
import ExampleSidebar from "./sidebar/ExampleSidebar";
declare global {
interface Window {
ExcalidrawLib: typeof TExcalidraw;
}
}
import "./ExampleApp.scss";
import type { ResolvablePromise } from "../utils";
type Comment = {
x: number;
@@ -57,27 +60,6 @@ type PointerDownState = {
};
};
// This is so that we use the bundled excalidraw.development.js file instead
// of the actual source code
const {
exportToCanvas,
exportToSvg,
exportToBlob,
exportToClipboard,
Excalidraw,
useHandleLibrary,
MIME_TYPES,
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
restoreElements,
Sidebar,
Footer,
WelcomeScreen,
MainMenu,
LiveCollaborationTrigger,
convertToExcalidrawElements,
} = window.ExcalidrawLib;
const COMMENT_ICON_DIMENSION = 32;
const COMMENT_INPUT_HEIGHT = 50;
const COMMENT_INPUT_WIDTH = 150;
@@ -86,18 +68,57 @@ export interface AppProps {
appTitle: string;
useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
customArgs?: any[];
children: React.ReactNode;
excalidrawLib: typeof TExcalidraw;
}
export default function App({ appTitle, useCustom, customArgs }: AppProps) {
export default function ExampleApp({
appTitle,
useCustom,
customArgs,
children,
excalidrawLib,
}: AppProps) {
const {
exportScene,
exportToClipboard,
useHandleLibrary,
MIME_TYPES,
sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords,
restoreElements,
Sidebar,
Footer,
WelcomeScreen,
MainMenu,
LiveCollaborationTrigger,
convertToExcalidrawElements,
TTDDialog,
TTDDialogTrigger,
ROUNDNESS,
loadSceneOrLibraryFromBlob,
} = excalidrawLib;
const appRef = useRef<any>(null);
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [interactive, setInteractive] = useState(true);
const [interactiveLinks, setInteractiveLinks] = useState(false);
const [interactiveEmbeds, setInteractiveEmbeds] = useState(false);
const [interactiveNavigation, setInteractiveNavigation] = useState(false);
const [interactiveBrowserZoom, setInteractiveBrowserZoom] = useState(false);
const [ui, setUi] = useState(true);
const [zenModeEnabled, setZenModeEnabled] = useState(false);
const [gridModeEnabled, setGridModeEnabled] = useState(false);
const [renderScrollbars, setRenderScrollbars] = useState(false);
const [blobUrl, setBlobUrl] = useState<string>("");
const [canvasUrl, setCanvasUrl] = useState<string>("");
const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
const [exportEmbedScene, setExportEmbedScene] = useState(false);
const exportConfig = {
theme: exportWithDarkMode ? "dark" : "light",
padding: 10,
} as const;
const [theme, setTheme] = useState<Theme>("light");
const [disableImageTool, setDisableImageTool] = useState(false);
const [isCollaborating, setIsCollaborating] = useState(false);
const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
{},
@@ -149,8 +170,117 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
};
};
fetchData();
}, [excalidrawAPI]);
}, [excalidrawAPI, convertToExcalidrawElements, MIME_TYPES]);
const renderExcalidraw = (children: React.ReactNode) => {
const Excalidraw: any = Children.toArray(children).find(
(child) =>
React.isValidElement(child) &&
typeof child.type !== "string" &&
//@ts-ignore
child.type.displayName === "Excalidraw",
);
if (!Excalidraw) {
return;
}
const newElement = cloneElement(
Excalidraw,
{
excalidrawAPI: (api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api),
initialData: initialStatePromiseRef.current.promise,
onChange: (
elements: NonDeletedExcalidrawElement[],
state: AppState,
) => {
console.info("Elements :", elements, "State : ", state);
},
onPointerUpdate: (payload: {
pointer: { x: number; y: number };
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => setPointerData(payload),
viewModeEnabled,
interaction: interactive
? true
: {
enabled: {
links: interactiveLinks,
embeds: interactiveEmbeds,
navigation: interactiveNavigation,
browserZoom: interactiveBrowserZoom,
},
},
ui,
zenModeEnabled,
renderScrollbars,
gridModeEnabled,
theme,
name: "Custom name of drawing",
UIOptions: {
canvasActions: {
loadScene: false,
},
tools: { image: !disableImageTool },
},
renderTopRightUI,
onLinkOpen,
onPointerDown,
onScrollChange: rerenderCommentIcons,
validateEmbeddable: true,
},
<>
{excalidrawAPI && (
<Footer>
<CustomFooter
excalidrawAPI={excalidrawAPI}
excalidrawLib={excalidrawLib}
/>
</Footer>
)}
<WelcomeScreen />
<Sidebar name="custom">
<Sidebar.Tabs>
<Sidebar.Header />
<Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
<Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
</Sidebar.Tabs>
</Sidebar>
<Sidebar.Trigger
name="custom"
tab="one"
style={{
position: "absolute",
left: "50%",
transform: "translateX(-50%)",
bottom: "20px",
zIndex: 9999999999999999,
}}
>
Toggle Custom Sidebar
</Sidebar.Trigger>
{renderMenu()}
{excalidrawAPI && (
<TTDDialogTrigger icon={<span>😀</span>}>
Text to diagram
</TTDDialogTrigger>
)}
<TTDDialog
onTextSubmit={async (_) => {
console.info("submit");
// sleep for 2s
await new Promise((resolve) => setTimeout(resolve, 2000));
throw new Error("error, go away now");
// return "dummy";
}}
/>
</>,
);
return newElement;
};
const renderTopRightUI = (isMobile: boolean) => {
return (
<>
@@ -259,10 +389,12 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
return false;
}
await exportToClipboard({
elements: excalidrawAPI.getSceneElements(),
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
type,
data: {
elements: excalidrawAPI.getSceneElements(),
appState: excalidrawAPI.getAppState(),
files: excalidrawAPI.getFiles(),
},
});
window.alert(`Copied to clipboard as ${type} successfully`);
};
@@ -334,8 +466,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
pointerDownState: PointerDownState,
) => {
return withBatchedUpdates((event) => {
window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.onMove);
window.removeEventListener(EVENT.POINTER_UP, pointerDownState.onUp);
window.removeEventListener("pointermove", pointerDownState.onMove);
window.removeEventListener("pointerup", pointerDownState.onUp);
excalidrawAPI?.setActiveTool({ type: "selection" });
const distance = distance2d(
pointerDownState.x,
@@ -399,8 +531,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
onPointerMoveFromPointerDownHandler(pointerDownState);
const onPointerUp =
onPointerUpFromPointerDownHandler(pointerDownState);
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
pointerDownState.onMove = onPointerMove;
pointerDownState.onUp = onPointerUp;
@@ -492,7 +624,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
}}
onBlur={saveComment}
onKeyDown={(event) => {
if (!event.shiftKey && event.key === KEYS.ENTER) {
if (!event.shiftKey && event.key === "Enter") {
event.preventDefault();
saveComment();
}
@@ -525,7 +657,12 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.Help />
{excalidrawAPI && <MobileFooter excalidrawAPI={excalidrawAPI} />}
{excalidrawAPI && (
<MobileFooter
excalidrawLib={excalidrawLib}
excalidrawAPI={excalidrawAPI}
/>
)}
</MainMenu>
);
};
@@ -580,6 +717,52 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
/>
View mode
</label>
<label>
<input
type="checkbox"
checked={!interactive}
onChange={() => setInteractive(!interactive)}
/>
Non-interactive
</label>
<label>
<input
type="checkbox"
checked={interactiveLinks}
onChange={() => setInteractiveLinks(!interactiveLinks)}
/>
Links when non-interactive
</label>
<label>
<input
type="checkbox"
checked={interactiveEmbeds}
onChange={() => setInteractiveEmbeds(!interactiveEmbeds)}
/>
Embeds when non-interactive
</label>
<label>
<input
type="checkbox"
checked={interactiveNavigation}
onChange={() => setInteractiveNavigation(!interactiveNavigation)}
/>
Navigation when non-interactive
</label>
<label>
<input
type="checkbox"
checked={interactiveBrowserZoom}
onChange={() =>
setInteractiveBrowserZoom(!interactiveBrowserZoom)
}
/>
Browser zoom when non-interactive
</label>
<label>
<input type="checkbox" checked={!ui} onChange={() => setUi(!ui)} />
Hide UI
</label>
<label>
<input
type="checkbox"
@@ -596,6 +779,14 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
/>
Grid mode
</label>
<label>
<input
type="checkbox"
checked={renderScrollbars}
onChange={() => setRenderScrollbars(!renderScrollbars)}
/>
Render scrollbars
</label>
<label>
<input
type="checkbox"
@@ -606,6 +797,16 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
/>
Switch to Dark Theme
</label>
<label>
<input
type="checkbox"
checked={disableImageTool === true}
onChange={() => {
setDisableImageTool(!disableImageTool);
}}
/>
Disable Image Tool
</label>
<label>
<input
type="checkbox"
@@ -664,66 +865,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
</div>
</div>
<div className="excalidraw-wrapper">
<Excalidraw
ref={(api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api)}
initialData={initialStatePromiseRef.current.promise}
onChange={(elements, state) => {
console.info("Elements :", elements, "State : ", state);
}}
onPointerUpdate={(payload: {
pointer: { x: number; y: number };
button: "down" | "up";
pointersMap: Gesture["pointers"];
}) => setPointerData(payload)}
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
theme={theme}
name="Custom name of drawing"
UIOptions={{
canvasActions: {
loadScene: false,
},
}}
renderTopRightUI={renderTopRightUI}
onLinkOpen={onLinkOpen}
onPointerDown={onPointerDown}
onScrollChange={rerenderCommentIcons}
// allow all urls
validateEmbeddable={true}
>
{excalidrawAPI && (
<Footer>
<CustomFooter excalidrawAPI={excalidrawAPI} />
</Footer>
)}
<WelcomeScreen />
<Sidebar name="custom">
<Sidebar.Tabs>
<Sidebar.Header />
<Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
<Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
<Sidebar.TabTriggers>
<Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
<Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
</Sidebar.TabTriggers>
</Sidebar.Tabs>
</Sidebar>
<Sidebar.Trigger
name="custom"
tab="one"
style={{
position: "absolute",
left: "50%",
transform: "translateX(-50%)",
bottom: "20px",
zIndex: 9999999999999999,
}}
>
Toggle Custom Sidebar
</Sidebar.Trigger>
{renderMenu()}
</Excalidraw>
{renderExcalidraw(children)}
{Object.keys(commentIcons || []).length > 0 && renderCommentIcons()}
{comment && renderComment()}
</div>
@@ -750,16 +892,14 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
if (!excalidrawAPI) {
return;
}
const svg = await exportToSvg({
elements: excalidrawAPI?.getSceneElements(),
appState: {
...initialData.appState,
exportWithDarkMode,
exportEmbedScene,
width: 300,
height: 100,
const svg = await exportScene({
type: "svg",
data: {
elements: excalidrawAPI.getSceneElements(),
appState: initialData.appState,
files: excalidrawAPI.getFiles(),
},
files: excalidrawAPI?.getFiles(),
config: { ...exportConfig, embedScene: exportEmbedScene },
});
appRef.current.querySelector(".export-svg").innerHTML =
svg.outerHTML;
@@ -774,15 +914,14 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
if (!excalidrawAPI) {
return;
}
const blob = await exportToBlob({
elements: excalidrawAPI?.getSceneElements(),
mimeType: "image/png",
appState: {
...initialData.appState,
exportEmbedScene,
exportWithDarkMode,
const blob = await exportScene({
type: "png",
data: {
elements: excalidrawAPI.getSceneElements(),
appState: initialData.appState,
files: excalidrawAPI.getFiles(),
},
files: excalidrawAPI?.getFiles(),
config: { ...exportConfig, embedScene: exportEmbedScene },
});
setBlobUrl(window.URL.createObjectURL(blob));
}}
@@ -797,16 +936,17 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
if (!excalidrawAPI) {
return;
}
const canvas = await exportToCanvas({
elements: excalidrawAPI.getSceneElements(),
appState: {
...initialData.appState,
exportWithDarkMode,
const canvas = await exportScene({
type: "canvas",
data: {
elements: excalidrawAPI.getSceneElements(),
appState: initialData.appState,
files: excalidrawAPI.getFiles(),
},
files: excalidrawAPI.getFiles(),
config: exportConfig,
});
const ctx = canvas.getContext("2d")!;
ctx.font = "30px Virgil";
ctx.font = "30px Excalifont";
ctx.strokeText("My custom text", 50, 60);
setCanvasUrl(canvas.toDataURL());
}}
@@ -818,16 +958,17 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
if (!excalidrawAPI) {
return;
}
const canvas = await exportToCanvas({
elements: excalidrawAPI.getSceneElements(),
appState: {
...initialData.appState,
exportWithDarkMode,
const canvas = await exportScene({
type: "canvas",
data: {
elements: excalidrawAPI.getSceneElements(),
appState: initialData.appState,
files: excalidrawAPI.getFiles(),
},
files: excalidrawAPI.getFiles(),
config: exportConfig,
});
const ctx = canvas.getContext("2d")!;
ctx.font = "30px Virgil";
ctx.font = "30px Excalifont";
ctx.strokeText("My custom text", 50, 60);
setCanvasUrl(canvas.toDataURL());
}}
@@ -842,8 +983,9 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
}
const elements = excalidrawAPI.getSceneElements();
excalidrawAPI.scrollToContent(elements[0], {
fitToViewport: true,
excalidrawAPI.setViewport({
target: elements[0],
fit: "contain",
});
}}
>
@@ -857,34 +999,14 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
}
const elements = excalidrawAPI.getSceneElements();
excalidrawAPI.scrollToContent(elements[0], {
fitToContent: true,
});
excalidrawAPI.scrollToContent(elements[0], {
fitToContent: true,
excalidrawAPI.setViewport({
target: elements[0],
fit: "scale-down",
});
}}
>
Fit to content, first element
</button>
<button
type="button"
onClick={() => {
if (!excalidrawAPI) {
return;
}
const elements = excalidrawAPI.getSceneElements();
excalidrawAPI.scrollToContent(elements[0], {
fitToContent: true,
});
excalidrawAPI.scrollToContent(elements[0]);
}}
>
Scroll to first element, no fitToContent, no fitToViewport
</button>
<div className="export export-canvas">
<img src={canvasUrl} alt="" />
</div>
@@ -0,0 +1,30 @@
import React from "react";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
import CustomFooter from "./CustomFooter";
const MobileFooter = ({
excalidrawAPI,
excalidrawLib,
}: {
excalidrawAPI: ExcalidrawImperativeAPI;
excalidrawLib: typeof TExcalidraw;
}) => {
const { useEditorInterface, Footer } = excalidrawLib;
const editorInterface = useEditorInterface();
if (editorInterface.formFactor === "phone") {
return (
<Footer>
<CustomFooter
excalidrawAPI={excalidrawAPI}
excalidrawLib={excalidrawLib}
/>
</Footer>
);
}
return null;
};
export default MobileFooter;
@@ -1,5 +1,7 @@
import React, { useState } from "react";
import "./ExampleSidebar.scss";
export default function Sidebar({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
@@ -11,19 +11,21 @@
<title>React App</title>
<script>
window.name = "codesandbox";
window.EXCALIDRAW_ASSET_PATH =
"https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/";
</script>
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
<!-- This is so that we use the bundled excalidraw.development.js file instead
of the actual source code -->
<script src="./excalidraw.development.js"></script>
<script type="module">
import * as ExcalidrawLib from "@excalidraw/excalidraw";
<script src="./bundle.js"></script>
console.log(ExcalidrawLib);
window.ExcalidrawLib = ExcalidrawLib;
</script>
<script type="module" src="index.tsx"></script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "@excalidraw/excalidraw/index.css";
import type * as TExcalidraw from "@excalidraw/excalidraw";
import App from "./components/ExampleApp";
declare global {
interface Window {
ExcalidrawLib: typeof TExcalidraw;
}
}
const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement);
const { Excalidraw } = window.ExcalidrawLib;
root.render(
<StrictMode>
<App
appTitle={"Excalidraw Example"}
useCustom={(api: any, args?: any[]) => {}}
excalidrawLib={window.ExcalidrawLib}
>
<Excalidraw />
</App>
</StrictMode>,
);
@@ -1,5 +1,5 @@
import { ExcalidrawElementSkeleton } from "../../../data/transform";
import { FileId } from "../../../element/types";
import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/element/transform";
import type { FileId } from "@excalidraw/excalidraw/element/types";
const elements: ExcalidrawElementSkeleton[] = [
{
@@ -7,6 +7,7 @@ const elements: ExcalidrawElementSkeleton[] = [
x: 10,
y: 10,
strokeWidth: 2,
id: "1",
},
{
type: "diamond",
@@ -19,6 +20,7 @@ const elements: ExcalidrawElementSkeleton[] = [
strokeColor: "#099268",
fontSize: 30,
},
id: "2",
},
{
type: "arrow",
@@ -36,10 +38,15 @@ const elements: ExcalidrawElementSkeleton[] = [
height: 230,
fileId: "rocket" as FileId,
},
{
type: "frame",
children: ["1", "2"],
name: "My frame",
},
];
export default {
elements,
appState: { viewBackgroundColor: "#AFEEEE", currentItemFontFamily: 1 },
appState: { viewBackgroundColor: "#AFEEEE", currentItemFontFamily: 5 },
scrollToContent: true,
libraryItems: [
[
@@ -0,0 +1,22 @@
{
"name": "with-script-in-browser",
"version": "1.0.0",
"private": true,
"dependencies": {
"@excalidraw/excalidraw": "*",
"browser-fs-access": "0.38.0",
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"typescript": "^5",
"vite": "5.0.12"
},
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview --port 5002",
"build:preview": "yarn build && yarn preview",
"build:packages": "yarn --cwd ../../ build:packages"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Some files were not shown because too many files have changed in this diff Show More