@team-monolith/cds 1.70.3 → 1.71.0

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.
@@ -8,7 +8,7 @@ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
8
8
  import { ContentEditable } from "@lexical/react/LexicalContentEditable";
9
9
  import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
10
10
  import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
11
- import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
11
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
12
12
  import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
13
13
  import { CUSTOM_TRANSFORMERS } from "./plugins/MarkdownTransformers";
14
14
  import { ListPlugin } from "@lexical/react/LexicalListPlugin";
@@ -22,9 +22,9 @@ import ImagesPlugin from "./plugins/ImagesPlugin";
22
22
  import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
23
23
  import hr from "./hr.svg";
24
24
  import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
25
- import LexicalClickableLinkPlugin from "@lexical/react/LexicalClickableLinkPlugin";
25
+ import { ClickableLinkPlugin } from "@lexical/react/LexicalClickableLinkPlugin";
26
26
  import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
27
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
27
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
28
28
  import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
29
29
  import styled from "@emotion/styled";
30
30
  import ProblemInputPlugin from "./plugins/ProblemInputPlugin";
@@ -47,7 +47,7 @@ export default function Plugins(props) {
47
47
  onChange === null || onChange === void 0 ? void 0 : onChange(editorState.toJSON());
48
48
  },
49
49
  // ignore 하지 않으면 Form에서 수정하지 않았는데 Dirty로 처리됨.
50
- ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, { isSheetEnabled: isSheetEnabled, isQuizEnabled: isQuizEnabled }), _jsx(MarkdownShortcutPlugin, { transformers: CUSTOM_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem, isSheetEnabled: isSheetEnabled, isQuizEnabled: isQuizEnabled }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode })] })), !isEditable && _jsx(LexicalClickableLinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(HorizontalRulePlugin, {}), _jsx(ImagesPlugin, {}), _jsx(TablePlugin, {}), _jsx(LinkPlugin, {}), _jsx(ListMaxIndentLevelPlugin, { maxDepth: 5 }), _jsx(ProblemInputPlugin, {}), _jsx(ProblemSelectPlugin, {}), _jsx(LayoutPlugin, {}), _jsx(SheetSelectPlugin, {}), _jsx(SheetInputPlugin, {}), _jsx(SelfEvaluationPlugin, {})] }));
50
+ ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, { isSheetEnabled: isSheetEnabled, isQuizEnabled: isQuizEnabled }), _jsx(MarkdownShortcutPlugin, { transformers: CUSTOM_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem, isSheetEnabled: isSheetEnabled, isQuizEnabled: isQuizEnabled }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem }), _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode })] })), !isEditable && _jsx(ClickableLinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(HorizontalRulePlugin, {}), _jsx(ImagesPlugin, {}), _jsx(TablePlugin, {}), _jsx(LinkPlugin, {}), _jsx(ListMaxIndentLevelPlugin, { maxDepth: 5 }), _jsx(ProblemInputPlugin, {}), _jsx(ProblemSelectPlugin, {}), _jsx(LayoutPlugin, {}), _jsx(SheetSelectPlugin, {}), _jsx(SheetInputPlugin, {}), _jsx(SelfEvaluationPlugin, {})] }));
51
51
  }
52
52
  const ScrollArea = styled.div `
53
53
  min-height: 150px;
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource @emotion/react */
1
2
  /**
2
3
  * Copyright (c) Meta Platforms, Inc. and affiliates.
3
4
  *
@@ -8,11 +9,12 @@
8
9
  /// <reference types="react" />
9
10
  import type { LexicalCommand, NodeKey } from "lexical";
10
11
  export declare const RIGHT_CLICK_IMAGE_COMMAND: LexicalCommand<MouseEvent>;
11
- export default function ImageComponent({ src, altText, nodeKey, width, height, maxWidth, }: {
12
+ export default function ImageComponent({ src, altText, nodeKey, width, height, maxWidth, resizable, }: {
12
13
  altText: string;
13
14
  height: "inherit" | number;
14
15
  maxWidth: number;
15
16
  nodeKey: NodeKey;
17
+ resizable: boolean;
16
18
  src: string;
17
19
  width: "inherit" | number;
18
20
  }): JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
3
3
  import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
4
4
  import { mergeRegister } from "@lexical/utils";
@@ -9,8 +9,10 @@ import SquareButton from "../../../../components/SquareButton";
9
9
  import { Settings3FillIcon } from "../../../../icons";
10
10
  import styled from "@emotion/styled";
11
11
  import { InsertImageDialog } from "../../components/InsertImageDialog";
12
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
12
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
13
13
  import { ImageNotAvailable } from "./ImageNotAvailable";
14
+ import ImageResizer from "./ImageResizer";
15
+ import { css } from "@emotion/react";
14
16
  const imageCache = new Set();
15
17
  export const RIGHT_CLICK_IMAGE_COMMAND = createCommand("RIGHT_CLICK_IMAGE_COMMAND");
16
18
  function useSuspenseImage(src) {
@@ -22,10 +24,15 @@ function useSuspenseImage(src) {
22
24
  imageCache.add(src);
23
25
  resolve(null);
24
26
  };
27
+ img.onerror = () => {
28
+ imageCache.add(src);
29
+ };
25
30
  });
26
31
  }
27
32
  }
28
- function LazyImage({ altText, className, imageRef, src, width, height, maxWidth, }) {
33
+ function LazyImage({ altText, className, imageRef, src, width, height,
34
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
35
+ maxWidth, onError, }) {
29
36
  useSuspenseImage(src);
30
37
  return (_jsx("img", { className: className || undefined, src: src, alt: altText, ref: imageRef, style: {
31
38
  height,
@@ -35,27 +42,33 @@ function LazyImage({ altText, className, imageRef, src, width, height, maxWidth,
35
42
  // fixme: maxWidth를 수정할 수 있게 되면 원복합니다.
36
43
  maxWidth: "100%",
37
44
  width,
38
- }, draggable: "false" }));
45
+ }, onError: onError, draggable: "false" }));
39
46
  }
40
- export default function ImageComponent({ src, altText, nodeKey, width, height, maxWidth, }) {
47
+ export default function ImageComponent({ src, altText, nodeKey, width, height, maxWidth, resizable, }) {
41
48
  const imageRef = useRef(null);
42
49
  const buttonRef = useRef(null);
43
50
  const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
51
+ const [isResizing, setIsResizing] = useState(false);
44
52
  const [editor] = useLexicalComposerContext();
45
53
  const [selection, setSelection] = useState(null);
46
54
  const activeEditorRef = useRef(null);
47
- const onDelete = useCallback((payload) => {
48
- if (isSelected && $isNodeSelection($getSelection())) {
55
+ const [isLoadError, setIsLoadError] = useState(false);
56
+ const $onDelete = useCallback((payload) => {
57
+ const deleteSelection = $getSelection();
58
+ if (isSelected && $isNodeSelection(deleteSelection)) {
49
59
  const event = payload;
50
60
  event.preventDefault();
51
- const node = $getNodeByKey(nodeKey);
52
- if ($isImageNode(node)) {
53
- node.remove();
54
- }
61
+ editor.update(() => {
62
+ deleteSelection.getNodes().forEach((node) => {
63
+ if ($isImageNode(node)) {
64
+ node.remove();
65
+ }
66
+ });
67
+ });
55
68
  }
56
69
  return false;
57
- }, [isSelected, nodeKey]);
58
- const onEnter = useCallback((event) => {
70
+ }, [editor, isSelected]);
71
+ const $onEnter = useCallback((event) => {
59
72
  const latestSelection = $getSelection();
60
73
  const buttonElem = buttonRef.current;
61
74
  if (isSelected &&
@@ -69,7 +82,7 @@ export default function ImageComponent({ src, altText, nodeKey, width, height, m
69
82
  }
70
83
  return false;
71
84
  }, [isSelected]);
72
- const onEscape = useCallback((event) => {
85
+ const $onEscape = useCallback((event) => {
73
86
  if (buttonRef.current === event.target) {
74
87
  $setSelection(null);
75
88
  editor.update(() => {
@@ -85,6 +98,9 @@ export default function ImageComponent({ src, altText, nodeKey, width, height, m
85
98
  }, [editor, setSelected]);
86
99
  const onClick = useCallback((payload) => {
87
100
  const event = payload;
101
+ if (isResizing) {
102
+ return true;
103
+ }
88
104
  if (event.target === imageRef.current) {
89
105
  if (event.shiftKey) {
90
106
  setSelected(!isSelected);
@@ -96,7 +112,7 @@ export default function ImageComponent({ src, altText, nodeKey, width, height, m
96
112
  return true;
97
113
  }
98
114
  return false;
99
- }, [isSelected, setSelected, clearSelection]);
115
+ }, [isResizing, isSelected, setSelected, clearSelection]);
100
116
  const onRightClick = useCallback((event) => {
101
117
  editor.getEditorState().read(() => {
102
118
  const latestSelection = $getSelection();
@@ -126,7 +142,7 @@ export default function ImageComponent({ src, altText, nodeKey, width, height, m
126
142
  return true;
127
143
  }
128
144
  return false;
129
- }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_BACKSPACE_COMMAND, onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ENTER_COMMAND, onEnter, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ESCAPE_COMMAND, onEscape, COMMAND_PRIORITY_LOW));
145
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ENTER_COMMAND, $onEnter, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ESCAPE_COMMAND, $onEscape, COMMAND_PRIORITY_LOW));
130
146
  rootElement === null || rootElement === void 0 ? void 0 : rootElement.addEventListener("contextmenu", onRightClick);
131
147
  return () => {
132
148
  isMounted = false;
@@ -136,27 +152,46 @@ export default function ImageComponent({ src, altText, nodeKey, width, height, m
136
152
  }, [
137
153
  clearSelection,
138
154
  editor,
155
+ isResizing,
139
156
  isSelected,
140
157
  nodeKey,
141
- onDelete,
142
- onEnter,
143
- onEscape,
158
+ $onDelete,
159
+ $onEnter,
160
+ $onEscape,
144
161
  onClick,
145
162
  onRightClick,
146
163
  setSelected,
147
164
  ]);
148
- const draggable = isSelected && $isNodeSelection(selection);
149
- const isFocused = isSelected;
165
+ const onResizeEnd = (nextWidth, nextHeight) => {
166
+ // Delay hiding the resize bars for click case
167
+ setTimeout(() => {
168
+ setIsResizing(false);
169
+ }, 200);
170
+ editor.update(() => {
171
+ const node = $getNodeByKey(nodeKey);
172
+ if ($isImageNode(node)) {
173
+ node.setWidthAndHeight(nextWidth, nextHeight);
174
+ }
175
+ });
176
+ };
177
+ const onResizeStart = () => {
178
+ setIsResizing(true);
179
+ };
180
+ const draggable = isSelected && $isNodeSelection(selection) && !isResizing;
181
+ const isFocused = isSelected || isResizing;
150
182
  const [open, setOpen] = useState(false);
151
183
  const isEditable = useLexicalEditable();
152
184
  if (!isEditable) {
153
- return (_jsx(Suspense, Object.assign({ fallback: _jsx(ImageNotAvailable, {}) }, { children: _jsx("div", Object.assign({ draggable: draggable }, { children: _jsx(LazyImage, { className: isFocused
185
+ return (_jsx(Suspense, Object.assign({ fallback: null }, { children: _jsx("div", Object.assign({ draggable: draggable }, { children: isLoadError ? (_jsx(ImageNotAvailable, {})) : (_jsx(LazyImage, { className: isFocused
154
186
  ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}`
155
- : null, src: src, altText: altText, imageRef: imageRef, width: width, height: height, maxWidth: maxWidth }) })) })));
187
+ : null, src: src, altText: altText, imageRef: imageRef, width: width, height: height, maxWidth: maxWidth, onError: () => setIsLoadError(true) })) })) })));
156
188
  }
157
- return (_jsxs(_Fragment, { children: [_jsxs(EditContainer, { children: [_jsx(Suspense, Object.assign({ fallback: _jsx(ImageNotAvailable, {}) }, { children: _jsx("div", Object.assign({ draggable: draggable }, { children: _jsx(LazyImage, { className: isFocused
158
- ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}`
159
- : null, src: src, altText: altText, imageRef: imageRef, width: width, height: height, maxWidth: maxWidth }) })) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
189
+ return (_jsxs(_Fragment, { children: [_jsxs(EditContainer, { children: [_jsx(Suspense, Object.assign({ fallback: null }, { children: _jsx("div", Object.assign({ draggable: draggable, css: css `
190
+ // ImageResizer를 위한 relative 설정입니다.
191
+ position: relative;
192
+ ` }, { children: isLoadError ? (_jsx(ImageNotAvailable, {})) : (_jsxs(_Fragment, { children: [_jsx(LazyImage, { className: isFocused
193
+ ? `focused ${$isNodeSelection(selection) ? "draggable" : ""}`
194
+ : null, src: src, altText: altText, imageRef: imageRef, width: width, height: height, maxWidth: maxWidth, onError: () => setIsLoadError(true) }), resizable && $isNodeSelection(selection) && isFocused && (_jsx(ImageResizer, { showCaption: false, setShowCaption: () => { }, editor: editor, buttonRef: buttonRef, imageRef: imageRef, maxWidth: maxWidth, onResizeStart: onResizeStart, onResizeEnd: onResizeEnd, captionsEnabled: false }))] })) })) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
160
195
  setOpen(true);
161
196
  }, buttonProps: {
162
197
  "aria-label": "이미지 수정하기",
@@ -2,10 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { $applyNodeReplacement, createEditor, DecoratorNode } from "lexical";
3
3
  import * as React from "react";
4
4
  import { Suspense } from "react";
5
- const ImageComponent = React.lazy(
6
- // @ts-ignore
7
- () => import("./ImageComponent"));
8
- function convertImageElement(domNode) {
5
+ const ImageComponent = React.lazy(() => import("./ImageComponent"));
6
+ function $convertImageElement(domNode) {
9
7
  if (domNode instanceof HTMLImageElement) {
10
8
  const { alt: altText, src, width, height } = domNode;
11
9
  const node = $createImageNode({ altText, height, src, width });
@@ -47,8 +45,9 @@ export class ImageNode extends DecoratorNode {
47
45
  }
48
46
  static importDOM() {
49
47
  return {
48
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
49
  img: (node) => ({
51
- conversion: convertImageElement,
50
+ conversion: $convertImageElement,
52
51
  priority: 0,
53
52
  }),
54
53
  };
@@ -61,7 +60,7 @@ export class ImageNode extends DecoratorNode {
61
60
  this.__width = width || "inherit";
62
61
  this.__height = height || "inherit";
63
62
  this.__showCaption = showCaption || false;
64
- this.__caption = caption || createEditor();
63
+ this.__caption = caption || createEditor({ nodes: [] });
65
64
  this.__captionsEnabled = captionsEnabled || captionsEnabled === undefined;
66
65
  }
67
66
  exportJSON() {
@@ -114,7 +113,7 @@ export class ImageNode extends DecoratorNode {
114
113
  return this.__altText;
115
114
  }
116
115
  decorate() {
117
- return (_jsx(Suspense, Object.assign({ fallback: null }, { children: _jsx(ImageComponent, { src: this.__src, altText: this.__altText, width: this.__width, height: this.__height, maxWidth: this.__maxWidth, nodeKey: this.getKey() }) })));
116
+ return (_jsx(Suspense, Object.assign({ fallback: null }, { children: _jsx(ImageComponent, { src: this.__src, altText: this.__altText, width: this.__width, height: this.__height, maxWidth: this.__maxWidth, nodeKey: this.getKey(), resizable: true }) })));
118
117
  }
119
118
  }
120
119
  export function $createImageNode({ altText, height, maxWidth = 500, captionsEnabled, src, width, showCaption, caption, key, }) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ /// <reference types="react" />
9
+ import type { LexicalEditor } from "lexical";
10
+ export default function ImageResizer({ onResizeStart, onResizeEnd, buttonRef, imageRef, maxWidth, editor, showCaption, setShowCaption, captionsEnabled, }: {
11
+ editor: LexicalEditor;
12
+ buttonRef: {
13
+ current: null | HTMLButtonElement;
14
+ };
15
+ imageRef: {
16
+ current: null | HTMLElement;
17
+ };
18
+ maxWidth?: number;
19
+ onResizeEnd: (width: "inherit" | number, height: "inherit" | number) => void;
20
+ onResizeStart: () => void;
21
+ setShowCaption: (show: boolean) => void;
22
+ showCaption: boolean;
23
+ captionsEnabled: boolean;
24
+ }): JSX.Element;
@@ -0,0 +1,233 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { calculateZoomLevel } from "@lexical/utils";
3
+ import { useRef } from "react";
4
+ import styled from "@emotion/styled";
5
+ function clamp(value, min, max) {
6
+ return Math.min(Math.max(value, min), max);
7
+ }
8
+ const Direction = {
9
+ east: 1 << 0,
10
+ north: 1 << 3,
11
+ south: 1 << 1,
12
+ west: 1 << 2,
13
+ };
14
+ export default function ImageResizer({ onResizeStart, onResizeEnd, buttonRef, imageRef, maxWidth, editor, showCaption, setShowCaption, captionsEnabled, }) {
15
+ const controlWrapperRef = useRef(null);
16
+ const userSelect = useRef({
17
+ priority: "",
18
+ value: "default",
19
+ });
20
+ const positioningRef = useRef({
21
+ currentHeight: 0,
22
+ currentWidth: 0,
23
+ direction: 0,
24
+ isResizing: false,
25
+ ratio: 0,
26
+ startHeight: 0,
27
+ startWidth: 0,
28
+ startX: 0,
29
+ startY: 0,
30
+ });
31
+ const editorRootElement = editor.getRootElement();
32
+ // Find max width, accounting for editor padding.
33
+ const maxWidthContainer = maxWidth
34
+ ? maxWidth
35
+ : editorRootElement !== null
36
+ ? editorRootElement.getBoundingClientRect().width - 20
37
+ : 100;
38
+ const maxHeightContainer = editorRootElement !== null
39
+ ? editorRootElement.getBoundingClientRect().height - 20
40
+ : 100;
41
+ const minWidth = 100;
42
+ const minHeight = 100;
43
+ const setStartCursor = (direction) => {
44
+ const ew = direction === Direction.east || direction === Direction.west;
45
+ const ns = direction === Direction.north || direction === Direction.south;
46
+ const nwse = (direction & Direction.north && direction & Direction.west) ||
47
+ (direction & Direction.south && direction & Direction.east);
48
+ const cursorDir = ew ? "ew" : ns ? "ns" : nwse ? "nwse" : "nesw";
49
+ if (editorRootElement !== null) {
50
+ editorRootElement.style.setProperty("cursor", `${cursorDir}-resize`, "important");
51
+ }
52
+ if (document.body !== null) {
53
+ document.body.style.setProperty("cursor", `${cursorDir}-resize`, "important");
54
+ userSelect.current.value = document.body.style.getPropertyValue("-webkit-user-select");
55
+ userSelect.current.priority = document.body.style.getPropertyPriority("-webkit-user-select");
56
+ document.body.style.setProperty("-webkit-user-select", `none`, "important");
57
+ }
58
+ };
59
+ const setEndCursor = () => {
60
+ if (editorRootElement !== null) {
61
+ editorRootElement.style.setProperty("cursor", "text");
62
+ }
63
+ if (document.body !== null) {
64
+ document.body.style.setProperty("cursor", "default");
65
+ document.body.style.setProperty("-webkit-user-select", userSelect.current.value, userSelect.current.priority);
66
+ }
67
+ };
68
+ const handlePointerDown = (event, direction) => {
69
+ if (!editor.isEditable()) {
70
+ return;
71
+ }
72
+ const image = imageRef.current;
73
+ const controlWrapper = controlWrapperRef.current;
74
+ if (image !== null && controlWrapper !== null) {
75
+ event.preventDefault();
76
+ const { width, height } = image.getBoundingClientRect();
77
+ const zoom = calculateZoomLevel(image);
78
+ const positioning = positioningRef.current;
79
+ positioning.startWidth = width;
80
+ positioning.startHeight = height;
81
+ positioning.ratio = width / height;
82
+ positioning.currentWidth = width;
83
+ positioning.currentHeight = height;
84
+ positioning.startX = event.clientX / zoom;
85
+ positioning.startY = event.clientY / zoom;
86
+ positioning.isResizing = true;
87
+ positioning.direction = direction;
88
+ setStartCursor(direction);
89
+ onResizeStart();
90
+ controlWrapper.classList.add("image-control-wrapper--resizing");
91
+ image.style.height = `${height}px`;
92
+ image.style.width = `${width}px`;
93
+ document.addEventListener("pointermove", handlePointerMove);
94
+ document.addEventListener("pointerup", handlePointerUp);
95
+ }
96
+ };
97
+ const handlePointerMove = (event) => {
98
+ const image = imageRef.current;
99
+ const positioning = positioningRef.current;
100
+ const isHorizontal = positioning.direction & (Direction.east | Direction.west);
101
+ const isVertical = positioning.direction & (Direction.south | Direction.north);
102
+ if (image !== null && positioning.isResizing) {
103
+ const zoom = calculateZoomLevel(image);
104
+ // Corner cursor
105
+ if (isHorizontal && isVertical) {
106
+ let diff = Math.floor(positioning.startX - event.clientX / zoom);
107
+ diff = positioning.direction & Direction.east ? -diff : diff;
108
+ const width = clamp(positioning.startWidth + diff, minWidth, maxWidthContainer);
109
+ const height = width / positioning.ratio;
110
+ image.style.width = `${width}px`;
111
+ image.style.height = `${height}px`;
112
+ positioning.currentHeight = height;
113
+ positioning.currentWidth = width;
114
+ }
115
+ else if (isVertical) {
116
+ let diff = Math.floor(positioning.startY - event.clientY / zoom);
117
+ diff = positioning.direction & Direction.south ? -diff : diff;
118
+ const height = clamp(positioning.startHeight + diff, minHeight, maxHeightContainer);
119
+ image.style.height = `${height}px`;
120
+ positioning.currentHeight = height;
121
+ }
122
+ else {
123
+ let diff = Math.floor(positioning.startX - event.clientX / zoom);
124
+ diff = positioning.direction & Direction.east ? -diff : diff;
125
+ const width = clamp(positioning.startWidth + diff, minWidth, maxWidthContainer);
126
+ image.style.width = `${width}px`;
127
+ positioning.currentWidth = width;
128
+ }
129
+ }
130
+ };
131
+ const handlePointerUp = () => {
132
+ const image = imageRef.current;
133
+ const positioning = positioningRef.current;
134
+ const controlWrapper = controlWrapperRef.current;
135
+ if (image !== null && controlWrapper !== null && positioning.isResizing) {
136
+ const width = positioning.currentWidth;
137
+ const height = positioning.currentHeight;
138
+ positioning.startWidth = 0;
139
+ positioning.startHeight = 0;
140
+ positioning.ratio = 0;
141
+ positioning.startX = 0;
142
+ positioning.startY = 0;
143
+ positioning.currentWidth = 0;
144
+ positioning.currentHeight = 0;
145
+ positioning.isResizing = false;
146
+ controlWrapper.classList.remove("image-control-wrapper--resizing");
147
+ setEndCursor();
148
+ onResizeEnd(width, height);
149
+ document.removeEventListener("pointermove", handlePointerMove);
150
+ document.removeEventListener("pointerup", handlePointerUp);
151
+ }
152
+ };
153
+ return (_jsxs(Container, Object.assign({ ref: controlWrapperRef }, { children: [!showCaption && captionsEnabled && (_jsx("button", Object.assign({ className: "image-caption-button", ref: buttonRef, onClick: () => {
154
+ setShowCaption(!showCaption);
155
+ } }, { children: "Add Caption" }))), _jsx("div", { className: "image-resizer image-resizer-n", onPointerDown: (event) => {
156
+ handlePointerDown(event, Direction.north);
157
+ } }), _jsx("div", { className: "image-resizer image-resizer-ne", onPointerDown: (event) => {
158
+ handlePointerDown(event, Direction.north | Direction.east);
159
+ } }), _jsx("div", { className: "image-resizer image-resizer-e", onPointerDown: (event) => {
160
+ handlePointerDown(event, Direction.east);
161
+ } }), _jsx("div", { className: "image-resizer image-resizer-se", onPointerDown: (event) => {
162
+ handlePointerDown(event, Direction.south | Direction.east);
163
+ } }), _jsx("div", { className: "image-resizer image-resizer-s", onPointerDown: (event) => {
164
+ handlePointerDown(event, Direction.south);
165
+ } }), _jsx("div", { className: "image-resizer image-resizer-sw", onPointerDown: (event) => {
166
+ handlePointerDown(event, Direction.south | Direction.west);
167
+ } }), _jsx("div", { className: "image-resizer image-resizer-w", onPointerDown: (event) => {
168
+ handlePointerDown(event, Direction.west);
169
+ } }), _jsx("div", { className: "image-resizer image-resizer-nw", onPointerDown: (event) => {
170
+ handlePointerDown(event, Direction.north | Direction.west);
171
+ } })] })));
172
+ }
173
+ const Container = styled.div `
174
+ &.image-control-wrapper--resizing {
175
+ touch-action: none;
176
+ }
177
+ .image-resizer {
178
+ display: block;
179
+ width: 7px;
180
+ height: 7px;
181
+ position: absolute;
182
+ background-color: rgb(60, 132, 244);
183
+ border: 1px solid #fff;
184
+ }
185
+
186
+ .image-resizer.image-resizer-n {
187
+ top: -6px;
188
+ left: 48%;
189
+ cursor: n-resize;
190
+ }
191
+
192
+ .image-resizer.image-resizer-ne {
193
+ top: -6px;
194
+ right: -6px;
195
+ cursor: ne-resize;
196
+ }
197
+
198
+ .image-resizer.image-resizer-e {
199
+ bottom: 48%;
200
+ right: -6px;
201
+ cursor: e-resize;
202
+ }
203
+
204
+ .image-resizer.image-resizer-se {
205
+ bottom: -2px;
206
+ right: -6px;
207
+ cursor: nwse-resize;
208
+ }
209
+
210
+ .image-resizer.image-resizer-s {
211
+ bottom: -2px;
212
+ left: 48%;
213
+ cursor: s-resize;
214
+ }
215
+
216
+ .image-resizer.image-resizer-sw {
217
+ bottom: -2px;
218
+ left: -6px;
219
+ cursor: sw-resize;
220
+ }
221
+
222
+ .image-resizer.image-resizer-w {
223
+ bottom: 48%;
224
+ left: -6px;
225
+ cursor: w-resize;
226
+ }
227
+
228
+ .image-resizer.image-resizer-nw {
229
+ top: -6px;
230
+ left: -6px;
231
+ cursor: nw-resize;
232
+ }
233
+ `;
@@ -17,7 +17,7 @@ import SquareButton from "../../../../components/SquareButton";
17
17
  import { Settings3FillIcon } from "../../../../icons";
18
18
  import styled from "@emotion/styled";
19
19
  import SettingForm from "./SettingForm";
20
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
20
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
21
21
  import { $isProblemInputNode } from "./ProblemInputNode";
22
22
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
23
23
  import { $getNodeByKey } from "lexical";
@@ -4,7 +4,7 @@ import { $getNodeByKey } from "lexical";
4
4
  import { $isProblemSelectNode, } from "./ProblemSelectNode";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { useContext, useState } from "react";
7
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
7
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
8
8
  import { SelectBoxEdit, SelectBoxView } from "./SelectBox";
9
9
  import { css } from "@emotion/react";
10
10
  import SquareButton from "../../../../components/SquareButton";
@@ -4,7 +4,7 @@ import { $getNodeByKey } from "lexical";
4
4
  import { $isSelfEvaluationNode, } from "../SelfEvaluationNode";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { useContext, useState } from "react";
7
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
7
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
8
8
  import { LexicalCustomConfigContext } from "../../../LexicalCustomConfigContext";
9
9
  import { Evaluation, SettingForm } from "./Evaluation";
10
10
  import { ICON_DATA } from "../iconData";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
3
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
4
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
4
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
5
5
  import { $getNodeByKey } from "lexical";
6
6
  import { useContext, useState } from "react";
7
7
  import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
@@ -4,7 +4,7 @@ import { $getNodeByKey } from "lexical";
4
4
  import { $isSheetSelectNode } from "../SheetSelectNode";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { useContext, useState } from "react";
7
- import useLexicalEditable from "@lexical/react/useLexicalEditable";
7
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
8
8
  import { LexicalCustomConfigContext } from "../../../LexicalCustomConfigContext";
9
9
  import styled from "@emotion/styled";
10
10
  import { css } from "@emotion/react";
@@ -29,7 +29,7 @@ import styled from "@emotion/styled";
29
29
  import { useContextMenuOptions } from "./useContextMenuOptions";
30
30
  import { ZINDEX } from "../../../../utils/zIndex";
31
31
  import { useTheme } from "@emotion/react";
32
- import { InsertImageDialog, } from "../../components/InsertImageDialog";
32
+ import { InsertImageDialog } from "../../components/InsertImageDialog";
33
33
  import { INSERT_IMAGE_COMMAND } from "../ImagesPlugin";
34
34
  export const COMPONENT_ADDER_MENU_CLASSNAME = "component-adder-menu";
35
35
  function isOnMenu(element) {
@@ -29,7 +29,7 @@ import { INSERT_LAYOUT_COMMAND } from "../LayoutPlugin";
29
29
  import { INSERT_SHEET_SELECT_COMMAND } from "../SheetSelectPlugin";
30
30
  import { INSERT_SHEET_INPUT_COMMAND } from "../SheetInputPlugin";
31
31
  import { INSERT_SELF_EVALUATION_COMMAND } from "../SelfEvaluationPlugin";
32
- import { InsertImageDialog, } from "../../components/InsertImageDialog";
32
+ import { InsertImageDialog } from "../../components/InsertImageDialog";
33
33
  import { INSERT_IMAGE_COMMAND } from "../ImagesPlugin";
34
34
  // import useModal from "../../hooks/useModal";
35
35
  // import catTypingGif from "../../images/cat-typing.gif";
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { sanitizeUrl } from "../../utils/url";
4
4
  import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from "lexical";
5
5
  import { getSelectedNode } from "../../utils/getSelectedNode";
6
- import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
6
+ import { $createLinkNode, $isAutoLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND, } from "@lexical/link";
7
7
  import { setFloatingElemPositionForLinkEditor } from "../../utils/setFloatingElemPositionForLinkEditor";
8
8
  import { $findMatchingParent, mergeRegister } from "@lexical/utils";
9
9
  import styled from "@emotion/styled";
@@ -16,7 +16,7 @@ export function FloatingLinkEditor(props) {
16
16
  const [linkUrl, setLinkUrl] = useState("");
17
17
  const [editedLinkUrl, setEditedLinkUrl] = useState("https://");
18
18
  const [lastSelection, setLastSelection] = useState(null);
19
- const updateLinkEditor = useCallback(() => {
19
+ const $updateLinkEditor = useCallback(() => {
20
20
  var _a, _b;
21
21
  const selection = $getSelection();
22
22
  if ($isRangeSelection(selection)) {
@@ -31,6 +31,9 @@ export function FloatingLinkEditor(props) {
31
31
  else {
32
32
  setLinkUrl("");
33
33
  }
34
+ if (isLinkEditMode) {
35
+ setEditedLinkUrl(linkUrl);
36
+ }
34
37
  }
35
38
  const editorElem = editorRef.current;
36
39
  const nativeSelection = window.getSelection();
@@ -60,12 +63,12 @@ export function FloatingLinkEditor(props) {
60
63
  setLinkUrl("");
61
64
  }
62
65
  return true;
63
- }, [anchorElem, editor, setIsLinkEditMode]);
66
+ }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, linkUrl]);
64
67
  useEffect(() => {
65
68
  const scrollerElem = anchorElem.parentElement;
66
69
  const update = () => {
67
70
  editor.getEditorState().read(() => {
68
- updateLinkEditor();
71
+ $updateLinkEditor();
69
72
  });
70
73
  };
71
74
  window.addEventListener("resize", update);
@@ -78,14 +81,14 @@ export function FloatingLinkEditor(props) {
78
81
  scrollerElem.removeEventListener("scroll", update);
79
82
  }
80
83
  };
81
- }, [anchorElem.parentElement, editor, updateLinkEditor]);
84
+ }, [anchorElem.parentElement, editor, $updateLinkEditor]);
82
85
  useEffect(() => {
83
86
  return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
84
87
  editorState.read(() => {
85
- updateLinkEditor();
88
+ $updateLinkEditor();
86
89
  });
87
90
  }), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
88
- updateLinkEditor();
91
+ $updateLinkEditor();
89
92
  return true;
90
93
  }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
91
94
  if (isLink) {
@@ -94,12 +97,12 @@ export function FloatingLinkEditor(props) {
94
97
  }
95
98
  return false;
96
99
  }, COMMAND_PRIORITY_HIGH));
97
- }, [editor, updateLinkEditor, setIsLink, isLink]);
100
+ }, [editor, $updateLinkEditor, setIsLink, isLink]);
98
101
  useEffect(() => {
99
102
  editor.getEditorState().read(() => {
100
- updateLinkEditor();
103
+ $updateLinkEditor();
101
104
  });
102
- }, [editor, updateLinkEditor]);
105
+ }, [editor, $updateLinkEditor]);
103
106
  useEffect(() => {
104
107
  if (isLinkEditMode && inputRef.current) {
105
108
  inputRef.current.focus();
@@ -119,6 +122,20 @@ export function FloatingLinkEditor(props) {
119
122
  if (lastSelection !== null) {
120
123
  if (linkUrl !== "") {
121
124
  editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));
125
+ editor.update(() => {
126
+ const selection = $getSelection();
127
+ if ($isRangeSelection(selection)) {
128
+ const parent = getSelectedNode(selection).getParent();
129
+ if ($isAutoLinkNode(parent)) {
130
+ const linkNode = $createLinkNode(parent.getURL(), {
131
+ rel: parent.__rel,
132
+ target: parent.__target,
133
+ title: parent.__title,
134
+ });
135
+ parent.replace(linkNode, true);
136
+ }
137
+ }
138
+ });
122
139
  }
123
140
  setEditedLinkUrl("https://");
124
141
  setIsLinkEditMode(false);
@@ -9,7 +9,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
9
9
  import { $isAutoLinkNode, $isLinkNode } from "@lexical/link";
10
10
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
11
11
  import { $findMatchingParent, mergeRegister } from "@lexical/utils";
12
- import { $getSelection, $isRangeSelection, CLICK_COMMAND, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, } from "lexical";
12
+ import { $getSelection, $isLineBreakNode, $isRangeSelection, CLICK_COMMAND, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, } from "lexical";
13
13
  import { useEffect, useState } from "react";
14
14
  import { createPortal } from "react-dom";
15
15
  import { getSelectedNode } from "../../utils/getSelectedNode";
@@ -18,14 +18,30 @@ function useFloatingLinkEditorToolbar(editor, anchorElem, isLinkEditMode, setIsL
18
18
  const [activeEditor, setActiveEditor] = useState(editor);
19
19
  const [isLink, setIsLink] = useState(false);
20
20
  useEffect(() => {
21
- function updateToolbar() {
21
+ function $updateToolbar() {
22
22
  const selection = $getSelection();
23
23
  if ($isRangeSelection(selection)) {
24
- const node = getSelectedNode(selection);
25
- const linkParent = $findMatchingParent(node, $isLinkNode);
26
- const autoLinkParent = $findMatchingParent(node, $isAutoLinkNode);
27
- // We don't want this menu to open for auto links.
28
- if (linkParent !== null && autoLinkParent === null) {
24
+ const focusNode = getSelectedNode(selection);
25
+ const focusLinkNode = $findMatchingParent(focusNode, $isLinkNode);
26
+ const focusAutoLinkNode = $findMatchingParent(focusNode, $isAutoLinkNode);
27
+ if (!(focusLinkNode || focusAutoLinkNode)) {
28
+ setIsLink(false);
29
+ return;
30
+ }
31
+ const badNode = selection
32
+ .getNodes()
33
+ .filter((node) => !$isLineBreakNode(node))
34
+ .find((node) => {
35
+ const linkNode = $findMatchingParent(node, $isLinkNode);
36
+ const autoLinkNode = $findMatchingParent(node, $isAutoLinkNode);
37
+ return ((focusLinkNode && !focusLinkNode.is(linkNode)) ||
38
+ (linkNode && !linkNode.is(focusLinkNode)) ||
39
+ (focusAutoLinkNode && !focusAutoLinkNode.is(autoLinkNode)) ||
40
+ (autoLinkNode &&
41
+ (!autoLinkNode.is(focusAutoLinkNode) ||
42
+ autoLinkNode.getIsUnlinked())));
43
+ });
44
+ if (!badNode) {
29
45
  setIsLink(true);
30
46
  }
31
47
  else {
@@ -35,10 +51,10 @@ function useFloatingLinkEditorToolbar(editor, anchorElem, isLinkEditMode, setIsL
35
51
  }
36
52
  return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
37
53
  editorState.read(() => {
38
- updateToolbar();
54
+ $updateToolbar();
39
55
  });
40
56
  }), editor.registerCommand(SELECTION_CHANGE_COMMAND, (_payload, newEditor) => {
41
- updateToolbar();
57
+ $updateToolbar();
42
58
  setActiveEditor(newEditor);
43
59
  return false;
44
60
  }, COMMAND_PRIORITY_CRITICAL), editor.registerCommand(CLICK_COMMAND, (payload) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.70.3",
3
+ "version": "1.71.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -8,14 +8,14 @@
8
8
  "@emotion/css": "^11.11.2",
9
9
  "@emotion/react": "^11.8.2",
10
10
  "@emotion/styled": "^11.8.1",
11
- "@lexical/react": "^0.12.4",
11
+ "@lexical/react": "^0.17.1",
12
12
  "@mui/material": "^5.13.6",
13
13
  "@types/node": "^16.11.26",
14
14
  "@types/react": "^18.2.28",
15
15
  "@types/react-dom": "^18.2.13",
16
16
  "framer-motion": "^11.3.19",
17
17
  "hex-to-css-filter": "^5.4.0",
18
- "lexical": "^0.12.4",
18
+ "lexical": "^0.17.1",
19
19
  "lodash": "^4.17.21",
20
20
  "react": "^18.2.0",
21
21
  "react-dom": "^18.2.0",