@team-monolith/cds 1.51.2 → 1.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/patterns/LexicalEditor/LexicalEditor.d.ts +2 -0
- package/dist/patterns/LexicalEditor/LexicalEditor.js +6 -2
- package/dist/patterns/LexicalEditor/Plugins.d.ts +1 -0
- package/dist/patterns/LexicalEditor/Plugins.js +4 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/ProblemSelectNode.d.ts +1 -4
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxComponent.d.ts +1 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxEdit.d.ts +3 -2
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.d.ts +1 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +2 -1
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +16 -16
- package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +25 -3
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.d.ts +7 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +84 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SettingForm.d.ts +11 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SettingForm.js +93 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SheetInputNode.d.ts +33 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SheetInputNode.js +77 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetInputNode/index.js +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxComponent.d.ts +18 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxComponent.js +80 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxEdit.d.ts +9 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxEdit.js +7 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxView.d.ts +9 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxView.js +22 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/index.d.ts +2 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/index.js +2 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.d.ts +9 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js +82 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormAllowMultipleAnswers.d.ts +6 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormAllowMultipleAnswers.js +10 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormSelection.d.ts +11 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormSelection.js +86 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.d.ts +13 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.js +170 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/index.js +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/index.js +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SheetSelectNode.d.ts +43 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SheetSelectNode.js +82 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/index.js +1 -0
- package/dist/patterns/LexicalEditor/nodes/{ProblemSelectNode/SettingForm → insertImageDialog}/InsertImageDialog.d.ts +4 -1
- package/dist/patterns/LexicalEditor/nodes/{ProblemSelectNode/SettingForm → insertImageDialog}/InsertImageDialog.js +4 -4
- package/dist/patterns/LexicalEditor/nodes/{ProblemSelectNode/SettingForm → insertImageDialog}/InsertImageUploadedDialogBody.d.ts +1 -1
- package/dist/patterns/LexicalEditor/nodes/{ProblemSelectNode/SettingForm → insertImageDialog}/InsertImageUploadedDialogBody.js +5 -5
- package/dist/patterns/LexicalEditor/nodes/{ProblemSelectNode/SettingForm → insertImageDialog}/InsertImageUriDialogBody.d.ts +1 -1
- package/dist/patterns/LexicalEditor/nodes/{ProblemSelectNode/SettingForm → insertImageDialog}/InsertImageUriDialogBody.js +4 -4
- package/dist/patterns/LexicalEditor/nodes/insertImageDialog/index.d.ts +1 -0
- package/dist/patterns/LexicalEditor/nodes/insertImageDialog/index.js +1 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.d.ts +1 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +6 -2
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.d.ts +1 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +38 -2
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +2 -0
- package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +56 -4
- package/dist/patterns/LexicalEditor/plugins/SheetInputPlugin/index.d.ts +5 -0
- package/dist/patterns/LexicalEditor/plugins/SheetInputPlugin/index.js +20 -0
- package/dist/patterns/LexicalEditor/plugins/SheetSelectPlugin/index.d.ts +5 -0
- package/dist/patterns/LexicalEditor/plugins/SheetSelectPlugin/index.js +20 -0
- package/dist/patterns/LexicalEditor/theme.d.ts +2 -0
- package/dist/patterns/LexicalEditor/theme.js +14 -0
- package/package.json +1 -1
|
@@ -14,6 +14,8 @@ export interface LexicalEditorProps {
|
|
|
14
14
|
freezeProblemNode?: boolean;
|
|
15
15
|
/** 퀴즈 정답이 공개되는지 여부. false가 default입니다. 공개하고자 하는곳에서 true로 설정해야 합니다. */
|
|
16
16
|
showQuizSolution?: boolean;
|
|
17
|
+
/** 활동지 노드가 허용되는지 여부. false가 디폴트이고, 전달하지 않아도 로컬스토리지 devMode로도 노출시킬 수 있습니다. */
|
|
18
|
+
isSheetEnabled?: boolean;
|
|
17
19
|
/** 퀴즈 노드가 허용되는지 여부. false가 디폴트이고, 전달하지 않아도 로컬스토리지 devMode로도 노출시킬 수 있습니다. */
|
|
18
20
|
isQuizEnabled?: boolean;
|
|
19
21
|
/** 외부에서 플러그인을 주입하는 경우 활용함 */
|
|
@@ -16,6 +16,8 @@ import { ProblemSelectNode } from "./nodes/ProblemSelectNode";
|
|
|
16
16
|
import { LayoutContainerNode } from "./nodes/LayoutContainerNode";
|
|
17
17
|
import { LayoutItemNode } from "./nodes/LayoutItemNode";
|
|
18
18
|
import _ from "lodash";
|
|
19
|
+
import { SheetSelectNode } from "./nodes/SheetSelectNode";
|
|
20
|
+
import { SheetInputNode } from "./nodes/SheetInputNode";
|
|
19
21
|
function validateValue(value) {
|
|
20
22
|
var _a, _b;
|
|
21
23
|
if (value && typeof value !== "object") {
|
|
@@ -52,7 +54,7 @@ function getCleanObject(obj) {
|
|
|
52
54
|
return newObj;
|
|
53
55
|
}
|
|
54
56
|
export function LexicalEditor(props) {
|
|
55
|
-
const { className, contentEditableClassName, value, onChange, editable = true, showQuizSolution = false, freezeProblemNode = false, isQuizEnabled = false, children, } = props;
|
|
57
|
+
const { className, contentEditableClassName, value, onChange, editable = true, showQuizSolution = false, freezeProblemNode = false, isSheetEnabled = false, isQuizEnabled = false, children, } = props;
|
|
56
58
|
const theme = useTheme();
|
|
57
59
|
const initialConfig = {
|
|
58
60
|
namespace: "CodleLexicalEditor",
|
|
@@ -76,6 +78,8 @@ export function LexicalEditor(props) {
|
|
|
76
78
|
ColoredQuoteNode,
|
|
77
79
|
LayoutContainerNode,
|
|
78
80
|
LayoutItemNode,
|
|
81
|
+
SheetSelectNode,
|
|
82
|
+
SheetInputNode,
|
|
79
83
|
{
|
|
80
84
|
replace: QuoteNode,
|
|
81
85
|
with: (_node) => {
|
|
@@ -100,5 +104,5 @@ export function LexicalEditor(props) {
|
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
: undefined;
|
|
103
|
-
return (_jsx(LexicalCustomConfigContext.Provider, Object.assign({ value: { freezeProblemNode, showQuizSolution } }, { children: _jsxs(LexicalComposer, Object.assign({ initialConfig: initialConfig }, { children: [_jsx(Plugins, { className: className, contentEditableClassName: contentEditableClassName, onChange: onChangeHandler, isQuizEnabled: isQuizEnabled }), _jsx(_Fragment, { children: children })] })) })));
|
|
107
|
+
return (_jsx(LexicalCustomConfigContext.Provider, Object.assign({ value: { freezeProblemNode, showQuizSolution } }, { children: _jsxs(LexicalComposer, Object.assign({ initialConfig: initialConfig }, { children: [_jsx(Plugins, { className: className, contentEditableClassName: contentEditableClassName, onChange: onChangeHandler, isSheetEnabled: isSheetEnabled, isQuizEnabled: isQuizEnabled }), _jsx(_Fragment, { children: children })] })) })));
|
|
104
108
|
}
|
|
@@ -8,6 +8,7 @@ export interface PluginsProps {
|
|
|
8
8
|
className?: string;
|
|
9
9
|
contentEditableClassName?: string;
|
|
10
10
|
onChange?: (blocks: SerializedEditorState<SerializedLexicalNode>) => void;
|
|
11
|
+
isSheetEnabled: boolean;
|
|
11
12
|
isQuizEnabled: boolean;
|
|
12
13
|
}
|
|
13
14
|
export default function Plugins(props: PluginsProps): ReactElement;
|
|
@@ -30,8 +30,10 @@ import styled from "@emotion/styled";
|
|
|
30
30
|
import ProblemInputPlugin from "./plugins/ProblemInputPlugin";
|
|
31
31
|
import ProblemSelectPlugin from "./plugins/ProblemSelectPlugin";
|
|
32
32
|
import { LayoutPlugin } from "./plugins/LayoutPlugin";
|
|
33
|
+
import SheetSelectPlugin from "./plugins/SheetSelectPlugin";
|
|
34
|
+
import SheetInputPlugin from "./plugins/SheetInputPlugin";
|
|
33
35
|
export default function Plugins(props) {
|
|
34
|
-
const { className, contentEditableClassName, onChange, isQuizEnabled } = props;
|
|
36
|
+
const { className, contentEditableClassName, onChange, isSheetEnabled, isQuizEnabled, } = props;
|
|
35
37
|
const isEditable = useLexicalEditable();
|
|
36
38
|
const [floatingAnchorElem, setFloatingAnchorElem] = useState(null);
|
|
37
39
|
const [isLinkEditMode, setIsLinkEditMode] = useState(false);
|
|
@@ -44,7 +46,7 @@ export default function Plugins(props) {
|
|
|
44
46
|
onChange === null || onChange === void 0 ? void 0 : onChange(editorState.toJSON());
|
|
45
47
|
},
|
|
46
48
|
// ignore 하지 않으면 Form에서 수정하지 않았는데 Dirty로 처리됨.
|
|
47
|
-
ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, { isQuizEnabled: isQuizEnabled }), _jsx(MarkdownShortcutPlugin, { transformers: CODLE_TRANSFORMERS }), _jsx(HistoryPlugin, {})] })), floatingAnchorElem && isEditable && (_jsxs(_Fragment, { children: [_jsx(ComponentAdderPlugin, { anchorElem: floatingAnchorElem, 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, {})] }));
|
|
49
|
+
ignoreSelectionChange: true }), _jsx(AutoFocusPlugin, {}), isEditable && (_jsxs(_Fragment, { children: [_jsx(TabIndentationPlugin, {}), _jsx(ComponentPickerMenuPlugin, { isSheetEnabled: isSheetEnabled, isQuizEnabled: isQuizEnabled }), _jsx(MarkdownShortcutPlugin, { transformers: CODLE_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, {})] }));
|
|
48
50
|
}
|
|
49
51
|
const ScrollArea = styled.div `
|
|
50
52
|
min-height: 150px;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
src: string;
|
|
5
|
-
altText: string;
|
|
6
|
-
}
|
|
3
|
+
import { ImageProps } from "../insertImageDialog";
|
|
7
4
|
export interface SelectionWithoutSolution {
|
|
8
5
|
show: {
|
|
9
6
|
image: ImageProps | null;
|
package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxComponent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { ImageProps } from "
|
|
2
|
+
import { ImageProps } from "../../insertImageDialog";
|
|
3
3
|
export type SelectBoxType = "primary" | "success" | "danger";
|
|
4
4
|
/** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
|
|
5
5
|
export declare function SelectBoxComponent(props: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ImageProps } from "
|
|
1
|
+
import { ImageProps } from "../../insertImageDialog";
|
|
2
|
+
import React from "react";
|
|
2
3
|
export interface SelectBoxEditProps {
|
|
3
4
|
index: number;
|
|
4
5
|
isAnswer: boolean;
|
|
5
6
|
image?: ImageProps | null;
|
|
6
|
-
text:
|
|
7
|
+
text: React.ReactNode;
|
|
7
8
|
onClick: () => void;
|
|
8
9
|
}
|
|
9
10
|
export declare function SelectBoxEdit(props: SelectBoxEditProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -65,7 +65,8 @@ export function SelectComponent(props) {
|
|
|
65
65
|
display: flex;
|
|
66
66
|
flex-direction: column;
|
|
67
67
|
gap: 4px;
|
|
68
|
-
` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text ||
|
|
68
|
+
` }, { children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text ||
|
|
69
|
+
(selection.show.image ? null : `${index + 1}번 선택지`), onClick: () => setSettingOpen(true) }, index))) })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
|
|
69
70
|
setSettingOpen(true);
|
|
70
71
|
} })] })), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
|
|
71
72
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
2
|
/** @jsxImportSource @emotion/react */
|
|
3
3
|
import styled from "@emotion/styled";
|
|
4
4
|
import { Controller } from "react-hook-form";
|
|
@@ -8,27 +8,27 @@ import { DeleteBinLineIcon, ErrorWarningFillIcon, ImageAddFillIcon, ImageEditFil
|
|
|
8
8
|
import SquareButton from "../../../../../components/SquareButton";
|
|
9
9
|
import Switch from "../../../../../components/Switch";
|
|
10
10
|
import { useState } from "react";
|
|
11
|
-
import { InsertImageDialog } from "
|
|
11
|
+
import { InsertImageDialog } from "../../insertImageDialog";
|
|
12
12
|
export function FormSelection(props) {
|
|
13
13
|
const { index, control, watch, rules, onDelete } = props;
|
|
14
14
|
const [imageOpen, setImageOpen] = useState(false);
|
|
15
15
|
const [inputFocused, setInputFocused] = useState(false);
|
|
16
|
-
return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsxs("div", Object.assign({ css: css `
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
return (_jsxs(Container, { children: [_jsx(Index, { children: index + 1 }), _jsx(Controller, { name: `selections.${index}.show`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsxs("div", Object.assign({ css: css `
|
|
17
|
+
display: flex;
|
|
18
|
+
flex: 1;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 4px;
|
|
21
|
+
` }, { children: [_jsx(InsertImageDialog, { title: value.image ? "이미지 바꾸기" : "이미지 삽입하기", open: imageOpen, onClose: () => setImageOpen(false), updateImg: (props) => onChange(Object.assign(Object.assign({}, value), { image: props })), deleteButton: Boolean(value.image) }), value.image && (_jsx("img", { src: value.image.src, alt: value.image.altText, css: css `
|
|
22
|
+
height: auto;
|
|
23
|
+
// 이미지로 인해 좌우로 스크롤이 생기는 것을 방지
|
|
24
|
+
max-width: min(400px, 100%);
|
|
25
|
+
width: fit-content;
|
|
26
|
+
border-radius: 6px;
|
|
27
|
+
`, draggable: "false" })), _jsx(Input, { size: "small", color: invalid
|
|
28
28
|
? "activeDanger"
|
|
29
29
|
: inputFocused
|
|
30
30
|
? "activePrimary"
|
|
31
|
-
: "default", value: value, onChange: onChange, inputProps: {
|
|
31
|
+
: "default", value: value.text, onChange: (e) => onChange(Object.assign(Object.assign({}, value), { text: e.target.value })), inputProps: {
|
|
32
32
|
onFocus: (_e) => {
|
|
33
33
|
setInputFocused(true);
|
|
34
34
|
},
|
|
@@ -37,7 +37,7 @@ export function FormSelection(props) {
|
|
|
37
37
|
},
|
|
38
38
|
}, placeholder: `${index + 1}번 선택지`, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, multiline: true, fullWidth: true, css: css `
|
|
39
39
|
flex: 1;
|
|
40
|
-
` })
|
|
40
|
+
` })] }))) }), _jsxs("div", Object.assign({ css: css `
|
|
41
41
|
display: flex;
|
|
42
42
|
height: 36px;
|
|
43
43
|
gap: 8px;
|
|
@@ -38,13 +38,30 @@ export default function SettingForm(props) {
|
|
|
38
38
|
});
|
|
39
39
|
onClose();
|
|
40
40
|
};
|
|
41
|
+
function validateRequiredSelection(index) {
|
|
42
|
+
const selections = watch("selections");
|
|
43
|
+
const selection = selections[index];
|
|
44
|
+
if (selection.show.image || selection.show.text)
|
|
45
|
+
return true;
|
|
46
|
+
return "필수 입력 항목입니다.";
|
|
47
|
+
}
|
|
41
48
|
function validateDuplicatedSelection(index) {
|
|
42
49
|
const selections = watch("selections");
|
|
43
50
|
if (index === 0)
|
|
44
51
|
return true;
|
|
52
|
+
const image = selections[index].show.image;
|
|
53
|
+
const text = selections[index].show.text;
|
|
45
54
|
const duplicatedIndex = selections
|
|
46
55
|
.slice(0, index)
|
|
47
|
-
.findIndex((selection) =>
|
|
56
|
+
.findIndex((selection) => {
|
|
57
|
+
var _a;
|
|
58
|
+
// text를 비교하고, text가 같은 경우 image도 비교합니다.
|
|
59
|
+
if (selection.show.text !== text)
|
|
60
|
+
return false;
|
|
61
|
+
if (((_a = selection.show.image) === null || _a === void 0 ? void 0 : _a.src) !== (image === null || image === void 0 ? void 0 : image.src))
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
48
65
|
if (duplicatedIndex < 0)
|
|
49
66
|
return true;
|
|
50
67
|
return `${duplicatedIndex + 1}번 선택지와 같은 내용입니다.`;
|
|
@@ -56,8 +73,13 @@ export default function SettingForm(props) {
|
|
|
56
73
|
width: 12px;
|
|
57
74
|
height: 12px;
|
|
58
75
|
` }), "\uAC1D\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uB2F5\uC548" }), fields.map((field, index) => (_jsx(FormSelection, { index: index, control: control, watch: watch, rules: {
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
validate: () => {
|
|
77
|
+
const required = validateRequiredSelection(index);
|
|
78
|
+
if (required !== true) {
|
|
79
|
+
return required;
|
|
80
|
+
}
|
|
81
|
+
return validateDuplicatedSelection(index);
|
|
82
|
+
},
|
|
61
83
|
}, onDelete: index !== 0
|
|
62
84
|
? () => {
|
|
63
85
|
remove(index);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
4
|
+
import useLexicalEditable from "@lexical/react/useLexicalEditable";
|
|
5
|
+
import { $getNodeByKey } from "lexical";
|
|
6
|
+
import { useContext, useState } from "react";
|
|
7
|
+
import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
|
|
8
|
+
import styled from "@emotion/styled";
|
|
9
|
+
import { css } from "@emotion/react";
|
|
10
|
+
import { Input, Settings3FillIcon, SquareButton } from "../../../..";
|
|
11
|
+
import { SettingForm } from "./SettingForm";
|
|
12
|
+
import { $isSheetInputNode } from "./SheetInputNode";
|
|
13
|
+
const TEXTAREA_HEIGHT = 84;
|
|
14
|
+
/** VirtualInput을 Input(size: small)처럼 만들기 위한 padding 값입니다. */
|
|
15
|
+
const INPUT_VERTICAL_PADDING = 8;
|
|
16
|
+
export function InputComponent(props) {
|
|
17
|
+
const { multiline, value, placeholder, nodeKey } = props;
|
|
18
|
+
const [editor] = useLexicalComposerContext();
|
|
19
|
+
const [settingOpen, setSettingOpen] = useState(false);
|
|
20
|
+
const isEditable = useLexicalEditable();
|
|
21
|
+
const [focus, setFocus] = useState(false);
|
|
22
|
+
// SoT를 EditorState로 설정하는 경우 한글 입력시 문제가 생김.
|
|
23
|
+
// ex) "나비" 입력시 -> "ㄴ나납ㅂ비"
|
|
24
|
+
// SoT를 내부 State로 관리하고 EditorState를 따로 설정.
|
|
25
|
+
// 문제를 더 파악하고 추후 개선 필요.
|
|
26
|
+
const [valueInput, setValueInput] = useState(value);
|
|
27
|
+
const { freezeProblemNode } = useContext(LexicalCustomConfigContext);
|
|
28
|
+
const handleChange = (value) => {
|
|
29
|
+
setValueInput(value);
|
|
30
|
+
editor.update(() => {
|
|
31
|
+
const node = $getNodeByKey(nodeKey);
|
|
32
|
+
if (!$isSheetInputNode(node)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
node.setValue(value);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
// view
|
|
39
|
+
if (!isEditable) {
|
|
40
|
+
return (_jsx(Input, { multiline: multiline, css: css `
|
|
41
|
+
textarea {
|
|
42
|
+
// textarea의 height를 고정시키기 위해 max-height와 min-height를 설정합니다.
|
|
43
|
+
max-height: ${TEXTAREA_HEIGHT}px;
|
|
44
|
+
min-height: ${TEXTAREA_HEIGHT}px;
|
|
45
|
+
// https://github.com/mui/material-ui/blob/next/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx
|
|
46
|
+
// MUI TextareaAutoSize는 overflow를 hidden으로 설정하는 로직이 있습니다.
|
|
47
|
+
// 그래서 부득이하게 !important를 사용합니다.
|
|
48
|
+
overflow: auto !important;
|
|
49
|
+
}
|
|
50
|
+
`, inputProps: freezeProblemNode
|
|
51
|
+
? { readOnly: true }
|
|
52
|
+
: {
|
|
53
|
+
onFocus: () => setFocus(true),
|
|
54
|
+
onBlur: () => setFocus(false),
|
|
55
|
+
}, size: "small", color: focus ? "activePrimary" : "default", placeholder: placeholder || "여기에 입력하세요.", value: valueInput, onChange: (e) => {
|
|
56
|
+
handleChange(e.target.value);
|
|
57
|
+
}, fullWidth: true }));
|
|
58
|
+
}
|
|
59
|
+
// edit
|
|
60
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 4px;
|
|
64
|
+
` }, { children: [_jsx(VirtualInput, Object.assign({ onClick: () => setSettingOpen((open) => !open), css: multiline &&
|
|
65
|
+
css `
|
|
66
|
+
height: ${TEXTAREA_HEIGHT + 2 * INPUT_VERTICAL_PADDING}px;
|
|
67
|
+
` }, { children: multiline ? "서술형 입력 칸" : "단답형 입력 칸" })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => setSettingOpen((open) => !open) })] })), settingOpen && (_jsx(SettingForm, { multiline: multiline, placeholder: placeholder, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
|
|
68
|
+
}
|
|
69
|
+
const VirtualInput = styled.div(({ theme }) => css `
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
height: 36px;
|
|
72
|
+
width: 400px;
|
|
73
|
+
padding: ${INPUT_VERTICAL_PADDING}px 16px;
|
|
74
|
+
align-items: center;
|
|
75
|
+
border-radius: 8px;
|
|
76
|
+
background: ${theme.color.background.neutralAlt};
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
color: ${theme.color.foreground.neutralBaseDisabled};
|
|
79
|
+
font-family: ${theme.fontFamily.ui};
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
font-style: normal;
|
|
82
|
+
font-weight: 400;
|
|
83
|
+
line-height: 20px; /* 142.857% */
|
|
84
|
+
`);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NodeKey } from "lexical";
|
|
2
|
+
export interface SettingFormData {
|
|
3
|
+
placeholder: string;
|
|
4
|
+
}
|
|
5
|
+
/** 활동지 활동의 입력칸 설정 컴포넌트입니다. */
|
|
6
|
+
export declare function SettingForm(props: {
|
|
7
|
+
multiline: boolean;
|
|
8
|
+
placeholder: string;
|
|
9
|
+
nodeKey: NodeKey;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
}): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @emotion/react */
|
|
3
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
4
|
+
import { $getNodeByKey } from "lexical";
|
|
5
|
+
import { Controller, useForm } from "react-hook-form";
|
|
6
|
+
import styled from "@emotion/styled";
|
|
7
|
+
import { css } from "@emotion/react";
|
|
8
|
+
import { InputMethodLineIcon, QuestionFillIcon } from "../../../../icons";
|
|
9
|
+
import { Button, Input, shadows, Tooltip } from "../../../..";
|
|
10
|
+
import { $isSheetInputNode } from "./SheetInputNode";
|
|
11
|
+
/** 활동지 활동의 입력칸 설정 컴포넌트입니다. */
|
|
12
|
+
export function SettingForm(props) {
|
|
13
|
+
const { multiline, placeholder, nodeKey, onClose } = props;
|
|
14
|
+
const [editor] = useLexicalComposerContext();
|
|
15
|
+
const { control, handleSubmit } = useForm({
|
|
16
|
+
mode: "all",
|
|
17
|
+
defaultValues: {
|
|
18
|
+
placeholder,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const onSettingSubmit = (data) => {
|
|
22
|
+
editor.update(() => {
|
|
23
|
+
const node = $getNodeByKey(nodeKey);
|
|
24
|
+
if (!$isSheetInputNode(node)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
node.setPlaceholder(data.placeholder);
|
|
28
|
+
});
|
|
29
|
+
onClose();
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
|
|
32
|
+
width: 12px;
|
|
33
|
+
height: 12px;
|
|
34
|
+
` }), multiline ? "서술형 입력 칸" : "단답형 입력 칸"] }), _jsx(Content, { children: _jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC790\uB9AC \uD45C\uC2DC\uC790", _jsx(Tooltip, Object.assign({ text: _jsx("span", { children: "\uC785\uB825 \uCE78\uC5D0 \uAE30\uBCF8\uC73C\uB85C \uB178\uCD9C\uB418\uB294 \uD14D\uC2A4\uD2B8\uC785\uB2C8\uB2E4." }), placement: "top" }, { children: _jsx(QuestionFillIcon, { css: css `
|
|
35
|
+
width: 12px;
|
|
36
|
+
height: 12px;
|
|
37
|
+
` }) }))] }), _jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => {
|
|
38
|
+
return (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: "\uC608) \uC5EC\uAE30\uC5D0 \uC785\uB825\uD558\uC138\uC694." }));
|
|
39
|
+
} })] }) }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "\uB2EB\uAE30", onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: "\uC774\uB300\uB85C \uB123\uAE30", bold: true, type: "submit" })] })] })));
|
|
40
|
+
}
|
|
41
|
+
const Form = styled.form(({ theme }) => css `
|
|
42
|
+
display: flex;
|
|
43
|
+
width: 620px;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
border-radius: 6px;
|
|
46
|
+
border: 1px solid ${theme.color.background.neutralAltActive};
|
|
47
|
+
background: ${theme.color.background.neutralBase};
|
|
48
|
+
box-shadow: ${shadows.shadow08};
|
|
49
|
+
`);
|
|
50
|
+
const Title = styled.div(({ theme }) => css `
|
|
51
|
+
display: flex;
|
|
52
|
+
padding: 8px 12px;
|
|
53
|
+
gap: 4px;
|
|
54
|
+
align-items: center;
|
|
55
|
+
color: ${theme.color.foreground.neutralBase};
|
|
56
|
+
/* Default/Label/12px-Md */
|
|
57
|
+
font-family: ${theme.fontFamily.ui};
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
font-style: normal;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
line-height: 16px; /* 133.333% */
|
|
62
|
+
`);
|
|
63
|
+
const Content = styled.div(({ theme }) => css `
|
|
64
|
+
display: flex;
|
|
65
|
+
border-top: 1px solid ${theme.color.background.neutralAltActive};
|
|
66
|
+
border-bottom: 1px solid ${theme.color.background.neutralAltActive};
|
|
67
|
+
`);
|
|
68
|
+
const FormArea = styled.div `
|
|
69
|
+
width: 240px;
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
gap: 8px;
|
|
73
|
+
padding: 12px;
|
|
74
|
+
`;
|
|
75
|
+
const Label = styled.div(({ theme }) => css `
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: 4px;
|
|
78
|
+
align-items: center;
|
|
79
|
+
color: ${theme.color.foreground.neutralBaseDisabled};
|
|
80
|
+
/* Default/Label/12px-Md */
|
|
81
|
+
font-family: ${theme.fontFamily.ui};
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
font-style: normal;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
line-height: 16px; /* 133.333% */
|
|
86
|
+
`);
|
|
87
|
+
const Buttons = styled.div `
|
|
88
|
+
display: flex;
|
|
89
|
+
padding: 12px;
|
|
90
|
+
justify-content: flex-end;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 8px;
|
|
93
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export interface SheetInputPayload {
|
|
4
|
+
multiline: boolean;
|
|
5
|
+
value: string;
|
|
6
|
+
placeholder: string;
|
|
7
|
+
key?: NodeKey;
|
|
8
|
+
}
|
|
9
|
+
export type SerializedSheetInputNode = Spread<SheetInputPayload, SerializedLexicalNode>;
|
|
10
|
+
/**
|
|
11
|
+
* 입력칸을 나타내는 노드입니다.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SheetInputNode extends DecoratorNode<ReactNode> {
|
|
14
|
+
__multiline: boolean;
|
|
15
|
+
__value: string;
|
|
16
|
+
__placeholder: string;
|
|
17
|
+
isInline(): boolean;
|
|
18
|
+
static getType(): string;
|
|
19
|
+
getMultiline(): boolean;
|
|
20
|
+
getValue(): string;
|
|
21
|
+
getPlaceholder(): string;
|
|
22
|
+
setValue(value: string): void;
|
|
23
|
+
setPlaceholder(placeholder: string): void;
|
|
24
|
+
static clone(node: SheetInputNode): SheetInputNode;
|
|
25
|
+
constructor(multiline: boolean, value: string, placeholder: string, key?: NodeKey);
|
|
26
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
27
|
+
updateDOM(): boolean;
|
|
28
|
+
static importJSON(serializedNode: SerializedSheetInputNode): SheetInputNode;
|
|
29
|
+
exportJSON(): SerializedSheetInputNode;
|
|
30
|
+
decorate(): ReactNode;
|
|
31
|
+
}
|
|
32
|
+
export declare function $createSheetInputNode({ multiline, value, placeholder, key, }: SheetInputPayload): SheetInputNode;
|
|
33
|
+
export declare function $isSheetInputNode(node: LexicalNode | null | undefined): node is SheetInputNode;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { $applyNodeReplacement, DecoratorNode, } from "lexical";
|
|
3
|
+
import { addClassNamesToElement } from "@lexical/utils";
|
|
4
|
+
import { InputComponent } from "./InputComponent";
|
|
5
|
+
/**
|
|
6
|
+
* 입력칸을 나타내는 노드입니다.
|
|
7
|
+
*/
|
|
8
|
+
export class SheetInputNode extends DecoratorNode {
|
|
9
|
+
isInline() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
static getType() {
|
|
13
|
+
return "sheet-input";
|
|
14
|
+
}
|
|
15
|
+
getMultiline() {
|
|
16
|
+
return this.__multiline;
|
|
17
|
+
}
|
|
18
|
+
getValue() {
|
|
19
|
+
return this.__value;
|
|
20
|
+
}
|
|
21
|
+
getPlaceholder() {
|
|
22
|
+
return this.__placeholder;
|
|
23
|
+
}
|
|
24
|
+
setValue(value) {
|
|
25
|
+
const self = this.getWritable();
|
|
26
|
+
self.__value = value;
|
|
27
|
+
}
|
|
28
|
+
setPlaceholder(placeholder) {
|
|
29
|
+
const self = this.getWritable();
|
|
30
|
+
self.__placeholder = placeholder;
|
|
31
|
+
}
|
|
32
|
+
static clone(node) {
|
|
33
|
+
return new SheetInputNode(node.__multiline, node.__value, node.__placeholder, node.__key);
|
|
34
|
+
}
|
|
35
|
+
constructor(multiline, value, placeholder, key) {
|
|
36
|
+
super(key);
|
|
37
|
+
this.__multiline = multiline;
|
|
38
|
+
this.__value = value;
|
|
39
|
+
this.__placeholder = placeholder;
|
|
40
|
+
}
|
|
41
|
+
createDOM(config) {
|
|
42
|
+
// Define the DOM element here
|
|
43
|
+
const root = document.createElement("div");
|
|
44
|
+
addClassNamesToElement(root, config.theme.sheetInput);
|
|
45
|
+
return root;
|
|
46
|
+
}
|
|
47
|
+
updateDOM() {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
static importJSON(serializedNode) {
|
|
51
|
+
const node = $createSheetInputNode({
|
|
52
|
+
key: serializedNode.key,
|
|
53
|
+
multiline: serializedNode.multiline,
|
|
54
|
+
value: serializedNode.value,
|
|
55
|
+
placeholder: serializedNode.placeholder,
|
|
56
|
+
});
|
|
57
|
+
return node;
|
|
58
|
+
}
|
|
59
|
+
exportJSON() {
|
|
60
|
+
return {
|
|
61
|
+
version: 1,
|
|
62
|
+
type: "sheet-input",
|
|
63
|
+
multiline: this.__multiline,
|
|
64
|
+
value: this.__value,
|
|
65
|
+
placeholder: this.__placeholder,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
decorate() {
|
|
69
|
+
return (_jsx(InputComponent, { multiline: this.__multiline, value: this.__value, placeholder: this.__placeholder, nodeKey: this.getKey() }));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function $createSheetInputNode({ multiline, value, placeholder, key, }) {
|
|
73
|
+
return $applyNodeReplacement(new SheetInputNode(multiline, value, placeholder, key));
|
|
74
|
+
}
|
|
75
|
+
export function $isSheetInputNode(node) {
|
|
76
|
+
return node instanceof SheetInputNode;
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SheetInputNode";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SheetInputNode";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { ImageProps } from "../../../insertImageDialog";
|
|
3
|
+
export declare const SelectBoxClasses: {
|
|
4
|
+
readonly container: "SheetSelectNode-SelectBox-container";
|
|
5
|
+
readonly index: "SheetSelectNode-SelectBox-index";
|
|
6
|
+
readonly content: "SheetSelectNode-SelectBox-content";
|
|
7
|
+
};
|
|
8
|
+
export type SelectBoxType = "primary" | "normal";
|
|
9
|
+
/** 비지니스 로직과 무관한 SelectBox를 그리는 공통 컴포넌트입니다. */
|
|
10
|
+
export declare function SelectBoxComponent(props: {
|
|
11
|
+
className?: string;
|
|
12
|
+
type: SelectBoxType;
|
|
13
|
+
index: React.ReactNode;
|
|
14
|
+
image?: ImageProps | null;
|
|
15
|
+
text: React.ReactNode;
|
|
16
|
+
/** 박스를 클릭할 때 실행될 콜백. 존재하지 않으면 cursor: pointer가 적용되지 않습니다. */
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
}): import("@emotion/react/jsx-runtime").JSX.Element;
|