@team-monolith/cds 1.8.5 → 1.8.7

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 (25) hide show
  1. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.d.ts +3 -3
  2. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +45 -153
  3. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.d.ts +14 -14
  4. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.js +24 -27
  5. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form → SettingForm}/FormCharacterCount.d.ts +2 -3
  6. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form → SettingForm}/FormCharacterCount.js +2 -2
  7. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form/FormAnswer.d.ts → SettingForm/FormSolution.d.ts} +2 -2
  8. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.js +42 -0
  9. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.d.ts +10 -0
  10. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +145 -0
  11. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form → SettingForm}/index.d.ts +2 -1
  12. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form → SettingForm}/index.js +2 -1
  13. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +10 -4
  14. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.d.ts +2 -2
  15. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.d.ts +2 -2
  16. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js +2 -1
  17. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +7 -4
  18. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +22 -24
  19. package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.d.ts +1 -1
  20. package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.js +5 -6
  21. package/dist/patterns/LexicalEditor/theme.js +2 -1
  22. package/package.json +1 -1
  23. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/Form/FormAnswer.js +0 -41
  24. /package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form → SettingForm}/FormPlaceholder.d.ts +0 -0
  25. /package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/{Form → SettingForm}/FormPlaceholder.js +0 -0
@@ -1,9 +1,9 @@
1
- import { Answer } from "./ProblemInputNode";
1
+ import { Solution } from "./ProblemInputNode";
2
2
  import { NodeKey } from "lexical";
3
3
  export declare function InputComponent(props: {
4
- answers: Answer[];
4
+ solutions: Solution[];
5
5
  showCharacterCount: boolean;
6
6
  placeholder: string;
7
- response: string;
7
+ answer: string;
8
8
  nodeKey: NodeKey;
9
9
  }): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -1,179 +1,71 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
2
13
  /** @jsxImportSource @emotion/react */
3
- import { css, useTheme } from "@emotion/react";
4
- import { Suspense, useState } from "react";
5
- import { useForm, useFieldArray } from "react-hook-form";
14
+ import { css } from "@emotion/react";
15
+ import { useState } from "react";
6
16
  import Input from "../../../../components/Input";
7
17
  import SquareButton from "../../../../components/SquareButton";
8
- import { AddFillIcon, AlarmWarningFillIcon, InputMethodLineIcon, QuestionFillIcon, Settings3FillIcon, } from "../../../../icons";
18
+ import { Settings3FillIcon } from "../../../../icons";
9
19
  import styled from "@emotion/styled";
10
- import shadows from "../../../../foundation/shadows";
11
- import Button from "../../../../components/Button";
12
- import Tooltip from "../../../../components/Tooltip";
13
- import { FormAnswer, FormCharacterCount, FormPlaceholder } from "./Form";
20
+ import SettingForm from "./SettingForm";
14
21
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
15
- import { $isProblemInputNode, } from "./ProblemInputNode";
22
+ import { $isProblemInputNode } from "./ProblemInputNode";
16
23
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
24
  import { $getNodeByKey } from "lexical";
18
25
  export function InputComponent(props) {
19
- const theme = useTheme();
20
- const { answers, showCharacterCount, placeholder, response, nodeKey } = props;
26
+ const { answer } = props, settingFormProps = __rest(props, ["answer"]);
27
+ const { placeholder, nodeKey } = settingFormProps;
21
28
  const [editor] = useLexicalComposerContext();
22
29
  const [settingOpen, setSettingOpen] = useState(false);
23
- const [multiAnswerDisabled, setMultiAnswerDisabled] = useState(showCharacterCount);
24
30
  const isEditable = useLexicalEditable();
25
- const { control, handleSubmit } = useForm({
26
- defaultValues: {
27
- answers,
28
- showCharacterCount,
29
- placeholder,
30
- },
31
- });
32
- const { fields, append, remove, update } = useFieldArray({
33
- control,
34
- name: "answers",
35
- keyName: "uid",
36
- });
37
- const onSettingSubmit = (data) => {
38
- editor.update(() => {
39
- const node = $getNodeByKey(nodeKey);
40
- if (!$isProblemInputNode(node)) {
41
- return;
42
- }
43
- node.setAnswers(data.answers);
44
- node.setShowCharacterCount(data.showCharacterCount);
45
- node.setPlaceholder(data.placeholder);
46
- });
47
- setSettingOpen(false);
48
- };
31
+ const [answerInput, setAnswerInput] = useState(answer);
49
32
  // 학생 view
50
33
  // TODO: "글자 수대로" 옵션시에 글자 수대로 입력칸을 표시해야 합니다.
51
34
  if (!isEditable) {
52
- return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: response, onChange: (e) => {
53
- editor.update(() => {
54
- const node = $getNodeByKey(nodeKey);
55
- if (!$isProblemInputNode(node)) {
56
- return;
57
- }
58
- node.setResponse(e.target.value);
59
- });
60
- }, color: "default", css: css `
61
- width: 300px;
62
- ` }));
35
+ return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => setAnswerInput(e.target.value), inputProps: {
36
+ onBlur: (_e) => {
37
+ editor.update(() => {
38
+ const node = $getNodeByKey(nodeKey);
39
+ if (!$isProblemInputNode(node)) {
40
+ return;
41
+ }
42
+ node.setAnswer(answerInput);
43
+ });
44
+ },
45
+ }, color: "default", fullWidth: true }));
63
46
  }
64
47
  // 교사 edit view
65
- return (_jsxs(Suspense, Object.assign({ fallback: null }, { children: [_jsxs("div", Object.assign({ css: css `
48
+ return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
66
49
  display: flex;
67
50
  align-items: center;
68
51
  gap: 4px;
69
- ` }, { children: [_jsx(Input, { size: "small", placeholder: "주관식 입력 ", value: "", onChange: () => {
70
- // 수정모드에서는 입력칸 수정을 허용하지 않습니다.
71
- }, color: "default", css: css `
72
- width: 300px;
73
- ` }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
52
+ ` }, { children: [_jsx(VirtualInput, Object.assign({ onClick: () => setSettingOpen(true) }, { children: "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78" })), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
74
53
  setSettingOpen(true);
75
- } })] })), settingOpen && (_jsxs(SettingForm, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
76
- width: 12px;
77
- height: 12px;
78
- ` }), "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC815\uB2F5" }), fields
79
- .map((field, index) => ({
80
- field,
81
- index,
82
- }))
83
- .filter(({ field }) => !field.destroyed)
84
- .map(({ field, index }) => (_jsx(FormAnswer, { index: index, control: control, disabled: index !== 0 && multiAnswerDisabled, onDelete: index !== 0
85
- ? () => {
86
- if (field.value) {
87
- update(index, Object.assign(Object.assign({}, field), { destroyed: true }));
88
- }
89
- else {
90
- remove(index);
91
- }
92
- }
93
- : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled: multiAnswerDisabled, onClick: () => {
94
- append({
95
- textType: "normal",
96
- value: "",
97
- });
98
- } }), _jsxs("div", Object.assign({ css: css `
99
- display: flex;
100
- gap: 4px;
101
- ` }, { children: [_jsx(AlarmWarningFillIcon, { color: theme.color.foreground.neutralBaseDisabled, css: css `
102
- width: 14px;
103
- height: 14px;
104
- ` }), _jsxs(Label, { children: ["\uB744\uC5B4\uC4F0\uAE30, \uC54C\uD30C\uBCB3\uC758 \uB300\uC18C\uBB38\uC790 \uAD6C\uBD84\uAE4C\uC9C0 \uC77C\uCE58\uD574\uC57C \uC815\uB2F5\uC73C\uB85C \uCC98\uB9AC\uB429\uB2C8\uB2E4.", _jsx("br", {}), "\uAC00\uB2A5\uD55C \uC815\uB2F5\uC744 \uBAA8\uB450 \uCD94\uAC00\uD574\uC57C \uC6D0\uD65C\uD558\uAC8C \uC790\uB3D9 \uCC44\uC810\uD560 \uC218 \uC788\uC5B4\uC694."] })] }))] }), _jsxs(Right, { children: [_jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC785\uB825 \uCE78", _jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC608\uB97C \uB4E4\uC5B4 \uC815\uB2F5\uC774 '\uAE00\uC790 \uC218'\uC774\uACE0", _jsx("br", {}), _jsx("b", { children: "\uAE00\uC790 \uC218\uB300\uB85C" }), " \uC635\uC158\uC744 \uC120\uD0DD\uD588\uB2E4\uBA74", _jsx("br", {}), "\uC785\uB825 \uCE78\uC774 '\u2610\u2610 \u2610' \uCC98\uB7FC \uD45C\uC2DC\uB429\uB2C8\uB2E4."] }), placement: "top" }, { children: _jsx(QuestionFillIcon, { css: css `
105
- width: 12px;
106
- height: 12px;
107
- ` }) }))] }), _jsx(FormCharacterCount, { control: control, setMultiAnswerDisabled: setMultiAnswerDisabled })] }), _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 `
108
- width: 12px;
109
- height: 12px;
110
- ` }) }))] }), _jsx(FormPlaceholder, { control: control })] })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "\uB2EB\uAE30", onClick: () => {
111
- setSettingOpen(false);
112
- } }), _jsx(Button, { color: "primary", size: "xsmall", label: "\uC774\uB300\uB85C \uB123\uAE30", type: "submit" })] })] })))] })));
54
+ } })] })), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
113
55
  }
114
- const SettingForm = styled.form(({ theme }) => css `
115
- display: flex;
116
- width: 620px;
117
- flex-direction: column;
118
- border-radius: 6px;
119
- border: 1px solid ${theme.color.background.neutralAltActive};
120
- background: ${theme.color.background.neutralBase};
121
- box-shadow: ${shadows.shadow08};
122
- `);
123
- const Title = styled.div(({ theme }) => css `
124
- display: flex;
125
- padding: 8px 12px;
126
- gap: 4px;
127
- align-items: center;
128
- color: ${theme.color.foreground.neutralBase};
129
- /* Default/Label/12px-Md */
130
- font-family: ${theme.fontFamily.ui};
131
- font-size: 12px;
132
- font-style: normal;
133
- font-weight: 500;
134
- line-height: 16px; /* 133.333% */
135
- `);
136
- const Content = styled.div(({ theme }) => css `
137
- display: flex;
138
- border-top: 1px solid ${theme.color.background.neutralAltActive};
139
- border-bottom: 1px solid ${theme.color.background.neutralAltActive};
140
- `);
141
- const Left = styled.div `
142
- display: flex;
143
- flex-direction: column;
144
- padding: 12px;
145
- gap: 12px;
146
- flex: 1;
147
- `;
148
- const FormArea = styled.div `
149
- display: flex;
150
- flex-direction: column;
151
- gap: 8px;
152
- `;
153
- const Right = styled.div `
154
- display: flex;
155
- box-sizing: border-box;
156
- width: 240px;
157
- flex-direction: column;
158
- padding: 12px;
159
- gap: 12px;
160
- `;
161
- const Label = styled.div(({ theme }) => css `
162
- display: flex;
163
- gap: 4px;
56
+ const VirtualInput = styled.div(({ theme }) => css `
57
+ box-sizing: border-box;
58
+ height: 36px;
59
+ width: 300px;
60
+ padding: 8px 16px;
164
61
  align-items: center;
62
+ border-radius: 8px;
63
+ background: ${theme.color.background.neutralAlt};
64
+ cursor: pointer;
165
65
  color: ${theme.color.foreground.neutralBaseDisabled};
166
- /* Default/Label/12px-Md */
167
66
  font-family: ${theme.fontFamily.ui};
168
- font-size: 12px;
67
+ font-size: 14px;
169
68
  font-style: normal;
170
- font-weight: 500;
171
- line-height: 16px; /* 133.333% */
69
+ font-weight: 400;
70
+ line-height: 20px; /* 142.857% */
172
71
  `);
173
- const Buttons = styled.div `
174
- display: flex;
175
- padding: 12px;
176
- justify-content: flex-end;
177
- align-items: center;
178
- gap: 8px;
179
- `;
@@ -1,43 +1,43 @@
1
1
  import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical";
2
2
  import { ReactNode } from "react";
3
- export interface Answer {
3
+ export interface Solution {
4
4
  textType: "normal" | "code";
5
5
  value: string;
6
- destroyed?: boolean;
7
6
  }
8
7
  export interface ProblemInputPayload {
9
8
  showCharacterCount: boolean;
10
9
  placeholder: string;
11
- answers: Answer[];
12
- response: string;
10
+ solutions: Solution[];
11
+ answer: string;
13
12
  key?: NodeKey;
14
13
  }
15
14
  export type SerializedProblemInputNode = Spread<ProblemInputPayload, SerializedLexicalNode>;
16
15
  /**
17
- * answersAnswer타입의 배열로서 정답을 담고 있습니다. (교사용)
18
- * response는 학생이 입력한 답을 담고 있습니다.(학생용)
16
+ * solutionsSolution타입의 배열로서 정답을 담고 있습니다. (교사용)
17
+ * answer는 학생이 입력한 답을 담고 있습니다.(학생용)
19
18
  */
20
19
  export declare class ProblemInputNode extends DecoratorNode<ReactNode> {
21
- __answers: Answer[];
20
+ __solutions: Solution[];
22
21
  __showCharacterCount: boolean;
23
22
  __placeholder: string;
24
- __response: string;
23
+ __answer: string;
24
+ isInline(): boolean;
25
25
  static getType(): string;
26
- getAnswers(): Answer[];
26
+ getSolutions(): Solution[];
27
27
  getShowCharacterCount(): boolean;
28
28
  getPlaceholder(): string;
29
- getResponse(): string;
30
- setAnswers(answers: Answer[]): void;
29
+ getAnswer(): string;
30
+ setSolutions(solutions: Solution[]): void;
31
31
  setShowCharacterCount(showCharacterCount: boolean): void;
32
32
  setPlaceholder(placeholder: string): void;
33
- setResponse(response: string): void;
33
+ setAnswer(answer: string): void;
34
34
  static clone(node: ProblemInputNode): ProblemInputNode;
35
- constructor(showCharacterCount: boolean, placeholder: string, answers: Answer[], response: string, key?: NodeKey);
35
+ constructor(showCharacterCount: boolean, placeholder: string, solutions: Solution[], answer: string, key?: NodeKey);
36
36
  createDOM(config: EditorConfig): HTMLElement;
37
37
  updateDOM(): boolean;
38
38
  static importJSON(serializedNode: SerializedProblemInputNode): ProblemInputNode;
39
39
  exportJSON(): SerializedProblemInputNode;
40
40
  decorate(): ReactNode;
41
41
  }
42
- export declare function $createProblemInputNode({ showCharacterCount, placeholder, answers, response, key, }: ProblemInputPayload): ProblemInputNode;
42
+ export declare function $createProblemInputNode({ showCharacterCount, placeholder, solutions, answer, key, }: ProblemInputPayload): ProblemInputNode;
43
43
  export declare function $isProblemInputNode(node: LexicalNode | null | undefined): node is ProblemInputNode;
@@ -3,15 +3,18 @@ import { $applyNodeReplacement, DecoratorNode, } from "lexical";
3
3
  import { addClassNamesToElement } from "@lexical/utils";
4
4
  import { InputComponent } from "./InputComponent";
5
5
  /**
6
- * answersAnswer타입의 배열로서 정답을 담고 있습니다. (교사용)
7
- * response는 학생이 입력한 답을 담고 있습니다.(학생용)
6
+ * solutionsSolution타입의 배열로서 정답을 담고 있습니다. (교사용)
7
+ * answer는 학생이 입력한 답을 담고 있습니다.(학생용)
8
8
  */
9
9
  export class ProblemInputNode extends DecoratorNode {
10
+ isInline() {
11
+ return false;
12
+ }
10
13
  static getType() {
11
14
  return "problem-input";
12
15
  }
13
- getAnswers() {
14
- return this.__answers;
16
+ getSolutions() {
17
+ return this.__solutions;
15
18
  }
16
19
  getShowCharacterCount() {
17
20
  return this.__showCharacterCount;
@@ -19,12 +22,12 @@ export class ProblemInputNode extends DecoratorNode {
19
22
  getPlaceholder() {
20
23
  return this.__placeholder;
21
24
  }
22
- getResponse() {
23
- return this.__response;
25
+ getAnswer() {
26
+ return this.__answer;
24
27
  }
25
- setAnswers(answers) {
28
+ setSolutions(solutions) {
26
29
  const self = this.getWritable();
27
- self.__answers = answers;
30
+ self.__solutions = solutions;
28
31
  }
29
32
  setShowCharacterCount(showCharacterCount) {
30
33
  const self = this.getWritable();
@@ -34,25 +37,19 @@ export class ProblemInputNode extends DecoratorNode {
34
37
  const self = this.getWritable();
35
38
  self.__placeholder = placeholder;
36
39
  }
37
- setResponse(response) {
40
+ setAnswer(answer) {
38
41
  const self = this.getWritable();
39
- self.__response = response;
42
+ self.__answer = answer;
40
43
  }
41
44
  static clone(node) {
42
- return $createProblemInputNode({
43
- showCharacterCount: node.__showCharacterCount,
44
- placeholder: node.__placeholder,
45
- answers: node.__answers,
46
- response: node.__response,
47
- key: node.__key,
48
- });
45
+ return new ProblemInputNode(node.__showCharacterCount, node.__placeholder, node.__solutions, node.__answer, node.__key);
49
46
  }
50
- constructor(showCharacterCount, placeholder, answers, response, key) {
47
+ constructor(showCharacterCount, placeholder, solutions, answer, key) {
51
48
  super(key);
52
- this.__answers = answers;
53
49
  this.__showCharacterCount = showCharacterCount;
54
50
  this.__placeholder = placeholder;
55
- this.__response = response;
51
+ this.__solutions = solutions;
52
+ this.__answer = answer;
56
53
  }
57
54
  createDOM(config) {
58
55
  // Define the DOM element here
@@ -66,10 +63,10 @@ export class ProblemInputNode extends DecoratorNode {
66
63
  static importJSON(serializedNode) {
67
64
  const node = $createProblemInputNode({
68
65
  key: serializedNode.key,
69
- answers: serializedNode.answers,
66
+ solutions: serializedNode.solutions,
70
67
  showCharacterCount: serializedNode.showCharacterCount,
71
68
  placeholder: serializedNode.placeholder,
72
- response: serializedNode.response,
69
+ answer: serializedNode.answer,
73
70
  });
74
71
  return node;
75
72
  }
@@ -79,16 +76,16 @@ export class ProblemInputNode extends DecoratorNode {
79
76
  type: "problem-input",
80
77
  showCharacterCount: this.__showCharacterCount,
81
78
  placeholder: this.__placeholder,
82
- answers: this.__answers,
83
- response: this.__response,
79
+ solutions: this.__solutions,
80
+ answer: this.__answer,
84
81
  };
85
82
  }
86
83
  decorate() {
87
- return (_jsx(InputComponent, { answers: this.__answers, showCharacterCount: this.__showCharacterCount, placeholder: this.__placeholder, response: this.__response, nodeKey: this.getKey() }));
84
+ return (_jsx(InputComponent, { solutions: this.__solutions, showCharacterCount: this.__showCharacterCount, placeholder: this.__placeholder, answer: this.__answer, nodeKey: this.getKey() }));
88
85
  }
89
86
  }
90
- export function $createProblemInputNode({ showCharacterCount, placeholder, answers, response, key, }) {
91
- return $applyNodeReplacement(new ProblemInputNode(showCharacterCount, placeholder, answers, response, key));
87
+ export function $createProblemInputNode({ showCharacterCount, placeholder, solutions, answer, key, }) {
88
+ return $applyNodeReplacement(new ProblemInputNode(showCharacterCount, placeholder, solutions, answer, key));
92
89
  }
93
90
  export function $isProblemInputNode(node) {
94
91
  return node instanceof ProblemInputNode;
@@ -1,8 +1,7 @@
1
- import { Control } from "react-hook-form";
2
- import React from "react";
1
+ import { Control, UseFormTrigger } from "react-hook-form";
3
2
  import { ProblemInputPayload } from "../ProblemInputNode";
4
3
  export interface FormCharacterCountProps {
5
4
  control: Control<ProblemInputPayload, any>;
6
- setMultiAnswerDisabled: React.Dispatch<React.SetStateAction<boolean>>;
5
+ trigger: UseFormTrigger<ProblemInputPayload>;
7
6
  }
8
7
  export declare function FormCharacterCount(props: FormCharacterCountProps): import("react/jsx-runtime").JSX.Element;
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Controller } from "react-hook-form";
3
3
  import { SegmentedControlButton, SegmentedControlGroup, } from "../../../../SegmentedControl";
4
4
  export function FormCharacterCount(props) {
5
- const { control, setMultiAnswerDisabled } = props;
5
+ const { control, trigger } = props;
6
6
  return (_jsx(Controller, { name: "showCharacterCount", control: control, render: ({ field: { value, onChange } }) => {
7
7
  return (_jsxs(SegmentedControlGroup, Object.assign({ size: "xsmall", value: value.toString(), onChange: (value) => {
8
8
  onChange(value === "true");
9
- setMultiAnswerDisabled(value === "true");
9
+ trigger("solutions");
10
10
  }, fullWidth: true }, { children: [_jsx(SegmentedControlButton, { value: "false", label: "\uD55C \uCE78\uC73C\uB85C" }), _jsx(SegmentedControlButton, { value: "true", label: "\uAE00\uC790 \uC218\uB300\uB85C" })] })));
11
11
  } }));
12
12
  }
@@ -3,7 +3,7 @@ import { ProblemInputPayload } from "../ProblemInputNode";
3
3
  export interface FormAnswerProps {
4
4
  index: number;
5
5
  control: Control<ProblemInputPayload, any>;
6
+ rules?: any;
6
7
  onDelete?: () => void;
7
- disabled?: boolean;
8
8
  }
9
- export declare function FormAnswer(props: FormAnswerProps): import("@emotion/react/jsx-runtime").JSX.Element;
9
+ export declare function FormSolution(props: FormAnswerProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { css, useTheme } from "@emotion/react";
4
+ import { Controller } from "react-hook-form";
5
+ import Dropdown from "../../../../Dropdown";
6
+ import DropdownItem from "../../../../Dropdown/DropdownItem";
7
+ import Input from "../../../../../components/Input";
8
+ import { AlertFillIcon, DeleteBinLineIcon, ErrorWarningFillIcon, } from "../../../../../icons";
9
+ import SquareButton from "../../../../../components/SquareButton";
10
+ import Tooltip from "../../../../../components/Tooltip";
11
+ export function FormSolution(props) {
12
+ const { index, control, rules, onDelete } = props;
13
+ const theme = useTheme();
14
+ const TextTypeDropdown = (disabled) => (_jsx(Controller, { name: `solutions.${index}.textType`, control: control, render: ({ field: { value, onChange } }) => (_jsx(Dropdown, Object.assign({ label: value === "normal" ? "일반 텍스트" : "코드 텍스트", size: "xsmall", color: "textNeutral", closeOnItemClick: true, disabled: disabled, buttonCss: css `
15
+ ${disabled && `color: ${theme.color.foreground.neutralAlt};`}
16
+ > span {
17
+ font-weight: 700;
18
+ }
19
+ `, menuProps: {
20
+ anchorOrigin: {
21
+ vertical: "bottom",
22
+ horizontal: "center",
23
+ },
24
+ transformOrigin: {
25
+ vertical: "top",
26
+ horizontal: "center",
27
+ },
28
+ } }, { children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? "코드 텍스트" : "일반 텍스트", onClick: () => {
29
+ onChange(value === "normal" ? "code" : "normal");
30
+ } }) }))) }));
31
+ return (_jsx(Controller, { name: `solutions.${index}.value`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => {
32
+ const disabled = (error === null || error === void 0 ? void 0 : error.type) === "multiAnswerDisabled";
33
+ return (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: !disabled && invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: !disabled ? error === null || error === void 0 ? void 0 : error.message : undefined, placeholder: "\uC548\uB155\uD558\uC138\uC694", fullWidth: true, css: css `
34
+ > div {
35
+ padding: 4px 12px;
36
+ }
37
+ `, startIcon: TextTypeDropdown(disabled), endIcon: _jsxs("div", Object.assign({ css: css `
38
+ display: flex;
39
+ gap: 4px;
40
+ ` }, { children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete })), disabled && (_jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0", _jsx("br", {}), "\uC815\uB2F5\uC744 \uD558\uB098\uB9CC \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."] }) }, { children: _jsx(SquareButton, { color: "danger", size: "xsmall", icon: _jsx(AlertFillIcon, { color: theme.color.foreground.neutralAlt }), disabled: true }) })))] })) }));
41
+ } }));
42
+ }
@@ -0,0 +1,10 @@
1
+ import { Solution } from "../ProblemInputNode";
2
+ import { NodeKey } from "lexical";
3
+ export interface SettingFormProps {
4
+ solutions: Solution[];
5
+ showCharacterCount: boolean;
6
+ placeholder: string;
7
+ nodeKey: NodeKey;
8
+ onClose: () => void;
9
+ }
10
+ export default function SettingForm(props: SettingFormProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,145 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { css, useTheme } from "@emotion/react";
4
+ import styled from "@emotion/styled";
5
+ import shadows from "../../../../../foundation/shadows";
6
+ import { AddFillIcon, AlarmWarningFillIcon, InputMethodLineIcon, QuestionFillIcon, } from "../../../../../icons";
7
+ import { useFieldArray, useForm, } from "react-hook-form";
8
+ import { $isProblemInputNode, } from "../ProblemInputNode";
9
+ import { FormSolution } from "./FormSolution";
10
+ import Button from "../../../../../components/Button";
11
+ import Tooltip from "../../../../../components/Tooltip";
12
+ import { FormCharacterCount } from "./FormCharacterCount";
13
+ import { FormPlaceholder } from "./FormPlaceholder";
14
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
15
+ import { $getNodeByKey } from "lexical";
16
+ export default function SettingForm(props) {
17
+ const { solutions, showCharacterCount, placeholder, nodeKey, onClose } = props;
18
+ const [editor] = useLexicalComposerContext();
19
+ const { control, handleSubmit, watch, trigger } = useForm({
20
+ mode: "all",
21
+ defaultValues: {
22
+ solutions,
23
+ showCharacterCount,
24
+ placeholder,
25
+ },
26
+ });
27
+ const theme = useTheme();
28
+ const { fields, append, remove } = useFieldArray({
29
+ control,
30
+ name: "solutions",
31
+ keyName: "uid",
32
+ });
33
+ const onSettingSubmit = (data) => {
34
+ editor.update(() => {
35
+ const node = $getNodeByKey(nodeKey);
36
+ if (!$isProblemInputNode(node)) {
37
+ return;
38
+ }
39
+ node.setSolutions(data.solutions);
40
+ node.setShowCharacterCount(data.showCharacterCount);
41
+ node.setPlaceholder(data.placeholder);
42
+ });
43
+ onClose();
44
+ };
45
+ const multiAnswerDisabled = watch("showCharacterCount");
46
+ return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
47
+ width: 12px;
48
+ height: 12px;
49
+ ` }), "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC815\uB2F5" }), fields.map((field, index) => (_jsx(FormSolution, { index: index, control: control, rules: {
50
+ validate: {
51
+ // required 옵션보다 먼저 검증되어야 하는데 priority 옵션이 없어서 validate에서 통합해서 검증합니다.
52
+ multiAnswerDisabled: () => index === 0 ||
53
+ !multiAnswerDisabled ||
54
+ "복수 정답이 불가능합니다.",
55
+ required: (value) => value !== "" || "정답을 입력해주세요.",
56
+ },
57
+ }, onDelete: index !== 0
58
+ ? () => {
59
+ remove(index);
60
+ }
61
+ : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled: multiAnswerDisabled, onClick: () => {
62
+ append({
63
+ textType: "normal",
64
+ value: "",
65
+ });
66
+ } }), _jsxs("div", Object.assign({ css: css `
67
+ display: flex;
68
+ gap: 4px;
69
+ ` }, { children: [_jsx(AlarmWarningFillIcon, { color: theme.color.foreground.neutralBaseDisabled, css: css `
70
+ width: 14px;
71
+ height: 14px;
72
+ ` }), _jsxs(Label, { children: ["\uB744\uC5B4\uC4F0\uAE30, \uC54C\uD30C\uBCB3\uC758 \uB300\uC18C\uBB38\uC790 \uAD6C\uBD84\uAE4C\uC9C0 \uC77C\uCE58\uD574\uC57C \uC815\uB2F5\uC73C\uB85C \uCC98\uB9AC\uB429\uB2C8\uB2E4.", _jsx("br", {}), "\uAC00\uB2A5\uD55C \uC815\uB2F5\uC744 \uBAA8\uB450 \uCD94\uAC00\uD574\uC57C \uC6D0\uD65C\uD558\uAC8C \uC790\uB3D9 \uCC44\uC810\uD560 \uC218 \uC788\uC5B4\uC694."] })] }))] }), _jsxs(Right, { children: [_jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC785\uB825 \uCE78", _jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC608\uB97C \uB4E4\uC5B4 \uC815\uB2F5\uC774 '\uAE00\uC790 \uC218'\uC774\uACE0", _jsx("br", {}), _jsx("b", { children: "\uAE00\uC790 \uC218\uB300\uB85C" }), " \uC635\uC158\uC744 \uC120\uD0DD\uD588\uB2E4\uBA74", _jsx("br", {}), "\uC785\uB825 \uCE78\uC774 '\u2610\u2610 \u2610' \uCC98\uB7FC \uD45C\uC2DC\uB429\uB2C8\uB2E4."] }), placement: "top" }, { children: _jsx(QuestionFillIcon, { css: css `
73
+ width: 12px;
74
+ height: 12px;
75
+ ` }) }))] }), _jsx(FormCharacterCount, { control: control, trigger: trigger })] }), _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 `
76
+ width: 12px;
77
+ height: 12px;
78
+ ` }) }))] }), _jsx(FormPlaceholder, { control: control })] })] })] }), _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", type: "submit" })] })] })));
79
+ }
80
+ const Form = styled.form(({ theme }) => css `
81
+ display: flex;
82
+ width: 620px;
83
+ flex-direction: column;
84
+ border-radius: 6px;
85
+ border: 1px solid ${theme.color.background.neutralAltActive};
86
+ background: ${theme.color.background.neutralBase};
87
+ box-shadow: ${shadows.shadow08};
88
+ `);
89
+ const Title = styled.div(({ theme }) => css `
90
+ display: flex;
91
+ padding: 8px 12px;
92
+ gap: 4px;
93
+ align-items: center;
94
+ color: ${theme.color.foreground.neutralBase};
95
+ /* Default/Label/12px-Md */
96
+ font-family: ${theme.fontFamily.ui};
97
+ font-size: 12px;
98
+ font-style: normal;
99
+ font-weight: 500;
100
+ line-height: 16px; /* 133.333% */
101
+ `);
102
+ const Content = styled.div(({ theme }) => css `
103
+ display: flex;
104
+ border-top: 1px solid ${theme.color.background.neutralAltActive};
105
+ border-bottom: 1px solid ${theme.color.background.neutralAltActive};
106
+ `);
107
+ const Left = styled.div `
108
+ display: flex;
109
+ flex-direction: column;
110
+ padding: 12px;
111
+ gap: 12px;
112
+ flex: 1;
113
+ `;
114
+ const FormArea = styled.div `
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: 8px;
118
+ `;
119
+ const Right = styled.div `
120
+ display: flex;
121
+ box-sizing: border-box;
122
+ width: 240px;
123
+ flex-direction: column;
124
+ padding: 12px;
125
+ gap: 12px;
126
+ `;
127
+ const Label = styled.div(({ theme }) => css `
128
+ display: flex;
129
+ gap: 4px;
130
+ align-items: center;
131
+ color: ${theme.color.foreground.neutralBaseDisabled};
132
+ /* Default/Label/12px-Md */
133
+ font-family: ${theme.fontFamily.ui};
134
+ font-size: 12px;
135
+ font-style: normal;
136
+ font-weight: 500;
137
+ line-height: 16px; /* 133.333% */
138
+ `);
139
+ const Buttons = styled.div `
140
+ display: flex;
141
+ padding: 12px;
142
+ justify-content: flex-end;
143
+ align-items: center;
144
+ gap: 8px;
145
+ `;
@@ -1,3 +1,4 @@
1
- export * from "./FormAnswer";
1
+ export * from "./FormSolution";
2
2
  export * from "./FormCharacterCount";
3
3
  export * from "./FormPlaceholder";
4
+ export { default } from "./SettingForm";
@@ -1,3 +1,4 @@
1
- export * from "./FormAnswer";
1
+ export * from "./FormSolution";
2
2
  export * from "./FormCharacterCount";
3
3
  export * from "./FormPlaceholder";
4
+ export { default } from "./SettingForm";
@@ -22,7 +22,7 @@ import ReactDOM, { createPortal } from "react-dom";
22
22
  import { LexicalNodeMenuPlugin } from "@lexical/react/LexicalNodeMenuPlugin";
23
23
  import { useDraggableBlockMenu } from "./useDraggableBlockMenu";
24
24
  import { css as cssToClassName } from "@emotion/css";
25
- import { ComponentPickerMenuList, getBaseOptions, } from "../ComponentPickerMenuPlugin";
25
+ import { ComponentPickerMenuList, getBaseOptions, ComponentDrawerOption, } from "../ComponentPickerMenuPlugin";
26
26
  import { useFloatingMenu } from "./useFloatingMenu";
27
27
  import ComponentAdder from "./ComponentAdder";
28
28
  import styled from "@emotion/styled";
@@ -115,6 +115,9 @@ export function ComponentAdderPlugin(props) {
115
115
  };
116
116
  }, [editor, nodeKey, plusOrMenu]);
117
117
  const onSelectOption = useCallback((selectedOption, textNodeContainingQuery, closeMenu, matchingString) => {
118
+ if (selectedOption instanceof ComponentDrawerOption) {
119
+ return;
120
+ }
118
121
  editor.update(() => {
119
122
  selectedOption.onSelect(matchingString);
120
123
  setNodeKey(null);
@@ -130,10 +133,13 @@ export function ComponentAdderPlugin(props) {
130
133
  });
131
134
  const getContextMenuOptions = useContextMenuOptions();
132
135
  const filteredOptions = options.filter((option) => {
136
+ if (!query) {
137
+ return true;
138
+ }
133
139
  const regex = new RegExp(query, "i");
134
- return (option.component ||
135
- regex.test(option.title) ||
136
- option.keywords.some((keyword) => regex.test(keyword)));
140
+ return (option.keywords &&
141
+ (regex.test(option.title) ||
142
+ option.keywords.some((keyword) => regex.test(keyword))));
137
143
  });
138
144
  const { onDragStart, onDragEnd, targetLineRef } = useDraggableBlockMenu(editor, anchorElem, blockElem, setBlockElem);
139
145
  return (_jsxs(_Fragment, { children: [_jsx(InsertImageDialog, { open: imageOpen, activeEditor: editor, onClose: () => setImageOpen(false) }), _jsx(LexicalNodeMenuPlugin, { nodeKey: nodeKey, anchorClassName: cssToClassName `
@@ -2,5 +2,5 @@
2
2
  * Context Menu (:: 버튼)
3
3
  */
4
4
  import { LexicalEditor, LexicalNode } from "lexical";
5
- import { ComponentPickerOption } from "../ComponentPickerMenuPlugin";
6
- export declare function useContextMenuOptions(): (editor: LexicalEditor, node: LexicalNode, setImageOpen: (open: boolean) => void) => ComponentPickerOption[];
5
+ import { ComponentDrawerOption, ComponentPickerOption } from "../ComponentPickerMenuPlugin";
6
+ export declare function useContextMenuOptions(): (editor: LexicalEditor, node: LexicalNode, setImageOpen: (open: boolean) => void) => (ComponentPickerOption | ComponentDrawerOption)[];
@@ -1,7 +1,7 @@
1
1
  /// <reference types="react" />
2
- import { ComponentPickerOption } from "./ComponentPickerMenuPlugin";
2
+ import { ComponentDrawerOption, ComponentPickerOption } from "./ComponentPickerMenuPlugin";
3
3
  export interface ComponentPickerMenuListProps {
4
- options: ComponentPickerOption[];
4
+ options: (ComponentPickerOption | ComponentDrawerOption)[];
5
5
  selectedIndex: number | null;
6
6
  selectOptionAndCleanUp: (option: ComponentPickerOption) => void;
7
7
  setHighlightedIndex: (index: number) => void;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import styled from "@emotion/styled";
3
+ import { ComponentDrawerOption, } from "./ComponentPickerMenuPlugin";
3
4
  import { ComponentPickerMenuItem } from "./ComponentPickerMenuItem";
4
5
  import React from "react";
5
6
  export function ComponentPickerMenuList(props) {
6
7
  const { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex, } = props;
7
8
  return (_jsx(Container, { children: _jsx(ListContainer, { children: options.map((option, i) => {
8
- if ("component" in option) {
9
+ if (option instanceof ComponentDrawerOption) {
9
10
  return (_jsx(React.Fragment, { children: option.component }, option.key));
10
11
  }
11
12
  return (_jsx(ComponentPickerMenuItem, { index: i, isSelected: selectedIndex === i, onClick: () => {
@@ -10,8 +10,13 @@ import { MenuOption } from "@lexical/react/LexicalTypeaheadMenuPlugin";
10
10
  import { LexicalEditor } from "lexical";
11
11
  import { ReactElement } from "react";
12
12
  import { Theme } from "@emotion/react";
13
+ export declare class ComponentDrawerOption extends MenuOption {
14
+ title: string;
15
+ component: ReactElement;
16
+ keywords?: Array<string>;
17
+ constructor(title: string, component: ReactElement, keywords?: Array<string>);
18
+ }
13
19
  export declare class ComponentPickerOption extends MenuOption {
14
- component?: ReactElement;
15
20
  title: string;
16
21
  icon?: ReactElement;
17
22
  iconContainerClassName?: string;
@@ -24,9 +29,7 @@ export declare class ComponentPickerOption extends MenuOption {
24
29
  keywords?: Array<string>;
25
30
  keyboardShortcut?: string;
26
31
  onSelect: (queryString: string) => void;
27
- } | {
28
- component: ReactElement;
29
32
  });
30
33
  }
31
- export declare function getBaseOptions(editor: LexicalEditor, theme: Theme, setImageOpen: (open: boolean) => void): ComponentPickerOption[];
34
+ export declare function getBaseOptions(editor: LexicalEditor, theme: Theme, setImageOpen: (open: boolean) => void): (ComponentPickerOption | ComponentDrawerOption)[];
32
35
  export declare function ComponentPickerMenuPlugin(): JSX.Element;
@@ -24,20 +24,21 @@ import { InsertImageDialog } from "../ImagesPlugin/InsertImageDialog";
24
24
  import { TextIcon, H1Icon, H2Icon, H3Icon, ListUnorderedIcon, ListOrderedIcon, DoubleQuotesLIcon, CodeViewIcon, SeparatorIcon, ImageLineIcon, InputMethodLineIcon, ListRadioIcon, } from "../../../../icons";
25
25
  import { ZINDEX } from "../../../../utils/zIndex";
26
26
  import { css, useTheme } from "@emotion/react";
27
- import { INSERT_SHORT_ANSWER_COMMAND } from "../ProblemInputPlugin";
27
+ import { INSERT_PROBLEM_INPUT_COMMAND } from "../ProblemInputPlugin";
28
28
  // import useModal from "../../hooks/useModal";
29
29
  // import catTypingGif from "../../images/cat-typing.gif";
30
30
  // import { INSERT_IMAGE_COMMAND, InsertImageDialog } from "../ImagesPlugin";
31
+ export class ComponentDrawerOption extends MenuOption {
32
+ constructor(title, component, keywords) {
33
+ super(title);
34
+ this.title = title;
35
+ this.component = component;
36
+ this.keywords = keywords;
37
+ }
38
+ }
31
39
  export class ComponentPickerOption extends MenuOption {
32
40
  constructor(title, options) {
33
41
  super(title);
34
- if ("component" in options) {
35
- this.title = title;
36
- this.component = options.component;
37
- this.keywords = [];
38
- this.onSelect = () => { };
39
- return;
40
- }
41
42
  this.title = title;
42
43
  this.keywords = options.keywords || [];
43
44
  this.icon = options.icon;
@@ -69,9 +70,9 @@ function getQuizContextOptions(editor, theme) {
69
70
  return [
70
71
  new ComponentPickerOption("주관식 입력 칸", {
71
72
  icon: _jsx(InputMethodLineIcon, { color: theme.color.foreground.primary }),
72
- keywords: ["short answer", "short answer input", "주관식", "주관식 입력"],
73
- onSelect: () => editor.dispatchCommand(INSERT_SHORT_ANSWER_COMMAND, {
74
- answers: [
73
+ keywords: ["problem input", "주관식 입력"],
74
+ onSelect: () => editor.dispatchCommand(INSERT_PROBLEM_INPUT_COMMAND, {
75
+ solutions: [
75
76
  {
76
77
  textType: "normal",
77
78
  value: "",
@@ -79,29 +80,22 @@ function getQuizContextOptions(editor, theme) {
79
80
  ],
80
81
  showCharacterCount: false,
81
82
  placeholder: "",
82
- response: "",
83
+ answer: "",
83
84
  }),
84
85
  }),
85
86
  new ComponentPickerOption("객관식 입력 칸", {
86
87
  icon: _jsx(ListRadioIcon, { color: theme.color.foreground.primary }),
87
- keywords: [
88
- "multiple select",
89
- "multiple select input",
90
- "객관식",
91
- "객관식 입력",
92
- ],
88
+ keywords: ["problem select", "객관식 입력"],
93
89
  onSelect: () => {
94
90
  // TODO: 객관식 입력 칸 추가
95
91
  },
96
92
  }),
97
- new ComponentPickerOption("메뉴구분선", {
98
- component: (_jsx("div", { css: css `
93
+ new ComponentDrawerOption("메뉴구분선", (_jsx("div", { css: css `
99
94
  width: 100%;
100
95
  height: 1px;
101
96
  background: ${theme.color.background.neutralAltActive};
102
97
  margin: 4px 0;
103
- ` })),
104
- }),
98
+ ` }))),
105
99
  ];
106
100
  }
107
101
  export function getBaseOptions(editor, theme, setImageOpen) {
@@ -211,11 +205,15 @@ export function ComponentPickerMenuPlugin() {
211
205
  const regex = new RegExp(queryString, "i");
212
206
  return [
213
207
  ...getDynamicOptions(editor, queryString),
214
- ...baseOptions.filter((option) => regex.test(option.title) ||
215
- option.keywords.some((keyword) => regex.test(keyword))),
208
+ ...baseOptions.filter((option) => option.keywords &&
209
+ (regex.test(option.title) ||
210
+ option.keywords.some((keyword) => regex.test(keyword)))),
216
211
  ];
217
212
  }, [editor, queryString, setOpen]);
218
213
  const onSelectOption = useCallback((selectedOption, nodeToRemove, closeMenu, matchingString) => {
214
+ if (selectedOption instanceof ComponentDrawerOption) {
215
+ return;
216
+ }
219
217
  editor.update(() => {
220
218
  nodeToRemove === null || nodeToRemove === void 0 ? void 0 : nodeToRemove.remove();
221
219
  selectedOption.onSelect(matchingString);
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
2
  import { LexicalCommand } from "lexical";
3
3
  import { ProblemInputPayload } from "../../nodes/ProblemInputNode";
4
- export declare const INSERT_SHORT_ANSWER_COMMAND: LexicalCommand<ProblemInputPayload>;
4
+ export declare const INSERT_PROBLEM_INPUT_COMMAND: LexicalCommand<ProblemInputPayload>;
5
5
  export default function ProblemInputPlugin(): JSX.Element | null;
@@ -1,19 +1,18 @@
1
1
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
2
- import { $getSelection, COMMAND_PRIORITY_EDITOR, createCommand, } from "lexical";
2
+ import { $insertNodeToNearestRoot } from "@lexical/utils";
3
+ import { COMMAND_PRIORITY_EDITOR, createCommand, } from "lexical";
3
4
  import { useEffect } from "react";
4
5
  import { $createProblemInputNode, ProblemInputNode, } from "../../nodes/ProblemInputNode";
5
- export const INSERT_SHORT_ANSWER_COMMAND = createCommand("INSERT_SHORT_ANSWER_COMMAND");
6
+ export const INSERT_PROBLEM_INPUT_COMMAND = createCommand("INSERT_PROBLEM_INPUT_COMMAND");
6
7
  export default function ProblemInputPlugin() {
7
8
  const [editor] = useLexicalComposerContext();
8
9
  useEffect(() => {
9
10
  if (!editor.hasNodes([ProblemInputNode])) {
10
11
  throw new Error("ProblemInputNode: ProblemInputNode not registered on editor");
11
12
  }
12
- editor.registerCommand(INSERT_SHORT_ANSWER_COMMAND, (payload) => {
13
- var _a;
13
+ editor.registerCommand(INSERT_PROBLEM_INPUT_COMMAND, (payload) => {
14
14
  const problemInputNode = $createProblemInputNode(payload);
15
- const currentNode = (_a = $getSelection()) === null || _a === void 0 ? void 0 : _a.getNodes()[0];
16
- currentNode === null || currentNode === void 0 ? void 0 : currentNode.replace(problemInputNode);
15
+ $insertNodeToNearestRoot(problemInputNode);
17
16
  return true;
18
17
  }, COMMAND_PRIORITY_EDITOR);
19
18
  }, [editor]);
@@ -249,8 +249,9 @@ export function getTheme(theme) {
249
249
  problemInput: css `
250
250
  display: flex;
251
251
  flex-direction: column;
252
- flex: 1;
252
+ flex: 1;
253
253
  gap: 4px;
254
+ margin-bottom: 8px;
254
255
  `,
255
256
  };
256
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.8.5",
3
+ "version": "1.8.7",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -1,41 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
- /** @jsxImportSource @emotion/react */
3
- import { css, useTheme } from "@emotion/react";
4
- import { Controller } from "react-hook-form";
5
- import Dropdown from "../../../../Dropdown";
6
- import DropdownItem from "../../../../Dropdown/DropdownItem";
7
- import Input from "../../../../../components/Input";
8
- import { AlertFillIcon, DeleteBinLineIcon, ErrorWarningFillIcon, } from "../../../../../icons";
9
- import SquareButton from "../../../../../components/SquareButton";
10
- import Tooltip from "../../../../../components/Tooltip";
11
- export function FormAnswer(props) {
12
- const { index, control, onDelete, disabled } = props;
13
- const theme = useTheme();
14
- const TextTypeDropdown = (_jsx(Controller, { name: `answers.${index}.textType`, control: control, render: ({ field: { value, onChange } }) => (_jsx(Dropdown, Object.assign({ label: value === "normal" ? "일반 텍스트" : "코드 텍스트", size: "xsmall", color: "textNeutral", closeOnItemClick: true, disabled: disabled, buttonCss: css `
15
- ${disabled && `color: ${theme.color.foreground.neutralAlt};`}
16
- > span {
17
- font-weight: 700;
18
- }
19
- `, menuProps: {
20
- anchorOrigin: {
21
- vertical: "bottom",
22
- horizontal: "center",
23
- },
24
- transformOrigin: {
25
- vertical: "top",
26
- horizontal: "center",
27
- },
28
- } }, { children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? "코드 텍스트" : "일반 텍스트", onClick: () => {
29
- onChange(value === "normal" ? "code" : "normal");
30
- } }) }))) }));
31
- return (_jsx(Controller, { name: `answers.${index}.value`, control: control, rules: {
32
- required: "정답을 입력해주세요.",
33
- }, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: error === null || error === void 0 ? void 0 : error.message, placeholder: "\uC548\uB155\uD558\uC138\uC694", fullWidth: true, css: css `
34
- > div {
35
- padding: 4px 12px;
36
- }
37
- `, startIcon: TextTypeDropdown, endIcon: _jsxs("div", Object.assign({ css: css `
38
- display: flex;
39
- gap: 4px;
40
- ` }, { children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete })), disabled && (_jsx(Tooltip, Object.assign({ text: _jsxs("span", { children: ["\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0", _jsx("br", {}), "\uC815\uB2F5\uC744 \uD558\uB098\uB9CC \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."] }) }, { children: _jsx(SquareButton, { color: "danger", size: "xsmall", icon: _jsx(AlertFillIcon, { color: theme.color.foreground.neutralAlt }), disabled: true }) })))] })) })) }));
41
- }