From 8551823da9fca1eea03b30be1f27993ebd2951e3 Mon Sep 17 00:00:00 2001 From: Arnost Pleskot Date: Thu, 16 Jan 2025 16:59:11 +0100 Subject: [PATCH] feat: update jotai (#9015) * feat: update jotai in excalidraw package * feat: update jotai in excalidraw-app * fix: exports from excalidraw/jotai * fix: use isolated react hooks * test: use jotai provider in test * remove unused package * refactor & make safer --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- .eslintrc.json | 16 +++++++- excalidraw-app/App.tsx | 17 +++++---- excalidraw-app/app-jotai.ts | 38 ++++++++++++++++++- excalidraw-app/app-language/LanguageList.tsx | 2 +- excalidraw-app/app-language/language-state.ts | 2 +- excalidraw-app/collab/Collab.tsx | 3 +- excalidraw-app/collab/CollabError.tsx | 2 +- excalidraw-app/package.json | 2 +- excalidraw-app/share/ShareDialog.tsx | 6 +-- excalidraw-app/useHandleAppTheme.ts | 19 +++++----- .../components/ActiveConfirmDialog.tsx | 4 +- packages/excalidraw/components/App.tsx | 10 ++--- .../components/ColorPicker/ColorInput.tsx | 8 +--- .../components/ColorPicker/ColorPicker.tsx | 8 +--- .../ColorPicker/CustomColorList.tsx | 2 +- .../components/ColorPicker/Picker.tsx | 2 +- .../ColorPicker/PickerColorList.tsx | 2 +- .../components/ColorPicker/ShadeList.tsx | 2 +- .../ColorPicker/colorPickerUtils.ts | 2 +- .../CommandPalette/CommandPalette.tsx | 5 +-- .../excalidraw/components/ConfirmDialog.tsx | 5 +-- packages/excalidraw/components/Dialog.tsx | 5 +-- packages/excalidraw/components/EyeDropper.tsx | 2 +- packages/excalidraw/components/IconPicker.tsx | 12 ++---- packages/excalidraw/components/LayerUI.tsx | 16 ++++---- .../excalidraw/components/LibraryMenu.tsx | 5 +-- .../components/LibraryMenuHeaderContent.tsx | 8 ++-- .../OverwriteConfirm/OverwriteConfirm.tsx | 4 +- .../OverwriteConfirm/OverwriteConfirmState.ts | 5 +-- packages/excalidraw/components/SearchMenu.tsx | 10 ++--- .../excalidraw/components/Sidebar/Sidebar.tsx | 5 +-- .../components/TTDDialog/TTDDialog.tsx | 2 +- packages/excalidraw/components/Trans.test.tsx | 5 ++- .../components/hoc/withInternalFallback.tsx | 8 ++-- .../components/main-menu/DefaultItems.tsx | 8 +--- packages/excalidraw/context/tunnels.ts | 9 ++++- packages/excalidraw/data/library.ts | 9 ++--- packages/excalidraw/editor-jotai.ts | 13 +++++++ .../excalidraw/hooks/useLibraryItemSvg.ts | 5 +-- .../excalidraw/hooks/useScrollPosition.ts | 2 +- packages/excalidraw/i18n.ts | 7 ++-- packages/excalidraw/index.tsx | 7 ++-- packages/excalidraw/jotai.ts | 28 -------------- packages/excalidraw/package.json | 3 +- yarn.lock | 13 +++++-- 45 files changed, 179 insertions(+), 169 deletions(-) create mode 100644 packages/excalidraw/editor-jotai.ts delete mode 100644 packages/excalidraw/jotai.ts diff --git a/.eslintrc.json b/.eslintrc.json index 86d5c2990..095543a85 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,20 @@ "rules": { "import/no-anonymous-default-export": "off", "no-restricted-globals": "off", - "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports", "disallowTypeAnnotations": false, "fixStyle": "separate-type-imports" }] + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "disallowTypeAnnotations": false, + "fixStyle": "separate-type-imports" + } + ], + "no-restricted-imports": [ + "error", + { + "name": "jotai", + "message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")." + } + ] } } diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index 883482286..f7842505a 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -90,9 +90,13 @@ import { import { AppMainMenu } from "./components/AppMainMenu"; import { AppWelcomeScreen } from "./components/AppWelcomeScreen"; import { AppFooter } from "./components/AppFooter"; -import { Provider, useAtom, useAtomValue } from "jotai"; -import { useAtomWithInitialValue } from "../packages/excalidraw/jotai"; -import { appJotaiStore } from "./app-jotai"; +import { + Provider, + useAtom, + useAtomValue, + useAtomWithInitialValue, + appJotaiStore, +} from "./app-jotai"; import "./index.scss"; import type { ResolutionType } from "../packages/excalidraw/utility-types"; @@ -117,7 +121,7 @@ import { share, youtubeIcon, } from "../packages/excalidraw/components/icons"; -import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme"; +import { useHandleAppTheme } from "./useHandleAppTheme"; import { getPreferredLanguage } from "./app-language/language-detector"; import { useAppLangCode } from "./app-language/language-state"; import DebugCanvas, { @@ -328,8 +332,7 @@ const ExcalidrawWrapper = () => { const [errorMessage, setErrorMessage] = useState(""); const isCollabDisabled = isRunningInIframe(); - const [appTheme, setAppTheme] = useAtom(appThemeAtom); - const { editorTheme } = useHandleAppTheme(); + const { editorTheme, appTheme, setAppTheme } = useHandleAppTheme(); const [langCode, setLangCode] = useAppLangCode(); @@ -1141,7 +1144,7 @@ const ExcalidrawApp = () => { return ( - appJotaiStore}> + diff --git a/excalidraw-app/app-jotai.ts b/excalidraw-app/app-jotai.ts index 8c6c796f6..15918039e 100644 --- a/excalidraw-app/app-jotai.ts +++ b/excalidraw-app/app-jotai.ts @@ -1,3 +1,37 @@ -import { unstable_createStore } from "jotai"; +// eslint-disable-next-line no-restricted-imports +import { + atom, + Provider, + useAtom, + useAtomValue, + useSetAtom, + createStore, + type PrimitiveAtom, +} from "jotai"; +import { useLayoutEffect } from "react"; -export const appJotaiStore = unstable_createStore(); +export const appJotaiStore = createStore(); + +export { atom, Provider, useAtom, useAtomValue, useSetAtom }; + +export const useAtomWithInitialValue = < + T extends unknown, + A extends PrimitiveAtom, +>( + atom: A, + initialValue: T | (() => T), +) => { + const [value, setValue] = useAtom(atom); + + useLayoutEffect(() => { + if (typeof initialValue === "function") { + // @ts-ignore + setValue(initialValue()); + } else { + setValue(initialValue); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return [value, setValue] as const; +}; diff --git a/excalidraw-app/app-language/LanguageList.tsx b/excalidraw-app/app-language/LanguageList.tsx index 08142b1f6..aaba3b9c4 100644 --- a/excalidraw-app/app-language/LanguageList.tsx +++ b/excalidraw-app/app-language/LanguageList.tsx @@ -1,6 +1,6 @@ -import { useSetAtom } from "jotai"; import React from "react"; import { useI18n, languages } from "../../packages/excalidraw/i18n"; +import { useSetAtom } from "../app-jotai"; import { appLangCodeAtom } from "./language-state"; export const LanguageList = ({ style }: { style?: React.CSSProperties }) => { diff --git a/excalidraw-app/app-language/language-state.ts b/excalidraw-app/app-language/language-state.ts index 5198a8ea8..f491c2215 100644 --- a/excalidraw-app/app-language/language-state.ts +++ b/excalidraw-app/app-language/language-state.ts @@ -1,5 +1,5 @@ -import { atom, useAtom } from "jotai"; import { useEffect } from "react"; +import { atom, useAtom } from "../app-jotai"; import { getPreferredLanguage, languageDetector } from "./language-detector"; export const appLangCodeAtom = atom(getPreferredLanguage()); diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx index 944c0deb0..1097aeda9 100644 --- a/excalidraw-app/collab/Collab.tsx +++ b/excalidraw-app/collab/Collab.tsx @@ -79,8 +79,7 @@ import { newElementWith } from "../../packages/excalidraw/element/mutateElement" import { decryptData } from "../../packages/excalidraw/data/encryption"; import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; -import { atom } from "jotai"; -import { appJotaiStore } from "../app-jotai"; +import { appJotaiStore, atom } from "../app-jotai"; import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types"; import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds"; import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils"; diff --git a/excalidraw-app/collab/CollabError.tsx b/excalidraw-app/collab/CollabError.tsx index 45a98ac8d..10ff1e1d0 100644 --- a/excalidraw-app/collab/CollabError.tsx +++ b/excalidraw-app/collab/CollabError.tsx @@ -2,9 +2,9 @@ import { Tooltip } from "../../packages/excalidraw/components/Tooltip"; import { warning } from "../../packages/excalidraw/components/icons"; import clsx from "clsx"; import { useEffect, useRef, useState } from "react"; +import { atom } from "../app-jotai"; import "./CollabError.scss"; -import { atom } from "jotai"; type ErrorIndicator = { message: string | null; diff --git a/excalidraw-app/package.json b/excalidraw-app/package.json index 53bf8e3a1..c5e1cdc5d 100644 --- a/excalidraw-app/package.json +++ b/excalidraw-app/package.json @@ -32,7 +32,7 @@ "firebase": "8.3.3", "i18next-browser-languagedetector": "6.1.4", "idb-keyval": "6.0.3", - "jotai": "1.13.1", + "jotai": "2.11.0", "react": "18.2.0", "react-dom": "18.2.0", "socket.io-client": "4.7.2", diff --git a/excalidraw-app/share/ShareDialog.tsx b/excalidraw-app/share/ShareDialog.tsx index d0a078cd2..b3a307526 100644 --- a/excalidraw-app/share/ShareDialog.tsx +++ b/excalidraw-app/share/ShareDialog.tsx @@ -18,11 +18,11 @@ import { TextField } from "../../packages/excalidraw/components/TextField"; import { FilledButton } from "../../packages/excalidraw/components/FilledButton"; import type { CollabAPI } from "../collab/Collab"; import { activeRoomLinkAtom } from "../collab/Collab"; -import { atom, useAtom, useAtomValue } from "jotai"; - -import "./ShareDialog.scss"; import { useUIAppState } from "../../packages/excalidraw/context/ui-appState"; import { useCopyStatus } from "../../packages/excalidraw/hooks/useCopiedIndicator"; +import { atom, useAtom, useAtomValue } from "../app-jotai"; + +import "./ShareDialog.scss"; type OnExportToBackend = () => void; type ShareDialogType = "share" | "collaborationOnly"; diff --git a/excalidraw-app/useHandleAppTheme.ts b/excalidraw-app/useHandleAppTheme.ts index 7dc45431e..5ce7260fd 100644 --- a/excalidraw-app/useHandleAppTheme.ts +++ b/excalidraw-app/useHandleAppTheme.ts @@ -1,4 +1,3 @@ -import { atom, useAtom } from "jotai"; import { useEffect, useLayoutEffect, useState } from "react"; import { THEME } from "../packages/excalidraw"; import { EVENT } from "../packages/excalidraw/constants"; @@ -6,18 +5,18 @@ import type { Theme } from "../packages/excalidraw/element/types"; import { CODES, KEYS } from "../packages/excalidraw/keys"; import { STORAGE_KEYS } from "./app_constants"; -export const appThemeAtom = atom( - (localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) as - | Theme - | "system" - | null) || THEME.LIGHT, -); - const getDarkThemeMediaQuery = (): MediaQueryList | undefined => window.matchMedia?.("(prefers-color-scheme: dark)"); export const useHandleAppTheme = () => { - const [appTheme, setAppTheme] = useAtom(appThemeAtom); + const [appTheme, setAppTheme] = useState(() => { + return ( + (localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) as + | Theme + | "system" + | null) || THEME.LIGHT + ); + }); const [editorTheme, setEditorTheme] = useState(THEME.LIGHT); useEffect(() => { @@ -66,5 +65,5 @@ export const useHandleAppTheme = () => { } }, [appTheme]); - return { editorTheme }; + return { editorTheme, appTheme, setAppTheme }; }; diff --git a/packages/excalidraw/components/ActiveConfirmDialog.tsx b/packages/excalidraw/components/ActiveConfirmDialog.tsx index 44a26e9a6..699fbc61f 100644 --- a/packages/excalidraw/components/ActiveConfirmDialog.tsx +++ b/packages/excalidraw/components/ActiveConfirmDialog.tsx @@ -1,7 +1,6 @@ -import { atom, useAtom } from "jotai"; import { actionClearCanvas } from "../actions"; import { t } from "../i18n"; -import { jotaiScope } from "../jotai"; +import { atom, useAtom } from "../editor-jotai"; import { useExcalidrawActionManager } from "./App"; import ConfirmDialog from "./ConfirmDialog"; @@ -10,7 +9,6 @@ export const activeConfirmDialogAtom = atom<"clearCanvas" | null>(null); export const ActiveConfirmDialog = () => { const [activeConfirmDialog, setActiveConfirmDialog] = useAtom( activeConfirmDialogAtom, - jotaiScope, ); const actionManager = useExcalidrawActionManager(); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index ced118600..6912bbba0 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -381,7 +381,7 @@ import { actionWrapSelectionInFrame, } from "../actions/actionFrame"; import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas"; -import { jotaiStore } from "../jotai"; +import { editorJotaiStore } from "../editor-jotai"; import { activeConfirmDialogAtom } from "./ActiveConfirmDialog"; import { ImageSceneDataError } from "../errors"; import { @@ -2077,7 +2077,7 @@ class App extends React.Component { }; private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => { - jotaiStore.set(activeEyeDropperAtom, { + editorJotaiStore.set(activeEyeDropperAtom, { swapPreviewOnAlt: true, colorPickerType: type === "stroke" ? "elementStroke" : "elementBackground", @@ -3325,7 +3325,7 @@ class App extends React.Component { openSidebar: this.state.openSidebar && this.device.editor.canFitSidebar && - jotaiStore.get(isSidebarDockedAtom) + editorJotaiStore.get(isSidebarDockedAtom) ? this.state.openSidebar : null, ...selectGroupsForSelectedElements( @@ -4553,7 +4553,7 @@ class App extends React.Component { event[KEYS.CTRL_OR_CMD] && (event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE) ) { - jotaiStore.set(activeConfirmDialogAtom, "clearCanvas"); + editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas"); } // eye dropper @@ -6292,7 +6292,7 @@ class App extends React.Component { focus: false, })), })); - jotaiStore.set(searchItemInFocusAtom, null); + editorJotaiStore.set(searchItemInFocusAtom, null); } // since contextMenu options are potentially evaluated on each render, diff --git a/packages/excalidraw/components/ColorPicker/ColorInput.tsx b/packages/excalidraw/components/ColorPicker/ColorInput.tsx index 7e77e06ff..837c88709 100644 --- a/packages/excalidraw/components/ColorPicker/ColorInput.tsx +++ b/packages/excalidraw/components/ColorPicker/ColorInput.tsx @@ -1,10 +1,9 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { getColor } from "./ColorPicker"; -import { useAtom } from "jotai"; import type { ColorPickerType } from "./colorPickerUtils"; import { activeColorPickerSectionAtom } from "./colorPickerUtils"; import { eyeDropperIcon } from "../icons"; -import { jotaiScope } from "../../jotai"; +import { useAtom } from "../../editor-jotai"; import { KEYS } from "../../keys"; import { activeEyeDropperAtom } from "../EyeDropper"; import clsx from "clsx"; @@ -57,10 +56,7 @@ export const ColorInput = ({ } }, [activeSection]); - const [eyeDropperState, setEyeDropperState] = useAtom( - activeEyeDropperAtom, - jotaiScope, - ); + const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom); useEffect(() => { return () => { diff --git a/packages/excalidraw/components/ColorPicker/ColorPicker.tsx b/packages/excalidraw/components/ColorPicker/ColorPicker.tsx index 6fd99dd46..74d552701 100644 --- a/packages/excalidraw/components/ColorPicker/ColorPicker.tsx +++ b/packages/excalidraw/components/ColorPicker/ColorPicker.tsx @@ -5,7 +5,6 @@ import { TopPicks } from "./TopPicks"; import { ButtonSeparator } from "../ButtonSeparator"; import { Picker } from "./Picker"; import * as Popover from "@radix-ui/react-popover"; -import { useAtom } from "jotai"; import type { ColorPickerType } from "./colorPickerUtils"; import { activeColorPickerSectionAtom } from "./colorPickerUtils"; import { useExcalidrawContainer } from "../App"; @@ -15,7 +14,7 @@ import PickerHeading from "./PickerHeading"; import { t } from "../../i18n"; import clsx from "clsx"; import { useRef } from "react"; -import { jotaiScope } from "../../jotai"; +import { useAtom } from "../../editor-jotai"; import { ColorInput } from "./ColorInput"; import { activeEyeDropperAtom } from "../EyeDropper"; import { PropertiesPopover } from "../PropertiesPopover"; @@ -76,10 +75,7 @@ const ColorPickerPopupContent = ({ const { container } = useExcalidrawContainer(); const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom); - const [eyeDropperState, setEyeDropperState] = useAtom( - activeEyeDropperAtom, - jotaiScope, - ); + const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom); const colorInputJSX = (
diff --git a/packages/excalidraw/components/ColorPicker/CustomColorList.tsx b/packages/excalidraw/components/ColorPicker/CustomColorList.tsx index b028dcc76..5fe1e3e53 100644 --- a/packages/excalidraw/components/ColorPicker/CustomColorList.tsx +++ b/packages/excalidraw/components/ColorPicker/CustomColorList.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { useAtom } from "jotai"; +import { useAtom } from "../../editor-jotai"; import { useEffect, useRef } from "react"; import { activeColorPickerSectionAtom } from "./colorPickerUtils"; import HotkeyLabel from "./HotkeyLabel"; diff --git a/packages/excalidraw/components/ColorPicker/Picker.tsx b/packages/excalidraw/components/ColorPicker/Picker.tsx index 722fc47e4..88d687670 100644 --- a/packages/excalidraw/components/ColorPicker/Picker.tsx +++ b/packages/excalidraw/components/ColorPicker/Picker.tsx @@ -5,7 +5,7 @@ import type { ExcalidrawElement } from "../../element/types"; import { ShadeList } from "./ShadeList"; import PickerColorList from "./PickerColorList"; -import { useAtom } from "jotai"; +import { useAtom } from "../../editor-jotai"; import { CustomColorList } from "./CustomColorList"; import { colorPickerKeyNavHandler } from "./keyboardNavHandlers"; import PickerHeading from "./PickerHeading"; diff --git a/packages/excalidraw/components/ColorPicker/PickerColorList.tsx b/packages/excalidraw/components/ColorPicker/PickerColorList.tsx index 406209a8e..f43559d95 100644 --- a/packages/excalidraw/components/ColorPicker/PickerColorList.tsx +++ b/packages/excalidraw/components/ColorPicker/PickerColorList.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { useAtom } from "jotai"; +import { useAtom } from "../../editor-jotai"; import { useEffect, useRef } from "react"; import { activeColorPickerSectionAtom, diff --git a/packages/excalidraw/components/ColorPicker/ShadeList.tsx b/packages/excalidraw/components/ColorPicker/ShadeList.tsx index 292457cbc..8d3d4cc2a 100644 --- a/packages/excalidraw/components/ColorPicker/ShadeList.tsx +++ b/packages/excalidraw/components/ColorPicker/ShadeList.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { useAtom } from "jotai"; +import { useAtom } from "../../editor-jotai"; import { useEffect, useRef } from "react"; import { activeColorPickerSectionAtom, diff --git a/packages/excalidraw/components/ColorPicker/colorPickerUtils.ts b/packages/excalidraw/components/ColorPicker/colorPickerUtils.ts index 311f5eba9..2733b7aba 100644 --- a/packages/excalidraw/components/ColorPicker/colorPickerUtils.ts +++ b/packages/excalidraw/components/ColorPicker/colorPickerUtils.ts @@ -1,7 +1,7 @@ import type { ExcalidrawElement } from "../../element/types"; -import { atom } from "jotai"; import type { ColorPickerColor, ColorPaletteCustom } from "../../colors"; import { MAX_CUSTOM_COLORS_USED_IN_CANVAS } from "../../colors"; +import { atom } from "../../editor-jotai"; export const getColorNameAndShadeFromColor = ({ palette, diff --git a/packages/excalidraw/components/CommandPalette/CommandPalette.tsx b/packages/excalidraw/components/CommandPalette/CommandPalette.tsx index 43a29883c..cc3c782c4 100644 --- a/packages/excalidraw/components/CommandPalette/CommandPalette.tsx +++ b/packages/excalidraw/components/CommandPalette/CommandPalette.tsx @@ -36,7 +36,7 @@ import { getShortcutKey, isWritableElement, } from "../../utils"; -import { atom, useAtom } from "jotai"; +import { atom, useAtom, editorJotaiStore } from "../../editor-jotai"; import { deburr } from "../../deburr"; import type { MarkRequired } from "../../utility-types"; import { InlineIcon } from "../InlineIcon"; @@ -48,7 +48,6 @@ import { actionLink, actionToggleSearchMenu, } from "../../actions"; -import { jotaiStore } from "../../jotai"; import { activeConfirmDialogAtom } from "../ActiveConfirmDialog"; import type { CommandPaletteItem } from "./types"; import * as defaultItems from "./defaultCommandPaletteItems"; @@ -349,7 +348,7 @@ function CommandPaletteInner({ keywords: ["delete", "destroy"], viewMode: false, perform: () => { - jotaiStore.set(activeConfirmDialogAtom, "clearCanvas"); + editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas"); }, }, { diff --git a/packages/excalidraw/components/ConfirmDialog.tsx b/packages/excalidraw/components/ConfirmDialog.tsx index 637f0659a..81073ecdb 100644 --- a/packages/excalidraw/components/ConfirmDialog.tsx +++ b/packages/excalidraw/components/ConfirmDialog.tsx @@ -5,10 +5,9 @@ import { Dialog } from "./Dialog"; import "./ConfirmDialog.scss"; import DialogActionButton from "./DialogActionButton"; -import { useSetAtom } from "jotai"; import { isLibraryMenuOpenAtom } from "./LibraryMenu"; import { useExcalidrawContainer, useExcalidrawSetAppState } from "./App"; -import { jotaiScope } from "../jotai"; +import { useSetAtom } from "../editor-jotai"; interface Props extends Omit { onConfirm: () => void; @@ -27,7 +26,7 @@ const ConfirmDialog = (props: Props) => { ...rest } = props; const setAppState = useExcalidrawSetAppState(); - const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope); + const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom); const { container } = useExcalidrawContainer(); return ( diff --git a/packages/excalidraw/components/Dialog.tsx b/packages/excalidraw/components/Dialog.tsx index 06e2a4fb8..0a105cf8d 100644 --- a/packages/excalidraw/components/Dialog.tsx +++ b/packages/excalidraw/components/Dialog.tsx @@ -11,9 +11,8 @@ import "./Dialog.scss"; import { Island } from "./Island"; import { Modal } from "./Modal"; import { queryFocusableElements } from "../utils"; -import { useSetAtom } from "jotai"; import { isLibraryMenuOpenAtom } from "./LibraryMenu"; -import { jotaiScope } from "../jotai"; +import { useSetAtom } from "../editor-jotai"; import { t } from "../i18n"; import { CloseIcon } from "./icons"; @@ -92,7 +91,7 @@ export const Dialog = (props: DialogProps) => { }, [islandNode, props.autofocus]); const setAppState = useExcalidrawSetAppState(); - const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope); + const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom); const onClose = () => { setAppState({ openMenu: null }); diff --git a/packages/excalidraw/components/EyeDropper.tsx b/packages/excalidraw/components/EyeDropper.tsx index f07697662..429c68a4a 100644 --- a/packages/excalidraw/components/EyeDropper.tsx +++ b/packages/excalidraw/components/EyeDropper.tsx @@ -1,4 +1,3 @@ -import { atom } from "jotai"; import { useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import { rgbToHex } from "../colors"; @@ -14,6 +13,7 @@ import { useStable } from "../hooks/useStable"; import "./EyeDropper.scss"; import type { ColorPickerType } from "./ColorPicker/colorPickerUtils"; import type { ExcalidrawElement } from "../element/types"; +import { atom } from "../editor-jotai"; export type EyeDropperProperties = { keepOpenOnAlt: boolean; diff --git a/packages/excalidraw/components/IconPicker.tsx b/packages/excalidraw/components/IconPicker.tsx index f72433106..830712bbf 100644 --- a/packages/excalidraw/components/IconPicker.tsx +++ b/packages/excalidraw/components/IconPicker.tsx @@ -1,15 +1,14 @@ import React, { useEffect } from "react"; import * as Popover from "@radix-ui/react-popover"; - -import "./IconPicker.scss"; import { isArrowKey, KEYS } from "../keys"; import { getLanguage, t } from "../i18n"; import clsx from "clsx"; import Collapsible from "./Stats/Collapsible"; -import { atom, useAtom } from "jotai"; -import { jotaiScope } from "../jotai"; +import { atom, useAtom } from "../editor-jotai"; import { useDevice } from ".."; +import "./IconPicker.scss"; + const moreOptionsAtom = atom(false); type Option = { @@ -94,10 +93,7 @@ function Picker({ event.stopPropagation(); }; - const [showMoreOptions, setShowMoreOptions] = useAtom( - moreOptionsAtom, - jotaiScope, - ); + const [showMoreOptions, setShowMoreOptions] = useAtom(moreOptionsAtom); const alwaysVisibleOptions = React.useMemo( () => options.slice(0, numberOfOptionsToAlwaysShow), diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index f51ba37ae..cca66a6a4 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -41,8 +41,7 @@ import { trackEvent } from "../analytics"; import { useDevice } from "./App"; import Footer from "./footer/Footer"; import { isSidebarDockedAtom } from "./Sidebar/Sidebar"; -import { jotaiScope } from "../jotai"; -import { Provider, useAtom, useAtomValue } from "jotai"; +import { useAtom, useAtomValue } from "../editor-jotai"; import MainMenu from "./main-menu/MainMenu"; import { ActiveConfirmDialog } from "./ActiveConfirmDialog"; import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm"; @@ -148,10 +147,9 @@ const LayerUI = ({ const device = useDevice(); const tunnels = useInitializeTunnels(); - const [eyeDropperState, setEyeDropperState] = useAtom( - activeEyeDropperAtom, - jotaiScope, - ); + const TunnelsJotaiProvider = tunnels.tunnelsJotai.Provider; + + const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom); const renderJSONExportDialog = () => { if (!UIOptions.canvasActions.export) { @@ -382,7 +380,7 @@ const LayerUI = ({ ); }; - const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope); + const isSidebarDocked = useAtomValue(isSidebarDockedAtom); const layerUIJSX = ( <> @@ -566,11 +564,11 @@ const LayerUI = ({ return ( - + {layerUIJSX} - + ); }; diff --git a/packages/excalidraw/components/LibraryMenu.tsx b/packages/excalidraw/components/LibraryMenu.tsx index 6192c7e71..4b4cccbae 100644 --- a/packages/excalidraw/components/LibraryMenu.tsx +++ b/packages/excalidraw/components/LibraryMenu.tsx @@ -14,8 +14,7 @@ import type { } from "../types"; import LibraryMenuItems from "./LibraryMenuItems"; import { trackEvent } from "../analytics"; -import { atom, useAtom } from "jotai"; -import { jotaiScope } from "../jotai"; +import { atom, useAtom } from "../editor-jotai"; import Spinner from "./Spinner"; import { useApp, @@ -61,7 +60,7 @@ export const LibraryMenuContent = ({ selectedItems: LibraryItem["id"][]; onSelectItems: (id: LibraryItem["id"][]) => void; }) => { - const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope); + const [libraryItemsData] = useAtom(libraryItemsAtom); const _onAddToLibrary = useCallback( (elements: LibraryItem["elements"]) => { diff --git a/packages/excalidraw/components/LibraryMenuHeaderContent.tsx b/packages/excalidraw/components/LibraryMenuHeaderContent.tsx index e26507803..bec31aa36 100644 --- a/packages/excalidraw/components/LibraryMenuHeaderContent.tsx +++ b/packages/excalidraw/components/LibraryMenuHeaderContent.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; import { t } from "../i18n"; import Trans from "./Trans"; -import { jotaiScope } from "../jotai"; +import { useAtom } from "../editor-jotai"; import type { LibraryItem, LibraryItems, UIAppState } from "../types"; import { useApp, useExcalidrawSetAppState } from "./App"; import { saveLibraryAsJSON } from "../data/json"; @@ -17,7 +17,6 @@ import { import { ToolButton } from "./ToolButton"; import { fileOpen } from "../data/filesystem"; import { muteFSAbortError } from "../utils"; -import { useAtom } from "jotai"; import ConfirmDialog from "./ConfirmDialog"; import PublishLibrary from "./PublishLibrary"; import { Dialog } from "./Dialog"; @@ -51,10 +50,9 @@ export const LibraryDropdownMenuButton: React.FC<{ appState, className, }) => { - const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope); + const [libraryItemsData] = useAtom(libraryItemsAtom); const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom( isLibraryMenuOpenAtom, - jotaiScope, ); const renderRemoveLibAlert = () => { @@ -286,7 +284,7 @@ export const LibraryDropdownMenu = ({ const appState = useUIAppState(); const setAppState = useExcalidrawSetAppState(); - const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope); + const [libraryItemsData] = useAtom(libraryItemsAtom); const removeFromLibrary = async (libraryItems: LibraryItems) => { const nextItems = libraryItems.filter( diff --git a/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm.tsx b/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm.tsx index 9a674cd37..4bf8d6717 100644 --- a/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm.tsx +++ b/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm.tsx @@ -1,8 +1,7 @@ import React from "react"; -import { useAtom } from "jotai"; import { useTunnels } from "../../context/tunnels"; -import { jotaiScope } from "../../jotai"; +import { useAtom } from "../../editor-jotai"; import { Dialog } from "../Dialog"; import { withInternalFallback } from "../hoc/withInternalFallback"; import { overwriteConfirmStateAtom } from "./OverwriteConfirmState"; @@ -23,7 +22,6 @@ const OverwriteConfirmDialog = Object.assign( const { OverwriteConfirmDialogTunnel } = useTunnels(); const [overwriteConfirmState, setState] = useAtom( overwriteConfirmStateAtom, - jotaiScope, ); if (!overwriteConfirmState.active) { diff --git a/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.ts b/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.ts index 9236a588b..04616aa8d 100644 --- a/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.ts +++ b/packages/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.ts @@ -1,5 +1,4 @@ -import { atom } from "jotai"; -import { jotaiStore } from "../../jotai"; +import { atom, editorJotaiStore } from "../../editor-jotai"; import type React from "react"; export type OverwriteConfirmState = @@ -32,7 +31,7 @@ export async function openConfirmModal({ color: "danger" | "warning"; }) { return new Promise((resolve) => { - jotaiStore.set(overwriteConfirmStateAtom, { + editorJotaiStore.set(overwriteConfirmStateAtom, { active: true, onConfirm: () => resolve(true), onClose: () => resolve(false), diff --git a/packages/excalidraw/components/SearchMenu.tsx b/packages/excalidraw/components/SearchMenu.tsx index b4bb91b90..c8bf08ac3 100644 --- a/packages/excalidraw/components/SearchMenu.tsx +++ b/packages/excalidraw/components/SearchMenu.tsx @@ -11,8 +11,7 @@ import { measureText } from "../element/textElement"; import { addEventListener, getFontString } from "../utils"; import { KEYS } from "../keys"; import clsx from "clsx"; -import { atom, useAtom } from "jotai"; -import { jotaiScope } from "../jotai"; +import { atom, useAtom } from "../editor-jotai"; import { t } from "../i18n"; import { isElementCompletelyInViewport } from "../element/sizeHelpers"; import { randomInteger } from "../random"; @@ -58,7 +57,7 @@ export const SearchMenu = () => { const searchInputRef = useRef(null); - const [inputValue, setInputValue] = useAtom(searchQueryAtom, jotaiScope); + const [inputValue, setInputValue] = useAtom(searchQueryAtom); const searchQuery = inputValue.trim() as SearchQuery; const [isSearching, setIsSearching] = useState(false); @@ -70,10 +69,7 @@ export const SearchMenu = () => { const searchedQueryRef = useRef(null); const lastSceneNonceRef = useRef(undefined); - const [focusIndex, setFocusIndex] = useAtom( - searchItemInFocusAtom, - jotaiScope, - ); + const [focusIndex, setFocusIndex] = useAtom(searchItemInFocusAtom); const elementsMap = app.scene.getNonDeletedElementsMap(); useEffect(() => { diff --git a/packages/excalidraw/components/Sidebar/Sidebar.tsx b/packages/excalidraw/components/Sidebar/Sidebar.tsx index efa6ccbe3..7c747d2f4 100644 --- a/packages/excalidraw/components/Sidebar/Sidebar.tsx +++ b/packages/excalidraw/components/Sidebar/Sidebar.tsx @@ -8,8 +8,7 @@ import React, { useCallback, } from "react"; import { Island } from "../Island"; -import { atom, useSetAtom } from "jotai"; -import { jotaiScope } from "../../jotai"; +import { atom, useSetAtom } from "../../editor-jotai"; import type { SidebarProps, SidebarPropsContextValue } from "./common"; import { SidebarPropsContext } from "./common"; import { SidebarHeader } from "./SidebarHeader"; @@ -58,7 +57,7 @@ export const SidebarInner = forwardRef( const setAppState = useExcalidrawSetAppState(); - const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom, jotaiScope); + const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom); useLayoutEffect(() => { setIsSidebarDockedAtom(!!docked); diff --git a/packages/excalidraw/components/TTDDialog/TTDDialog.tsx b/packages/excalidraw/components/TTDDialog/TTDDialog.tsx index f0c63770a..a683d80ce 100644 --- a/packages/excalidraw/components/TTDDialog/TTDDialog.tsx +++ b/packages/excalidraw/components/TTDDialog/TTDDialog.tsx @@ -25,7 +25,7 @@ import type { BinaryFiles } from "../../types"; import { ArrowRightIcon } from "../icons"; import "./TTDDialog.scss"; -import { atom, useAtom } from "jotai"; +import { atom, useAtom } from "../../editor-jotai"; import { trackEvent } from "../../analytics"; import { InlineIcon } from "../InlineIcon"; import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut"; diff --git a/packages/excalidraw/components/Trans.test.tsx b/packages/excalidraw/components/Trans.test.tsx index df8ec526e..c331796bd 100644 --- a/packages/excalidraw/components/Trans.test.tsx +++ b/packages/excalidraw/components/Trans.test.tsx @@ -4,6 +4,7 @@ import fallbackLangData from "../locales/en.json"; import Trans from "./Trans"; import type { TranslationKeys } from "../i18n"; +import { EditorJotaiProvider } from "../editor-jotai"; describe("Test ", () => { it("should translate the the strings correctly", () => { @@ -17,7 +18,7 @@ describe("Test ", () => { }; const { getByTestId } = render( - <> +
", () => { connect-link={(el) => {el}} />
- , +
, ); expect(getByTestId("test1").innerHTML).toEqual("Hello world"); diff --git a/packages/excalidraw/components/hoc/withInternalFallback.tsx b/packages/excalidraw/components/hoc/withInternalFallback.tsx index 4131c51ec..5906b30f5 100644 --- a/packages/excalidraw/components/hoc/withInternalFallback.tsx +++ b/packages/excalidraw/components/hoc/withInternalFallback.tsx @@ -1,6 +1,6 @@ -import { atom, useAtom } from "jotai"; import React, { useLayoutEffect, useRef } from "react"; import { useTunnels } from "../../context/tunnels"; +import { atom } from "../../editor-jotai"; export const withInternalFallback = ( componentName: string, @@ -13,9 +13,11 @@ export const withInternalFallback = ( __fallback?: boolean; } > = (props) => { - const { jotaiScope } = useTunnels(); + const { + tunnelsJotai: { useAtom }, + } = useTunnels(); // for rerenders - const [, setCounter] = useAtom(renderAtom, jotaiScope); + const [, setCounter] = useAtom(renderAtom); // for initial & subsequent renders. Tracked as component state // due to excalidraw multi-instance scanerios. const metaRef = useRef({ diff --git a/packages/excalidraw/components/main-menu/DefaultItems.tsx b/packages/excalidraw/components/main-menu/DefaultItems.tsx index bb3059db5..632ea4ffa 100644 --- a/packages/excalidraw/components/main-menu/DefaultItems.tsx +++ b/packages/excalidraw/components/main-menu/DefaultItems.tsx @@ -32,9 +32,8 @@ import { actionToggleTheme, } from "../../actions"; import clsx from "clsx"; -import { useSetAtom } from "jotai"; import { activeConfirmDialogAtom } from "../ActiveConfirmDialog"; -import { jotaiScope } from "../../jotai"; +import { useSetAtom } from "../../editor-jotai"; import { useUIAppState } from "../../context/ui-appState"; import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState"; import Trans from "../Trans"; @@ -189,10 +188,7 @@ Help.displayName = "Help"; export const ClearCanvas = () => { const { t } = useI18n(); - const setActiveConfirmDialog = useSetAtom( - activeConfirmDialogAtom, - jotaiScope, - ); + const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom); const actionManager = useExcalidrawActionManager(); if (!actionManager.isActionEnabled(actionClearCanvas)) { diff --git a/packages/excalidraw/context/tunnels.ts b/packages/excalidraw/context/tunnels.ts index 8dc325ff9..73b85ba6a 100644 --- a/packages/excalidraw/context/tunnels.ts +++ b/packages/excalidraw/context/tunnels.ts @@ -1,5 +1,6 @@ import React from "react"; import tunnel from "tunnel-rat"; +import { createIsolation } from "jotai-scope"; export type Tunnel = ReturnType; @@ -14,13 +15,17 @@ type TunnelsContextValue = { DefaultSidebarTabTriggersTunnel: Tunnel; OverwriteConfirmDialogTunnel: Tunnel; TTDDialogTriggerTunnel: Tunnel; - jotaiScope: symbol; + // this can be removed once we create jotai stores per each editor + // instance + tunnelsJotai: ReturnType; }; export const TunnelsContext = React.createContext(null!); export const useTunnels = () => React.useContext(TunnelsContext); +const tunnelsJotai = createIsolation(); + export const useInitializeTunnels = () => { return React.useMemo((): TunnelsContextValue => { return { @@ -34,7 +39,7 @@ export const useInitializeTunnels = () => { DefaultSidebarTabTriggersTunnel: tunnel(), OverwriteConfirmDialogTunnel: tunnel(), TTDDialogTriggerTunnel: tunnel(), - jotaiScope: Symbol(), + tunnelsJotai, }; }, []); }; diff --git a/packages/excalidraw/data/library.ts b/packages/excalidraw/data/library.ts index 331e7b84e..7eb760c64 100644 --- a/packages/excalidraw/data/library.ts +++ b/packages/excalidraw/data/library.ts @@ -8,8 +8,7 @@ import type { } from "../types"; import { restoreLibraryItems } from "./restore"; import type App from "../components/App"; -import { atom } from "jotai"; -import { jotaiStore } from "../jotai"; +import { atom, editorJotaiStore } from "../editor-jotai"; import type { ExcalidrawElement } from "../element/types"; import { getCommonBoundingBox } from "../element/bounds"; import { AbortError } from "../errors"; @@ -191,13 +190,13 @@ class Library { private notifyListeners = () => { if (this.updateQueue.length > 0) { - jotaiStore.set(libraryItemsAtom, (s) => ({ + editorJotaiStore.set(libraryItemsAtom, (s) => ({ status: "loading", libraryItems: this.currLibraryItems, isInitialized: s.isInitialized, })); } else { - jotaiStore.set(libraryItemsAtom, { + editorJotaiStore.set(libraryItemsAtom, { status: "loaded", libraryItems: this.currLibraryItems, isInitialized: true, @@ -225,7 +224,7 @@ class Library { destroy = () => { this.updateQueue = []; this.currLibraryItems = []; - jotaiStore.set(libraryItemSvgsCache, new Map()); + editorJotaiStore.set(libraryItemSvgsCache, new Map()); // TODO uncomment after/if we make jotai store scoped to each excal instance // jotaiStore.set(libraryItemsAtom, { // status: "loading", diff --git a/packages/excalidraw/editor-jotai.ts b/packages/excalidraw/editor-jotai.ts new file mode 100644 index 000000000..28bc69306 --- /dev/null +++ b/packages/excalidraw/editor-jotai.ts @@ -0,0 +1,13 @@ +// eslint-disable-next-line no-restricted-imports +import { atom, createStore, type PrimitiveAtom } from "jotai"; +import { createIsolation } from "jotai-scope"; + +const jotai = createIsolation(); + +export { atom, PrimitiveAtom }; +export const { useAtom, useSetAtom, useAtomValue, useStore } = jotai; +export const EditorJotaiProvider: ReturnType< + typeof createIsolation +>["Provider"] = jotai.Provider; + +export const editorJotaiStore: ReturnType = createStore(); diff --git a/packages/excalidraw/hooks/useLibraryItemSvg.ts b/packages/excalidraw/hooks/useLibraryItemSvg.ts index b1332cb05..b2ee746e4 100644 --- a/packages/excalidraw/hooks/useLibraryItemSvg.ts +++ b/packages/excalidraw/hooks/useLibraryItemSvg.ts @@ -1,7 +1,6 @@ -import { atom, useAtom } from "jotai"; import { useEffect, useState } from "react"; import { COLOR_PALETTE } from "../colors"; -import { jotaiScope } from "../jotai"; +import { atom, useAtom } from "../editor-jotai"; import { exportToSvg } from "../../utils/export"; import type { LibraryItem } from "../types"; @@ -64,7 +63,7 @@ export const useLibraryItemSvg = ( }; export const useLibraryCache = () => { - const [svgCache] = useAtom(libraryItemSvgsCache, jotaiScope); + const [svgCache] = useAtom(libraryItemSvgsCache); const clearLibraryCache = () => svgCache.clear(); diff --git a/packages/excalidraw/hooks/useScrollPosition.ts b/packages/excalidraw/hooks/useScrollPosition.ts index e4efb460e..cd913c07d 100644 --- a/packages/excalidraw/hooks/useScrollPosition.ts +++ b/packages/excalidraw/hooks/useScrollPosition.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { atom, useAtom } from "jotai"; +import { atom, useAtom } from "../editor-jotai"; import throttle from "lodash.throttle"; const scrollPositionAtom = atom(0); diff --git a/packages/excalidraw/i18n.ts b/packages/excalidraw/i18n.ts index 10373ef56..e1da5fc44 100644 --- a/packages/excalidraw/i18n.ts +++ b/packages/excalidraw/i18n.ts @@ -1,7 +1,6 @@ import fallbackLangData from "./locales/en.json"; import percentages from "./locales/percentages.json"; -import { jotaiScope, jotaiStore } from "./jotai"; -import { atom, useAtomValue } from "jotai"; +import { useAtomValue, editorJotaiStore, atom } from "./editor-jotai"; import type { NestedKeyOf } from "./utility-types"; const COMPLETION_THRESHOLD = 85; @@ -103,7 +102,7 @@ export const setLanguage = async (lang: Language) => { } } - jotaiStore.set(editorLangCodeAtom, lang.code); + editorJotaiStore.set(editorLangCodeAtom, lang.code); }; export const getLanguage = () => currentLang; @@ -165,6 +164,6 @@ const editorLangCodeAtom = atom(defaultLang.code); // - component is rendered internally by , but the component // is memoized w/o being updated on `langCode`, `AppState`, or `UIAppState` export const useI18n = () => { - const langCode = useAtomValue(editorLangCodeAtom, jotaiScope); + const langCode = useAtomValue(editorLangCodeAtom); return { t, langCode }; }; diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index c83bd7c16..0af660f19 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -11,8 +11,7 @@ import "./fonts/fonts.css"; import type { AppProps, ExcalidrawProps } from "./types"; import { defaultLang } from "./i18n"; import { DEFAULT_UI_OPTIONS } from "./constants"; -import { Provider } from "jotai"; -import { jotaiScope, jotaiStore } from "./jotai"; +import { EditorJotaiProvider, editorJotaiStore } from "./editor-jotai"; import Footer from "./components/footer/FooterCenter"; import MainMenu from "./components/main-menu/MainMenu"; import WelcomeScreen from "./components/welcome-screen/WelcomeScreen"; @@ -108,7 +107,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { }, []); return ( - jotaiStore} scope={jotaiScope}> + { {children} - + ); }; diff --git a/packages/excalidraw/jotai.ts b/packages/excalidraw/jotai.ts deleted file mode 100644 index 415188425..000000000 --- a/packages/excalidraw/jotai.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { PrimitiveAtom } from "jotai"; -import { unstable_createStore, useAtom } from "jotai"; -import { useLayoutEffect } from "react"; - -export const jotaiScope = Symbol(); -export const jotaiStore = unstable_createStore(); - -export const useAtomWithInitialValue = < - T extends unknown, - A extends PrimitiveAtom, ->( - atom: A, - initialValue: T | (() => T), -) => { - const [value, setValue] = useAtom(atom); - - useLayoutEffect(() => { - if (typeof initialValue === "function") { - // @ts-ignore - setValue(initialValue()); - } else { - setValue(initialValue); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return [value, setValue] as const; -}; diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index a58bd185a..0cbf636fb 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -70,7 +70,8 @@ "fractional-indexing": "3.2.0", "fuzzy": "0.1.3", "image-blob-reduce": "3.0.1", - "jotai": "1.13.1", + "jotai": "2.11.0", + "jotai-scope": "0.7.2", "lodash.throttle": "4.1.1", "nanoid": "3.3.3", "open-color": "1.9.1", diff --git a/yarn.lock b/yarn.lock index b97d9a706..7eb706e64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7339,10 +7339,15 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jotai@1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.13.1.tgz#20cc46454cbb39096b12fddfa635b873b3668236" - integrity sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw== +jotai-scope@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/jotai-scope/-/jotai-scope-0.7.2.tgz#3e9ec5b743bd9f36b08b32cf5151786049bfcce7" + integrity sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg== + +jotai@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.11.0.tgz#923f8351e0b2d721036af892c0ae25625049d120" + integrity sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0"