@excalidraw/excalidraw 0.17.1-c0b80a0 → 0.17.1-c329470
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-JGDL4H2X.js → chunk-3DLVY5XU.js} +8272 -6864
- package/dist/browser/dev/excalidraw-assets-dev/chunk-3DLVY5XU.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-V7NFEZA6.js → chunk-NOAEU4NM.js} +9 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-NOAEU4NM.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js → en-7IBTMWBG.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js → image-N5AC7SEK.js} +2 -6
- package/dist/browser/dev/index.css +85 -50
- package/dist/browser/dev/index.css.map +3 -3
- package/dist/browser/dev/index.js +4375 -3766
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/{chunk-LDVEIXGO.js → chunk-7CSIPVOW.js} +2 -2
- package/dist/browser/prod/excalidraw-assets/chunk-TX3BU7T2.js +47 -0
- package/dist/browser/prod/excalidraw-assets/{en-UPNEHLDS.js → en-LOGQBETY.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-3V4U7GZE.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +40 -40
- package/dist/dev/index.css +85 -50
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +8688 -6706
- package/dist/dev/index.js.map +4 -4
- package/dist/{prod/locales/en-ZXYG7GCR.json → dev/locales/en-V6KXFSCK.json} +8 -1
- package/dist/excalidraw/actions/actionAlign.d.ts +7 -6
- package/dist/excalidraw/actions/actionAlign.js +14 -14
- package/dist/excalidraw/actions/actionClipboard.d.ts +7 -3
- package/dist/excalidraw/actions/actionDeleteSelected.d.ts +7 -3
- package/dist/excalidraw/actions/actionDeleteSelected.js +103 -34
- package/dist/excalidraw/actions/actionDuplicateSelection.js +105 -95
- package/dist/excalidraw/actions/actionFlip.js +16 -7
- package/dist/excalidraw/actions/actionFrame.d.ts +493 -0
- package/dist/excalidraw/actions/actionFrame.js +45 -2
- package/dist/excalidraw/actions/actionGroup.js +6 -4
- package/dist/excalidraw/actions/actionProperties.js +145 -116
- package/dist/excalidraw/actions/actionSelectAll.js +4 -3
- package/dist/excalidraw/actions/shortcuts.d.ts +1 -1
- package/dist/excalidraw/actions/shortcuts.js +1 -0
- package/dist/excalidraw/actions/types.d.ts +1 -1
- package/dist/excalidraw/align.d.ts +2 -1
- package/dist/excalidraw/align.js +15 -6
- package/dist/excalidraw/clipboard.d.ts +27 -5
- package/dist/excalidraw/clipboard.js +55 -28
- package/dist/excalidraw/components/Actions.d.ts +2 -1
- package/dist/excalidraw/components/Actions.js +4 -2
- package/dist/excalidraw/components/ActiveConfirmDialog.d.ts +1 -1
- package/dist/excalidraw/components/ActiveConfirmDialog.js +2 -3
- package/dist/excalidraw/components/App.d.ts +1 -0
- package/dist/excalidraw/components/App.js +216 -111
- package/dist/excalidraw/components/ColorPicker/ColorInput.js +2 -3
- package/dist/excalidraw/components/ColorPicker/ColorPicker.js +2 -3
- package/dist/excalidraw/components/ColorPicker/CustomColorList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
- package/dist/excalidraw/components/ColorPicker/PickerColorList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/ShadeList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
- package/dist/excalidraw/components/ColorPicker/colorPickerUtils.js +1 -1
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +3 -3
- package/dist/excalidraw/components/ConfirmDialog.js +17 -5
- package/dist/excalidraw/components/Dialog.js +2 -3
- package/dist/excalidraw/components/EyeDropper.d.ts +1 -1
- package/dist/excalidraw/components/EyeDropper.js +1 -1
- package/dist/excalidraw/components/IconPicker.d.ts +2 -2
- package/dist/excalidraw/components/IconPicker.js +56 -53
- package/dist/excalidraw/components/LayerUI.js +6 -6
- package/dist/excalidraw/components/LibraryMenu.d.ts +2 -16
- package/dist/excalidraw/components/LibraryMenu.js +70 -28
- package/dist/excalidraw/components/LibraryMenuHeaderContent.js +4 -5
- package/dist/excalidraw/components/MobileMenu.js +1 -1
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirm.js +2 -3
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.js +2 -3
- package/dist/excalidraw/components/Range.d.ts +9 -0
- package/dist/excalidraw/components/Range.js +24 -0
- package/dist/excalidraw/components/SearchMenu.d.ts +1 -1
- package/dist/excalidraw/components/SearchMenu.js +3 -4
- package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
- package/dist/excalidraw/components/Sidebar/Sidebar.js +2 -3
- package/dist/excalidraw/components/Stats/Collapsible.d.ts +2 -1
- package/dist/excalidraw/components/Stats/Collapsible.js +2 -2
- package/dist/excalidraw/components/Stats/Dimension.js +94 -8
- package/dist/excalidraw/components/Stats/MultiDimension.js +8 -5
- package/dist/excalidraw/components/Stats/Position.js +63 -3
- package/dist/excalidraw/components/Stats/index.js +21 -4
- package/dist/excalidraw/components/Stats/utils.d.ts +1 -1
- package/dist/excalidraw/components/Stats/utils.js +2 -55
- package/dist/excalidraw/components/TTDDialog/TTDDialog.js +1 -1
- package/dist/excalidraw/components/ToolButton.js +4 -9
- package/dist/excalidraw/components/hoc/withInternalFallback.js +3 -3
- package/dist/excalidraw/components/hyperlink/Hyperlink.js +6 -12
- package/dist/excalidraw/components/icons.d.ts +9 -0
- package/dist/excalidraw/components/icons.js +4 -4
- package/dist/excalidraw/components/main-menu/DefaultItems.js +2 -3
- package/dist/excalidraw/constants.d.ts +5 -1
- package/dist/excalidraw/constants.js +9 -1
- package/dist/excalidraw/context/tunnels.d.ts +2 -1
- package/dist/excalidraw/context/tunnels.js +3 -1
- package/dist/excalidraw/data/blob.d.ts +1 -0
- package/dist/excalidraw/data/blob.js +7 -3
- package/dist/excalidraw/data/filesystem.d.ts +2 -1
- package/dist/excalidraw/data/filesystem.js +1 -0
- package/dist/excalidraw/data/image.d.ts +0 -6
- package/dist/excalidraw/data/image.js +1 -43
- package/dist/excalidraw/data/index.js +6 -6
- package/dist/excalidraw/data/library.d.ts +9 -3
- package/dist/excalidraw/data/library.js +43 -6
- package/dist/excalidraw/data/restore.js +26 -8
- package/dist/excalidraw/data/url.d.ts +0 -1
- package/dist/excalidraw/data/url.js +2 -4
- package/dist/excalidraw/editor-jotai.d.ts +56 -0
- package/dist/excalidraw/editor-jotai.js +8 -0
- package/dist/excalidraw/element/binding.d.ts +9 -6
- package/dist/excalidraw/element/binding.js +124 -44
- package/dist/excalidraw/element/bounds.js +10 -0
- package/dist/excalidraw/element/cropElement.d.ts +5 -0
- package/dist/excalidraw/element/cropElement.js +28 -1
- package/dist/excalidraw/element/dragElements.js +13 -7
- package/dist/excalidraw/element/elbowArrow.d.ts +16 -0
- package/dist/excalidraw/element/elbowArrow.js +1268 -0
- package/dist/excalidraw/element/embeddable.js +4 -5
- package/dist/excalidraw/element/flowchart.d.ts +1 -1
- package/dist/excalidraw/element/flowchart.js +25 -9
- package/dist/excalidraw/element/heading.d.ts +5 -1
- package/dist/excalidraw/element/heading.js +5 -1
- package/dist/excalidraw/element/image.js +19 -5
- package/dist/excalidraw/element/linearElementEditor.d.ts +9 -10
- package/dist/excalidraw/element/linearElementEditor.js +97 -38
- package/dist/excalidraw/element/mutateElement.d.ts +3 -1
- package/dist/excalidraw/element/mutateElement.js +31 -4
- package/dist/excalidraw/element/newElement.d.ts +8 -12
- package/dist/excalidraw/element/newElement.js +36 -21
- package/dist/excalidraw/element/resizeElements.d.ts +20 -5
- package/dist/excalidraw/element/resizeElements.js +593 -361
- package/dist/excalidraw/element/sortElements.js +1 -4
- package/dist/excalidraw/element/types.d.ts +23 -1
- package/dist/excalidraw/fonts/Fonts.d.ts +0 -16
- package/dist/excalidraw/fonts/Fonts.js +6 -31
- package/dist/excalidraw/frame.d.ts +11 -5
- package/dist/excalidraw/frame.js +146 -35
- package/dist/excalidraw/groups.js +3 -0
- package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
- package/dist/excalidraw/hooks/useLibraryItemSvg.js +2 -3
- package/dist/excalidraw/hooks/useScrollPosition.js +1 -1
- package/dist/excalidraw/i18n.js +3 -4
- package/dist/excalidraw/index.js +3 -4
- package/dist/excalidraw/locales/en.json +8 -1
- package/dist/excalidraw/renderer/interactiveScene.js +43 -32
- package/dist/excalidraw/renderer/staticScene.js +6 -4
- package/dist/excalidraw/renderer/staticSvgScene.js +1 -1
- package/dist/excalidraw/scene/Shape.js +40 -17
- package/dist/excalidraw/scene/comparisons.d.ts +0 -477
- package/dist/excalidraw/scene/comparisons.js +0 -37
- package/dist/excalidraw/scene/export.d.ts +7 -0
- package/dist/excalidraw/scene/export.js +107 -43
- package/dist/excalidraw/scene/index.d.ts +1 -1
- package/dist/excalidraw/scene/index.js +1 -1
- package/dist/excalidraw/scene/selection.js +4 -1
- package/dist/excalidraw/types.d.ts +15 -0
- package/dist/excalidraw/utility-types.d.ts +1 -0
- package/dist/excalidraw/utils.d.ts +8 -1
- package/dist/excalidraw/utils.js +9 -0
- package/dist/excalidraw/visualdebug.d.ts +8 -1
- package/dist/excalidraw/visualdebug.js +3 -0
- package/dist/math/line.d.ts +19 -0
- package/dist/math/line.js +32 -3
- package/dist/math/point.d.ts +10 -0
- package/dist/math/point.js +12 -1
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +29 -44
- package/dist/{dev/locales/en-ZXYG7GCR.json → prod/locales/en-V6KXFSCK.json} +8 -1
- package/package.json +5 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-JGDL4H2X.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-V7NFEZA6.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-S2XKB3DE.js +0 -62
- package/dist/browser/prod/excalidraw-assets/image-OFI2YYMP.js +0 -1
- package/dist/excalidraw/element/routing.d.ts +0 -12
- package/dist/excalidraw/element/routing.js +0 -642
- package/dist/excalidraw/jotai.d.ts +0 -34
- package/dist/excalidraw/jotai.js +0 -18
- /package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js.map → en-7IBTMWBG.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js.map → image-N5AC7SEK.js.map} +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { getColor } from "./ColorPicker";
|
|
4
|
-
import { useAtom } from "jotai";
|
|
5
4
|
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
|
6
5
|
import { eyeDropperIcon } from "../icons";
|
|
7
|
-
import {
|
|
6
|
+
import { useAtom } from "../../editor-jotai";
|
|
8
7
|
import { KEYS } from "../../keys";
|
|
9
8
|
import { activeEyeDropperAtom } from "../EyeDropper";
|
|
10
9
|
import clsx from "clsx";
|
|
@@ -33,7 +32,7 @@ export const ColorInput = ({ color, onChange, label, colorPickerType, }) => {
|
|
|
33
32
|
inputRef.current.focus();
|
|
34
33
|
}
|
|
35
34
|
}, [activeSection]);
|
|
36
|
-
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom
|
|
35
|
+
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
|
37
36
|
useEffect(() => {
|
|
38
37
|
return () => {
|
|
39
38
|
setEyeDropperState(null);
|
|
@@ -4,7 +4,6 @@ import { TopPicks } from "./TopPicks";
|
|
|
4
4
|
import { ButtonSeparator } from "../ButtonSeparator";
|
|
5
5
|
import { Picker } from "./Picker";
|
|
6
6
|
import * as Popover from "@radix-ui/react-popover";
|
|
7
|
-
import { useAtom } from "jotai";
|
|
8
7
|
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
|
9
8
|
import { useExcalidrawContainer } from "../App";
|
|
10
9
|
import { COLOR_PALETTE } from "../../colors";
|
|
@@ -12,7 +11,7 @@ import PickerHeading from "./PickerHeading";
|
|
|
12
11
|
import { t } from "../../i18n";
|
|
13
12
|
import clsx from "clsx";
|
|
14
13
|
import { useRef } from "react";
|
|
15
|
-
import {
|
|
14
|
+
import { useAtom } from "../../editor-jotai";
|
|
16
15
|
import { ColorInput } from "./ColorInput";
|
|
17
16
|
import { activeEyeDropperAtom } from "../EyeDropper";
|
|
18
17
|
import { PropertiesPopover } from "../PropertiesPopover";
|
|
@@ -38,7 +37,7 @@ export const getColor = (color) => {
|
|
|
38
37
|
const ColorPickerPopupContent = ({ type, color, onChange, label, elements, palette = COLOR_PALETTE, updateData, }) => {
|
|
39
38
|
const { container } = useExcalidrawContainer();
|
|
40
39
|
const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom);
|
|
41
|
-
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom
|
|
40
|
+
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
|
42
41
|
const colorInputJSX = (_jsxs("div", { children: [_jsx(PickerHeading, { children: t("colorPicker.hexCode") }), _jsx(ColorInput, { color: color, label: label, onChange: (color) => {
|
|
43
42
|
onChange(color);
|
|
44
43
|
}, colorPickerType: type })] }));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
-
import { useAtom } from "jotai";
|
|
3
|
+
import { useAtom } from "../../editor-jotai";
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
5
|
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
|
6
6
|
import HotkeyLabel from "./HotkeyLabel";
|
|
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
|
|
|
3
3
|
import { t } from "../../i18n";
|
|
4
4
|
import { ShadeList } from "./ShadeList";
|
|
5
5
|
import PickerColorList from "./PickerColorList";
|
|
6
|
-
import { useAtom } from "jotai";
|
|
6
|
+
import { useAtom } from "../../editor-jotai";
|
|
7
7
|
import { CustomColorList } from "./CustomColorList";
|
|
8
8
|
import { colorPickerKeyNavHandler } from "./keyboardNavHandlers";
|
|
9
9
|
import PickerHeading from "./PickerHeading";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
-
import { useAtom } from "jotai";
|
|
3
|
+
import { useAtom } from "../../editor-jotai";
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
5
|
import { activeColorPickerSectionAtom, colorPickerHotkeyBindings, getColorNameAndShadeFromColor, } from "./colorPickerUtils";
|
|
6
6
|
import HotkeyLabel from "./HotkeyLabel";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
-
import { useAtom } from "jotai";
|
|
3
|
+
import { useAtom } from "../../editor-jotai";
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
5
|
import { activeColorPickerSectionAtom, getColorNameAndShadeFromColor, } from "./colorPickerUtils";
|
|
6
6
|
import HotkeyLabel from "./HotkeyLabel";
|
|
@@ -14,7 +14,7 @@ export declare const isCustomColor: ({ color, palette, }: {
|
|
|
14
14
|
}) => boolean;
|
|
15
15
|
export declare const getMostUsedCustomColors: (elements: readonly ExcalidrawElement[], type: "elementBackground" | "elementStroke", palette: ColorPaletteCustom) => string[];
|
|
16
16
|
export type ActiveColorPickerSectionAtomType = "custom" | "baseColors" | "shades" | "hex" | null;
|
|
17
|
-
export declare const activeColorPickerSectionAtom: import("jotai").PrimitiveAtom<ActiveColorPickerSectionAtomType> & {
|
|
17
|
+
export declare const activeColorPickerSectionAtom: import("jotai/vanilla/atom").PrimitiveAtom<ActiveColorPickerSectionAtomType> & {
|
|
18
18
|
init: ActiveColorPickerSectionAtomType;
|
|
19
19
|
};
|
|
20
20
|
export declare const getContrastYIQ: (bgHex: string, isCustomColor: boolean) => "white" | "black";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { atom } from "jotai";
|
|
2
1
|
import { MAX_CUSTOM_COLORS_USED_IN_CANVAS } from "../../colors";
|
|
2
|
+
import { atom } from "../../editor-jotai";
|
|
3
3
|
export const getColorNameAndShadeFromColor = ({ palette, color, }) => {
|
|
4
4
|
for (const [colorName, colorVal] of Object.entries(palette)) {
|
|
5
5
|
if (Array.isArray(colorVal)) {
|
|
@@ -13,14 +13,13 @@ import { LockedIcon, UnlockedIcon, clockIcon, searchIcon, boltIcon, bucketFillIc
|
|
|
13
13
|
import fuzzy from "fuzzy";
|
|
14
14
|
import { useUIAppState } from "../../context/ui-appState";
|
|
15
15
|
import { capitalizeString, getShortcutKey, isWritableElement, } from "../../utils";
|
|
16
|
-
import { atom, useAtom } from "jotai";
|
|
16
|
+
import { atom, useAtom, editorJotaiStore } from "../../editor-jotai";
|
|
17
17
|
import { deburr } from "../../deburr";
|
|
18
18
|
import { InlineIcon } from "../InlineIcon";
|
|
19
19
|
import { SHAPES } from "../../shapes";
|
|
20
20
|
import { canChangeBackgroundColor, canChangeStrokeColor } from "../Actions";
|
|
21
21
|
import { useStableCallback } from "../../hooks/useStableCallback";
|
|
22
22
|
import { actionClearCanvas, actionLink, actionToggleSearchMenu, } from "../../actions";
|
|
23
|
-
import { jotaiStore } from "../../jotai";
|
|
24
23
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
|
25
24
|
import * as defaultItems from "./defaultCommandPaletteItems";
|
|
26
25
|
import { trackEvent } from "../../analytics";
|
|
@@ -163,6 +162,7 @@ function CommandPaletteInner({ customCommandPaletteItems, }) {
|
|
|
163
162
|
actionManager.actions.cut,
|
|
164
163
|
actionManager.actions.copy,
|
|
165
164
|
actionManager.actions.deleteSelectedElements,
|
|
165
|
+
actionManager.actions.wrapSelectionInFrame,
|
|
166
166
|
actionManager.actions.copyStyles,
|
|
167
167
|
actionManager.actions.pasteStyles,
|
|
168
168
|
actionManager.actions.bringToFront,
|
|
@@ -234,7 +234,7 @@ function CommandPaletteInner({ customCommandPaletteItems, }) {
|
|
|
234
234
|
keywords: ["delete", "destroy"],
|
|
235
235
|
viewMode: false,
|
|
236
236
|
perform: () => {
|
|
237
|
-
|
|
237
|
+
editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
|
|
238
238
|
},
|
|
239
239
|
},
|
|
240
240
|
{
|
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
2
3
|
import { t } from "../i18n";
|
|
3
4
|
import { Dialog } from "./Dialog";
|
|
4
5
|
import "./ConfirmDialog.scss";
|
|
5
6
|
import DialogActionButton from "./DialogActionButton";
|
|
6
|
-
import { useSetAtom } from "jotai";
|
|
7
7
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
|
8
8
|
import { useExcalidrawContainer, useExcalidrawSetAppState } from "./App";
|
|
9
|
-
import {
|
|
9
|
+
import { useSetAtom } from "../editor-jotai";
|
|
10
10
|
const ConfirmDialog = (props) => {
|
|
11
11
|
const { onConfirm, onCancel, children, confirmText = t("buttons.confirm"), cancelText = t("buttons.cancel"), className = "", ...rest } = props;
|
|
12
12
|
const setAppState = useExcalidrawSetAppState();
|
|
13
|
-
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom
|
|
13
|
+
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
|
|
14
14
|
const { container } = useExcalidrawContainer();
|
|
15
15
|
return (_jsxs(Dialog, { onCloseRequest: onCancel, size: "small", ...rest, className: `confirm-dialog ${className}`, children: [children, _jsxs("div", { className: "confirm-dialog-buttons", children: [_jsx(DialogActionButton, { label: cancelText, onClick: () => {
|
|
16
16
|
setAppState({ openMenu: null });
|
|
17
17
|
setIsLibraryMenuOpen(false);
|
|
18
|
-
|
|
18
|
+
// flush any pending updates synchronously,
|
|
19
|
+
// otherwise it could lead to crash in some chromium versions (131.0.6778.86),
|
|
20
|
+
// when `.focus` is invoked with container in some intermediate state
|
|
21
|
+
// (container seems mounted in DOM, but focus still causes a crash)
|
|
22
|
+
flushSync(() => {
|
|
23
|
+
onCancel();
|
|
24
|
+
});
|
|
19
25
|
container?.focus();
|
|
20
26
|
} }), _jsx(DialogActionButton, { label: confirmText, onClick: () => {
|
|
21
27
|
setAppState({ openMenu: null });
|
|
22
28
|
setIsLibraryMenuOpen(false);
|
|
23
|
-
|
|
29
|
+
// flush any pending updates synchronously,
|
|
30
|
+
// otherwise it leads to crash in some chromium versions (131.0.6778.86),
|
|
31
|
+
// when `.focus` is invoked with container in some intermediate state
|
|
32
|
+
// (container seems mounted in DOM, but focus still causes a crash)
|
|
33
|
+
flushSync(() => {
|
|
34
|
+
onConfirm();
|
|
35
|
+
});
|
|
24
36
|
container?.focus();
|
|
25
37
|
}, actionType: "danger" })] })] }));
|
|
26
38
|
};
|
|
@@ -8,9 +8,8 @@ import "./Dialog.scss";
|
|
|
8
8
|
import { Island } from "./Island";
|
|
9
9
|
import { Modal } from "./Modal";
|
|
10
10
|
import { queryFocusableElements } from "../utils";
|
|
11
|
-
import { useSetAtom } from "jotai";
|
|
12
11
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
|
13
|
-
import {
|
|
12
|
+
import { useSetAtom } from "../editor-jotai";
|
|
14
13
|
import { t } from "../i18n";
|
|
15
14
|
import { CloseIcon } from "./icons";
|
|
16
15
|
function getDialogSize(size) {
|
|
@@ -63,7 +62,7 @@ export const Dialog = (props) => {
|
|
|
63
62
|
return () => islandNode.removeEventListener("keydown", handleKeyDown);
|
|
64
63
|
}, [islandNode, props.autofocus]);
|
|
65
64
|
const setAppState = useExcalidrawSetAppState();
|
|
66
|
-
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom
|
|
65
|
+
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
|
|
67
66
|
const onClose = () => {
|
|
68
67
|
setAppState({ openMenu: null });
|
|
69
68
|
setIsLibraryMenuOpen(false);
|
|
@@ -14,7 +14,7 @@ export type EyeDropperProperties = {
|
|
|
14
14
|
**/
|
|
15
15
|
colorPickerType: ColorPickerType;
|
|
16
16
|
};
|
|
17
|
-
export declare const activeEyeDropperAtom: import("jotai").PrimitiveAtom<EyeDropperProperties | null> & {
|
|
17
|
+
export declare const activeEyeDropperAtom: import("jotai/vanilla/atom").PrimitiveAtom<EyeDropperProperties | null> & {
|
|
18
18
|
init: EyeDropperProperties | null;
|
|
19
19
|
};
|
|
20
20
|
export declare const EyeDropper: React.FC<{
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { atom } from "jotai";
|
|
3
2
|
import { useEffect, useRef } from "react";
|
|
4
3
|
import { createPortal } from "react-dom";
|
|
5
4
|
import { rgbToHex } from "../colors";
|
|
@@ -12,6 +11,7 @@ import { getSelectedElements } from "../scene";
|
|
|
12
11
|
import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App";
|
|
13
12
|
import { useStable } from "../hooks/useStable";
|
|
14
13
|
import "./EyeDropper.scss";
|
|
14
|
+
import { atom } from "../editor-jotai";
|
|
15
15
|
export const activeEyeDropperAtom = atom(null);
|
|
16
16
|
export const EyeDropper = ({ onCancel, onChange, onSelect, colorPickerType }) => {
|
|
17
17
|
const eyeDropperContainer = useCreatePortalContainer({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import "./IconPicker.scss";
|
|
3
|
-
export declare function IconPicker<T>({ value, label, options, onChange, group, }: {
|
|
3
|
+
export declare function IconPicker<T>({ value, label, options, onChange, group, numberOfOptionsToAlwaysShow, }: {
|
|
4
4
|
label: string;
|
|
5
5
|
value: T;
|
|
6
6
|
options: readonly {
|
|
@@ -8,8 +8,8 @@ export declare function IconPicker<T>({ value, label, options, onChange, group,
|
|
|
8
8
|
text: string;
|
|
9
9
|
icon: JSX.Element;
|
|
10
10
|
keyBinding: string | null;
|
|
11
|
-
showInPicker?: boolean;
|
|
12
11
|
}[];
|
|
13
12
|
onChange: (value: T) => void;
|
|
13
|
+
numberOfOptionsToAlwaysShow?: number;
|
|
14
14
|
group?: string;
|
|
15
15
|
}): JSX.Element;
|
|
@@ -1,66 +1,59 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import React from "react";
|
|
3
|
-
import
|
|
4
|
-
import "./IconPicker.scss";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect } from "react";
|
|
3
|
+
import * as Popover from "@radix-ui/react-popover";
|
|
5
4
|
import { isArrowKey, KEYS } from "../keys";
|
|
6
|
-
import { getLanguage } from "../i18n";
|
|
5
|
+
import { getLanguage, t } from "../i18n";
|
|
7
6
|
import clsx from "clsx";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
rActiveItem.current.focus();
|
|
16
|
-
}
|
|
17
|
-
else if (rGallery.current) {
|
|
18
|
-
rGallery.current.focus();
|
|
19
|
-
}
|
|
20
|
-
}, []);
|
|
7
|
+
import Collapsible from "./Stats/Collapsible";
|
|
8
|
+
import { atom, useAtom } from "../editor-jotai";
|
|
9
|
+
import { useDevice } from "./App";
|
|
10
|
+
import "./IconPicker.scss";
|
|
11
|
+
const moreOptionsAtom = atom(false);
|
|
12
|
+
function Picker({ options, value, label, onChange, onClose, numberOfOptionsToAlwaysShow = options.length, }) {
|
|
13
|
+
const device = useDevice();
|
|
21
14
|
const handleKeyDown = (event) => {
|
|
22
15
|
const pressedOption = options.find((option) => option.keyBinding === event.key.toLowerCase());
|
|
23
16
|
if (!(event.metaKey || event.altKey || event.ctrlKey) && pressedOption) {
|
|
24
17
|
// Keybinding navigation
|
|
25
|
-
|
|
26
|
-
rGallery.current.children[index].focus();
|
|
18
|
+
onChange(pressedOption.value);
|
|
27
19
|
event.preventDefault();
|
|
28
20
|
}
|
|
29
21
|
else if (event.key === KEYS.TAB) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const docActive = document.activeElement;
|
|
36
|
-
if (active !== docActive) {
|
|
37
|
-
onClose();
|
|
38
|
-
}
|
|
39
|
-
}, 0);
|
|
22
|
+
const index = options.findIndex((option) => option.value === value);
|
|
23
|
+
const nextIndex = event.shiftKey
|
|
24
|
+
? (options.length + index - 1) % options.length
|
|
25
|
+
: (index + 1) % options.length;
|
|
26
|
+
onChange(options[nextIndex].value);
|
|
40
27
|
}
|
|
41
28
|
else if (isArrowKey(event.key)) {
|
|
42
29
|
// Arrow navigation
|
|
43
|
-
const { activeElement } = document;
|
|
44
30
|
const isRTL = getLanguage().rtl;
|
|
45
|
-
const index =
|
|
31
|
+
const index = options.findIndex((option) => option.value === value);
|
|
46
32
|
if (index !== -1) {
|
|
47
33
|
const length = options.length;
|
|
48
34
|
let nextIndex = index;
|
|
49
35
|
switch (event.key) {
|
|
50
36
|
// Select the next option
|
|
51
37
|
case isRTL ? KEYS.ARROW_LEFT : KEYS.ARROW_RIGHT:
|
|
52
|
-
case KEYS.ARROW_DOWN: {
|
|
53
38
|
nextIndex = (index + 1) % length;
|
|
54
39
|
break;
|
|
55
|
-
}
|
|
56
40
|
// Select the previous option
|
|
57
41
|
case isRTL ? KEYS.ARROW_RIGHT : KEYS.ARROW_LEFT:
|
|
58
|
-
case KEYS.ARROW_UP: {
|
|
59
42
|
nextIndex = (length + index - 1) % length;
|
|
60
43
|
break;
|
|
44
|
+
// Go the next row
|
|
45
|
+
case KEYS.ARROW_DOWN: {
|
|
46
|
+
nextIndex = (index + (numberOfOptionsToAlwaysShow ?? 1)) % length;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
// Go the previous row
|
|
50
|
+
case KEYS.ARROW_UP: {
|
|
51
|
+
nextIndex =
|
|
52
|
+
(length + index - (numberOfOptionsToAlwaysShow ?? 1)) % length;
|
|
53
|
+
break;
|
|
61
54
|
}
|
|
62
55
|
}
|
|
63
|
-
|
|
56
|
+
onChange(options[nextIndex].value);
|
|
64
57
|
}
|
|
65
58
|
event.preventDefault();
|
|
66
59
|
}
|
|
@@ -72,28 +65,38 @@ function Picker({ options, value, label, onChange, onClose, }) {
|
|
|
72
65
|
event.nativeEvent.stopImmediatePropagation();
|
|
73
66
|
event.stopPropagation();
|
|
74
67
|
};
|
|
75
|
-
|
|
68
|
+
const [showMoreOptions, setShowMoreOptions] = useAtom(moreOptionsAtom);
|
|
69
|
+
const alwaysVisibleOptions = React.useMemo(() => options.slice(0, numberOfOptionsToAlwaysShow), [options, numberOfOptionsToAlwaysShow]);
|
|
70
|
+
const moreOptions = React.useMemo(() => options.slice(numberOfOptionsToAlwaysShow), [options, numberOfOptionsToAlwaysShow]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!alwaysVisibleOptions.some((option) => option.value === value)) {
|
|
73
|
+
setShowMoreOptions(true);
|
|
74
|
+
}
|
|
75
|
+
}, [value, alwaysVisibleOptions, setShowMoreOptions]);
|
|
76
|
+
const renderOptions = (options) => {
|
|
77
|
+
return (_jsx("div", { className: "picker-content", children: options.map((option, i) => (_jsxs("button", { type: "button", className: clsx("picker-option", {
|
|
76
78
|
active: value === option.value,
|
|
77
79
|
}), onClick: (event) => {
|
|
78
|
-
event.currentTarget.focus();
|
|
79
80
|
onChange(option.value);
|
|
80
|
-
}, title: `${option.text} ${option.keyBinding && `— ${option.keyBinding.toUpperCase()}`}`, "aria-label": option.text || "none", "aria-keyshortcuts": option.keyBinding || undefined, ref: (
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
}, title: `${option.text} ${option.keyBinding && `— ${option.keyBinding.toUpperCase()}`}`, "aria-label": option.text || "none", "aria-keyshortcuts": option.keyBinding || undefined, ref: (ref) => {
|
|
82
|
+
if (value === option.value) {
|
|
83
|
+
// Use a timeout here to render focus properly
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
ref?.focus();
|
|
86
|
+
}, 0);
|
|
86
87
|
}
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
}, children: [option.icon, option.keyBinding && (_jsx("span", { className: "picker-keybinding", children: option.keyBinding }))] }, option.text))) }));
|
|
89
|
+
};
|
|
90
|
+
return (_jsx(Popover.Content, { side: device.editor.isMobile && !device.viewport.isLandscape
|
|
91
|
+
? "top"
|
|
92
|
+
: "bottom", align: "start", sideOffset: 12, style: { zIndex: "var(--zIndex-popup)" }, onKeyDown: handleKeyDown, children: _jsxs("div", { className: `picker`, role: "dialog", "aria-modal": "true", "aria-label": label, children: [renderOptions(alwaysVisibleOptions), moreOptions.length > 0 && (_jsx(Collapsible, { label: t("labels.more_options"), open: showMoreOptions, openTrigger: () => {
|
|
93
|
+
setShowMoreOptions((value) => !value);
|
|
94
|
+
}, className: "picker-collapsible", children: renderOptions(moreOptions) }))] }) }));
|
|
90
95
|
}
|
|
91
|
-
export function IconPicker({ value, label, options, onChange, group = "", }) {
|
|
96
|
+
export function IconPicker({ value, label, options, onChange, group = "", numberOfOptionsToAlwaysShow, }) {
|
|
92
97
|
const [isActive, setActive] = React.useState(false);
|
|
93
98
|
const rPickerButton = React.useRef(null);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
rPickerButton.current?.focus();
|
|
98
|
-
} }) }), _jsx("div", { className: "picker-triangle" })] })) : null })] }));
|
|
99
|
+
return (_jsx("div", { children: _jsxs(Popover.Root, { open: isActive, onOpenChange: (open) => setActive(open), children: [_jsx(Popover.Trigger, { name: group, type: "button", "aria-label": label, onClick: () => setActive(!isActive), ref: rPickerButton, className: isActive ? "active" : "", children: options.find((option) => option.value === value)?.icon }), isActive && (_jsx(Picker, { options: options, value: value, label: label, onChange: onChange, onClose: () => {
|
|
100
|
+
setActive(false);
|
|
101
|
+
}, numberOfOptionsToAlwaysShow: numberOfOptionsToAlwaysShow }))] }) }));
|
|
99
102
|
}
|
|
@@ -26,8 +26,7 @@ import { trackEvent } from "../analytics";
|
|
|
26
26
|
import { useDevice } from "./App";
|
|
27
27
|
import Footer from "./footer/Footer";
|
|
28
28
|
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
|
29
|
-
import {
|
|
30
|
-
import { Provider, useAtom, useAtomValue } from "jotai";
|
|
29
|
+
import { useAtom, useAtomValue } from "../editor-jotai";
|
|
31
30
|
import MainMenu from "./main-menu/MainMenu";
|
|
32
31
|
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
|
33
32
|
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
|
@@ -57,7 +56,8 @@ const DefaultOverwriteConfirmDialog = () => {
|
|
|
57
56
|
const LayerUI = ({ actionManager, appState, files, setAppState, elements, canvas, onLockToggle, onHandToolToggle, onPenModeToggle, showExitZenModeBtn, renderTopRightUI, renderCustomStats, UIOptions, onExportImage, renderWelcomeScreen, children, app, isCollaborating, generateLinkForSelection, }) => {
|
|
58
57
|
const device = useDevice();
|
|
59
58
|
const tunnels = useInitializeTunnels();
|
|
60
|
-
const
|
|
59
|
+
const TunnelsJotaiProvider = tunnels.tunnelsJotai.Provider;
|
|
60
|
+
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
|
61
61
|
const renderJSONExportDialog = () => {
|
|
62
62
|
if (!UIOptions.canvasActions.export) {
|
|
63
63
|
return null;
|
|
@@ -78,7 +78,7 @@ const LayerUI = ({ actionManager, appState, files, setAppState, elements, canvas
|
|
|
78
78
|
// we want to make sure this doesn't overflow so subtracting the
|
|
79
79
|
// approximate height of hamburgerMenu + footer
|
|
80
80
|
maxHeight: `${appState.height - 166}px`,
|
|
81
|
-
}, children: _jsx(SelectedShapeActions, { appState: appState, elementsMap: app.scene.getNonDeletedElementsMap(), renderAction: actionManager.renderAction }) }) }));
|
|
81
|
+
}, children: _jsx(SelectedShapeActions, { appState: appState, elementsMap: app.scene.getNonDeletedElementsMap(), renderAction: actionManager.renderAction, app: app }) }) }));
|
|
82
82
|
const renderFixedSideContainer = () => {
|
|
83
83
|
const shouldRenderSelectedShapeActions = showSelectedShapeActions(appState, elements);
|
|
84
84
|
const shouldShowStats = appState.stats.open &&
|
|
@@ -109,7 +109,7 @@ const LayerUI = ({ actionManager, appState, files, setAppState, elements, canvas
|
|
|
109
109
|
trackEvent("sidebar", `toggleDock (${docked ? "dock" : "undock"})`, `(${device.editor.isMobile ? "mobile" : "desktop"})`);
|
|
110
110
|
} }));
|
|
111
111
|
};
|
|
112
|
-
const isSidebarDocked = useAtomValue(isSidebarDockedAtom
|
|
112
|
+
const isSidebarDocked = useAtomValue(isSidebarDockedAtom);
|
|
113
113
|
const layerUIJSX = (_jsxs(_Fragment, { children: [children, _jsx(DefaultMainMenu, { UIOptions: UIOptions }), _jsx(DefaultSidebar.Trigger, { __fallback: true, icon: LibraryIcon, title: capitalizeString(t("toolBar.library")), onToggle: (open) => {
|
|
114
114
|
if (open) {
|
|
115
115
|
trackEvent("sidebar", `${DEFAULT_SIDEBAR.name} (open)`, `button (${device.editor.isMobile ? "mobile" : "desktop"})`);
|
|
@@ -166,7 +166,7 @@ const LayerUI = ({ actionManager, appState, files, setAppState, elements, canvas
|
|
|
166
166
|
...calculateScrollCenter(elements, appState),
|
|
167
167
|
}));
|
|
168
168
|
}, children: t("buttons.scrollBackToContent") }))] }), renderSidebars()] }))] }));
|
|
169
|
-
return (_jsx(UIAppStateContext.Provider, { value: appState, children: _jsx(
|
|
169
|
+
return (_jsx(UIAppStateContext.Provider, { value: appState, children: _jsx(TunnelsJotaiProvider, { children: _jsx(TunnelsContext.Provider, { value: tunnels, children: layerUIJSX }) }) }));
|
|
170
170
|
};
|
|
171
171
|
const stripIrrelevantAppStateProps = (appState) => {
|
|
172
172
|
const { suggestedBindings, startBoundElement, cursorButton, scrollX, scrollY, ...ret } = appState;
|
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type Library from "../data/library";
|
|
3
|
-
import type { LibraryItems, LibraryItem, ExcalidrawProps, UIAppState } from "../types";
|
|
4
2
|
import "./LibraryMenu.scss";
|
|
5
|
-
export declare const isLibraryMenuOpenAtom: import("jotai").PrimitiveAtom<boolean> & {
|
|
3
|
+
export declare const isLibraryMenuOpenAtom: import("jotai/vanilla/atom").PrimitiveAtom<boolean> & {
|
|
6
4
|
init: boolean;
|
|
7
5
|
};
|
|
8
|
-
export declare const LibraryMenuContent: ({ onInsertLibraryItems, pendingElements, onAddToLibrary, setAppState, libraryReturnUrl, library, id, theme, selectedItems, onSelectItems, }: {
|
|
9
|
-
pendingElements: LibraryItem["elements"];
|
|
10
|
-
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
|
11
|
-
onAddToLibrary: () => void;
|
|
12
|
-
setAppState: React.Component<any, UIAppState>["setState"];
|
|
13
|
-
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
|
14
|
-
library: Library;
|
|
15
|
-
id: string;
|
|
16
|
-
theme: UIAppState["theme"];
|
|
17
|
-
selectedItems: LibraryItem["id"][];
|
|
18
|
-
onSelectItems: (id: LibraryItem["id"][]) => void;
|
|
19
|
-
}) => JSX.Element;
|
|
20
6
|
/**
|
|
21
7
|
* This component is meant to be rendered inside <Sidebar.Tab/> inside our
|
|
22
8
|
* <DefaultSidebar/> or host apps Sidebar components.
|
|
23
9
|
*/
|
|
24
|
-
export declare const LibraryMenu: () => JSX.Element
|
|
10
|
+
export declare const LibraryMenu: React.MemoExoticComponent<() => JSX.Element>;
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { useState, useCallback, useMemo, useEffect, memo, useRef, } from "react";
|
|
3
3
|
import { distributeLibraryItemsOnSquareGrid, libraryItemsAtom, } from "../data/library";
|
|
4
4
|
import { t } from "../i18n";
|
|
5
5
|
import { randomId } from "../random";
|
|
6
6
|
import LibraryMenuItems from "./LibraryMenuItems";
|
|
7
7
|
import { trackEvent } from "../analytics";
|
|
8
|
-
import { atom, useAtom } from "jotai";
|
|
9
|
-
import { jotaiScope } from "../jotai";
|
|
8
|
+
import { atom, useAtom } from "../editor-jotai";
|
|
10
9
|
import Spinner from "./Spinner";
|
|
11
10
|
import { useApp, useAppProps, useExcalidrawElements, useExcalidrawSetAppState, } from "./App";
|
|
12
11
|
import { getSelectedElements } from "../scene";
|
|
13
12
|
import { useUIAppState } from "../context/ui-appState";
|
|
14
13
|
import "./LibraryMenu.scss";
|
|
15
14
|
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
|
16
|
-
import { isShallowEqual } from "../utils";
|
|
17
15
|
import { LIBRARY_DISABLED_TYPES } from "../constants";
|
|
16
|
+
import { isShallowEqual } from "../utils";
|
|
18
17
|
export const isLibraryMenuOpenAtom = atom(false);
|
|
19
18
|
const LibraryMenuWrapper = ({ children }) => {
|
|
20
19
|
return _jsx("div", { className: "layer-ui__library", children: children });
|
|
21
20
|
};
|
|
22
|
-
|
|
23
|
-
const [libraryItemsData] = useAtom(libraryItemsAtom
|
|
21
|
+
const LibraryMenuContent = memo(({ onInsertLibraryItems, pendingElements, onAddToLibrary, setAppState, libraryReturnUrl, library, id, theme, selectedItems, onSelectItems, }) => {
|
|
22
|
+
const [libraryItemsData] = useAtom(libraryItemsAtom);
|
|
24
23
|
const _onAddToLibrary = useCallback((elements) => {
|
|
25
24
|
const addToLibrary = async (processedElements, libraryItems) => {
|
|
26
25
|
trackEvent("element", "addToLibrary", "ui");
|
|
@@ -54,37 +53,80 @@ export const LibraryMenuContent = ({ onInsertLibraryItems, pendingElements, onAd
|
|
|
54
53
|
}
|
|
55
54
|
const showBtn = libraryItemsData.libraryItems.length > 0 || pendingElements.length > 0;
|
|
56
55
|
return (_jsxs(LibraryMenuWrapper, { children: [_jsx(LibraryMenuItems, { isLoading: libraryItemsData.status === "loading", libraryItems: libraryItems, onAddToLibrary: _onAddToLibrary, onInsertLibraryItems: onInsertLibraryItems, pendingElements: pendingElements, id: id, libraryReturnUrl: libraryReturnUrl, theme: theme, onSelectItems: onSelectItems, selectedItems: selectedItems }), showBtn && (_jsx(LibraryMenuControlButtons, { className: "library-menu-control-buttons--at-bottom", style: { padding: "16px 12px 0 12px" }, id: id, libraryReturnUrl: libraryReturnUrl, theme: theme }))] }));
|
|
57
|
-
};
|
|
58
|
-
const
|
|
59
|
-
|
|
56
|
+
});
|
|
57
|
+
const getPendingElements = (elements, selectedElementIds) => ({
|
|
58
|
+
elements,
|
|
59
|
+
pending: getSelectedElements(elements, { selectedElementIds }, {
|
|
60
60
|
includeBoundTextElement: true,
|
|
61
61
|
includeElementsInFrames: true,
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
}),
|
|
63
|
+
selectedElementIds,
|
|
64
|
+
});
|
|
65
|
+
const usePendingElementsMemo = (appState, app) => {
|
|
66
|
+
const elements = useExcalidrawElements();
|
|
67
|
+
const [state, setState] = useState(() => getPendingElements(elements, appState.selectedElementIds));
|
|
68
|
+
const selectedElementVersions = useRef(new Map());
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
for (const element of state.pending) {
|
|
71
|
+
selectedElementVersions.current.set(element.id, element.version);
|
|
72
|
+
}
|
|
73
|
+
}, [state.pending]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (
|
|
76
|
+
// Only update once pointer is released.
|
|
77
|
+
// Reading directly from app.state to make it clear it's not reactive
|
|
78
|
+
// (hence, there's potential for stale state)
|
|
79
|
+
app.state.cursorButton === "up" &&
|
|
80
|
+
app.state.activeTool.type === "selection") {
|
|
81
|
+
setState((prev) => {
|
|
82
|
+
// if selectedElementIds changed, we don't have to compare versions
|
|
83
|
+
// ---------------------------------------------------------------------
|
|
84
|
+
if (!isShallowEqual(prev.selectedElementIds, appState.selectedElementIds)) {
|
|
85
|
+
selectedElementVersions.current.clear();
|
|
86
|
+
return getPendingElements(elements, appState.selectedElementIds);
|
|
87
|
+
}
|
|
88
|
+
// otherwise we need to check whether selected elements changed
|
|
89
|
+
// ---------------------------------------------------------------------
|
|
90
|
+
const elementsMap = app.scene.getNonDeletedElementsMap();
|
|
91
|
+
for (const id of Object.keys(appState.selectedElementIds)) {
|
|
92
|
+
const currVersion = elementsMap.get(id)?.version;
|
|
93
|
+
if (currVersion &&
|
|
94
|
+
currVersion !== selectedElementVersions.current.get(id)) {
|
|
95
|
+
// we can't update the selectedElementVersions in here
|
|
96
|
+
// because of double render in StrictMode which would overwrite
|
|
97
|
+
// the state in the second pass with the old `prev` state.
|
|
98
|
+
// Thus, we update versions in a separate effect. May create
|
|
99
|
+
// a race condition since current effect is not fully reactive.
|
|
100
|
+
return getPendingElements(elements, appState.selectedElementIds);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// nothing changed
|
|
104
|
+
// ---------------------------------------------------------------------
|
|
105
|
+
return prev;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}, [
|
|
109
|
+
app,
|
|
110
|
+
app.state.cursorButton,
|
|
111
|
+
app.state.activeTool.type,
|
|
112
|
+
appState.selectedElementIds,
|
|
113
|
+
elements,
|
|
114
|
+
]);
|
|
115
|
+
return state.pending;
|
|
73
116
|
};
|
|
74
117
|
/**
|
|
75
118
|
* This component is meant to be rendered inside <Sidebar.Tab/> inside our
|
|
76
119
|
* <DefaultSidebar/> or host apps Sidebar components.
|
|
77
120
|
*/
|
|
78
|
-
export const LibraryMenu = () => {
|
|
79
|
-
const
|
|
121
|
+
export const LibraryMenu = memo(() => {
|
|
122
|
+
const app = useApp();
|
|
123
|
+
const { onInsertElements } = app;
|
|
80
124
|
const appProps = useAppProps();
|
|
81
125
|
const appState = useUIAppState();
|
|
82
126
|
const setAppState = useExcalidrawSetAppState();
|
|
83
|
-
const elements = useExcalidrawElements();
|
|
84
127
|
const [selectedItems, setSelectedItems] = useState([]);
|
|
85
|
-
const memoizedLibrary = useMemo(() => library, [library]);
|
|
86
|
-
|
|
87
|
-
const pendingElements = usePendingElementsMemo(appState, elements);
|
|
128
|
+
const memoizedLibrary = useMemo(() => app.library, [app.library]);
|
|
129
|
+
const pendingElements = usePendingElementsMemo(appState, app);
|
|
88
130
|
const onInsertLibraryItems = useCallback((libraryItems) => {
|
|
89
131
|
onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
|
|
90
132
|
}, [onInsertElements]);
|
|
@@ -95,5 +137,5 @@ export const LibraryMenu = () => {
|
|
|
95
137
|
activeEmbeddable: null,
|
|
96
138
|
});
|
|
97
139
|
}, [setAppState]);
|
|
98
|
-
return (_jsx(LibraryMenuContent, { pendingElements: pendingElements, onInsertLibraryItems: onInsertLibraryItems, onAddToLibrary: deselectItems, setAppState: setAppState, libraryReturnUrl: appProps.libraryReturnUrl, library: memoizedLibrary, id: id, theme: appState.theme, selectedItems: selectedItems, onSelectItems: setSelectedItems }));
|
|
99
|
-
};
|
|
140
|
+
return (_jsx(LibraryMenuContent, { pendingElements: pendingElements, onInsertLibraryItems: onInsertLibraryItems, onAddToLibrary: deselectItems, setAppState: setAppState, libraryReturnUrl: appProps.libraryReturnUrl, library: memoizedLibrary, id: app.id, theme: appState.theme, selectedItems: selectedItems, onSelectItems: setSelectedItems }));
|
|
141
|
+
});
|