chore: release @excalidraw/excalidraw@18.0.0 🎉 (#9127)

This commit is contained in:
Marcel Mraz 2025-02-28 16:49:09 +01:00 committed by GitHub
parent 392118bf26
commit ecef5d12f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
232 changed files with 3412 additions and 2851 deletions

View file

@ -11,85 +11,715 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section.
-->
## Unreleased
## Excalidraw Library
### Features
## 18.0.0 (2025-02-28)
- Added hand-drawn font for Chinese, Japanese and Korean (CJK) as a fallback for Excalifont. Improved overal text wrapping algorithm, not only accounting for CJK, but covering various edge cases with white spaces and text-align center/right. Added support for multi-codepoint emojis wrapping. Offloaded SVG export to Web Workers, with an automatic fallback to the main thread if not supported or not desired.[#8530](https://github.com/excalidraw/excalidraw/pull/8530)
### Highlights
- Prefer user defined coordinates and dimensions when creating a frame using [`convertToExcalidrawElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements) [#8517](https://github.com/excalidraw/excalidraw/pull/8517)
- Command palette [#7804](https://github.com/excalidraw/excalidraw/pull/7804)
- `props.initialData` can now be a function that returns `ExcalidrawInitialDataState` or `Promise<ExcalidrawInitialDataState>`. [#8107](https://github.com/excalidraw/excalidraw/pull/8135)
- Multiplayer undo / redo [#7348](https://github.com/excalidraw/excalidraw/pull/7348)
- Added support for multiplayer undo/redo, by calculating invertible increments and storing them inside the local-only undo/redo stacks. [#7348](https://github.com/excalidraw/excalidraw/pull/7348)
- Editable element stats [#6382](https://github.com/excalidraw/excalidraw/pull/6382)
- Added font picker component to have the ability to choose from a range of different fonts. Also, changed the default fonts to `Excalifont`, `Nunito` and `Comic Shanns` and deprecated `Virgil`, `Helvetica` and `Cascadia`.
- Text element wrapping [#7999](https://github.com/excalidraw/excalidraw/pull/7999)
- `MainMenu.DefaultItems.ToggleTheme` now supports `onSelect(theme: string)` callback, and optionally `allowSystemTheme: boolean` alongside `theme: string` to indicate you want to allow users to set to system theme (you need to handle this yourself). [#7853](https://github.com/excalidraw/excalidraw/pull/7853)
- Font picker with more fonts [#8012](https://github.com/excalidraw/excalidraw/pull/8012)
- Add `useHandleLibrary`'s `opts.adapter` as the new recommended pattern to handle library initialization and persistence on library updates. [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Font for Chinese, Japanese and Korean [#8530](https://github.com/excalidraw/excalidraw/pull/8530)
- Add `useHandleLibrary`'s `opts.migrationAdapter` adapter to handle library migration during init, when migrating from one data store to another (e.g. from LocalStorage to IndexedDB). [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Font subsetting for SVG export [#8384](https://github.com/excalidraw/excalidraw/pull/8384)
- Soft-deprecate `useHandleLibrary`'s `opts.getInitialLibraryItems` in favor of `opts.adapter`. [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- Elbow arrows [#8299](https://github.com/excalidraw/excalidraw/pull/8299), [#8952](https://github.com/excalidraw/excalidraw/pull/8952)
- Add `onPointerUp` prop [#7638](https://github.com/excalidraw/excalidraw/pull/7638).
- Flowcharts [#8329](https://github.com/excalidraw/excalidraw/pull/8329)
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
- Scene search [#8438](https://github.com/excalidraw/excalidraw/pull/8438)
### Fixes
- Image cropping [#8613](https://github.com/excalidraw/excalidraw/pull/8613)
- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656)
- Element linking [#8812](https://github.com/excalidraw/excalidraw/pull/8812)
### Breaking Changes
### Breaking changes
- Stats container CSS changed, so if you're using `renderCustomStats`, you may need to adjust your styles to retain the same layout.
#### Deprecated UMD bundle in favor of ES modules [#7441](https://github.com/excalidraw/excalidraw/pull/7441), [#9127](https://github.com/excalidraw/excalidraw/pull/9127)
- `updateScene` API has changed due to the added `Store` component as part of the multiplayer undo / redo initiative. Specifically, `sceneData` property `commitToHistory: boolean` was replaced with `storeAction: StoreActionType`. Make sure to update all instances of `updateScene` according to the _before / after_ table below. [#7898](https://github.com/excalidraw/excalidraw/pull/7898)
We've transitioned from `UMD` to `ESM` bundle format. Our new `dist` bundles inside `@excalidraw/excalidraw` package now contain only bundled source files, making any dependencies tree-shakable. The npm package comes with the following structure:
| | Before `commitToHistory` | After `storeAction` | Notes |
> **Note**: The structure is simplified for the sake of brevity, omitting lazy-loadable modules, including locales (previously treated as json assets) and source maps in the development bundle.
```
@excalidraw/excalidraw/
├── dist/
│ ├── dev/
│ │ ├── fonts/
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── index.js.map
│ ├── prod/
│ │ ├── fonts/
│ │ ├── index.css
│ │ ├── index.js
│ └── types/
```
##### JavaScript: required `"type": "module"` in package.json
Make sure that your JavaScript environment supports ES modules, as it might be required to define `"type": "module"` in your `package.json` file or as part of the `<script type="module" />` attribute.
##### Typescript: deprecated "moduleResolution": `"node"` or `"node10"`
Since `"node"` and `"node10"` do not support `package.json` `"exports"` fields, having these values in your `tsconfig.json` will not work. Instead, use `"bundler"`, `"node16"` or `"nodenext"` values. For more information, see [Typescript's documentation](https://www.typescriptlang.org/tsconfig/#moduleResolution).
##### New structure of the imports
Dependening on the environment, this is how imports should look like with the `ESM`:
**With bundler (Vite, Next.js, etc.)**
```ts
// excalidraw library with public API
import * as excalidrawLib from "@excalidraw/excalidraw";
// excalidraw react component
import { Excalidraw } from "@excalidraw/excalidraw";
// excalidraw styles, usually auto-processed by the build tool (i.e. vite, next, etc.)
import "@excalidraw/excalidraw/index.css";
// excalidraw types (optional)
import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
```
**Without bundler (Browser)**
```html
<!-- Environment: browser with a script tag and no bundler -->
<!-- excalidraw styles -->
<link
rel="stylesheet"
href="https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.css"
/>
<!-- import maps used for deduplicating react & react-dom versions -->
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@19.0.0",
"react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime",
"react-dom": "https://esm.sh/react-dom@19.0.0"
}
}
</script>
<script type="module">
import React from "https://esm.sh/react@19.0.0";
import ReactDOM from "https://esm.sh/react-dom@19.0.0";
import * as ExcalidrawLib from "https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/dev/index.js?external=react,react-dom";
</script>
```
#### Deprecated `excalidraw-assets` and `excalidraw-assets-dev` folders [#8012](https://github.com/excalidraw/excalidraw/pull/8012), [#9127](https://github.com/excalidraw/excalidraw/pull/9127)
The `excalidraw-assets` and `excalidraw-assets-dev` folders, which contained locales and fonts, are no longer used and have been deprecated.
##### Locales
Locales are no longer treated as static `.json` assets, but are transpiled with `esbuild` dirrectly to the `.js` as ES modules. Note that some build tools (i.e. Vite) may require setting `es2022` as a build target, in order to support "Arbitrary module namespace identifier names", e.g. `export { english as "en-us" } )`.
```js
// vite.config.js
optimizeDeps: {
esbuildOptions: {
// Bumping to 2022 due to "Arbitrary module namespace identifier names" not being
// supported in Vite's default browser target https://github.com/vitejs/vite/issues/13556
target: "es2022",
// Tree shaking is optional, but recommended
treeShaking: true,
},
}
```
##### Fonts
New fonts, which we've added, are automatically loaded from the CDN. 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
<script>window.EXCALIDRAW_ASSET_PATH = "/";</script>
```
or, if you serve your assets from the root of your CDN, you would do:
```js
<script>
window.EXCALIDRAW_ASSET_PATH = "https://cdn.domain.com/subpath/";
</script>
```
or, if you prefer the path to be dynamicly set based on the `location.origin`, you could do the following:
```jsx
// Next.js
<Script id="load-env-variables" strategy="beforeInteractive">
{`window["EXCALIDRAW_ASSET_PATH"] = location.origin;`} // or use just "/"!
</Script>
```
#### Deprecated `commitToHistory` in favor of `captureUpdate` in `updateScene` API [#7348](https://github.com/excalidraw/excalidraw/pull/7348), [#7898](https://github.com/excalidraw/excalidraw/pull//7898)
```js
// before
updateScene({ elements, appState, commitToHistory: true }); // A
updateScene({ elements, appState, commitToHistory: false }); // B
// after
import { CaptureUpdateAction } from "@excalidraw/excalidraw";
updateScene({
elements,
appState,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
}); // A
updateScene({
elements,
appState,
captureUpdate: CaptureUpdateAction.NEVER,
}); // B
```
The `updateScene` API has changed due to the added `Store` component, as part of multiplayer undo / redo initiative. Specifically, optional `sceneData` parameter `commitToHistory: boolean` was replaced with optional `captureUpdate: CaptureUpdateActionType` parameter. Therefore, make sure to update all instances of `updateScene`, which use `commitToHistory` parameter according to the _before / after_ table below.
> **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.
| Undo behaviour | `commitToHistory` (before) | `captureUpdate` (after) | Notes |
| --- | --- | --- | --- |
| _Immediately undoable_ | `true` | `"capture"` | As before, use for all updates which should be recorded by the store & history. Should be used for the most of the local updates. These updates will _immediately_ make it to the local undo / redo stacks. |
| _Eventually undoable_ | `false` | `"none"` | Similar to before, use for all updates which should not be recorded immediately (likely exceptions which are part of some async multi-step process) or those not meant to be recorded at all (i.e. updates to `collaborators` object, parts of `AppState` which are not observed by the store & history - not `ObservedAppState`).<br/><br/>**IMPORTANT** It's likely you should switch to `"update"` in all the other cases. Otherwise, all such updates would end up being recorded with the next `"capture"` - 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_ | n/a | `"update"` | **NEW**: previously there was no equivalent for this value. Now, it's recommended to use `"update"` for all remote updates (from the other clients), scene initialization, or those updates, which should not be locally "undoable". These updates will _never_ make it to the local undo / redo stacks. |
| _Immediately undoable_ | `true` | `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_ | `false` (default) | `CaptureUpdateAction.EVENTUALLY` (default) | 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_ | n/a | `CaptureUpdateAction.NEVER` | **NEW**: Previously there was no equivalent for this value. Now, it's recommended to use `CaptureUpdateAction.NEVER` 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. |
#### Other
- `ExcalidrawTextElement.baseline` was removed and replaced with a vertical offset computation based on font metrics, performed on each text element re-render. In case of custom font usage, extend the `FONT_METRICS` object with the related properties. [#7693](https://github.com/excalidraw/excalidraw/pull/7693)
- `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539)
- `ExcalidrawTextElement.baseline` was removed and replaced with a vertical offset computation based on font metrics, performed on each text element re-render. In case of custom font usage, extend the `FONT_METRICS` object with the related properties.
- Stats container CSS has changed, so if you're using `renderCustomStats`, you may need to adjust your styles to retain the same layout. [#8361](https://github.com/excalidraw/excalidraw/pull/8361)
- Create an `ESM` build for `@excalidraw/excalidraw`. The API is in progress and subject to change before stable release. There are some changes on how the package will be consumed
- `<DefaultSidebar />` triggers are now always merged with host app triggers, rendered through `<DefaultSidebar.Triggers/>`. `<DefaultSidebar.Triggers/>` no longer accepts any props other than children. [#8498](https://github.com/excalidraw/excalidraw/pull/8498)
#### Bundler
### Features
- CSS needs to be imported so you will need to import the css along with the excalidraw component
- Prefer user defined coordinates and dimensions when creating a frame using [`convertToExcalidrawElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements) [#8517](https://github.com/excalidraw/excalidraw/pull/8517)
```js
import { Excalidraw } from "@excalidraw/excalidraw";
import "@excalidraw/excalidraw/index.css";
```
- `props.initialData` can now be a function that returns `ExcalidrawInitialDataState` or `Promise<ExcalidrawInitialDataState>` [#8107](https://github.com/excalidraw/excalidraw/pull/8135)
- The `types` path is updated
- `MainMenu.DefaultItems.ToggleTheme` now supports `onSelect(theme: string)` callback, and optionally `allowSystemTheme: boolean` alongside `theme: string` to indicate you want to allow users to set to system theme (you need to handle this yourself) [#7853](https://github.com/excalidraw/excalidraw/pull/7853)
Instead of importing from `@excalidraw/excalidraw/types/`, you will need to import from `@excalidraw/excalidraw/dist/excalidraw` or `@excalidraw/excalidraw/dist/utils` depending on the types you are using.
- Add `useHandleLibrary`'s `opts.adapter` as the new recommended pattern to handle library initialization and persistence on library updates [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
However this we will be fixing before stable release, so in case you want to try it out you will need to update the types for now.
- Add `useHandleLibrary`'s `opts.migrationAdapter` adapter to handle library migration during init, when migrating from one data store to another (e.g. from LocalStorage to IndexedDB) [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
#### Browser
- Add `onPointerUp` prop [#7638](https://github.com/excalidraw/excalidraw/pull/7638)
- Since its `ESM` so now script type `module` can be used to load it and css needs to be loaded as well.
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
```html
<link
rel="stylesheet"
href="https://unpkg.com/@excalidraw/excalidraw@next/dist/browser/dev/index.css"
/>
<script type="module">
import * as ExcalidrawLib from "https://unpkg.com/@excalidraw/excalidraw@next/dist/browser/dev/index.js";
window.ExcalidrawLib = ExcalidrawLib;
</script>
```
- Soft-deprecate `useHandleLibrary`'s `opts.getInitialLibraryItems` in favor of `opts.adapter`. [#7655](https://github.com/excalidraw/excalidraw/pull/7655)
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
- Extended `window.EXCALIDRAW_ASSET_PATH` to accept array of paths `string[]` as a value, allowing to specify multiple base `URL` fallbacks. [#8286](https://github.com/excalidraw/excalidraw/pull/8286)
- Custom text metrics provider [#9121](https://github.com/excalidraw/excalidraw/pull/9121)
- Add `props.onDuplicate` [#9117](https://github.com/excalidraw/excalidraw/pull/9117)
- Change empty arrowhead icon [#9100](https://github.com/excalidraw/excalidraw/pull/9100)
- Tweak slider colors to be more muted [#9076](https://github.com/excalidraw/excalidraw/pull/9076)
- Improve library sidebar performance [#9060](https://github.com/excalidraw/excalidraw/pull/9060)
- Implement custom Range component for opacity control [#9009](https://github.com/excalidraw/excalidraw/pull/9009)
- Box select frame & children to allow resizing at the same time [#9031](https://github.com/excalidraw/excalidraw/pull/9031)
- Allow installing libs from excal github [#9041](https://github.com/excalidraw/excalidraw/pull/9041)
- Update jotai [#9015](https://github.com/excalidraw/excalidraw/pull/9015)
- Do not delete frame children on frame delete [#9011](https://github.com/excalidraw/excalidraw/pull/9011)
- Add action to wrap selected items in a frame [#9005](https://github.com/excalidraw/excalidraw/pull/9005)
- Reintroduce `.excalidraw.png` default when embedding scene [#8979](https://github.com/excalidraw/excalidraw/pull/8979)
- Add mimeTypes on file save [#8946](https://github.com/excalidraw/excalidraw/pull/8946)
- Add crowfoot to arrowheads [#8942](https://github.com/excalidraw/excalidraw/pull/8942)
- Make HTML attribute sanitization stricter [#8977](https://github.com/excalidraw/excalidraw/pull/8977)
- Validate library install urls [#8976](https://github.com/excalidraw/excalidraw/pull/8976)
- Cleanup svg export and move payload to `<metadata>` [#8975](https://github.com/excalidraw/excalidraw/pull/8975)
- Use stats panel to crop [#8848](https://github.com/excalidraw/excalidraw/pull/8848)
- Snap when cropping as well [#8831](https://github.com/excalidraw/excalidraw/pull/8831)
- Update blog url [#8767](https://github.com/excalidraw/excalidraw/pull/8767)
- Export scene to e+ on workspace creation/redemption [#8514](https://github.com/excalidraw/excalidraw/pull/8514)
- Added sitemap & fixed robot txt [#8699](https://github.com/excalidraw/excalidraw/pull/8699)
- Do not strip unknown element properties on restore [#8682](https://github.com/excalidraw/excalidraw/pull/8682)
- Added reddit links as embeddable [#8099](https://github.com/excalidraw/excalidraw/pull/8099)
- Self-hosting existing google fonts [#8540](https://github.com/excalidraw/excalidraw/pull/8540)
- Flip arrowheads if only arrow(s) selected [#8525](https://github.com/excalidraw/excalidraw/pull/8525)
- Common elbow mid segments [#8440](https://github.com/excalidraw/excalidraw/pull/8440)
- Merge search sidebar back to default sidebar [#8497](https://github.com/excalidraw/excalidraw/pull/8497)
- Smarter zooming when scrolling to match & only match on search/switch [#8488](https://github.com/excalidraw/excalidraw/pull/8488)
- Reset copyStatus on export dialog settings change [#8443](https://github.com/excalidraw/excalidraw/pull/8443)
- Tweak copy button success animation [#8441](https://github.com/excalidraw/excalidraw/pull/8441)
- Enable panning/zoom while in wysiwyg [#8437](https://github.com/excalidraw/excalidraw/pull/8437)
- Visual debugger [#8344](https://github.com/excalidraw/excalidraw/pull/8344)
- Improve elbow arrow keyboard move [#8392](https://github.com/excalidraw/excalidraw/pull/8392)
- Rewrite d2c to not require token [#8269](https://github.com/excalidraw/excalidraw/pull/8269)
- Split `gridSize` from enabled state & support custom `gridStep` [#8364](https://github.com/excalidraw/excalidraw/pull/8364)
- Improve zoom-to-content when creating flowchart [#8368](https://github.com/excalidraw/excalidraw/pull/8368)
- Stats popup style tweaks [#8361](https://github.com/excalidraw/excalidraw/pull/8361)
- Remove automatic frame naming [#8302](https://github.com/excalidraw/excalidraw/pull/8302)
- Ability to debug the state of fractional indices [#8235](https://github.com/excalidraw/excalidraw/pull/8235)
- Improve mermaid detection on paste [#8287](https://github.com/excalidraw/excalidraw/pull/8287)
- Upgrade mermaid-to-excalidraw to v1.1.0 [#8226](https://github.com/excalidraw/excalidraw/pull/8226)
- Bump max file size [#8220](https://github.com/excalidraw/excalidraw/pull/8220)
- Smarter preferred lang detection [#8205](https://github.com/excalidraw/excalidraw/pull/8205)
- Support Stats bound text `fontSize` editing [#8187](https://github.com/excalidraw/excalidraw/pull/8187)
- Paste as mermaid if applicable [#8116](https://github.com/excalidraw/excalidraw/pull/8116)
- Stop autoselecting text on text edit on mobile [#8076](https://github.com/excalidraw/excalidraw/pull/8076)
- Create new text with width [#8038](https://github.com/excalidraw/excalidraw/pull/8038)
- Wrap long text when pasting [#8026](https://github.com/excalidraw/excalidraw/pull/8026)
- Upgrade to mermaid-to-excalidraw v1 🚀 [#8022](https://github.com/excalidraw/excalidraw/pull/8022)
- Rerender canvas on focus [#8035](https://github.com/excalidraw/excalidraw/pull/8035)
- Add missing `type="button"` [#8030](https://github.com/excalidraw/excalidraw/pull/8030)
- Add install-PWA to command palette [#7935](https://github.com/excalidraw/excalidraw/pull/7935)
- Tweak a few icons & add line editor button to side panel [#7990](https://github.com/excalidraw/excalidraw/pull/7990)
- Allow binding only via linear element ends [#7946](https://github.com/excalidraw/excalidraw/pull/7946)
- Resize elements from the sides [#7855](https://github.com/excalidraw/excalidraw/pull/7855)
- Record freedraw tool selection to history [#7949](https://github.com/excalidraw/excalidraw/pull/7949)
- Export reconciliation [#7917](https://github.com/excalidraw/excalidraw/pull/7917)
- Add "toggle grid" to command palette [#7887](https://github.com/excalidraw/excalidraw/pull/7887)
- Fractional indexing [#7359](https://github.com/excalidraw/excalidraw/pull/7359)
- Show firefox-compatible command palette shortcut alias [#7825](https://github.com/excalidraw/excalidraw/pull/7825)
- Upgrade mermaid-to-excalidraw to 0.3.0 [#7819](https://github.com/excalidraw/excalidraw/pull/7819)
- Support to not render remote cursor & username [#7130](https://github.com/excalidraw/excalidraw/pull/7130)
- Expose more collaborator status icons [#7777](https://github.com/excalidraw/excalidraw/pull/7777)
- Close dropdown on escape [#7750](https://github.com/excalidraw/excalidraw/pull/7750)
- Text measurements based on font metrics [#7693](https://github.com/excalidraw/excalidraw/pull/7693)
- Improve collab error notification [#7741](https://github.com/excalidraw/excalidraw/pull/7741)
- Grouped together Undo and Redo buttons on mobile [#9109](https://github.com/excalidraw/excalidraw/pull/9109)
- Load old library if migration fails
- Change LibraryPersistenceAdapter `load()` `source` -> `priority`
### Fixes
- Fix inconsistency in resizing while maintaining aspect ratio [#9116](https://github.com/excalidraw/excalidraw/pull/9116)
- IFrame and elbow arrow interaction fix [#9101](https://github.com/excalidraw/excalidraw/pull/9101)
- Duplicating/removing frame while children selected [#9079](https://github.com/excalidraw/excalidraw/pull/9079)
- Elbow arrow z-index binding [#9067](https://github.com/excalidraw/excalidraw/pull/9067)
- Library item checkbox style regression [#9080](https://github.com/excalidraw/excalidraw/pull/9080)
- Elbow arrow orthogonality [#9073](https://github.com/excalidraw/excalidraw/pull/9073)
- Button bg CSS variable leaking into other styles [#9075](https://github.com/excalidraw/excalidraw/pull/9075)
- Fonts not loading on export (again) [#9064](https://github.com/excalidraw/excalidraw/pull/9064)
- Merge server-side fonts with liberation sans [#9052](https://github.com/excalidraw/excalidraw/pull/9052)
- Hyperlinks html entities [#9063](https://github.com/excalidraw/excalidraw/pull/9063)
- Remove flushSync to fix flickering [#9057](https://github.com/excalidraw/excalidraw/pull/9057)
- Excalidraw issue #9045 flowcharts: align attributes of new node [#9047](https://github.com/excalidraw/excalidraw/pull/9047)
- Align arrows bound to elements excalidraw#8833 [#8998](https://github.com/excalidraw/excalidraw/pull/8998)
- Update elbow arrow on font size change #8798 [#9002](https://github.com/excalidraw/excalidraw/pull/9002)
- Undo for elbow arrows create incorrect routing [#9046](https://github.com/excalidraw/excalidraw/pull/9046)
- Flowchart clones the current arrowhead [#8581](https://github.com/excalidraw/excalidraw/pull/8581)
- Adding partial group to frame [#9014](https://github.com/excalidraw/excalidraw/pull/9014)
- Do not refocus element link input on unrelated updates [#9037](https://github.com/excalidraw/excalidraw/pull/9037)
- Arrow binding behaving unexpectedly on pointerup [#9010](https://github.com/excalidraw/excalidraw/pull/9010)
- Change cursor by tool change immediately [#8212](https://github.com/excalidraw/excalidraw/pull/8212)
- Package build fails on worker chunks [#8990](https://github.com/excalidraw/excalidraw/pull/8990)
- Z-index clash in mobile UI [#8985](https://github.com/excalidraw/excalidraw/pull/8985)
- Elbow arrows do not work within frames (issue: #8964) [#8969](https://github.com/excalidraw/excalidraw/pull/8969)
- NormalizeSVG width and height from viewbox when size includes decimal points [#8939](https://github.com/excalidraw/excalidraw/pull/8939)
- Make arrow binding area adapt to zoom levels [#8927](https://github.com/excalidraw/excalidraw/pull/8927)
- Robust `state.editingFrame` teardown [#8941](https://github.com/excalidraw/excalidraw/pull/8941)
- Regression on dragging a selected frame by its name [#8924](https://github.com/excalidraw/excalidraw/pull/8924)
- Right-click paste for images in clipboard (Issue #8826) [#8845](https://github.com/excalidraw/excalidraw/pull/8845)
- Fixed image transparency by adding alpha option to preserve image alpha channel [#8895](https://github.com/excalidraw/excalidraw/pull/8895)
- Flush pending DOM updates before .focus() [#8901](https://github.com/excalidraw/excalidraw/pull/8901)
- Normalize svg using only absolute sizing [#8854](https://github.com/excalidraw/excalidraw/pull/8854)
- Element link selector dialog z-index & positioning [#8853](https://github.com/excalidraw/excalidraw/pull/8853)
- Update old blog links & add canonical url [#8846](https://github.com/excalidraw/excalidraw/pull/8846)
- Optimize frameToHighlight state change and snapLines state change [#8763](https://github.com/excalidraw/excalidraw/pull/8763)
- Make some events expllicitly active to avoid console warnings [#8757](https://github.com/excalidraw/excalidraw/pull/8757)
- Unify binding update options for `updateBoundElements()` [#8832](https://github.com/excalidraw/excalidraw/pull/8832)
- Cleanup scripts and support upto node 22 [#8794](https://github.com/excalidraw/excalidraw/pull/8794)
- Usage of `node12 which is deprecated` [#8791](https://github.com/excalidraw/excalidraw/pull/8791)
- Remove manifest.json [#8783](https://github.com/excalidraw/excalidraw/pull/8783)
- Load env vars correctly and set debug and linter flags to false explicitly in prod mode [#8770](https://github.com/excalidraw/excalidraw/pull/8770)
- Console error in dev mode due to missing font path in non-prod [#8756](https://github.com/excalidraw/excalidraw/pull/8756)
- Text pushes UI due to padding [#8745](https://github.com/excalidraw/excalidraw/pull/8745)
- Fix trailing line whitespaces layout shift [#8714](https://github.com/excalidraw/excalidraw/pull/8714)
- Load font faces in Safari manually [#8693](https://github.com/excalidraw/excalidraw/pull/8693)
- Restore svg image DataURL dimensions [#8730](https://github.com/excalidraw/excalidraw/pull/8730)
- Image cropping svg + compat mode [#8710](https://github.com/excalidraw/excalidraw/pull/8710)
- Usage of `node12 which is deprecated` [#8709](https://github.com/excalidraw/excalidraw/pull/8709)
- Image render perf [#8697](https://github.com/excalidraw/excalidraw/pull/8697)
- Undo/redo action for international keyboard layouts [#8649](https://github.com/excalidraw/excalidraw/pull/8649)
- Comic Shanns issues, new fonts structure [#8641](https://github.com/excalidraw/excalidraw/pull/8641)
- Remove export-to-clip-as-svg shortcut for now [#8660](https://github.com/excalidraw/excalidraw/pull/8660)
- Text disappearing on edit [#8558](https://github.com/excalidraw/excalidraw/pull/8558) (#8624)
- Elbow arrow fixedpoint flipping now properly flips on inverted resize and flip action [#8324](https://github.com/excalidraw/excalidraw/pull/8324)
- Svg and png frame clipping cases [#8515](https://github.com/excalidraw/excalidraw/pull/8515)
- Re-route elbow arrows when pasted [#8448](https://github.com/excalidraw/excalidraw/pull/8448)
- Buffer dependency [#8474](https://github.com/excalidraw/excalidraw/pull/8474)
- Linear element complete button disabled [#8492](https://github.com/excalidraw/excalidraw/pull/8492)
- Aspect ratio of distorted images are not preserved in SVG exports [#8061](https://github.com/excalidraw/excalidraw/pull/8061)
- WYSIWYG editor padding is not normalized with zoom.value [#8481](https://github.com/excalidraw/excalidraw/pull/8481)
- Improve canvas search scroll behavior further [#8491](https://github.com/excalidraw/excalidraw/pull/8491)
- AddFiles clears the whole image cache when each file is added - regression from #8471 [#8490](https://github.com/excalidraw/excalidraw/pull/8490)
- `select` instead of `focus` search input [#8483](https://github.com/excalidraw/excalidraw/pull/8483)
- Image rendering issue when passed in `initialData` [#8471](https://github.com/excalidraw/excalidraw/pull/8471)
- Add partial mocking [#8473](https://github.com/excalidraw/excalidraw/pull/8473)
- PropertiesPopover maxWidth changing fixed units to relative units [#8456](https://github.com/excalidraw/excalidraw/pull/8456)
- View mode wheel zooming does not work [#8452](https://github.com/excalidraw/excalidraw/pull/8452)
- Fixed copy to clipboard button [#8426](https://github.com/excalidraw/excalidraw/pull/8426)
- Context menu does not work after after dragging on StatsDragInput [#8386](https://github.com/excalidraw/excalidraw/pull/8386)
- Perf regression in `getCommonBounds` [#8429](https://github.com/excalidraw/excalidraw/pull/8429)
- Object snapping not working [#8381](https://github.com/excalidraw/excalidraw/pull/8381)
- Reimplement rectangle intersection [#8367](https://github.com/excalidraw/excalidraw/pull/8367)
- Round coordinates and sizes for rectangle intersection [#8366](https://github.com/excalidraw/excalidraw/pull/8366)
- Text content with tab characters act different in view/edit [#8336](https://github.com/excalidraw/excalidraw/pull/8336)
- Drawing from 0-dimension canvas [#8356](https://github.com/excalidraw/excalidraw/pull/8356)
- Disable flowchart keybindings inside inputs [#8353](https://github.com/excalidraw/excalidraw/pull/8353)
- Yet more patching of intersect code [#8352](https://github.com/excalidraw/excalidraw/pull/8352)
- Missing `act()` in flowchart tests [#8354](https://github.com/excalidraw/excalidraw/pull/8354)
- Z-index change by one causes app to freeze [#8314](https://github.com/excalidraw/excalidraw/pull/8314)
- Patch over intersection calculation issue [#8350](https://github.com/excalidraw/excalidraw/pull/8350)
- Point duplication in LEE on ALT+click [#8347](https://github.com/excalidraw/excalidraw/pull/8347)
- Do not allow resizing unbound elbow arrows either [#8333](https://github.com/excalidraw/excalidraw/pull/8333)
- Docker build in CI [#8312](https://github.com/excalidraw/excalidraw/pull/8312)
- Duplicating arrow without bound elements throws error [#8316](https://github.com/excalidraw/excalidraw/pull/8316)
- CVE-2023-45133 [#7988](https://github.com/excalidraw/excalidraw/pull/7988)
- Throttle fractional indices validation [#8306](https://github.com/excalidraw/excalidraw/pull/8306)
- Allow binding elbow arrows to frame children [#8309](https://github.com/excalidraw/excalidraw/pull/8309)
- Skip registering font faces for local fonts [#8303](https://github.com/excalidraw/excalidraw/pull/8303)
- Load fonts for `exportToCanvas` [#8298](https://github.com/excalidraw/excalidraw/pull/8298)
- Re-add Cascadia Code with ligatures [#8291](https://github.com/excalidraw/excalidraw/pull/8291)
- Linear elements not selected on pointer up from hitting its bound text [#8285](https://github.com/excalidraw/excalidraw/pull/8285)
- Revert default element canvas padding change [#8266](https://github.com/excalidraw/excalidraw/pull/8266)
- Freedraw jittering [#8238](https://github.com/excalidraw/excalidraw/pull/8238)
- Messed up env variable [#8231](https://github.com/excalidraw/excalidraw/pull/8231)
- Log allowed events [#8224](https://github.com/excalidraw/excalidraw/pull/8224)
- Memory leak - scene.destroy() and window.launchQueue [#8198](https://github.com/excalidraw/excalidraw/pull/8198)
- Stop updating text versions on init [#8191](https://github.com/excalidraw/excalidraw/pull/8191)
- Add binding update to manual stat changes [#8183](https://github.com/excalidraw/excalidraw/pull/8183)
- Binding after duplicating is now applied for both the old and duplicate shapes [#8185](https://github.com/excalidraw/excalidraw/pull/8185)
- Incorrect point offsetting in LinearElementEditor.movePoints() [#8145](https://github.com/excalidraw/excalidraw/pull/8145)
- Stats state leaking & race conds [#8177](https://github.com/excalidraw/excalidraw/pull/8177)
- Only bind arrow [#8152](https://github.com/excalidraw/excalidraw/pull/8152)
- Repair invalid binding on restore & fix type check [#8133](https://github.com/excalidraw/excalidraw/pull/8133)
- Wysiwyg blur-submit on mobile [#8075](https://github.com/excalidraw/excalidraw/pull/8075)
- Restore linear dimensions from points [#8062](https://github.com/excalidraw/excalidraw/pull/8062)
- Lp plus url [#8056](https://github.com/excalidraw/excalidraw/pull/8056)
- Fix twitter og image [#8050](https://github.com/excalidraw/excalidraw/pull/8050)
- Flaky snapshot tests with floating point precision issues [#8049](https://github.com/excalidraw/excalidraw/pull/8049)
- Always re-generate index of defined moved elements [#8040](https://github.com/excalidraw/excalidraw/pull/8040)
- Undo/redo when exiting view mode [#8024](https://github.com/excalidraw/excalidraw/pull/8024)
- Two finger panning is slow [#7849](https://github.com/excalidraw/excalidraw/pull/7849)
- Compatible safari layers button svg [#8020](https://github.com/excalidraw/excalidraw/pull/8020)
- Correctly resolve the package version [#8016](https://github.com/excalidraw/excalidraw/pull/8016)
- Re-introduce wysiwyg width offset [#8014](https://github.com/excalidraw/excalidraw/pull/8014)
- Font not rendered correctly on init [#8002](https://github.com/excalidraw/excalidraw/pull/8002)
- Command palette filter [#7981](https://github.com/excalidraw/excalidraw/pull/7981)
- Remove unused param from drawImagePlaceholder [#7991](https://github.com/excalidraw/excalidraw/pull/7991)
- Docker build of Excalidraw app [#7430](https://github.com/excalidraw/excalidraw/pull/7430)
- Typo in doc api [#7466](https://github.com/excalidraw/excalidraw/pull/7466)
- Use Reflect API instead of Object.hasOwn [#7958](https://github.com/excalidraw/excalidraw/pull/7958)
- CTRL/CMD & arrow point drag unbinds both sides [#6459](https://github.com/excalidraw/excalidraw/pull/6459) (#7877)
- Z-index for laser pointer to be able to draw on embeds and such [#7918](https://github.com/excalidraw/excalidraw/pull/7918)
- Double text rendering on edit [#7904](https://github.com/excalidraw/excalidraw/pull/7904)
- Collision regressions from vector geometry rewrite [#7902](https://github.com/excalidraw/excalidraw/pull/7902)
- Correct unit from 'eg' to 'deg' [#7891](https://github.com/excalidraw/excalidraw/pull/7891)
- Allow same origin for all necessary domains [#7889](https://github.com/excalidraw/excalidraw/pull/7889)
- Always make sure we render bound text above containers [#7880](https://github.com/excalidraw/excalidraw/pull/7880)
- Parse embeddable srcdoc urls strictly [#7884](https://github.com/excalidraw/excalidraw/pull/7884)
- Hit test for closed sharp curves [#7881](https://github.com/excalidraw/excalidraw/pull/7881)
- Gist embed allowing unsafe html [#7883](https://github.com/excalidraw/excalidraw/pull/7883)
- Command palette tweaks and fixes [#7876](https://github.com/excalidraw/excalidraw/pull/7876)
- Include borders when testing insides of a shape [#7865](https://github.com/excalidraw/excalidraw/pull/7865)
- External link not opening [#7859](https://github.com/excalidraw/excalidraw/pull/7859)
- Add safe check for arrow points length in tranformToExcalidrawElements [#7863](https://github.com/excalidraw/excalidraw/pull/7863)
- Import [#7869](https://github.com/excalidraw/excalidraw/pull/7869)
- Theme toggle shortcut `event.code` [#7868](https://github.com/excalidraw/excalidraw/pull/7868)
- Remove incorrect check from index.html [#7867](https://github.com/excalidraw/excalidraw/pull/7867)
- Stop using lookbehind for backwards compat [#7824](https://github.com/excalidraw/excalidraw/pull/7824)
- Ejs support in html files [#7822](https://github.com/excalidraw/excalidraw/pull/7822)
- `excalidrawAPI.toggleSidebar` not switching between tabs correctly [#7821](https://github.com/excalidraw/excalidraw/pull/7821)
- Correcting Assistant metrics [#7758](https://github.com/excalidraw/excalidraw/pull/7758)
- Add missing font metrics for Assistant [#7752](https://github.com/excalidraw/excalidraw/pull/7752)
- Export utils from excalidraw package in excalidraw library [#7731](https://github.com/excalidraw/excalidraw/pull/7731)
- Split renderScene so that locales aren't imported unnecessarily [#7718](https://github.com/excalidraw/excalidraw/pull/7718)
- Remove dependency of t in blob.ts [#7717](https://github.com/excalidraw/excalidraw/pull/7717)
- Remove dependency of t from clipboard and image [#7712](https://github.com/excalidraw/excalidraw/pull/7712)
- Remove scene hack from export.ts & remove pass elementsMap to getContainingFrame [#7713](https://github.com/excalidraw/excalidraw/pull/7713)
- Decouple pure functions from hyperlink to prevent mermaid bundling [#7710](https://github.com/excalidraw/excalidraw/pull/7710)
- Make bounds independent of scene [#7679](https://github.com/excalidraw/excalidraw/pull/7679)
- Make LinearElementEditor independent of scene [#7670](https://github.com/excalidraw/excalidraw/pull/7670)
- Remove scene from getElementAbsoluteCoords and dependent functions and use elementsMap [#7663](https://github.com/excalidraw/excalidraw/pull/7663)
- Remove t from getDefaultAppState and allow name to be nullable [#7666](https://github.com/excalidraw/excalidraw/pull/7666)
- Stop using structuredClone [#9128](https://github.com/excalidraw/excalidraw/pull/9128)
### Refactor
- Remove `defaultProps` [#9035](https://github.com/excalidraw/excalidraw/pull/9035)
- Separate resizing logic from pointer [#8155](https://github.com/excalidraw/excalidraw/pull/8155)
- `point()` -> `pointFrom()` to fix compiler issue [#8578](https://github.com/excalidraw/excalidraw/pull/8578)
- Rename example `App.tsx` -> `ExampleApp.tsx` [#8501](https://github.com/excalidraw/excalidraw/pull/8501)
- Remove unused env variable [#8457](https://github.com/excalidraw/excalidraw/pull/8457)
- Rename `draggingElement` -> `newElement` [#8294](https://github.com/excalidraw/excalidraw/pull/8294)
- Update collision from ga to vector geometry [#7636](https://github.com/excalidraw/excalidraw/pull/7636)
### Performance
- Improved pointer events related performance when the sidebar is docked with a large library open [#9086](https://github.com/excalidraw/excalidraw/pull/9086)
- Reduce unnecessary frame clippings [#8980](https://github.com/excalidraw/excalidraw/pull/8980)
- Improve new element drawing [#8340](https://github.com/excalidraw/excalidraw/pull/8340)
- Cache the temp canvas created for labeled arrows [#8267](https://github.com/excalidraw/excalidraw/pull/8267)
### Build
- Set PWA flag in dev to false [#8788](https://github.com/excalidraw/excalidraw/pull/8788)
- Add a flag VITE_APP_ENABLE_PWA for enabling pwa in dev environment [#8784](https://github.com/excalidraw/excalidraw/pull/8784)
- Upgrade vite to 5.4.x, vitest to 2.x and related vite packages [#8459](https://github.com/excalidraw/excalidraw/pull/8459)
- Add example apps `public` and vite `dev-dist` to eslintignore [#8326](https://github.com/excalidraw/excalidraw/pull/8326)
- Add `rm:build`, `rm:node_modules` & `clean-install` scripts [#8323](https://github.com/excalidraw/excalidraw/pull/8323)
- Update release script to build esm [#8308](https://github.com/excalidraw/excalidraw/pull/8308)
- Run tests on master branch [#8072](https://github.com/excalidraw/excalidraw/pull/8072)
- Specify `packageManager` field [#8010](https://github.com/excalidraw/excalidraw/pull/8010)
- Enable consistent type imports eslint rule [#7992](https://github.com/excalidraw/excalidraw/pull/7992)
- Export types for @excalidraw/utils [#7736](https://github.com/excalidraw/excalidraw/pull/7736)
- Create ESM build for utils package 🥳 [#7500](https://github.com/excalidraw/excalidraw/pull/7500)
- Upgrade to react@19 [#9182](https://github.com/excalidraw/excalidraw/pull/9182)
## 0.17.3 (2024-02-09)
@ -215,6 +845,8 @@ define: {
### Fixes
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
- Image insertion bugs [#7278](https://github.com/excalidraw/excalidraw/pull/7278)
- ExportToSvg to honor frameRendering also for name not only for frame itself [#7270](https://github.com/excalidraw/excalidraw/pull/7270)

View file

@ -1,49 +1,45 @@
# Excalidraw
**Excalidraw** is exported as a component to directly embed in your projects.
**Excalidraw** is exported as a component to be directly embedded in your project.
## Installation
You can use `npm`
Use `npm` or `yarn` to install the package.
```bash
npm install react react-dom @excalidraw/excalidraw
```
or via `yarn`
```bash
# or
yarn add react react-dom @excalidraw/excalidraw
```
After installation you will see a folder `excalidraw-assets` and `excalidraw-assets-dev` in `dist` directory which contains the assets needed for this app in prod and dev mode respectively.
> **Note**: If you don't want to wait for the next stable release and try out the unreleased changes, use `@excalidraw/excalidraw@next`.
Move the folder `excalidraw-assets` and `excalidraw-assets-dev` to the path where your assets are served.
#### Self-hosting fonts
By default it will try to load the files from [`https://unpkg.com/@excalidraw/excalidraw/dist/`](https://unpkg.com/@excalidraw/excalidraw/dist)
By default, Excalidraw will try to download all the used fonts from the [CDN](https://esm.run/@excalidraw/excalidraw/dist/prod).
If you want to load assets from a different path you can set a variable `window.EXCALIDRAW_ASSET_PATH` depending on environment (for example if you have different URL's for dev and prod) to the url from where you want to load the assets.
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:
#### Note
```js
<script>window.EXCALIDRAW_ASSET_PATH = "/";</script>
```
**If you don't want to wait for the next stable release and try out the unreleased changes you can use `@excalidraw/excalidraw@next`.**
## Dimensions of Excalidraw
### Dimensions of Excalidraw
Excalidraw takes _100%_ of `width` and `height` of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.
### Demo
## Demo
[Try here](https://codesandbox.io/s/excalidraw-ehlz3).
Go to [CodeSandbox](https://codesandbox.io/p/sandbox/github/excalidraw/excalidraw/tree/mrazator/release-v18/examples/with-script-in-browser) example.
## Integration
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration)
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration).
## API
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api)
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api).
## Contributing
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/contributing)
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/contributing).

View file

@ -3,7 +3,7 @@ import { deepCopyElement } from "../element/newElement";
import { randomId } from "../random";
import { t } from "../i18n";
import { LIBRARY_DISABLED_TYPES } from "../constants";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionAddToLibrary = register({
name: "addToLibrary",
@ -18,7 +18,7 @@ export const actionAddToLibrary = register({
for (const type of LIBRARY_DISABLED_TYPES) {
if (selectedElements.some((element) => element.type === type)) {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
errorMessage: t(`errors.libraryElementTypeError.${type}`),
@ -42,7 +42,7 @@ export const actionAddToLibrary = register({
})
.then(() => {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
toast: { message: t("toast.addedToLibrary") },
@ -51,7 +51,7 @@ export const actionAddToLibrary = register({
})
.catch((error) => {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
errorMessage: error.message,

View file

@ -16,7 +16,7 @@ import { updateFrameMembershipOfSelectedElements } from "../frame";
import { t } from "../i18n";
import { KEYS } from "../keys";
import { isSomeElementSelected } from "../scene";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import type { AppClassProperties, AppState, UIAppState } from "../types";
import { arrayToMap, getShortcutKey } from "../utils";
import { register } from "./register";
@ -72,7 +72,7 @@ export const actionAlignTop = register({
position: "start",
axis: "y",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>
@ -106,7 +106,7 @@ export const actionAlignBottom = register({
position: "end",
axis: "y",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>
@ -140,7 +140,7 @@ export const actionAlignLeft = register({
position: "start",
axis: "x",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>
@ -174,7 +174,7 @@ export const actionAlignRight = register({
position: "end",
axis: "x",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>
@ -208,7 +208,7 @@ export const actionAlignVerticallyCentered = register({
position: "center",
axis: "y",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => (
@ -238,7 +238,7 @@ export const actionAlignHorizontallyCentered = register({
position: "center",
axis: "x",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => (

View file

@ -33,7 +33,7 @@ import type { Mutable } from "../utility-types";
import { arrayToMap, getFontString } from "../utils";
import { register } from "./register";
import { syncMovedIndices } from "../fractionalIndex";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { measureText } from "../element/textMeasurements";
export const actionUnbindText = register({
@ -86,7 +86,7 @@ export const actionUnbindText = register({
return {
elements,
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
});
@ -163,7 +163,7 @@ export const actionBindText = register({
return {
elements: pushTextAboveContainer(elements, container, textElement),
appState: { ...appState, selectedElementIds: { [container.id]: true } },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
});
@ -323,7 +323,7 @@ export const actionWrapTextInContainer = register({
...appState,
selectedElementIds: containerIds,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
});

View file

@ -37,8 +37,8 @@ import {
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
import type { SceneBounds } from "../element/bounds";
import { setCursor } from "../cursor";
import { StoreAction } from "../store";
import { clamp, roundToStep } from "../../math";
import { CaptureUpdateAction } from "../store";
import { clamp, roundToStep } from "@excalidraw/math";
export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor",
@ -54,9 +54,9 @@ export const actionChangeViewBackgroundColor = register({
perform: (_, appState, value) => {
return {
appState: { ...appState, ...value },
storeAction: !!value.viewBackgroundColor
? StoreAction.CAPTURE
: StoreAction.NONE,
captureUpdate: !!value.viewBackgroundColor
? CaptureUpdateAction.IMMEDIATELY
: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ elements, appState, updateData, appProps }) => {
@ -115,7 +115,7 @@ export const actionClearCanvas = register({
? { ...appState.activeTool, type: "selection" }
: appState.activeTool,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
});
@ -140,7 +140,7 @@ export const actionZoomIn = register({
),
userToFollow: null,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ updateData, appState }) => (
@ -181,7 +181,7 @@ export const actionZoomOut = register({
),
userToFollow: null,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ updateData, appState }) => (
@ -222,7 +222,7 @@ export const actionResetZoom = register({
),
userToFollow: null,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ updateData, appState }) => (
@ -341,7 +341,7 @@ export const zoomToFitBounds = ({
scrollY: centerScroll.scrollY,
zoom: { value: newZoomValue },
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
};
@ -472,7 +472,7 @@ export const actionToggleTheme = register({
theme:
value || (appState.theme === THEME.LIGHT ? THEME.DARK : THEME.LIGHT),
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
keyTest: (event) => event.altKey && event.shiftKey && event.code === CODES.D,
@ -510,7 +510,7 @@ export const actionToggleEraserTool = register({
activeEmbeddable: null,
activeTool,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) => event.key === KEYS.E,
@ -549,7 +549,7 @@ export const actionToggleHandTool = register({
activeEmbeddable: null,
activeTool,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>

View file

@ -14,7 +14,7 @@ import { getTextFromElements, isTextElement } from "../element";
import { t } from "../i18n";
import { isFirefox } from "../constants";
import { DuplicateIcon, cutIcon, pngIcon, svgIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionCopy = register({
name: "copy",
@ -32,7 +32,7 @@ export const actionCopy = register({
await copyToClipboard(elementsToCopy, app.files, event);
} catch (error: any) {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
errorMessage: error.message,
@ -41,7 +41,7 @@ export const actionCopy = register({
}
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
// don't supply a shortcut since we handle this conditionally via onCopy event
@ -67,7 +67,7 @@ export const actionPaste = register({
if (isFirefox) {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
errorMessage: t("hints.firefox_clipboard_write"),
@ -76,7 +76,7 @@ export const actionPaste = register({
}
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
errorMessage: t("errors.asyncPasteFailedOnRead"),
@ -89,7 +89,7 @@ export const actionPaste = register({
} catch (error: any) {
console.error(error);
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
errorMessage: t("errors.asyncPasteFailedOnParse"),
@ -98,7 +98,7 @@ export const actionPaste = register({
}
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
// don't supply a shortcut since we handle this conditionally via onCopy event
@ -125,7 +125,7 @@ export const actionCopyAsSvg = register({
perform: async (elements, appState, _data, app) => {
if (!app.canvas) {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
@ -167,7 +167,7 @@ export const actionCopyAsSvg = register({
}),
},
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
} catch (error: any) {
console.error(error);
@ -175,7 +175,7 @@ export const actionCopyAsSvg = register({
appState: {
errorMessage: error.message,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
},
@ -193,7 +193,7 @@ export const actionCopyAsPng = register({
perform: async (elements, appState, _data, app) => {
if (!app.canvas) {
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
const selectedElements = app.scene.getSelectedElements({
@ -227,7 +227,7 @@ export const actionCopyAsPng = register({
}),
},
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
} catch (error: any) {
console.error(error);
@ -236,7 +236,7 @@ export const actionCopyAsPng = register({
...appState,
errorMessage: error.message,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
},
@ -263,7 +263,7 @@ export const copyText = register({
throw new Error(t("errors.copyToSystemClipboardFailed"));
}
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
predicate: (elements, appState, _, app) => {

View file

@ -1,6 +1,6 @@
import { register } from "./register";
import { cropIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
import { isImageElement } from "../element/typeChecks";
@ -25,7 +25,7 @@ export const actionToggleCropEditor = register({
isCropping: false,
croppingElementId: selectedElement.id,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
predicate: (elements, appState, _, app) => {

View file

@ -17,7 +17,7 @@ import {
} from "../element/typeChecks";
import { updateActiveTool } from "../utils";
import { TrashIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { getContainerElement } from "../element/textElement";
import { getFrameChildren } from "../frame";
@ -233,7 +233,7 @@ export const actionDeleteSelected = register({
...nextAppState,
editingLinearElement: null,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
}
@ -265,7 +265,7 @@ export const actionDeleteSelected = register({
: [0],
},
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
}
@ -287,12 +287,12 @@ export const actionDeleteSelected = register({
multiElement: null,
activeEmbeddable: null,
},
storeAction: isSomeElementSelected(
captureUpdate: isSomeElementSelected(
getNonDeletedElements(elements),
appState,
)
? StoreAction.CAPTURE
: StoreAction.NONE,
? CaptureUpdateAction.IMMEDIATELY
: CaptureUpdateAction.EVENTUALLY,
};
},
keyTest: (event, appState, elements) =>

View file

@ -12,7 +12,7 @@ import { updateFrameMembershipOfSelectedElements } from "../frame";
import { t } from "../i18n";
import { CODES, KEYS } from "../keys";
import { isSomeElementSelected } from "../scene";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import type { AppClassProperties, AppState } from "../types";
import { arrayToMap, getShortcutKey } from "../utils";
import { register } from "./register";
@ -60,7 +60,7 @@ export const distributeHorizontally = register({
space: "between",
axis: "x",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>
@ -91,7 +91,7 @@ export const distributeVertically = register({
space: "between",
axis: "y",
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>

View file

@ -42,7 +42,7 @@ import {
excludeElementsInFramesFromSelection,
getSelectedElements,
} from "../scene/selection";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionDuplicateSelection = register({
name: "duplicateSelection",
@ -62,7 +62,7 @@ export const actionDuplicateSelection = register({
return {
elements,
appState: newAppState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
} catch {
return false;
@ -83,7 +83,7 @@ export const actionDuplicateSelection = register({
return {
...nextState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.D,

View file

@ -7,7 +7,7 @@ import {
} from "../element/elementLink";
import { t } from "../i18n";
import { getSelectedElements } from "../scene";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { register } from "./register";
export const actionCopyElementLink = register({
@ -42,14 +42,14 @@ export const actionCopyElementLink = register({
closable: true,
},
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
return {
appState,
elements,
app,
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
} catch (error: any) {
@ -60,7 +60,7 @@ export const actionCopyElementLink = register({
appState,
elements,
app,
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
predicate: (elements, appState) =>
@ -78,7 +78,12 @@ export const actionLinkToElement = register({
selectedElements.length !== 1 ||
!canCreateLinkFromElements(selectedElements)
) {
return { elements, appState, app, storeAction: StoreAction.NONE };
return {
elements,
appState,
app,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
return {
@ -89,7 +94,7 @@ export const actionLinkToElement = register({
sourceElementId: getSelectedElements(elements, appState)[0].id,
},
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
predicate: (elements, appState, appProps, app) => {

View file

@ -4,7 +4,7 @@ import { isFrameLikeElement } from "../element/typeChecks";
import type { ExcalidrawElement } from "../element/types";
import { KEYS } from "../keys";
import { getSelectedElements } from "../scene";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { arrayToMap } from "../utils";
import { register } from "./register";
@ -67,7 +67,7 @@ export const actionToggleElementLock = register({
? null
: appState.selectedLinearElement,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event, appState, elements, app) => {
@ -112,7 +112,7 @@ export const actionUnlockAllElements = register({
lockedElements.map((el) => [el.id, true]),
),
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
label: "labels.elementLock.unlockAll",

View file

@ -19,7 +19,7 @@ import { nativeFileSystemSupported } from "../data/filesystem";
import type { Theme } from "../element/types";
import "../components/ToolIcon.scss";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionChangeProjectName = register({
name: "changeProjectName",
@ -28,7 +28,7 @@ export const actionChangeProjectName = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, name: value },
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ appState, updateData, appProps, data, app }) => (
@ -48,7 +48,7 @@ export const actionChangeExportScale = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportScale: value },
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ elements: allElements, appState, updateData }) => {
@ -98,7 +98,7 @@ export const actionChangeExportBackground = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportBackground: value },
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ appState, updateData }) => (
@ -118,7 +118,7 @@ export const actionChangeExportEmbedScene = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportEmbedScene: value },
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ appState, updateData }) => (
@ -160,7 +160,7 @@ export const actionSaveToActiveFile = register({
: await saveAsJSON(elements, appState, app.files, app.getName());
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
fileHandle,
@ -182,7 +182,7 @@ export const actionSaveToActiveFile = register({
} else {
console.warn(error);
}
return { storeAction: StoreAction.NONE };
return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
}
},
keyTest: (event) =>
@ -207,7 +207,7 @@ export const actionSaveFileToDisk = register({
app.getName(),
);
return {
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: {
...appState,
openDialog: null,
@ -221,7 +221,7 @@ export const actionSaveFileToDisk = register({
} else {
console.warn(error);
}
return { storeAction: StoreAction.NONE };
return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
}
},
keyTest: (event) =>
@ -260,7 +260,7 @@ export const actionLoadScene = register({
elements: loadedElements,
appState: loadedAppState,
files,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
} catch (error: any) {
if (error?.name === "AbortError") {
@ -271,7 +271,7 @@ export const actionLoadScene = register({
elements,
appState: { ...appState, errorMessage: error.message },
files: app.files,
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
},
@ -285,7 +285,7 @@ export const actionExportWithDarkMode = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportWithDarkMode: value },
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ appState, updateData }) => (

View file

@ -14,8 +14,8 @@ import {
import { isBindingElement, isLinearElement } from "../element/typeChecks";
import type { AppState } from "../types";
import { resetCursor } from "../cursor";
import { StoreAction } from "../store";
import { pointFrom } from "../../math";
import { CaptureUpdateAction } from "../store";
import { pointFrom } from "@excalidraw/math";
import { isPathALoop } from "../shapes";
export const actionFinalize = register({
@ -52,7 +52,7 @@ export const actionFinalize = register({
cursorButton: "up",
editingLinearElement: null,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
}
}
@ -199,7 +199,7 @@ export const actionFinalize = register({
pendingImageElementId: null,
},
// TODO: #7348 we should not capture everything, but if we don't, it leads to incosistencies -> revisit
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event, appState) =>

View file

@ -2,7 +2,7 @@ import React from "react";
import { Excalidraw } from "../index";
import { render } from "../tests/test-utils";
import { API } from "../tests/helpers/api";
import { pointFrom } from "../../math";
import { pointFrom } from "@excalidraw/math";
import { actionFlipHorizontal, actionFlipVertical } from "./actionFlip";
const { h } = window;

View file

@ -18,7 +18,7 @@ import {
} from "../element/binding";
import { updateFrameMembershipOfSelectedElements } from "../frame";
import { flipHorizontal, flipVertical } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import {
isArrowElement,
isElbowArrow,
@ -47,7 +47,7 @@ export const actionFlipHorizontal = register({
app,
),
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) => event.shiftKey && event.code === CODES.H,
@ -72,7 +72,7 @@ export const actionFlipVertical = register({
app,
),
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>

View file

@ -9,7 +9,7 @@ import { setCursorForShape } from "../cursor";
import { register } from "./register";
import { isFrameLikeElement } from "../element/typeChecks";
import { frameToolIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { getSelectedElements } from "../scene";
import { newFrameElement } from "../element/newElement";
import { getElementsInGroup } from "../groups";
@ -49,14 +49,14 @@ export const actionSelectAllElementsInFrame = register({
return acc;
}, {} as Record<ExcalidrawElement["id"], true>),
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
}
return {
elements,
appState,
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
predicate: (elements, appState, _, app) =>
@ -80,14 +80,14 @@ export const actionRemoveAllElementsFromFrame = register({
[selectedElement.id]: true,
},
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
}
return {
elements,
appState,
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
predicate: (elements, appState, _, app) =>
@ -109,7 +109,7 @@ export const actionupdateFrameRendering = register({
enabled: !appState.frameRendering.enabled,
},
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState: AppState) => appState.frameRendering.enabled,
@ -139,7 +139,7 @@ export const actionSetFrameAsActiveTool = register({
type: "frame",
}),
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
keyTest: (event) =>
@ -208,7 +208,7 @@ export const actionWrapSelectionInFrame = register({
appState: {
selectedElementIds: { [frame.id]: true },
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
});

View file

@ -34,7 +34,7 @@ import {
replaceAllElementsInFrame,
} from "../frame";
import { syncMovedIndices } from "../fractionalIndex";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
const allElementsInSameGroup = (elements: readonly ExcalidrawElement[]) => {
if (elements.length >= 2) {
@ -84,7 +84,11 @@ export const actionGroup = register({
);
if (selectedElements.length < 2) {
// nothing to group
return { appState, elements, storeAction: StoreAction.NONE };
return {
appState,
elements,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
// if everything is already grouped into 1 group, there is nothing to do
const selectedGroupIds = getSelectedGroupIds(appState);
@ -104,7 +108,11 @@ export const actionGroup = register({
]);
if (combinedSet.size === elementIdsInGroup.size) {
// no incremental ids in the selected ids
return { appState, elements, storeAction: StoreAction.NONE };
return {
appState,
elements,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
}
@ -170,7 +178,7 @@ export const actionGroup = register({
),
},
elements: reorderedElements,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
predicate: (elements, appState, _, app) =>
@ -200,7 +208,11 @@ export const actionUngroup = register({
const elementsMap = arrayToMap(elements);
if (groupIds.length === 0) {
return { appState, elements, storeAction: StoreAction.NONE };
return {
appState,
elements,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
let nextElements = [...elements];
@ -273,7 +285,7 @@ export const actionUngroup = register({
return {
appState: { ...appState, ...updateAppState },
elements: nextElements,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>

View file

@ -10,7 +10,7 @@ import { arrayToMap } from "../utils";
import { isWindows } from "../constants";
import type { SceneElementsMap } from "../element/types";
import type { Store } from "../store";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { useEmitter } from "../hooks/useEmitter";
const executeHistoryAction = (
@ -30,7 +30,7 @@ const executeHistoryAction = (
const result = updater();
if (!result) {
return { storeAction: StoreAction.NONE };
return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
}
const [nextElementsMap, nextAppState] = result;
@ -39,11 +39,11 @@ const executeHistoryAction = (
return {
appState: nextAppState,
elements: nextElements,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
};
}
return { storeAction: StoreAction.NONE };
return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
};
type ActionCreator = (history: History, store: Store) => Action;

View file

@ -2,7 +2,7 @@ import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette"
import { LinearElementEditor } from "../element/linearElementEditor";
import { isElbowArrow, isLinearElement } from "../element/typeChecks";
import type { ExcalidrawLinearElement } from "../element/types";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { register } from "./register";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
@ -51,7 +51,7 @@ export const actionToggleLinearEditor = register({
...appState,
editingLinearElement,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ appState, updateData, app }) => {

View file

@ -5,7 +5,7 @@ import { isEmbeddableElement } from "../element/typeChecks";
import { t } from "../i18n";
import { KEYS } from "../keys";
import { getSelectedElements } from "../scene";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { getShortcutKey } from "../utils";
import { register } from "./register";
@ -25,7 +25,7 @@ export const actionLink = register({
showHyperlinkPopup: "editor",
openMenu: null,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
trackEvent: { category: "hyperlink", action: "click" },

View file

@ -4,7 +4,7 @@ import { t } from "../i18n";
import { showSelectedShapeActions, getNonDeletedElements } from "../element";
import { register } from "./register";
import { KEYS } from "../keys";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionToggleCanvasMenu = register({
name: "toggleCanvasMenu",
@ -15,7 +15,7 @@ export const actionToggleCanvasMenu = register({
...appState,
openMenu: appState.openMenu === "canvas" ? null : "canvas",
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
}),
PanelComponent: ({ appState, updateData }) => (
<ToolButton
@ -37,7 +37,7 @@ export const actionToggleEditMenu = register({
...appState,
openMenu: appState.openMenu === "shape" ? null : "shape",
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
}),
PanelComponent: ({ elements, appState, updateData }) => (
<ToolButton
@ -74,7 +74,7 @@ export const actionShortcuts = register({
name: "help",
},
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
keyTest: (event) => event.key === KEYS.QUESTION_MARK,

View file

@ -7,7 +7,7 @@ import {
microphoneMutedIcon,
} from "../components/icons";
import { t } from "../i18n";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import type { Collaborator } from "../types";
import { register } from "./register";
import clsx from "clsx";
@ -28,7 +28,7 @@ export const actionGoToCollaborator = register({
...appState,
userToFollow: null,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
@ -42,7 +42,7 @@ export const actionGoToCollaborator = register({
// Close mobile menu
openMenu: appState.openMenu === "canvas" ? null : appState.openMenu,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ updateData, data, appState }) => {

View file

@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef, useState } from "react";
import type { AppClassProperties, AppState, Primitive } from "../types";
import type { StoreActionType } from "../store";
import type { CaptureUpdateActionType } from "../store";
import {
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
DEFAULT_ELEMENT_BACKGROUND_PICKS,
@ -109,7 +109,7 @@ import {
tupleToCoors,
} from "../utils";
import { register } from "./register";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { Fonts, getLineHeight } from "../fonts";
import {
bindLinearElement,
@ -119,8 +119,8 @@ import {
updateBoundElements,
} from "../element/binding";
import { LinearElementEditor } from "../element/linearElementEditor";
import type { LocalPoint } from "../../math";
import { pointFrom } from "../../math";
import type { LocalPoint } from "@excalidraw/math";
import { pointFrom } from "@excalidraw/math";
import { Range } from "../components/Range";
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
@ -271,7 +271,7 @@ const changeFontSize = (
? [...newFontSizes][0]
: fallbackValue ?? appState.currentItemFontSize,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
};
@ -301,9 +301,9 @@ export const actionChangeStrokeColor = register({
...appState,
...value,
},
storeAction: !!value.currentItemStrokeColor
? StoreAction.CAPTURE
: StoreAction.NONE,
captureUpdate: !!value.currentItemStrokeColor
? CaptureUpdateAction.IMMEDIATELY
: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ elements, appState, updateData, appProps }) => (
@ -347,9 +347,9 @@ export const actionChangeBackgroundColor = register({
...appState,
...value,
},
storeAction: !!value.currentItemBackgroundColor
? StoreAction.CAPTURE
: StoreAction.NONE,
captureUpdate: !!value.currentItemBackgroundColor
? CaptureUpdateAction.IMMEDIATELY
: CaptureUpdateAction.EVENTUALLY,
};
},
PanelComponent: ({ elements, appState, updateData, appProps }) => (
@ -393,7 +393,7 @@ export const actionChangeFillStyle = register({
}),
),
appState: { ...appState, currentItemFillStyle: value },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
@ -466,7 +466,7 @@ export const actionChangeStrokeWidth = register({
}),
),
appState: { ...appState, currentItemStrokeWidth: value },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@ -521,7 +521,7 @@ export const actionChangeSloppiness = register({
}),
),
appState: { ...appState, currentItemRoughness: value },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@ -572,7 +572,7 @@ export const actionChangeStrokeStyle = register({
}),
),
appState: { ...appState, currentItemStrokeStyle: value },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@ -627,7 +627,7 @@ export const actionChangeOpacity = register({
true,
),
appState: { ...appState, currentItemOpacity: value },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@ -802,22 +802,23 @@ export const actionChangeFontFamily = register({
...appState,
...nextAppState,
},
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
};
}
const { currentItemFontFamily, currentHoveredFontFamily } = value;
let nexStoreAction: StoreActionType = StoreAction.NONE;
let nextCaptureUpdateAction: CaptureUpdateActionType =
CaptureUpdateAction.EVENTUALLY;
let nextFontFamily: FontFamilyValues | undefined;
let skipOnHoverRender = false;
if (currentItemFontFamily) {
nextFontFamily = currentItemFontFamily;
nexStoreAction = StoreAction.CAPTURE;
nextCaptureUpdateAction = CaptureUpdateAction.IMMEDIATELY;
} else if (currentHoveredFontFamily) {
nextFontFamily = currentHoveredFontFamily;
nexStoreAction = StoreAction.NONE;
nextCaptureUpdateAction = CaptureUpdateAction.EVENTUALLY;
const selectedTextElements = getSelectedElements(elements, appState, {
includeBoundTextElement: true,
@ -850,7 +851,7 @@ export const actionChangeFontFamily = register({
...appState,
...nextAppState,
},
storeAction: nexStoreAction,
captureUpdate: nextCaptureUpdateAction,
};
if (nextFontFamily && !skipOnHoverRender) {
@ -1175,7 +1176,7 @@ export const actionChangeTextAlign = register({
...appState,
currentItemTextAlign: value,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => {
@ -1265,7 +1266,7 @@ export const actionChangeVerticalAlign = register({
appState: {
...appState,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => {
@ -1350,7 +1351,7 @@ export const actionChangeRoundness = register({
...appState,
currentItemRoundness: value,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
@ -1509,7 +1510,7 @@ export const actionChangeArrowhead = register({
? "currentItemStartArrowhead"
: "currentItemEndArrowhead"]: value.type,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
@ -1721,7 +1722,7 @@ export const actionChangeArrowType = register({
return {
elements: newElements,
appState: newState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => {

View file

@ -6,7 +6,7 @@ import type { ExcalidrawElement } from "../element/types";
import { isLinearElement } from "../element/typeChecks";
import { LinearElementEditor } from "../element/linearElementEditor";
import { selectAllIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionSelectAll = register({
name: "selectAll",
@ -50,7 +50,7 @@ export const actionSelectAll = register({
? new LinearElementEditor(elements[0])
: null,
},
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.A,

View file

@ -23,7 +23,7 @@ import {
import { getSelectedElements } from "../scene";
import type { ExcalidrawTextElement } from "../element/types";
import { paintIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { getLineHeight } from "../fonts";
// `copiedStyles` is exported only for tests.
@ -53,7 +53,7 @@ export const actionCopyStyles = register({
...appState,
toast: { message: t("toast.copyStyles") },
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
keyTest: (event) =>
@ -70,7 +70,7 @@ export const actionPasteStyles = register({
const pastedElement = elementsCopied[0];
const boundTextElement = elementsCopied[1];
if (!isExcalidrawElement(pastedElement)) {
return { elements, storeAction: StoreAction.NONE };
return { elements, captureUpdate: CaptureUpdateAction.EVENTUALLY };
}
const selectedElements = getSelectedElements(elements, appState, {
@ -159,7 +159,7 @@ export const actionPasteStyles = register({
}
return element;
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>

View file

@ -2,7 +2,7 @@ import { isTextElement } from "../element";
import { newElementWith } from "../element/mutateElement";
import { measureText } from "../element/textMeasurements";
import { getSelectedElements } from "../scene";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import type { AppClassProperties } from "../types";
import { getFontString } from "../utils";
import { register } from "./register";
@ -42,7 +42,7 @@ export const actionTextAutoResize = register({
}
return element;
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
});

View file

@ -2,7 +2,7 @@ import { CODES, KEYS } from "../keys";
import { register } from "./register";
import type { AppState } from "../types";
import { gridIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionToggleGridMode = register({
name: "gridMode",
@ -21,7 +21,7 @@ export const actionToggleGridMode = register({
gridModeEnabled: !this.checked!(appState),
objectsSnapModeEnabled: false,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState: AppState) => appState.gridModeEnabled,

View file

@ -1,6 +1,6 @@
import { magnetIcon } from "../components/icons";
import { CODES, KEYS } from "../keys";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { register } from "./register";
export const actionToggleObjectsSnapMode = register({
@ -19,7 +19,7 @@ export const actionToggleObjectsSnapMode = register({
objectsSnapModeEnabled: !this.checked!(appState),
gridModeEnabled: false,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState) => appState.objectsSnapModeEnabled,

View file

@ -2,7 +2,7 @@ import { KEYS } from "../keys";
import { register } from "./register";
import type { AppState } from "../types";
import { searchIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { CANVAS_SEARCH_TAB, CLASSES, DEFAULT_SIDEBAR } from "../constants";
export const actionToggleSearchMenu = register({
@ -29,7 +29,7 @@ export const actionToggleSearchMenu = register({
if (searchInput?.matches(":focus")) {
return {
appState: { ...appState, openSidebar: null },
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
}
@ -44,7 +44,7 @@ export const actionToggleSearchMenu = register({
openSidebar: { name: DEFAULT_SIDEBAR.name, tab: CANVAS_SEARCH_TAB },
openDialog: null,
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState: AppState) => appState.gridModeEnabled,

View file

@ -1,7 +1,7 @@
import { register } from "./register";
import { CODES, KEYS } from "../keys";
import { abacusIcon } from "../components/icons";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionToggleStats = register({
name: "stats",
@ -17,7 +17,7 @@ export const actionToggleStats = register({
...appState,
stats: { ...appState.stats, open: !this.checked!(appState) },
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState) => appState.stats.open,

View file

@ -1,6 +1,6 @@
import { eyeIcon } from "../components/icons";
import { CODES, KEYS } from "../keys";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { register } from "./register";
export const actionToggleViewMode = register({
@ -19,7 +19,7 @@ export const actionToggleViewMode = register({
...appState,
viewModeEnabled: !this.checked!(appState),
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState) => appState.viewModeEnabled,

View file

@ -1,6 +1,6 @@
import { coffeeIcon } from "../components/icons";
import { CODES, KEYS } from "../keys";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
import { register } from "./register";
export const actionToggleZenMode = register({
@ -19,7 +19,7 @@ export const actionToggleZenMode = register({
...appState,
zenModeEnabled: !this.checked!(appState),
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
checked: (appState) => appState.zenModeEnabled,

View file

@ -15,7 +15,7 @@ import {
SendToBackIcon,
} from "../components/icons";
import { isDarwin } from "../constants";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
export const actionSendBackward = register({
name: "sendBackward",
@ -27,7 +27,7 @@ export const actionSendBackward = register({
return {
elements: moveOneLeft(elements, appState),
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyPriority: 40,
@ -57,7 +57,7 @@ export const actionBringForward = register({
return {
elements: moveOneRight(elements, appState),
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyPriority: 40,
@ -87,7 +87,7 @@ export const actionSendToBack = register({
return {
elements: moveAllLeft(elements, appState),
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>
@ -125,7 +125,7 @@ export const actionBringToFront = register({
return {
elements: moveAllRight(elements, appState),
appState,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
keyTest: (event) =>

View file

@ -10,7 +10,7 @@ import type {
BinaryFiles,
UIAppState,
} from "../types";
import type { StoreActionType } from "../store";
import type { CaptureUpdateActionType } from "../store";
export type ActionSource =
| "ui"
@ -25,7 +25,7 @@ export type ActionResult =
elements?: readonly ExcalidrawElement[] | null;
appState?: Partial<AppState> | null;
files?: BinaryFiles | null;
storeAction: StoreActionType;
captureUpdate: CaptureUpdateActionType;
replaceFiles?: boolean;
}
| false;

View file

@ -1,5 +1,5 @@
import type { Radians } from "../math";
import { pointFrom } from "../math";
import type { Radians } from "@excalidraw/math";
import { pointFrom } from "@excalidraw/math";
import {
COLOR_PALETTE,
DEFAULT_CHART_COLOR_INDEX,

View file

@ -3,6 +3,7 @@ import {
COLOR_VOICE_CALL,
COLOR_WHITE,
THEME,
UserIdleState,
} from "./constants";
import { roundRect } from "./renderer/roundRect";
import type { InteractiveCanvasRenderConfig } from "./scene/types";
@ -11,7 +12,6 @@ import type {
InteractiveCanvasAppState,
SocketId,
} from "./types";
import { UserIdleState } from "./types";
function hashToInteger(id: string) {
let hash = 0;

View file

@ -236,8 +236,8 @@ import {
getElementShape,
isPathALoop,
} from "../shapes";
import { getSelectionBoxShape } from "../../utils/geometry/shape";
import { isPointInShape } from "../../utils/collision";
import { getSelectionBoxShape } from "@excalidraw/utils/geometry/shape";
import { isPointInShape } from "@excalidraw/utils/collision";
import type {
AppClassProperties,
AppProps,
@ -412,7 +412,7 @@ import { COLOR_PALETTE } from "../colors";
import { ElementCanvasButton } from "./MagicButton";
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
import FollowMode from "./FollowMode/FollowMode";
import { Store, StoreAction } from "../store";
import { Store, CaptureUpdateAction } from "../store";
import { AnimationFrameHandler } from "../animation-frame-handler";
import { AnimatedTrail } from "../animated-trail";
import { LaserTrails } from "../laser-trails";
@ -441,7 +441,7 @@ import {
getLinkDirectionFromKey,
} from "../element/flowchart";
import { searchItemInFocusAtom } from "./SearchMenu";
import type { LocalPoint, Radians } from "../../math";
import type { LocalPoint, Radians } from "@excalidraw/math";
import {
clamp,
pointFrom,
@ -453,7 +453,7 @@ import {
vectorSubtract,
vectorDot,
vectorNormalize,
} from "../../math";
} from "@excalidraw/math";
import { cropElement } from "../element/cropElement";
import { wrapText } from "../element/textWrapping";
import { actionCopyElementLink } from "../actions/actionElementLink";
@ -2097,12 +2097,12 @@ class App extends React.Component<AppProps, AppState> {
if (shouldUpdateStrokeColor) {
this.syncActionResult({
appState: { ...this.state, currentItemStrokeColor: color },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
} else {
this.syncActionResult({
appState: { ...this.state, currentItemBackgroundColor: color },
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
}
} else {
@ -2116,7 +2116,7 @@ class App extends React.Component<AppProps, AppState> {
}
return el;
}),
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
}
},
@ -2137,9 +2137,9 @@ class App extends React.Component<AppProps, AppState> {
return;
}
if (actionResult.storeAction === StoreAction.UPDATE) {
if (actionResult.captureUpdate === CaptureUpdateAction.NEVER) {
this.store.shouldUpdateSnapshot();
} else if (actionResult.storeAction === StoreAction.CAPTURE) {
} else if (actionResult.captureUpdate === CaptureUpdateAction.IMMEDIATELY) {
this.store.shouldCaptureIncrement();
}
@ -2214,7 +2214,10 @@ class App extends React.Component<AppProps, AppState> {
didUpdate = true;
}
if (!didUpdate && actionResult.storeAction !== StoreAction.NONE) {
if (
!didUpdate &&
actionResult.captureUpdate !== CaptureUpdateAction.EVENTUALLY
) {
this.scene.triggerUpdate();
}
});
@ -2342,7 +2345,7 @@ class App extends React.Component<AppProps, AppState> {
this.resetHistory();
this.syncActionResult({
...scene,
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
// clear the shape and image cache so that any images in initialData
@ -2822,7 +2825,7 @@ class App extends React.Component<AppProps, AppState> {
this.state.editingLinearElement &&
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]
) {
// defer so that the storeAction flag isn't reset via current update
// defer so that the shouldCaptureIncrement flag isn't reset via current update
setTimeout(() => {
// execute only if the condition still holds when the deferred callback
// executes (it can be scheduled multiple times depending on how
@ -3883,12 +3886,25 @@ class App extends React.Component<AppProps, AppState> {
elements?: SceneData["elements"];
appState?: Pick<AppState, K> | null;
collaborators?: SceneData["collaborators"];
/** @default StoreAction.NONE */
storeAction?: SceneData["storeAction"];
/**
* 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.
*
* - `CaptureUpdateAction.IMMEDIATELY`: Updates are immediately undoable. Use for most local updates.
* - `CaptureUpdateAction.NEVER`: Updates never make it to undo/redo stack. Use for remote updates or scene initialization.
* - `CaptureUpdateAction.EVENTUALLY`: Updates will be eventually be captured as part of a future increment.
*
* Check [API docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api#captureUpdate) for more details.
*
* @default CaptureUpdateAction.EVENTUALLY
*/
captureUpdate?: SceneData["captureUpdate"];
}) => {
const nextElements = syncInvalidIndices(sceneData.elements ?? []);
if (sceneData.storeAction && sceneData.storeAction !== StoreAction.NONE) {
if (
sceneData.captureUpdate &&
sceneData.captureUpdate !== CaptureUpdateAction.EVENTUALLY
) {
const prevCommittedAppState = this.store.snapshot.appState;
const prevCommittedElements = this.store.snapshot.elements;
@ -3905,12 +3921,12 @@ class App extends React.Component<AppProps, AppState> {
// WARN: store action always performs deep clone of changed elements, for ephemeral remote updates (i.e. remote dragging, resizing, drawing) we might consider doing something smarter
// do NOT schedule store actions (execute after re-render), as it might cause unexpected concurrency issues if not handled well
if (sceneData.storeAction === StoreAction.CAPTURE) {
if (sceneData.captureUpdate === CaptureUpdateAction.IMMEDIATELY) {
this.store.captureIncrement(
nextCommittedElements,
nextCommittedAppState,
);
} else if (sceneData.storeAction === StoreAction.UPDATE) {
} else if (sceneData.captureUpdate === CaptureUpdateAction.NEVER) {
this.store.updateSnapshot(
nextCommittedElements,
nextCommittedAppState,
@ -4590,7 +4606,9 @@ class App extends React.Component<AppProps, AppState> {
if (!event.altKey) {
if (this.flowChartNavigator.isExploring) {
this.flowChartNavigator.clear();
this.syncActionResult({ storeAction: StoreAction.CAPTURE });
this.syncActionResult({
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
}
}
@ -4637,7 +4655,9 @@ class App extends React.Component<AppProps, AppState> {
}
this.flowChartCreator.clear();
this.syncActionResult({ storeAction: StoreAction.CAPTURE });
this.syncActionResult({
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
}
}
});
@ -6376,10 +6396,10 @@ class App extends React.Component<AppProps, AppState> {
this.state,
),
},
storeAction:
captureUpdate:
this.state.openDialog?.name === "elementLinkSelector"
? StoreAction.NONE
: StoreAction.UPDATE,
? CaptureUpdateAction.EVENTUALLY
: CaptureUpdateAction.NEVER,
});
return;
}
@ -9042,7 +9062,7 @@ class App extends React.Component<AppProps, AppState> {
appState: {
newElement: null,
},
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
return;
@ -9212,7 +9232,7 @@ class App extends React.Component<AppProps, AppState> {
elements: this.scene
.getElementsIncludingDeleted()
.filter((el) => el.id !== resizingElement.id),
storeAction: StoreAction.UPDATE,
captureUpdate: CaptureUpdateAction.NEVER,
});
}
@ -10183,7 +10203,7 @@ class App extends React.Component<AppProps, AppState> {
isLoading: false,
},
replaceFiles: true,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
return;
} catch (error: any) {
@ -10312,7 +10332,7 @@ class App extends React.Component<AppProps, AppState> {
isLoading: false,
},
replaceFiles: true,
storeAction: StoreAction.CAPTURE,
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
} else if (ret.type === MIME_TYPES.excalidrawlib) {
await this.library

View file

@ -23,7 +23,7 @@ import { nativeFileSystemSupported } from "../data/filesystem";
import type { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { isSomeElementSelected } from "../scene";
import { exportToCanvas } from "../../utils/export";
import { exportToCanvas } from "@excalidraw/utils/export";
import { copyIcon, downloadIcon, helpIcon } from "./icons";
import { Dialog } from "./Dialog";

View file

@ -1,12 +1,7 @@
import clsx from "clsx";
import React from "react";
import type { ActionManager } from "../actions/manager";
import {
CLASSES,
DEFAULT_SIDEBAR,
LIBRARY_SIDEBAR_WIDTH,
TOOL_TYPE,
} from "../constants";
import { CLASSES, DEFAULT_SIDEBAR, TOOL_TYPE } from "../constants";
import { showSelectedShapeActions } from "../element";
import type { NonDeletedExcalidrawElement } from "../element/types";
import type { Language } from "../i18n";
@ -531,7 +526,7 @@ const LayerUI = ({
appState.openSidebar &&
isSidebarDocked &&
device.editor.canFitSidebar
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
? { width: `calc(100% - var(--right-sidebar-width)px)` }
: {}
}
>

View file

@ -7,7 +7,7 @@ import { t } from "../i18n";
import Trans from "./Trans";
import type { LibraryItems, LibraryItem, UIAppState } from "../types";
import { exportToCanvas, exportToSvg } from "../../utils/export";
import { exportToCanvas, exportToSvg } from "@excalidraw/utils/export";
import {
EDITOR_LS_KEYS,
EXPORT_DATA_TYPES,

View file

@ -3,7 +3,7 @@ import { collapseDownIcon, upIcon, searchIcon } from "./icons";
import { TextField } from "./TextField";
import { Button } from "./Button";
import { useApp, useExcalidrawSetAppState } from "./App";
import { debounce } from "lodash";
import debounce from "lodash.debounce";
import type { AppClassProperties } from "../types";
import { isTextElement, newTextElement } from "../element";
import type { ExcalidrawTextElement } from "../element/types";
@ -18,7 +18,7 @@ import { CLASSES, EVENT } from "../constants";
import { useStable } from "../hooks/useStable";
import "./SearchMenu.scss";
import { round } from "../../math";
import { round } from "@excalidraw/math";
import { measureText } from "../element/textMeasurements";
const searchQueryAtom = atom<string>("");

View file

@ -30,7 +30,7 @@
overflow: hidden;
border-radius: 0;
width: calc(#{$right-sidebar-width} - var(--space-factor) * 2);
width: calc(var(--right-sidebar-width) - var(--space-factor) * 2);
border-left: 1px solid var(--sidebar-border-color);

View file

@ -136,6 +136,9 @@ export const SidebarInner = forwardRef(
<Island
{...rest}
className={clsx("sidebar", { "sidebar--docked": docked }, className)}
style={{
"--right-sidebar-width": "302px",
}}
ref={islandRef}
>
<SidebarPropsContext.Provider value={headerPropsRef.current}>

View file

@ -8,8 +8,8 @@ import type { DragInputCallbackType } from "./DragInput";
import { getStepSizedValue, isPropertyEditable, updateBindings } from "./utils";
import type Scene from "../../scene/Scene";
import type { AppState } from "../../types";
import type { Degrees } from "../../../math";
import { degreesToRadians, radiansToDegrees } from "../../../math";
import type { Degrees } from "@excalidraw/math";
import { degreesToRadians, radiansToDegrees } from "@excalidraw/math";
interface AngleProps {
element: ExcalidrawElement;

View file

@ -12,7 +12,7 @@ import {
getUncroppedWidthAndHeight,
} from "../../element/cropElement";
import { mutateElement } from "../../element/mutateElement";
import { clamp, round } from "../../../math";
import { clamp, round } from "@excalidraw/math";
interface DimensionDragInputProps {
property: "width" | "height";

View file

@ -8,7 +8,7 @@ import { useApp } from "../App";
import { InlineIcon } from "../InlineIcon";
import type { StatsInputProperty } from "./utils";
import { SMALLEST_DELTA } from "./utils";
import { StoreAction } from "../../store";
import { CaptureUpdateAction } from "../../store";
import type Scene from "../../scene/Scene";
import "./DragInput.scss";
@ -132,7 +132,9 @@ const StatsDragInput = <
originalAppState: appState,
setInputValue: (value) => setInputValue(String(value)),
});
app.syncActionResult({ storeAction: StoreAction.CAPTURE });
app.syncActionResult({
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
}
};
@ -276,7 +278,9 @@ const StatsDragInput = <
false,
);
app.syncActionResult({ storeAction: StoreAction.CAPTURE });
app.syncActionResult({
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
});
lastPointer = null;
accumulatedChange = 0;

View file

@ -9,8 +9,8 @@ import DragInput from "./DragInput";
import type { DragInputCallbackType } from "./DragInput";
import { getStepSizedValue, isPropertyEditable } from "./utils";
import type { AppState } from "../../types";
import type { Degrees } from "../../../math";
import { degreesToRadians, radiansToDegrees } from "../../../math";
import type { Degrees } from "@excalidraw/math";
import { degreesToRadians, radiansToDegrees } from "@excalidraw/math";
interface MultiAngleProps {
elements: readonly ExcalidrawElement[];

View file

@ -23,7 +23,7 @@ import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
import { getElementsInAtomicUnit } from "./utils";
import type { AtomicUnit } from "./utils";
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
import { pointFrom, type GlobalPoint } from "../../../math";
import { pointFrom, type GlobalPoint } from "@excalidraw/math";
interface MultiDimensionProps {
property: "width" | "height";

View file

@ -13,7 +13,7 @@ import { useMemo } from "react";
import { getElementsInAtomicUnit, moveElement } from "./utils";
import type { AtomicUnit } from "./utils";
import type { AppState } from "../../types";
import { pointFrom, pointRotateRads } from "../../../math";
import { pointFrom, pointRotateRads } from "@excalidraw/math";
interface MultiPositionProps {
property: "x" | "y";

View file

@ -4,7 +4,7 @@ import type { DragInputCallbackType } from "./DragInput";
import { getStepSizedValue, moveElement } from "./utils";
import type Scene from "../../scene/Scene";
import type { AppState } from "../../types";
import { clamp, pointFrom, pointRotateRads, round } from "../../../math";
import { clamp, pointFrom, pointRotateRads, round } from "@excalidraw/math";
import { isImageElement } from "../../element/typeChecks";
import {
getFlipAdjustedCropPosition,

View file

@ -9,7 +9,7 @@ import type {
} from "../../types";
import { CloseIcon } from "../icons";
import { Island } from "../Island";
import { throttle } from "lodash";
import throttle from "lodash.throttle";
import Dimension from "./Dimension";
import Angle from "./Angle";
import FontSize from "./FontSize";
@ -30,7 +30,7 @@ import clsx from "clsx";
import "./Stats.scss";
import { isGridModeEnabled } from "../../snapping";
import { getUncroppedWidthAndHeight } from "../../element/cropElement";
import { round } from "../../../math";
import { round } from "@excalidraw/math";
import { frameAndChildrenSelectedTogether } from "../../frame";
interface StatsProps {

View file

@ -24,8 +24,8 @@ import { getCommonBounds, isTextElement } from "../../element";
import { API } from "../../tests/helpers/api";
import { actionGroup } from "../../actions";
import { isInGroup } from "../../groups";
import type { Degrees } from "../../../math";
import { degreesToRadians, pointFrom, pointRotateRads } from "../../../math";
import type { Degrees } from "@excalidraw/math";
import { degreesToRadians, pointFrom, pointRotateRads } from "@excalidraw/math";
const { h } = window;
const mouse = new Pointer("mouse");

View file

@ -1,5 +1,5 @@
import type { Radians } from "../../../math";
import { pointFrom, pointRotateRads } from "../../../math";
import type { Radians } from "@excalidraw/math";
import { pointFrom, pointRotateRads } from "@excalidraw/math";
import {
bindOrUnbindLinearElements,
updateBoundElements,

View file

@ -29,7 +29,7 @@ import { atom, useAtom } from "../../editor-jotai";
import { trackEvent } from "../../analytics";
import { InlineIcon } from "../InlineIcon";
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
import { isFiniteNumber } from "../../../math";
import { isFiniteNumber } from "@excalidraw/math";
const MIN_PROMPT_LENGTH = 3;
const MAX_PROMPT_LENGTH = 1000;

View file

@ -34,7 +34,7 @@ import { trackEvent } from "../../analytics";
import { useAppProps, useDevice, useExcalidrawAppState } from "../App";
import { isEmbeddableElement } from "../../element/typeChecks";
import { getLinkHandleFromCoords } from "./helpers";
import { pointFrom, type GlobalPoint } from "../../../math";
import { pointFrom, type GlobalPoint } from "@excalidraw/math";
import { isElementLink } from "../../element/elementLink";
import "./Hyperlink.scss";

View file

@ -1,5 +1,5 @@
import type { GlobalPoint, Radians } from "../../../math";
import { pointFrom, pointRotateRads } from "../../../math";
import type { GlobalPoint, Radians } from "@excalidraw/math";
import { pointFrom, pointRotateRads } from "@excalidraw/math";
import { MIME_TYPES } from "../../constants";
import type { Bounds } from "../../element/bounds";
import { getElementAbsoluteCoords } from "../../element/bounds";

View file

@ -1,4 +1,3 @@
import cssVariables from "./css/variables.module.scss";
import type { AppProps, AppState } from "./types";
import type { ExcalidrawElement, FontFamilyValues } from "./element/types";
import { COLOR_PALETTE } from "./colors";
@ -269,7 +268,8 @@ export const IDLE_THRESHOLD = 60_000;
// Report a user active each ACTIVE_THRESHOLD milliseconds
export const ACTIVE_THRESHOLD = 3_000;
export const THEME_FILTER = cssVariables.themeFilter;
// duplicates --theme-filter, should be removed soon
export const THEME_FILTER = "invert(93%) hue-rotate(180deg)";
export const URL_QUERY_KEYS = {
addLibrary: "addLibrary",
@ -304,8 +304,6 @@ export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
// -----------------------------------------------------------------------------
export const LIBRARY_SIDEBAR_WIDTH = parseInt(cssVariables.rightSidebarWidth);
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
export const EXPORT_SCALES = [1, 2, 3];
@ -461,3 +459,9 @@ export const ELEMENT_LINK_KEY = "element";
/** used in tests */
export const ORIG_ID = Symbol.for("__test__originalId__");
export enum UserIdleState {
ACTIVE = "active",
AWAY = "away",
IDLE = "idle",
}

View file

@ -170,7 +170,7 @@
}
&.theme--dark {
--theme-filter: #{$theme-filter};
--theme-filter: invert(93%) hue-rotate(180deg);
--button-destructive-bg-color: #5a0000;
--button-destructive-color: #{$oc-red-3};

View file

@ -188,11 +188,3 @@
box-shadow: 0 0 0 1px var(--color-brand-active);
}
}
$theme-filter: "invert(93%) hue-rotate(180deg)";
$right-sidebar-width: "302px";
:export {
themeFilter: unquote($theme-filter);
rightSidebarWidth: unquote($right-sidebar-width);
}

View file

@ -57,8 +57,8 @@ import {
getNormalizedGridStep,
getNormalizedZoom,
} from "../scene";
import type { LocalPoint, Radians } from "../../math";
import { isFiniteNumber, pointFrom } from "../../math";
import type { LocalPoint, Radians } from "@excalidraw/math";
import { isFiniteNumber, pointFrom } from "@excalidraw/math";
import { detectLineHeight } from "../element/textMeasurements";
type RestoredAppState = Omit<

View file

@ -2,7 +2,7 @@ import { vi } from "vitest";
import type { ExcalidrawElementSkeleton } from "./transform";
import { convertToExcalidrawElements } from "./transform";
import type { ExcalidrawArrowElement } from "../element/types";
import { pointFrom } from "../../math";
import { pointFrom } from "@excalidraw/math";
const opts = { regenerateIds: false };

View file

@ -53,7 +53,7 @@ import { randomId } from "../random";
import { syncInvalidIndices } from "../fractionalIndex";
import { getLineHeight } from "../fonts";
import { isArrowElement } from "../element/typeChecks";
import { pointFrom, type LocalPoint } from "../../math";
import { pointFrom, type LocalPoint } from "@excalidraw/math";
import { measureText, normalizeText } from "../element/textMeasurements";
export type ValidLinearElement = {

View file

@ -19,7 +19,7 @@ import type {
import type { Bounds } from "./bounds";
import { getCenterForBounds } from "./bounds";
import type { AppState } from "../types";
import { isPointOnShape } from "../../utils/collision";
import { isPointOnShape } from "@excalidraw/utils/collision";
import {
isArrowElement,
isBindableElement,
@ -53,7 +53,7 @@ import {
vectorToHeading,
type Heading,
} from "./heading";
import type { LocalPoint, Radians } from "../../math";
import type { LocalPoint, Radians } from "@excalidraw/math";
import {
lineSegment,
pointFrom,
@ -69,7 +69,7 @@ import {
vectorCross,
pointsEqual,
lineSegmentIntersectionPoints,
} from "../../math";
} from "@excalidraw/math";
import { intersectElementWithLineSegment } from "./collision";
import { distanceToBindableElement } from "./distance";

View file

@ -1,5 +1,5 @@
import type { LocalPoint } from "../../math";
import { pointFrom } from "../../math";
import type { LocalPoint } from "@excalidraw/math";
import { pointFrom } from "@excalidraw/math";
import { ROUNDNESS } from "../constants";
import { arrayToMap } from "../utils";
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";

View file

@ -30,7 +30,7 @@ import type {
LineSegment,
LocalPoint,
Radians,
} from "../../math";
} from "@excalidraw/math";
import {
degreesToRadians,
lineSegment,
@ -38,7 +38,7 @@ import {
pointDistance,
pointFromArray,
pointRotateRads,
} from "../../math";
} from "@excalidraw/math";
import type { Mutable } from "../utility-types";
export type RectangleBox = {

View file

@ -8,9 +8,9 @@ import type {
} from "./types";
import { getElementBounds } from "./bounds";
import type { FrameNameBounds } from "../types";
import type { GeometricShape } from "../../utils/geometry/shape";
import { getPolygonShape } from "../../utils/geometry/shape";
import { isPointInShape, isPointOnShape } from "../../utils/collision";
import type { GeometricShape } from "@excalidraw/utils/geometry/shape";
import { getPolygonShape } from "@excalidraw/utils/geometry/shape";
import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision";
import { isTransparent } from "../utils";
import {
hasBoundTextElement,
@ -25,7 +25,7 @@ import type {
LocalPoint,
Polygon,
Radians,
} from "../../math";
} from "@excalidraw/math";
import {
curveIntersectLineSegment,
isPointWithinBounds,
@ -35,8 +35,11 @@ import {
pointFrom,
pointRotateRads,
pointsEqual,
} from "../../math";
import { ellipse, ellipseLineIntersectionPoints } from "../../math/ellipse";
} from "@excalidraw/math";
import {
ellipse,
ellipseLineIntersectionPoints,
} from "@excalidraw/math/ellipse";
import {
deconstructDiamondElement,
deconstructRectanguloidElement,

View file

@ -12,7 +12,7 @@ import {
pointFromVector,
clamp,
isCloseTo,
} from "../../math";
} from "@excalidraw/math";
import type { TransformHandleType } from "./transformHandles";
import type {
ElementsMap,

View file

@ -1,11 +1,11 @@
import type { GlobalPoint, Radians } from "../../math";
import type { GlobalPoint, Radians } from "@excalidraw/math";
import {
curvePointDistance,
distanceToLineSegment,
pointFrom,
pointRotateRads,
} from "../../math";
import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse";
} from "@excalidraw/math";
import { ellipse, ellipseDistanceFromPoint } from "@excalidraw/math/ellipse";
import type {
ExcalidrawBindableElement,
ExcalidrawDiamondElement,

View file

@ -16,9 +16,9 @@ import type {
ExcalidrawElbowArrowElement,
} from "./types";
import { ARROW_TYPE } from "../constants";
import type { LocalPoint } from "../../math";
import { pointFrom } from "../../math";
import "../../utils/test-utils";
import type { LocalPoint } from "@excalidraw/math";
import { pointFrom } from "@excalidraw/math";
const { h } = window;

View file

@ -11,7 +11,7 @@ import {
vectorScale,
type GlobalPoint,
type LocalPoint,
} from "../../math";
} from "@excalidraw/math";
import BinaryHeap from "../binaryheap";
import { getSizeFromPoints } from "../points";
import { aabbForElement, pointInsideBounds } from "../shapes";

View file

@ -12,7 +12,7 @@ import type {
IframeData,
} from "./types";
import type { MarkRequired } from "../utility-types";
import { StoreAction } from "../store";
import { CaptureUpdateAction } from "../store";
type IframeDataWithSandbox = MarkRequired<IframeData, "sandbox">;
@ -340,7 +340,7 @@ export const actionSetEmbeddableAsActiveTool = register({
type: "embeddable",
}),
},
storeAction: StoreAction.NONE,
captureUpdate: CaptureUpdateAction.EVENTUALLY,
};
},
});

View file

@ -31,7 +31,7 @@ import {
isFlowchartNodeElement,
} from "./typeChecks";
import { invariant, toBrandedType } from "../utils";
import { pointFrom, type LocalPoint } from "../../math";
import { pointFrom, type LocalPoint } from "@excalidraw/math";
import { aabbForElement } from "../shapes";
import { updateElbowArrowPoints } from "./elbowArrow";

View file

@ -4,7 +4,7 @@ import type {
Triangle,
Vector,
Radians,
} from "../../math";
} from "@excalidraw/math";
import {
pointFrom,
pointRotateRads,
@ -12,7 +12,7 @@ import {
radiansToDegrees,
triangleIncludesPoint,
vectorFromPoint,
} from "../../math";
} from "@excalidraw/math";
import { getCenterForBounds, type Bounds } from "./bounds";
import type { ExcalidrawBindableElement } from "./types";

View file

@ -47,7 +47,7 @@ import type { Mutable } from "../utility-types";
import { ShapeCache } from "../scene/ShapeCache";
import type { Store } from "../store";
import type Scene from "../scene/Scene";
import type { Radians } from "../../math";
import type { Radians } from "@excalidraw/math";
import {
pointCenter,
pointFrom,
@ -59,7 +59,7 @@ import {
pointDistance,
pointTranslate,
vectorFromPoint,
} from "../../math";
} from "@excalidraw/math";
import {
getBezierCurveLength,
getBezierXY,

View file

@ -7,7 +7,7 @@ import type { Mutable } from "../utility-types";
import { ShapeCache } from "../scene/ShapeCache";
import { isElbowArrow } from "./typeChecks";
import { updateElbowArrowPoints } from "./elbowArrow";
import type { Radians } from "../../math";
import type { Radians } from "@excalidraw/math";
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
Partial<TElement>,

View file

@ -4,8 +4,8 @@ import { API } from "../tests/helpers/api";
import { FONT_FAMILY, ROUNDNESS } from "../constants";
import { isPrimitive } from "../utils";
import type { ExcalidrawLinearElement } from "./types";
import type { LocalPoint } from "../../math";
import { pointFrom } from "../../math";
import type { LocalPoint } from "@excalidraw/math";
import { pointFrom } from "@excalidraw/math";
const assertCloneObjects = (source: any, clone: any) => {
for (const key in clone) {

View file

@ -46,7 +46,7 @@ import {
} from "../constants";
import type { MarkOptional, Merge, Mutable } from "../utility-types";
import { getLineHeight } from "../fonts";
import type { Radians } from "../../math";
import type { Radians } from "@excalidraw/math";
import { normalizeText, measureText } from "./textMeasurements";
export type ElementConstructorOpts = MarkOptional<

View file

@ -50,7 +50,7 @@ import {
import { wrapText } from "./textWrapping";
import { LinearElementEditor } from "./linearElementEditor";
import { isInGroup } from "../groups";
import type { GlobalPoint } from "../../math";
import type { GlobalPoint } from "@excalidraw/math";
import {
pointCenter,
normalizeRadians,
@ -59,7 +59,7 @@ import {
pointRotateRads,
type Radians,
type LocalPoint,
} from "../../math";
} from "@excalidraw/math";
import {
getMinTextElementWidth,
measureText,

View file

@ -21,13 +21,13 @@ import type { Bounds } from "./bounds";
import { getElementAbsoluteCoords } from "./bounds";
import { SIDE_RESIZING_THRESHOLD } from "../constants";
import { isImageElement, isLinearElement } from "./typeChecks";
import type { GlobalPoint, LineSegment, LocalPoint } from "../../math";
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
import {
pointFrom,
pointOnLineSegment,
pointRotateRads,
type Radians,
} from "../../math";
} from "@excalidraw/math";
const isInsideTransformHandle = (
transformHandle: TransformHandle,

View file

@ -23,7 +23,7 @@ import type {
import { API } from "../tests/helpers/api";
import { getOriginalContainerHeightFromCache } from "./containerCache";
import { getTextEditor, updateTextEditor } from "../tests/queries/dom";
import { pointFrom } from "../../math";
import { pointFrom } from "@excalidraw/math";
unmountComponent();

View file

@ -19,8 +19,8 @@ import {
isAndroid,
isIOS,
} from "../constants";
import type { Radians } from "../../math";
import { pointFrom, pointRotateRads } from "../../math";
import type { Radians } from "@excalidraw/math";
import { pointFrom, pointRotateRads } from "@excalidraw/math";
export type TransformHandleDirection =
| "n"

View file

@ -1,4 +1,4 @@
import type { LocalPoint, Radians } from "../../math";
import type { LocalPoint, Radians } from "@excalidraw/math";
import type {
FONT_FAMILY,
ROUNDNESS,

View file

@ -1,5 +1,5 @@
import { getDiamondPoints } from ".";
import type { Curve, LineSegment } from "../../math";
import type { Curve, LineSegment } from "@excalidraw/math";
import {
curve,
lineSegment,
@ -10,7 +10,7 @@ import {
vectorNormalize,
vectorScale,
type GlobalPoint,
} from "../../math";
} from "@excalidraw/math";
import { getCornerRadius } from "../shapes";
import type {
ExcalidrawDiamondElement,

View file

@ -8,7 +8,7 @@ export class ExcalidrawFontFace {
public readonly urls: URL[] | DataURL[];
public readonly fontFace: FontFace;
private static readonly UNPKG_FALLBACK_URL = `https://unpkg.com/${
private static readonly ASSETS_FALLBACK_URL = `https://esm.sh/${
import.meta.env.VITE_PKG_NAME
? `${import.meta.env.VITE_PKG_NAME}@${import.meta.env.PKG_VERSION}` // should be provided by vite during package build
: "@excalidraw/excalidraw" // fallback to latest package version (i.e. for app)
@ -164,7 +164,7 @@ export class ExcalidrawFontFace {
}
// fallback url for bundled fonts
urls.push(new URL(assetUrl, ExcalidrawFontFace.UNPKG_FALLBACK_URL));
urls.push(new URL(assetUrl, ExcalidrawFontFace.ASSETS_FALLBACK_URL));
return urls;
}

View file

@ -26,10 +26,13 @@ import { getElementsWithinSelection, getSelectedElements } from "./scene";
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
import type { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
import { getElementLineSegments } from "./element/bounds";
import { doLineSegmentsIntersect, elementsOverlappingBBox } from "../utils/";
import {
doLineSegmentsIntersect,
elementsOverlappingBBox,
} from "@excalidraw/utils";
import { isFrameElement, isFrameLikeElement } from "./element/typeChecks";
import type { ReadonlySetLike } from "./utility-types";
import { isPointWithinBounds, pointFrom } from "../math";
import { isPointWithinBounds, pointFrom } from "@excalidraw/math";
// --------------------------- Frame State ------------------------------------
export const bindElementsToFramesAfterDuplication = (

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { COLOR_PALETTE } from "../colors";
import { atom, useAtom } from "../editor-jotai";
import { exportToSvg } from "../../utils/export";
import { exportToSvg } from "@excalidraw/utils/export";
import type { LibraryItem } from "../types";
export type SvgCache = Map<LibraryItem["id"], SVGSVGElement>;

View file

@ -233,7 +233,7 @@ export {
exportToBlob,
exportToSvg,
exportToClipboard,
} from "../utils/export";
} from "@excalidraw/utils/export";
export { serializeAsJSON, serializeLibraryAsJSON } from "./data/json";
export {
@ -251,6 +251,7 @@ export {
MIME_TYPES,
ROUNDNESS,
DEFAULT_LASER_COLOR,
UserIdleState,
} from "./constants";
export {
@ -259,7 +260,7 @@ export {
bumpVersion,
} from "./element/mutateElement";
export { StoreAction } from "./store";
export { CaptureUpdateAction } from "./store";
export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library";
@ -290,7 +291,7 @@ export {
elementsOverlappingBBox,
isElementInsideBBox,
elementPartiallyOverlapsWithOrContainsBBox,
} from "../utils/withinBounds";
} from "@excalidraw/utils/withinBounds";
export { DiagramToCodePlugin } from "./components/DiagramToCodePlugin/DiagramToCodePlugin";
export { getDataURL } from "./data/blob";

View file

@ -1,21 +1,25 @@
{
"name": "@excalidraw/excalidraw",
"version": "0.17.1",
"main": "./dist/prod/index.js",
"version": "0.18.0",
"type": "module",
"types": "./dist/types/excalidraw/index.d.ts",
"main": "./dist/prod/index.js",
"module": "./dist/prod/index.js",
"exports": {
".": {
"development": "./dist/dev/index.js",
"types": "./dist/excalidraw/index.d.ts",
"default": "./dist/prod/index.js"
"./*": {
"types": "./dist/types/excalidraw/*.d.ts"
},
"./index.css": {
"development": "./dist/dev/index.css",
"default": "./dist/prod/index.css"
"production": "./dist/prod/index.css"
},
".": {
"types": "./dist/types/excalidraw/index.d.ts",
"development": "./dist/dev/index.js",
"production": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
}
},
"types": "./dist/excalidraw/index.d.ts",
"files": [
"dist/*"
],
@ -23,7 +27,6 @@
"access": "public"
},
"description": "Excalidraw as a React component",
"repository": "https://github.com/excalidraw/excalidraw",
"license": "MIT",
"keywords": [
"excalidraw",
@ -73,9 +76,10 @@
"jotai": "2.11.0",
"jotai-scope": "0.7.2",
"lodash.throttle": "4.1.1",
"lodash.debounce": "4.0.8",
"nanoid": "3.3.3",
"open-color": "1.9.1",
"pako": "1.0.11",
"pako": "2.0.3",
"perfect-freehand": "1.2.0",
"pica": "7.1.1",
"png-chunk-text": "1.0.0",
@ -88,53 +92,32 @@
"tunnel-rat": "0.1.2"
},
"devDependencies": {
"@babel/core": "7.24.5",
"@babel/plugin-transform-arrow-functions": "7.24.1",
"@babel/plugin-transform-async-to-generator": "7.24.1",
"@babel/plugin-transform-runtime": "7.24.3",
"@babel/plugin-transform-typescript": "7.24.5",
"@babel/preset-env": "7.24.5",
"@babel/preset-react": "7.24.1",
"@babel/preset-typescript": "7.24.1",
"@size-limit/preset-big-lib": "9.0.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"@types/pako": "1.0.3",
"@types/lodash.debounce": "4.0.8",
"@types/pako": "2.0.3",
"@types/pica": "5.1.3",
"@types/resize-observer-browser": "0.1.7",
"ansicolor": "2.0.3",
"autoprefixer": "10.4.7",
"babel-loader": "8.2.5",
"babel-plugin-transform-class-properties": "6.24.1",
"cross-env": "7.0.3",
"css-loader": "6.7.1",
"dotenv": "16.0.1",
"esbuild": "0.19.10",
"esbuild-plugin-external-global": "1.0.1",
"esbuild-sass-plugin": "2.16.0",
"eslint-plugin-react": "7.32.2",
"fake-indexeddb": "3.1.7",
"fonteditor-core": "2.4.1",
"harfbuzzjs": "0.3.6",
"import-meta-loader": "1.1.0",
"jest-diff": "29.7.0",
"mini-css-extract-plugin": "2.6.1",
"postcss-loader": "7.0.1",
"sass-loader": "13.0.2",
"size-limit": "9.0.0",
"style-loader": "3.3.3",
"ts-loader": "9.3.1",
"typescript": "4.9.4"
},
"repository": "https://github.com/excalidraw/excalidraw",
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"homepage": "https://github.com/excalidraw/excalidraw/tree/master/packages/excalidraw",
"scripts": {
"gen:types": "rm -rf types && tsc",
"build:esm": "rm -rf dist && node ../../scripts/buildPackage.js && yarn gen:types",
"pack": "yarn build:umd && yarn pack",
"start": "node ../../scripts/buildExample.mjs && vite",
"build:example": "node ../../scripts/buildExample.mjs",
"size": "yarn build:umd && size-limit"
"build:esm": "rm -rf dist && node ../../scripts/buildPackage.js && yarn gen:types"
}
}

View file

@ -1,4 +1,8 @@
import { pointFromPair, type GlobalPoint, type LocalPoint } from "../math";
import {
pointFromPair,
type GlobalPoint,
type LocalPoint,
} from "@excalidraw/math";
export const getSizeFromPoints = (
points: readonly (GlobalPoint | LocalPoint)[],

View file

@ -83,7 +83,7 @@ import {
type GlobalPoint,
type LocalPoint,
type Radians,
} from "../../math";
} from "@excalidraw/math";
import { getCornerRadius } from "../shapes";
const renderElbowArrowMidPointHighlight = (

View file

@ -60,7 +60,7 @@ import { LinearElementEditor } from "../element/linearElementEditor";
import { getContainingFrame } from "../frame";
import { ShapeCache } from "../scene/ShapeCache";
import { getVerticalOffset } from "../fonts";
import { isRightAngleRads } from "../../math";
import { isRightAngleRads } from "@excalidraw/math";
import { getCornerRadius } from "../shapes";
import { getUncroppedImageElement } from "../element/cropElement";
import { getLineHeightInPx } from "../element/textMeasurements";

View file

@ -1,4 +1,4 @@
import { pointFrom, type GlobalPoint, type LocalPoint } from "../../math";
import { pointFrom, type GlobalPoint, type LocalPoint } from "@excalidraw/math";
import { THEME } from "../constants";
import type { PointSnapLine, PointerSnapLine } from "../snapping";
import type { InteractiveCanvasAppState } from "../types";

View file

@ -23,7 +23,7 @@ import {
} from "../element/typeChecks";
import { canChangeRoundness } from "./comparisons";
import type { EmbedsValidationStatus } from "../types";
import { pointFrom, pointDistance, type LocalPoint } from "../../math";
import { pointFrom, pointDistance, type LocalPoint } from "@excalidraw/math";
import { getCornerRadius, isPathALoop } from "../shapes";
import { headingForPointIsHorizontal } from "../element/heading";

View file

@ -1,4 +1,4 @@
import { clamp, round } from "../../math";
import { clamp, round } from "@excalidraw/math";
import { MAX_ZOOM, MIN_ZOOM } from "../constants";
import type { NormalizedZoomValue } from "../types";

View file

@ -14,11 +14,11 @@ import type {
InteractiveCanvasAppState,
StaticCanvasAppState,
SocketId,
UserIdleState,
Device,
PendingExcalidrawElements,
} from "../types";
import type { MakeBrand } from "../utility-types";
import type { UserIdleState } from "../constants";
export type RenderableElementsMap = NonDeletedElementsMap &
MakeBrand<"RenderableElementsMap">;

View file

@ -7,7 +7,7 @@ import {
pointsEqual,
type GlobalPoint,
type LocalPoint,
} from "../math";
} from "@excalidraw/math";
import {
getClosedCurveShape,
getCurvePathOps,
@ -16,7 +16,7 @@ import {
getFreedrawShape,
getPolygonShape,
type GeometricShape,
} from "../utils/geometry/shape";
} from "@excalidraw/utils/geometry/shape";
import {
ArrowIcon,
DiamondIcon,

View file

@ -1,4 +1,4 @@
import type { InclusiveRange } from "../math";
import type { InclusiveRange } from "@excalidraw/math";
import {
pointFrom,
pointRotateRads,
@ -6,7 +6,7 @@ import {
rangeIntersection,
rangesOverlap,
type GlobalPoint,
} from "../math";
} from "@excalidraw/math";
import { TOOL_TYPE } from "./constants";
import type { Bounds } from "./element/bounds";
import {

View file

@ -37,7 +37,7 @@ const isObservedAppState = (
): appState is ObservedAppState =>
!!Reflect.get(appState, hiddenObservedAppStateProp);
export const StoreAction = {
export const CaptureUpdateAction = {
/**
* Immediately undoable.
*
@ -46,7 +46,7 @@ export const StoreAction = {
*
* These updates will _immediately_ make it to the local undo / redo stacks.
*/
CAPTURE: "capture",
IMMEDIATELY: "IMMEDIATELY",
/**
* Never undoable.
*
@ -55,22 +55,22 @@ export const StoreAction = {
*
* These updates will _never_ make it to the local undo / redo stacks.
*/
UPDATE: "update",
NEVER: "NEVER",
/**
* Eventually undoable.
*
* 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
* `StoreAction.CAPTURE` - triggered either by the next `updateScene`
* `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.
*/
NONE: "none",
EVENTUALLY: "EVENTUALLY",
} as const;
export type StoreActionType = ValueOf<typeof StoreAction>;
export type CaptureUpdateActionType = ValueOf<typeof CaptureUpdateAction>;
/**
* Represent an increment to the Store.
@ -133,7 +133,7 @@ export class Store implements IStore {
[StoreIncrementEvent]
>();
private scheduledActions: Set<StoreActionType> = new Set();
private scheduledActions: Set<CaptureUpdateActionType> = new Set();
private _snapshot = Snapshot.empty();
public get snapshot() {
@ -146,14 +146,14 @@ export class Store implements IStore {
// TODO: Suspicious that this is called so many places. Seems error-prone.
public shouldCaptureIncrement = () => {
this.scheduleAction(StoreAction.CAPTURE);
this.scheduleAction(CaptureUpdateAction.IMMEDIATELY);
};
public shouldUpdateSnapshot = () => {
this.scheduleAction(StoreAction.UPDATE);
this.scheduleAction(CaptureUpdateAction.NEVER);
};
private scheduleAction = (action: StoreActionType) => {
private scheduleAction = (action: CaptureUpdateActionType) => {
this.scheduledActions.add(action);
this.satisfiesScheduledActionsInvariant();
};
@ -164,9 +164,9 @@ export class Store implements IStore {
): void => {
try {
// Capture has precedence since it also performs update
if (this.scheduledActions.has(StoreAction.CAPTURE)) {
if (this.scheduledActions.has(CaptureUpdateAction.IMMEDIATELY)) {
this.captureIncrement(elements, appState);
} else if (this.scheduledActions.has(StoreAction.UPDATE)) {
} else if (this.scheduledActions.has(CaptureUpdateAction.NEVER)) {
this.updateSnapshot(elements, appState);
}
} finally {

Some files were not shown because too many files have changed in this diff Show more