@team-monolith/cds 1.3.2 → 1.4.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.
Files changed (79) hide show
  1. package/README.md +2 -2
  2. package/dist/CodleDesignSystemProvider.d.ts +3 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/patterns/LexicalEditor/LexicalEditor.d.ts +14 -0
  6. package/dist/patterns/LexicalEditor/LexicalEditor.js +39 -0
  7. package/dist/patterns/LexicalEditor/Plugins.d.ts +10 -0
  8. package/dist/patterns/LexicalEditor/Plugins.js +81 -0
  9. package/dist/patterns/LexicalEditor/hr.svg +3 -0
  10. package/dist/patterns/LexicalEditor/index.d.ts +2 -0
  11. package/dist/patterns/LexicalEditor/index.js +2 -0
  12. package/dist/patterns/LexicalEditor/nodes/ImageComponent.d.ts +18 -0
  13. package/dist/patterns/LexicalEditor/nodes/ImageComponent.js +143 -0
  14. package/dist/patterns/LexicalEditor/nodes/ImageNode.d.ts +56 -0
  15. package/dist/patterns/LexicalEditor/nodes/ImageNode.js +117 -0
  16. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdder.d.ts +10 -0
  17. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdder.js +68 -0
  18. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.d.ts +11 -0
  19. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +214 -0
  20. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.d.ts +6 -0
  21. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.js +90 -0
  22. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/index.d.ts +1 -0
  23. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/index.js +1 -0
  24. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/menu.svg +8 -0
  25. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/plus.svg +3 -0
  26. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useDraggableBlockMenu.d.ts +16 -0
  27. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useDraggableBlockMenu.js +234 -0
  28. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useFloatingMenu.d.ts +20 -0
  29. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useFloatingMenu.js +158 -0
  30. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuItem.d.ts +10 -0
  31. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuItem.js +40 -0
  32. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.d.ts +9 -0
  33. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js +46 -0
  34. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +26 -0
  35. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +174 -0
  36. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/index.d.ts +3 -0
  37. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/index.js +3 -0
  38. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.d.ts +11 -0
  39. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.js +201 -0
  40. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/index.d.ts +6 -0
  41. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/index.js +62 -0
  42. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/FloatingTextFormatPopup.d.ts +13 -0
  43. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/FloatingTextFormatPopup.js +60 -0
  44. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/index.d.ts +4 -0
  45. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/index.js +190 -0
  46. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageDialog.d.ts +7 -0
  47. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageDialog.js +39 -0
  48. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUploadedDialogBody.d.ts +5 -0
  49. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUploadedDialogBody.js +49 -0
  50. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUriDialogBody.d.ts +5 -0
  51. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUriDialogBody.js +24 -0
  52. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/index.d.ts +14 -0
  53. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/index.js +151 -0
  54. package/dist/patterns/LexicalEditor/plugins/ListMaxIndentLevelPlugin/index.d.ts +12 -0
  55. package/dist/patterns/LexicalEditor/plugins/ListMaxIndentLevelPlugin/index.js +49 -0
  56. package/dist/patterns/LexicalEditor/plugins/MarkdownTransformers/index.d.ts +12 -0
  57. package/dist/patterns/LexicalEditor/plugins/MarkdownTransformers/index.js +184 -0
  58. package/dist/patterns/LexicalEditor/theme.d.ts +24 -0
  59. package/dist/patterns/LexicalEditor/theme.js +178 -0
  60. package/dist/patterns/LexicalEditor/utils/getDOMRangeRect.d.ts +8 -0
  61. package/dist/patterns/LexicalEditor/utils/getDOMRangeRect.js +22 -0
  62. package/dist/patterns/LexicalEditor/utils/getSelectedNode.d.ts +2 -0
  63. package/dist/patterns/LexicalEditor/utils/getSelectedNode.js +24 -0
  64. package/dist/patterns/LexicalEditor/utils/guard.d.ts +8 -0
  65. package/dist/patterns/LexicalEditor/utils/guard.js +10 -0
  66. package/dist/patterns/LexicalEditor/utils/point.d.ts +21 -0
  67. package/dist/patterns/LexicalEditor/utils/point.js +41 -0
  68. package/dist/patterns/LexicalEditor/utils/rect.d.ts +45 -0
  69. package/dist/patterns/LexicalEditor/utils/rect.js +99 -0
  70. package/dist/patterns/LexicalEditor/utils/setFloatingElemPosition.d.ts +1 -0
  71. package/dist/patterns/LexicalEditor/utils/setFloatingElemPosition.js +36 -0
  72. package/dist/patterns/LexicalEditor/utils/setFloatingElemPositionForLinkEditor.d.ts +1 -0
  73. package/dist/patterns/LexicalEditor/utils/setFloatingElemPositionForLinkEditor.js +32 -0
  74. package/dist/patterns/LexicalEditor/utils/url.d.ts +9 -0
  75. package/dist/patterns/LexicalEditor/utils/url.js +34 -0
  76. package/dist/patterns/SegmentedControl/SegmentedControlButton.js +19 -6
  77. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.d.ts +1 -1
  78. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.js +35 -17
  79. package/package.json +6 -3
@@ -0,0 +1,201 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { sanitizeUrl } from "../../utils/url";
4
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from "lexical";
5
+ import { getSelectedNode } from "../../utils/getSelectedNode";
6
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
7
+ import { setFloatingElemPositionForLinkEditor } from "../../utils/setFloatingElemPositionForLinkEditor";
8
+ import { $findMatchingParent, mergeRegister } from "@lexical/utils";
9
+ import styled from "@emotion/styled";
10
+ import { CloseFillIcon, CheckLineIcon, EditLineIcon, DeleteBinLineIcon } from "../../../../icons";
11
+ import Input from "../../../../components/Input";
12
+ export function FloatingLinkEditor(props) {
13
+ const { editor, isLink, setIsLink, anchorElem, isLinkEditMode, setIsLinkEditMode, } = props;
14
+ const editorRef = useRef(null);
15
+ const inputRef = useRef(null);
16
+ const [linkUrl, setLinkUrl] = useState("");
17
+ const [editedLinkUrl, setEditedLinkUrl] = useState("https://");
18
+ const [lastSelection, setLastSelection] = useState(null);
19
+ const updateLinkEditor = useCallback(() => {
20
+ var _a, _b;
21
+ const selection = $getSelection();
22
+ if ($isRangeSelection(selection)) {
23
+ const node = getSelectedNode(selection);
24
+ const linkParent = $findMatchingParent(node, $isLinkNode);
25
+ if (linkParent) {
26
+ setLinkUrl(linkParent.getURL());
27
+ }
28
+ else if ($isLinkNode(node)) {
29
+ setLinkUrl(node.getURL());
30
+ }
31
+ else {
32
+ setLinkUrl("");
33
+ }
34
+ }
35
+ const editorElem = editorRef.current;
36
+ const nativeSelection = window.getSelection();
37
+ const activeElement = document.activeElement;
38
+ if (editorElem === null) {
39
+ return;
40
+ }
41
+ const rootElement = editor.getRootElement();
42
+ if (selection !== null &&
43
+ nativeSelection !== null &&
44
+ rootElement !== null &&
45
+ rootElement.contains(nativeSelection.anchorNode) &&
46
+ editor.isEditable()) {
47
+ const domRect = (_b = (_a = nativeSelection.focusNode) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
48
+ if (domRect) {
49
+ domRect.y += 40;
50
+ setFloatingElemPositionForLinkEditor(domRect, editorElem, anchorElem);
51
+ }
52
+ setLastSelection(selection);
53
+ }
54
+ else if (!activeElement || activeElement.className !== "link-input") {
55
+ if (rootElement !== null) {
56
+ setFloatingElemPositionForLinkEditor(null, editorElem, anchorElem);
57
+ }
58
+ setLastSelection(null);
59
+ setIsLinkEditMode(false);
60
+ setLinkUrl("");
61
+ }
62
+ return true;
63
+ }, [anchorElem, editor, setIsLinkEditMode]);
64
+ useEffect(() => {
65
+ const scrollerElem = anchorElem.parentElement;
66
+ const update = () => {
67
+ editor.getEditorState().read(() => {
68
+ updateLinkEditor();
69
+ });
70
+ };
71
+ window.addEventListener("resize", update);
72
+ if (scrollerElem) {
73
+ scrollerElem.addEventListener("scroll", update);
74
+ }
75
+ return () => {
76
+ window.removeEventListener("resize", update);
77
+ if (scrollerElem) {
78
+ scrollerElem.removeEventListener("scroll", update);
79
+ }
80
+ };
81
+ }, [anchorElem.parentElement, editor, updateLinkEditor]);
82
+ useEffect(() => {
83
+ return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
84
+ editorState.read(() => {
85
+ updateLinkEditor();
86
+ });
87
+ }), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
88
+ updateLinkEditor();
89
+ return true;
90
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
91
+ if (isLink) {
92
+ setIsLink(false);
93
+ return true;
94
+ }
95
+ return false;
96
+ }, COMMAND_PRIORITY_HIGH));
97
+ }, [editor, updateLinkEditor, setIsLink, isLink]);
98
+ useEffect(() => {
99
+ editor.getEditorState().read(() => {
100
+ updateLinkEditor();
101
+ });
102
+ }, [editor, updateLinkEditor]);
103
+ useEffect(() => {
104
+ if (isLinkEditMode && inputRef.current) {
105
+ inputRef.current.focus();
106
+ }
107
+ }, [isLinkEditMode, isLink]);
108
+ const monitorInputInteraction = (event) => {
109
+ if (event.key === "Enter") {
110
+ event.preventDefault();
111
+ handleLinkSubmission();
112
+ }
113
+ else if (event.key === "Escape") {
114
+ event.preventDefault();
115
+ setIsLinkEditMode(false);
116
+ }
117
+ };
118
+ const handleLinkSubmission = () => {
119
+ if (lastSelection !== null) {
120
+ if (linkUrl !== "") {
121
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));
122
+ }
123
+ setEditedLinkUrl("https://");
124
+ setIsLinkEditMode(false);
125
+ }
126
+ };
127
+ return (_jsx(Container, Object.assign({ ref: editorRef }, { children: !isLink ? null : isLinkEditMode ? (_jsxs(_Fragment, { children: [_jsx(StyledInput, { className: "link-input", fullWidth: true, color: "default", size: "small", ref: inputRef, value: editedLinkUrl, onChange: (event) => {
128
+ setEditedLinkUrl(event.target.value);
129
+ }, inputProps: {
130
+ onKeyDown: (event) => {
131
+ monitorInputInteraction(event);
132
+ },
133
+ } }), _jsxs(Buttons, { children: [_jsx(Button, Object.assign({ tabIndex: 0, onClick: (event) => {
134
+ event.preventDefault();
135
+ setIsLinkEditMode(false);
136
+ } }, { children: _jsx(CloseFillIcon, {}) })), _jsx(Button, Object.assign({ tabIndex: 0, onClick: (event) => {
137
+ event.preventDefault();
138
+ handleLinkSubmission();
139
+ } }, { children: _jsx(CheckLineIcon, {}) }))] })] })) : (_jsxs(_Fragment, { children: [_jsx(Link, Object.assign({ href: sanitizeUrl(linkUrl), target: "_blank", rel: "noopener noreferrer" }, { children: linkUrl })), _jsxs(Buttons, { children: [_jsx(Button, Object.assign({ tabIndex: 0, onClick: (event) => {
140
+ event.preventDefault();
141
+ setEditedLinkUrl(linkUrl);
142
+ setIsLinkEditMode(true);
143
+ } }, { children: _jsx(EditLineIcon, {}) })), _jsx(Button, Object.assign({ tabIndex: 0, onClick: (event) => {
144
+ event.preventDefault();
145
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
146
+ } }, { children: _jsx(DeleteBinLineIcon, {}) }))] })] })) })));
147
+ }
148
+ const Container = styled.div `
149
+ position: absolute;
150
+ top: 0;
151
+ left: 0;
152
+ z-index: 10;
153
+ will-change: transform;
154
+
155
+ display: flex;
156
+ padding: 0px 4px;
157
+ align-items: center;
158
+ gap: 4px;
159
+
160
+ border-radius: 8px;
161
+ // 메뉴가 활성화되지 않아도 클릭한 영역을 따라 다니도록 개발되었습니다.
162
+ // border가 있으면 활성화되지 않은 상태에서도 노출되므로 border를 제거합니다.
163
+ /* border: 1px solid #e8e8eb; */
164
+ background: ${({ theme }) => theme.color.background.neutralBase};
165
+ box-shadow: 0px 3px 15px -3px rgba(13, 20, 33, 0.13);
166
+
167
+ width: 400px;
168
+ `;
169
+ const Buttons = styled.div `
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 4px;
173
+ `;
174
+ const Button = styled.button `
175
+ // reset
176
+ border: none;
177
+ outline: none;
178
+ background: none;
179
+ cursor: pointer;
180
+
181
+ padding: 8px 4px;
182
+
183
+ svg {
184
+ width: 18px;
185
+ height: 18px;
186
+ }
187
+ `;
188
+ const Link = styled.a `
189
+ width: 100%;
190
+ padding: 0px 8px;
191
+
192
+ /* Default/Paragraph/16px-Rg */
193
+ font-family: ${({ theme }) => theme.fontFamily.ui};
194
+ font-size: 16px;
195
+ font-style: normal;
196
+ font-weight: 400;
197
+ line-height: 24px; /* 150% */
198
+ `;
199
+ const StyledInput = styled(Input) `
200
+ margin: 4px 0px;
201
+ `;
@@ -0,0 +1,6 @@
1
+ import { Dispatch } from "react";
2
+ export default function FloatingLinkEditorPlugin({ anchorElem, isLinkEditMode, setIsLinkEditMode, }: {
3
+ anchorElem?: HTMLElement;
4
+ isLinkEditMode: boolean;
5
+ setIsLinkEditMode: Dispatch<boolean>;
6
+ }): JSX.Element | null;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ */
9
+ import { $isAutoLinkNode, $isLinkNode, } from "@lexical/link";
10
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
11
+ import { $findMatchingParent, mergeRegister } from "@lexical/utils";
12
+ import { $getSelection, $isRangeSelection, CLICK_COMMAND, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, } from "lexical";
13
+ import { useEffect, useState } from "react";
14
+ import { createPortal } from "react-dom";
15
+ import { getSelectedNode } from "../../utils/getSelectedNode";
16
+ import { FloatingLinkEditor } from "./FloatingLinkEditor";
17
+ function useFloatingLinkEditorToolbar(editor, anchorElem, isLinkEditMode, setIsLinkEditMode) {
18
+ const [activeEditor, setActiveEditor] = useState(editor);
19
+ const [isLink, setIsLink] = useState(false);
20
+ useEffect(() => {
21
+ function updateToolbar() {
22
+ const selection = $getSelection();
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) {
29
+ setIsLink(true);
30
+ }
31
+ else {
32
+ setIsLink(false);
33
+ }
34
+ }
35
+ }
36
+ return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
37
+ editorState.read(() => {
38
+ updateToolbar();
39
+ });
40
+ }), editor.registerCommand(SELECTION_CHANGE_COMMAND, (_payload, newEditor) => {
41
+ updateToolbar();
42
+ setActiveEditor(newEditor);
43
+ return false;
44
+ }, COMMAND_PRIORITY_CRITICAL), editor.registerCommand(CLICK_COMMAND, (payload) => {
45
+ const selection = $getSelection();
46
+ if ($isRangeSelection(selection)) {
47
+ const node = getSelectedNode(selection);
48
+ const linkNode = $findMatchingParent(node, $isLinkNode);
49
+ if ($isLinkNode(linkNode) && (payload.metaKey || payload.ctrlKey)) {
50
+ window.open(linkNode.getURL(), "_blank");
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }, COMMAND_PRIORITY_LOW));
56
+ }, [editor]);
57
+ return createPortal(_jsx(FloatingLinkEditor, { editor: activeEditor, isLink: isLink, anchorElem: anchorElem, setIsLink: setIsLink, isLinkEditMode: isLinkEditMode, setIsLinkEditMode: setIsLinkEditMode }), anchorElem);
58
+ }
59
+ export default function FloatingLinkEditorPlugin({ anchorElem = document.body, isLinkEditMode, setIsLinkEditMode, }) {
60
+ const [editor] = useLexicalComposerContext();
61
+ return useFloatingLinkEditorToolbar(editor, anchorElem, isLinkEditMode, setIsLinkEditMode);
62
+ }
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ export interface FloatingTestFormatPopupProps {
3
+ className?: string;
4
+ isBold?: boolean;
5
+ isItalic?: boolean;
6
+ isUnderline?: boolean;
7
+ isStrikethrough?: boolean;
8
+ isCode?: boolean;
9
+ isLink?: boolean;
10
+ insertLink?: () => void;
11
+ }
12
+ declare const _default: import("react").ForwardRefExoticComponent<FloatingTestFormatPopupProps & import("react").RefAttributes<HTMLDivElement>>;
13
+ export default _default;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
3
+ import { FORMAT_TEXT_COMMAND } from "lexical";
4
+ import { forwardRef } from "react";
5
+ import styled from "@emotion/styled";
6
+ import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeFillIcon, LinkIcon, } from "../../../../icons";
7
+ function FloatingTextFormatPopup(props, ref) {
8
+ const { className, isBold, isItalic, isUnderline, isStrikethrough, isCode, isLink, insertLink, } = props;
9
+ const [editor] = useLexicalComposerContext();
10
+ return (_jsx(Container, Object.assign({ ref: ref, className: className }, { children: editor.isEditable() && (_jsxs(_Fragment, { children: [_jsx(IconButton, Object.assign({ type: "button", onClick: () => {
11
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
12
+ }, active: Boolean(isBold), "aria-label": "Format text as bold" }, { children: _jsx(BoldIcon, {}) })), _jsx(IconButton, Object.assign({ type: "button", onClick: () => {
13
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
14
+ }, active: Boolean(isItalic), "aria-label": "Format text as italics" }, { children: _jsx(ItalicIcon, {}) })), _jsx(IconButton, Object.assign({ type: "button", onClick: () => {
15
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline");
16
+ }, active: Boolean(isUnderline), "aria-label": "Format text to underlined" }, { children: _jsx(UnderlineIcon, {}) })), _jsx(IconButton, Object.assign({ type: "button", onClick: () => {
17
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough");
18
+ }, active: Boolean(isStrikethrough), "aria-label": "Format text with a strikethrough" }, { children: _jsx(StrikethroughIcon, {}) })), _jsx(IconButton, Object.assign({ type: "button", onClick: () => {
19
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code");
20
+ }, active: Boolean(isCode), "aria-label": "Insert code block" }, { children: _jsx(CodeFillIcon, {}) })), _jsx(IconButton, Object.assign({ type: "button", onClick: insertLink, active: Boolean(isLink), "aria-label": "Insert link" }, { children: _jsx(LinkIcon, {}) }))] })) })));
21
+ }
22
+ export default forwardRef(FloatingTextFormatPopup);
23
+ const Container = styled.div `
24
+ position: absolute;
25
+ top: 0;
26
+ left: 0;
27
+ z-index: 10;
28
+ will-change: transform;
29
+
30
+ display: inline-flex;
31
+ padding: 0px 4px;
32
+ align-items: flex-start;
33
+ gap: 4px;
34
+
35
+ border-radius: 8px;
36
+ border: 1px solid #e8e8eb;
37
+ background: ${({ theme }) => theme.color.background.neutralBase};
38
+ box-shadow: 0px 3px 15px -3px rgba(13, 20, 33, 0.13);
39
+ `;
40
+ const IconButton = styled.button `
41
+ border: none;
42
+ outline: none;
43
+
44
+ display: flex;
45
+ padding: 8px 4px;
46
+ align-items: center;
47
+ gap: 4px;
48
+
49
+ background: #fff;
50
+
51
+ svg {
52
+ width: 18px;
53
+ height: 18px;
54
+ ${({ theme, active }) => active && `color: ${theme.color.foreground.primary};`}
55
+ }
56
+
57
+ &:hover {
58
+ background: ${({ theme }) => theme.color.background.neutralAlt};
59
+ }
60
+ `;
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ export default function FloatingTextFormatToolbarPlugin({ anchorElem, }: {
3
+ anchorElem?: HTMLElement;
4
+ }): JSX.Element | null;
@@ -0,0 +1,190 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ */
9
+ import { $isCodeHighlightNode } from "@lexical/code";
10
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
11
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
12
+ import { mergeRegister } from "@lexical/utils";
13
+ import { $getSelection, $isParagraphNode, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, } from "lexical";
14
+ import { useCallback, useEffect, useRef, useState } from "react";
15
+ import { createPortal } from "react-dom";
16
+ import { getDOMRangeRect } from "../../utils/getDOMRangeRect";
17
+ import { getSelectedNode } from "../../utils/getSelectedNode";
18
+ import { setFloatingElemPosition } from "../../utils/setFloatingElemPosition";
19
+ import FloatingTextFormatPopup from "./FloatingTextFormatPopup";
20
+ function TextFormatFloatingToolbar({ editor, anchorElem, isLink, isBold, isItalic, isUnderline, isCode, isStrikethrough, }) {
21
+ const popupCharStylesEditorRef = useRef(null);
22
+ const insertLink = useCallback(() => {
23
+ if (!isLink) {
24
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, "https://codle.io");
25
+ }
26
+ else {
27
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
28
+ }
29
+ }, [editor, isLink]);
30
+ function mouseMoveListener(e) {
31
+ if ((popupCharStylesEditorRef === null || popupCharStylesEditorRef === void 0 ? void 0 : popupCharStylesEditorRef.current) &&
32
+ (e.buttons === 1 || e.buttons === 3)) {
33
+ if (popupCharStylesEditorRef.current.style.pointerEvents !== "none") {
34
+ const x = e.clientX;
35
+ const y = e.clientY;
36
+ const elementUnderMouse = document.elementFromPoint(x, y);
37
+ if (!popupCharStylesEditorRef.current.contains(elementUnderMouse)) {
38
+ // Mouse is not over the target element => not a normal click, but probably a drag
39
+ popupCharStylesEditorRef.current.style.pointerEvents = "none";
40
+ }
41
+ }
42
+ }
43
+ }
44
+ function mouseUpListener(e) {
45
+ if (popupCharStylesEditorRef === null || popupCharStylesEditorRef === void 0 ? void 0 : popupCharStylesEditorRef.current) {
46
+ if (popupCharStylesEditorRef.current.style.pointerEvents !== "auto") {
47
+ popupCharStylesEditorRef.current.style.pointerEvents = "auto";
48
+ }
49
+ }
50
+ }
51
+ useEffect(() => {
52
+ if (popupCharStylesEditorRef === null || popupCharStylesEditorRef === void 0 ? void 0 : popupCharStylesEditorRef.current) {
53
+ document.addEventListener("mousemove", mouseMoveListener);
54
+ document.addEventListener("mouseup", mouseUpListener);
55
+ return () => {
56
+ document.removeEventListener("mousemove", mouseMoveListener);
57
+ document.removeEventListener("mouseup", mouseUpListener);
58
+ };
59
+ }
60
+ }, [popupCharStylesEditorRef]);
61
+ const updateTextFormatFloatingToolbar = useCallback(() => {
62
+ const selection = $getSelection();
63
+ const popupCharStylesEditorElem = popupCharStylesEditorRef.current;
64
+ const nativeSelection = window.getSelection();
65
+ if (popupCharStylesEditorElem === null) {
66
+ return;
67
+ }
68
+ const rootElement = editor.getRootElement();
69
+ if (selection !== null &&
70
+ nativeSelection !== null &&
71
+ !nativeSelection.isCollapsed &&
72
+ rootElement !== null &&
73
+ rootElement.contains(nativeSelection.anchorNode)) {
74
+ const rangeRect = getDOMRangeRect(nativeSelection, rootElement);
75
+ setFloatingElemPosition(rangeRect, popupCharStylesEditorElem, anchorElem, isLink);
76
+ }
77
+ }, [editor, anchorElem, isLink]);
78
+ useEffect(() => {
79
+ const scrollerElem = anchorElem.parentElement;
80
+ const update = () => {
81
+ editor.getEditorState().read(() => {
82
+ updateTextFormatFloatingToolbar();
83
+ });
84
+ };
85
+ window.addEventListener("resize", update);
86
+ if (scrollerElem) {
87
+ scrollerElem.addEventListener("scroll", update);
88
+ }
89
+ return () => {
90
+ window.removeEventListener("resize", update);
91
+ if (scrollerElem) {
92
+ scrollerElem.removeEventListener("scroll", update);
93
+ }
94
+ };
95
+ }, [editor, updateTextFormatFloatingToolbar, anchorElem]);
96
+ useEffect(() => {
97
+ editor.getEditorState().read(() => {
98
+ updateTextFormatFloatingToolbar();
99
+ });
100
+ return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
101
+ editorState.read(() => {
102
+ updateTextFormatFloatingToolbar();
103
+ });
104
+ }), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
105
+ updateTextFormatFloatingToolbar();
106
+ return false;
107
+ }, COMMAND_PRIORITY_LOW));
108
+ }, [editor, updateTextFormatFloatingToolbar]);
109
+ return (_jsx(FloatingTextFormatPopup, { ref: popupCharStylesEditorRef, isBold: isBold, isItalic: isItalic, isUnderline: isUnderline, isStrikethrough: isStrikethrough, isCode: isCode, isLink: isLink, insertLink: insertLink }));
110
+ }
111
+ function useFloatingTextFormatToolbar(editor, anchorElem) {
112
+ const [isText, setIsText] = useState(false);
113
+ const [isLink, setIsLink] = useState(false);
114
+ const [isBold, setIsBold] = useState(false);
115
+ const [isItalic, setIsItalic] = useState(false);
116
+ const [isUnderline, setIsUnderline] = useState(false);
117
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
118
+ const [isCode, setIsCode] = useState(false);
119
+ const updatePopup = useCallback(() => {
120
+ editor.getEditorState().read(() => {
121
+ // Should not to pop up the floating toolbar when using IME input
122
+ if (editor.isComposing()) {
123
+ return;
124
+ }
125
+ const selection = $getSelection();
126
+ const nativeSelection = window.getSelection();
127
+ const rootElement = editor.getRootElement();
128
+ if (nativeSelection !== null &&
129
+ (!$isRangeSelection(selection) ||
130
+ rootElement === null ||
131
+ !rootElement.contains(nativeSelection.anchorNode))) {
132
+ setIsText(false);
133
+ return;
134
+ }
135
+ if (!$isRangeSelection(selection)) {
136
+ return;
137
+ }
138
+ const node = getSelectedNode(selection);
139
+ // Update text format
140
+ setIsBold(selection.hasFormat("bold"));
141
+ setIsItalic(selection.hasFormat("italic"));
142
+ setIsUnderline(selection.hasFormat("underline"));
143
+ setIsStrikethrough(selection.hasFormat("strikethrough"));
144
+ setIsCode(selection.hasFormat("code"));
145
+ // Update links
146
+ const parent = node.getParent();
147
+ if ($isLinkNode(parent) || $isLinkNode(node)) {
148
+ setIsLink(true);
149
+ }
150
+ else {
151
+ setIsLink(false);
152
+ }
153
+ if (!$isCodeHighlightNode(selection.anchor.getNode()) &&
154
+ selection.getTextContent() !== "") {
155
+ setIsText($isTextNode(node) || $isParagraphNode(node));
156
+ }
157
+ else {
158
+ setIsText(false);
159
+ }
160
+ const rawTextContent = selection.getTextContent().replace(/\n/g, "");
161
+ if (!selection.isCollapsed() && rawTextContent === "") {
162
+ setIsText(false);
163
+ return;
164
+ }
165
+ });
166
+ }, [editor]);
167
+ useEffect(() => {
168
+ document.addEventListener("selectionchange", updatePopup);
169
+ return () => {
170
+ document.removeEventListener("selectionchange", updatePopup);
171
+ };
172
+ }, [updatePopup]);
173
+ useEffect(() => {
174
+ return mergeRegister(editor.registerUpdateListener(() => {
175
+ updatePopup();
176
+ }), editor.registerRootListener(() => {
177
+ if (editor.getRootElement() === null) {
178
+ setIsText(false);
179
+ }
180
+ }));
181
+ }, [editor, updatePopup]);
182
+ if (!isText) {
183
+ return null;
184
+ }
185
+ return createPortal(_jsx(TextFormatFloatingToolbar, { editor: editor, anchorElem: anchorElem, isLink: isLink, isBold: isBold, isItalic: isItalic, isStrikethrough: isStrikethrough, isUnderline: isUnderline, isCode: isCode }), anchorElem);
186
+ }
187
+ export default function FloatingTextFormatToolbarPlugin({ anchorElem = document.body, }) {
188
+ const [editor] = useLexicalComposerContext();
189
+ return useFloatingTextFormatToolbar(editor, anchorElem);
190
+ }
@@ -0,0 +1,7 @@
1
+ import { LexicalEditor } from "lexical";
2
+ export interface InsertImageDialogProps {
3
+ open: boolean;
4
+ activeEditor: LexicalEditor;
5
+ onClose: () => void;
6
+ }
7
+ export declare function InsertImageDialog(props: InsertImageDialogProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { INSERT_IMAGE_COMMAND } from ".";
4
+ import { InsertImageUriDialogBody } from "./InsertImageUriDialogBody";
5
+ import { InsertImageUploadedDialogBody } from "./InsertImageUploadedDialogBody";
6
+ import styled from "@emotion/styled";
7
+ import { AlertDialog, AlertDialogContent, AlertDialogTitle } from "../../../../components/AlertDialog";
8
+ import { ImageFillIcon } from "../../../../icons";
9
+ import Button from "../../../../components/Button";
10
+ export function InsertImageDialog(props) {
11
+ const { open, activeEditor, onClose } = props;
12
+ const [mode, setMode] = useState(null);
13
+ const hasModifier = useRef(false);
14
+ useEffect(() => {
15
+ hasModifier.current = false;
16
+ const handler = (e) => {
17
+ hasModifier.current = e.altKey;
18
+ };
19
+ document.addEventListener("keydown", handler);
20
+ return () => {
21
+ document.removeEventListener("keydown", handler);
22
+ };
23
+ }, [activeEditor]);
24
+ const onClick = (payload) => {
25
+ activeEditor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);
26
+ onClose();
27
+ };
28
+ const handleOnClose = () => {
29
+ setMode(null);
30
+ onClose();
31
+ };
32
+ return (_jsxs(AlertDialog, Object.assign({ icon: _jsx(ImageFillIcon, {}), open: open, onClose: handleOnClose, disableIconPadding: true }, { children: [_jsx(AlertDialogTitle, Object.assign({ onClose: handleOnClose }, { children: "\uC774\uBBF8\uC9C0 \uC785\uB825\uD558\uAE30" })), !mode && (_jsx(AlertDialogContent, { children: _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "medium", fullWidth: true, label: "URL \uC785\uB825\uD558\uAE30", onClick: () => setMode("url") }), _jsx(Button, { color: "grey", size: "medium", fullWidth: true, label: "\uD30C\uC77C \uC62C\uB9AC\uAE30", onClick: () => setMode("file") })] }) })), mode === "url" && _jsx(InsertImageUriDialogBody, { onClick: onClick }), mode === "file" && _jsx(InsertImageUploadedDialogBody, { onClick: onClick })] })));
33
+ }
34
+ const Buttons = styled.div `
35
+ display: flex;
36
+ flex-direction: column;
37
+ margin-top: 20px;
38
+ gap: 20px;
39
+ `;
@@ -0,0 +1,5 @@
1
+ import { InsertImagePayload } from ".";
2
+ export interface InsertImageUploadedDialogBodyProps {
3
+ onClick: (payload: InsertImagePayload) => void;
4
+ }
5
+ export declare function InsertImageUploadedDialogBody(props: InsertImageUploadedDialogBodyProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,49 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
+ import { useContext, useRef, useState } from "react";
12
+ import styled from "@emotion/styled";
13
+ import { CodleDesignSystemContext } from "../../../../CodleDesignSystemProvider";
14
+ import { AlertDialogContent, AlertDialogActions } from "../../../../components/AlertDialog";
15
+ import Button from "../../../../components/Button";
16
+ import Input from "../../../../components/Input";
17
+ export function InsertImageUploadedDialogBody(props) {
18
+ const { onClick } = props;
19
+ const [src, setSrc] = useState("");
20
+ const [altText, setAltText] = useState("");
21
+ const inputRef = useRef(null);
22
+ const cdsContext = useContext(CodleDesignSystemContext);
23
+ const isDisabled = src === "";
24
+ return (_jsxs(_Fragment, { children: [_jsx(AlertDialogContent, { children: _jsxs(Inputs, { children: [_jsx(Button, { fullWidth: true, color: "grey", size: "medium", label: "\uD30C\uC77C \uC62C\uB9AC\uAE30", onClick: () => {
25
+ var _a;
26
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
27
+ } }), _jsx(HiddenInput, { ref: inputRef, type: "file", accept: "image/*", onChange: (event) => __awaiter(this, void 0, void 0, function* () {
28
+ var _a, _b;
29
+ const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
30
+ if (!file)
31
+ return;
32
+ const uploadByFile = (_b = cdsContext.lexical) === null || _b === void 0 ? void 0 : _b.uploadByFile;
33
+ if (uploadByFile) {
34
+ setSrc(yield uploadByFile(file));
35
+ }
36
+ }) }), _jsx(Input, { fullWidth: true, label: "\uC124\uBA85", placeholder: "\uC774\uBBF8\uC9C0\uC5D0 \uB300\uD55C \uC124\uBA85", color: "default", size: "medium", onChange: (e) => {
37
+ setAltText(e.target.value);
38
+ }, value: altText })] }) }), _jsx(AlertDialogActions, { children: _jsx(Button, { label: "\uD655\uC778", size: "large", color: "primary", disabled: isDisabled, onClick: () => onClick({ altText, src }) }) })] }));
39
+ }
40
+ const Inputs = styled.div `
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: 20px;
44
+ margin-top: 20px;
45
+ margin-bottom: 10px;
46
+ `;
47
+ const HiddenInput = styled.input `
48
+ display: none;
49
+ `;
@@ -0,0 +1,5 @@
1
+ import { InsertImagePayload } from ".";
2
+ export interface InsertImageUriDialogBodyProps {
3
+ onClick: (payload: InsertImagePayload) => void;
4
+ }
5
+ export declare function InsertImageUriDialogBody(props: InsertImageUriDialogBodyProps): import("react/jsx-runtime").JSX.Element;