diff --git a/apps/designer/app/canvas/canvas.tsx b/apps/designer/app/canvas/canvas.tsx index e58857dc1d..9193e3ad60 100644 --- a/apps/designer/app/canvas/canvas.tsx +++ b/apps/designer/app/canvas/canvas.tsx @@ -45,10 +45,7 @@ import { registerContainers } from "./shared/immerhin"; import { useTrackHoveredElement } from "./shared/use-track-hovered-element"; import { usePublishScrollState } from "./shared/use-publish-scroll-state"; import { useDragAndDrop } from "./shared/use-drag-drop"; -import { - LexicalComposer, - config, -} from "~/canvas/features/wrapper-component/text-editor"; + import { setInstanceChildrenMutable } from "~/shared/tree-utils"; registerContainers(); @@ -133,18 +130,14 @@ export const Canvas = ({ data }: CanvasProps): JSX.Element | null => { if (elements === undefined) return null; - const children = ( - {elements} - ); - if (isPreviewMode) { - return children; + return elements; } return ( <> - {children} + {elements} ); }; diff --git a/apps/designer/app/canvas/features/wrapper-component/text-editor/config.ts b/apps/designer/app/canvas/features/wrapper-component/text-editor/config.ts index 5e50a7a73a..d1c1b9adaf 100644 --- a/apps/designer/app/canvas/features/wrapper-component/text-editor/config.ts +++ b/apps/designer/app/canvas/features/wrapper-component/text-editor/config.ts @@ -1,7 +1,7 @@ import { InstanceNode } from "./nodes/node-instance"; export const config = { - namespace: "ComponentTextEditor", + namespace: "WsTextEditor", onError(error: Error) { throw error; }, diff --git a/apps/designer/app/canvas/features/wrapper-component/text-editor/editor-lazy.tsx b/apps/designer/app/canvas/features/wrapper-component/text-editor/editor-lazy.tsx new file mode 100644 index 0000000000..83d1fbce93 --- /dev/null +++ b/apps/designer/app/canvas/features/wrapper-component/text-editor/editor-lazy.tsx @@ -0,0 +1,22 @@ +import { memo, lazy, Suspense } from "react"; +import type { EditorProps } from "./editor"; + +const EditorLazy = lazy(() => import("./editor")); + +type EditorWithSuspenseProps = EditorProps & { + fallback: JSX.Element; +}; + +const EditorWithSuspense = ({ + fallback, + ...props +}: EditorWithSuspenseProps) => { + return ( + + + + ); +}; + +// Prevent rerender because in editing mode Editor controls the node. +export const Editor = memo(EditorWithSuspense, () => true); diff --git a/apps/designer/app/canvas/features/wrapper-component/text-editor/editor.tsx b/apps/designer/app/canvas/features/wrapper-component/text-editor/editor.tsx index 13b4bee56c..ec8fba195c 100644 --- a/apps/designer/app/canvas/features/wrapper-component/text-editor/editor.tsx +++ b/apps/designer/app/canvas/features/wrapper-component/text-editor/editor.tsx @@ -1,23 +1,40 @@ -import { type ChildrenUpdates, type Instance } from "@webstudio-is/react-sdk"; -import { RichTextPlugin, HistoryPlugin } from "./lexical"; +import type { ChildrenUpdates, Instance } from "@webstudio-is/react-sdk"; +import { + RichTextPlugin, + HistoryPlugin, + LexicalComposer, + useLexicalComposerContext, +} from "./lexical"; import { InstancePlugin } from "./plugins/plugin-instance"; import { ToolbarConnectorPlugin } from "./plugins/plugin-toolbar-connector"; import { OnChangePlugin } from "./plugins/plugin-on-change"; -import { memo } from "react"; +import { config } from "./config"; +import { useCallback } from "react"; -type EditorProps = { - children?: JSX.Element; - editable: JSX.Element; +type InstanceRenderProps = { + ref: (element: HTMLElement | null) => void; + contentEditable: boolean; +}; + +export type EditorProps = { + renderInstance: (renderProps: InstanceRenderProps) => JSX.Element; instance: Instance; onChange: (updates: ChildrenUpdates) => void; }; -export const Editor = ({ - instance, - editable, - children, - onChange, -}: EditorProps) => { +const useSetElement = () => { + const [editor] = useLexicalComposerContext(); + return useCallback( + (element) => { + editor.setRootElement(element); + }, + [editor] + ); +}; + +const Editor = ({ instance, renderInstance, onChange }: EditorProps) => { + const ref = useSetElement(); + const editable = renderInstance({ ref, contentEditable: true }); return ( <> @@ -25,10 +42,16 @@ export const Editor = ({ {instance.children} - {children} ); }; -// Prevent rerender because in editing mode Editor controls the node. -export const EditorMemoized = memo(Editor, () => true); +const EditorConfigured = (props: EditorProps) => { + return ( + + + + ); +}; + +export default EditorConfigured; diff --git a/apps/designer/app/canvas/features/wrapper-component/text-editor/example-text-editor.tsx b/apps/designer/app/canvas/features/wrapper-component/text-editor/example-text-editor.tsx index 38c55084f3..673c6d8651 100644 --- a/apps/designer/app/canvas/features/wrapper-component/text-editor/example-text-editor.tsx +++ b/apps/designer/app/canvas/features/wrapper-component/text-editor/example-text-editor.tsx @@ -9,7 +9,7 @@ import { Box, Button } from "@webstudio-is/design-system"; import { LexicalComposer, ContentEditable } from "./lexical"; import { TreeViewPlugin } from "./plugins/tree-view-plugin"; import { config } from "./config"; -import { Editor } from "./editor"; +import Editor from "./editor"; type ExampleTextEditorProps = { onChange: (state: ChildrenUpdates) => void; @@ -100,7 +100,7 @@ export const ExampleTextEditor = ({ onChange }: ExampleTextEditorProps) => { }} > ( { background: "#eee", }} /> - } + )} instance={ { component: "Paragraph", diff --git a/apps/designer/app/canvas/features/wrapper-component/text-editor/hooks/use-content-editable.tsx b/apps/designer/app/canvas/features/wrapper-component/text-editor/hooks/use-content-editable.tsx deleted file mode 100644 index 1c5a59704c..0000000000 --- a/apps/designer/app/canvas/features/wrapper-component/text-editor/hooks/use-content-editable.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { useCallback, useEffect } from "react"; - -type RefCallback = (rootElement: null | HTMLElement) => void; - -const props = { - //autoCapitalize: false, - //autoComplete: false, - contentEditable: true, -}; - -export const useContentEditable = ( - isEditing: boolean -): [RefCallback, typeof props | undefined] => { - const [editor] = useLexicalComposerContext(); - - const ref: RefCallback = useCallback( - (rootElement) => { - editor.setRootElement(rootElement); - }, - [editor] - ); - - useEffect(() => { - if (isEditing === true) return; - // Lets unset the root element when we are not editing - editor.setRootElement(null); - }, [editor, isEditing]); - - return [ref, isEditing ? props : undefined]; -}; diff --git a/apps/designer/app/canvas/features/wrapper-component/text-editor/index.ts b/apps/designer/app/canvas/features/wrapper-component/text-editor/index.ts index 03a81bb600..acb2865c26 100644 --- a/apps/designer/app/canvas/features/wrapper-component/text-editor/index.ts +++ b/apps/designer/app/canvas/features/wrapper-component/text-editor/index.ts @@ -1,4 +1 @@ -export { LexicalComposer } from "./lexical"; -export { config } from "./config"; -export { useContentEditable } from "./hooks/use-content-editable"; -export { EditorMemoized } from "./editor"; +export { Editor } from "./editor-lazy"; diff --git a/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx b/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx index 9e50e27eff..426ddce932 100644 --- a/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx +++ b/apps/designer/app/canvas/features/wrapper-component/wrapper-component.tsx @@ -11,7 +11,7 @@ import { import { useBreakpoints, useTextEditingInstanceId } from "~/shared/nano-states"; import { useCss } from "./use-css"; import { useEnsureFocus } from "./use-ensure-focus"; -import { EditorMemoized, useContentEditable } from "./text-editor"; +import { Editor } from "./text-editor"; import noop from "lodash.noop"; import { useSelectedElement } from "~/canvas/shared/nano-states"; @@ -32,18 +32,15 @@ export const WrapperComponentDev = ({ const className = useCss({ instance, css }); const [editingInstanceId] = useTextEditingInstanceId(); const [, setSelectedElement] = useSelectedElement(); - const isEditing = editingInstanceId === instance.id; - const [editableRefCallback, editableProps] = useContentEditable(isEditing); const focusRefCallback = useEnsureFocus(); const refCallback = useCallback( (element) => { - if (isEditing) editableRefCallback(element); focusRefCallback(element); // When entering text editing we unmount the instance element, so we need to update the reference, otherwise we have a detached element referenced and bounding box will be wrong. if (element !== null) setSelectedElement(element); }, - [focusRefCallback, editableRefCallback, setSelectedElement, isEditing] + [focusRefCallback, setSelectedElement] ); const userProps = useUserProps(instance.id); @@ -56,12 +53,13 @@ export const WrapperComponentDev = ({ ...userProps, ...rest, ...readonlyProps, - ...editableProps, // @todo merge className with props - className: className, - // @todo stop using id to free it up to the user - id: instance.id, + className, tabIndex: 0, + // @todo stop using id to free it up to the user + // we should replace id, data-component and data-id with "data-ws"=instance.id and grab the rest always over the id + // for this we need to also make search by id fast + id: instance.id, "data-component": instance.component, "data-id": instance.id, ref: refCallback, @@ -76,20 +74,32 @@ export const WrapperComponentDev = ({ }, }; - if (isEditing) { - return ( - } - onChange={(updates) => { - onChangeChildren({ instanceId: instance.id, updates }); - }} - /> - ); + const instanceElement = ( + {renderWrapperComponentChildren(children)} + ); + + if (editingInstanceId !== instance.id) { + return instanceElement; } return ( - {renderWrapperComponentChildren(children)} + ( + { + props.ref(element); + ref(element); + }} + /> + )} + onChange={(updates) => { + onChangeChildren({ instanceId: instance.id, updates }); + }} + /> ); }; diff --git a/packages/asset-uploader/tsconfig.json b/packages/asset-uploader/tsconfig.json index 654f479a30..6b67ca9bbf 100644 --- a/packages/asset-uploader/tsconfig.json +++ b/packages/asset-uploader/tsconfig.json @@ -4,7 +4,7 @@ "include": ["./src/**/*.ts"], "compilerOptions": { "module": "commonjs", - "target": "ES2019", + "target": "ES2020", "strict": true, "baseUrl": ".", "outDir": "./lib" diff --git a/packages/design-system/tsconfig.json b/packages/design-system/tsconfig.json index e599e806b6..eb77362863 100644 --- a/packages/design-system/tsconfig.json +++ b/packages/design-system/tsconfig.json @@ -3,10 +3,10 @@ "extends": "../../node_modules/@webstudio-is/tsconfig/base.json", "include": ["./src/**/*.ts", "./src/**/*.tsx"], "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2019"], + "lib": ["DOM", "DOM.Iterable", "ES2020"], "module": "commonjs", "jsx": "react-jsx", - "target": "ES2019", + "target": "ES2020", "strict": true, "baseUrl": ".", "paths": { diff --git a/packages/icons/tsconfig.json b/packages/icons/tsconfig.json index e599e806b6..eb77362863 100644 --- a/packages/icons/tsconfig.json +++ b/packages/icons/tsconfig.json @@ -3,10 +3,10 @@ "extends": "../../node_modules/@webstudio-is/tsconfig/base.json", "include": ["./src/**/*.ts", "./src/**/*.tsx"], "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2019"], + "lib": ["DOM", "DOM.Iterable", "ES2020"], "module": "commonjs", "jsx": "react-jsx", - "target": "ES2019", + "target": "ES2020", "strict": true, "baseUrl": ".", "paths": { diff --git a/packages/react-sdk/tsconfig.json b/packages/react-sdk/tsconfig.json index e599e806b6..eb77362863 100644 --- a/packages/react-sdk/tsconfig.json +++ b/packages/react-sdk/tsconfig.json @@ -3,10 +3,10 @@ "extends": "../../node_modules/@webstudio-is/tsconfig/base.json", "include": ["./src/**/*.ts", "./src/**/*.tsx"], "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2019"], + "lib": ["DOM", "DOM.Iterable", "ES2020"], "module": "commonjs", "jsx": "react-jsx", - "target": "ES2019", + "target": "ES2020", "strict": true, "baseUrl": ".", "paths": { diff --git a/packages/tsconfig/remix.json b/packages/tsconfig/remix.json index aa57d759c2..80cd315097 100644 --- a/packages/tsconfig/remix.json +++ b/packages/tsconfig/remix.json @@ -5,8 +5,9 @@ "compilerOptions": { "noEmit": true, "jsx": "react-jsx", - "target": "ES2019", - "lib": ["DOM", "DOM.Iterable", "ES2019"] + "target": "ES2020", + "module": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ES2020"] }, "include": ["app", "remix.env.d.ts"], "exclude": ["node_modules"]