* 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
* 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
* 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>
* 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>
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>
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>
* 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>
* 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>
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>
* 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
* 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
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>
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
* 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>
* enabled ctrl+y redo shortcut on linux and mac
* Apply suggestion from @dwelle
---------
Co-authored-by: David Luzar <5153846+dwelle@users.noreply.github.com>
* 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>
- 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>
* 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>
* 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>
* 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
* 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>
* 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: 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>
* 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>
* 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: 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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
* 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>
* 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>
* 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>
* 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
* 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>
* 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>
* 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>
* 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: 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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
* 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>
* 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>
* 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.
* 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>
* 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
* 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>
* 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>
* 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>
* 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>
- 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`.
<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>
<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">
@@ -63,7 +60,7 @@ The Excalidraw editor (npm package) supports:
- 🏗️ Customizable.
- 📷 Image support.
- 😀 Shape libraries support.
-👅 Localization (i18n) support.
-🌐 Localization (i18n) support.
- 🖼️ Export to PNG, SVG & clipboard.
- 💾 Open format - export drawings as an `.excalidraw` json file.
- ⚒️ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
@@ -87,13 +84,11 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
**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).
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.editor.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
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.
@@ -65,7 +65,7 @@ You can use this function to update the scene with the sceneData. It accepts the
| `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. |
| `commitToStore` | `boolean` | Implies if the change should be captured and commited to the `store`. Commited changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `false`. |
| `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() {
@@ -105,6 +105,7 @@ function App() {
appState: {
viewBackgroundColor: "#edf2ff",
},
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
excalidrawAPI.updateScene(sceneData);
};
@@ -121,6 +122,18 @@ function App() {
}
```
#### 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>
@@ -350,13 +363,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti
```ts
(
tool: (
| (
| { type: Exclude<ToolType, "image"> }
| {
type: Extract<ToolType, "image">;
insertOnCanvasDirectly?: boolean;
}
)
| { type: ToolType }
| { type: "custom"; customType: string }
) & { locked?: boolean },
) => {};
@@ -364,7 +371,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti
| 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. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
| `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 |
| [`initialData`](/docs/@excalidraw/excalidraw/api/props/initialdata) | `object` | `null` | <code>Promise<object | 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 |
@@ -13,7 +13,7 @@ All `props` are _optional_.
| [`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. |
| [`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 |
@@ -29,8 +29,9 @@ All `props` are _optional_.
| [`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 |
| [`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
@@ -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. |
@@ -13,47 +13,27 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
1. Install the dependencies
```bash
cd 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:excalidraw
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:excalidraw
```
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.
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:
{ `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.
@@ -128,11 +130,10 @@ If you are using `pages router` then importing the wrapper dynamically would wor
</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/).
Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/excalidraw/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs-gh6smrdnq-excalidraw.vercel.app/).
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
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
@@ -157,27 +158,9 @@ Since Vite removes env variables by default, you can update the vite config to e
## Browser
To use it in a browser directly:
To use it Excalidraw in a browser directly, use the following setup :point_down:
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";
@@ -191,13 +174,23 @@ import TabItem from "@theme/TabItem";
@@ -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.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.