@team-monolith/cds 1.8.6 → 1.8.8

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 (19) hide show
  1. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +16 -12
  2. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/ProblemInputNode.d.ts +0 -1
  3. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormCharacterCount.d.ts +2 -3
  4. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormCharacterCount.js +2 -2
  5. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.d.ts +3 -3
  6. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.js +13 -30
  7. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +17 -21
  8. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/TextTypeDropdown.d.ts +7 -0
  9. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/TextTypeDropdown.js +26 -0
  10. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +10 -4
  11. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.d.ts +2 -2
  12. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.d.ts +2 -2
  13. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js +2 -1
  14. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +6 -4
  15. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +19 -22
  16. package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.d.ts +1 -1
  17. package/dist/patterns/LexicalEditor/plugins/ProblemInputPlugin/index.js +5 -6
  18. package/dist/patterns/LexicalEditor/theme.js +2 -1
  19. package/package.json +1 -1
@@ -19,7 +19,7 @@ import { Settings3FillIcon } from "../../../../icons";
19
19
  import styled from "@emotion/styled";
20
20
  import SettingForm from "./SettingForm";
21
21
  import useLexicalEditable from "@lexical/react/useLexicalEditable";
22
- import { $isProblemInputNode, } from "./ProblemInputNode";
22
+ import { $isProblemInputNode } from "./ProblemInputNode";
23
23
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
24
24
  import { $getNodeByKey } from "lexical";
25
25
  export function InputComponent(props) {
@@ -28,20 +28,24 @@ export function InputComponent(props) {
28
28
  const [editor] = useLexicalComposerContext();
29
29
  const [settingOpen, setSettingOpen] = useState(false);
30
30
  const isEditable = useLexicalEditable();
31
+ const [answerInput, setAnswerInput] = useState(answer);
31
32
  // 학생 view
32
33
  // TODO: "글자 수대로" 옵션시에 글자 수대로 입력칸을 표시해야 합니다.
33
34
  if (!isEditable) {
34
- return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answer, onChange: (e) => {
35
- editor.update(() => {
36
- const node = $getNodeByKey(nodeKey);
37
- if (!$isProblemInputNode(node)) {
38
- return;
39
- }
40
- node.setAnswer(e.target.value);
41
- });
42
- }, color: "default", css: css `
43
- width: 300px;
44
- ` }));
35
+ return (_jsx(Input, { size: "small", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => setAnswerInput(e.target.value),
36
+ // 한글 입력시에 onChange마다 update 일어나는 것을 방지하기 위해 입력 완료후 onBlur시에 update하는 전략을 사용합니다.
37
+ // 이를 위해 answerInput을 state로 관리합니다.
38
+ inputProps: {
39
+ onBlur: (_e) => {
40
+ editor.update(() => {
41
+ const node = $getNodeByKey(nodeKey);
42
+ if (!$isProblemInputNode(node)) {
43
+ return;
44
+ }
45
+ node.setAnswer(answerInput);
46
+ });
47
+ },
48
+ }, color: "default", fullWidth: true }));
45
49
  }
46
50
  // 교사 edit view
47
51
  return (_jsxs(_Fragment, { children: [_jsxs("div", Object.assign({ css: css `
@@ -3,7 +3,6 @@ import { ReactNode } from "react";
3
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;
@@ -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
  }
@@ -1,9 +1,9 @@
1
1
  import { Control } from "react-hook-form";
2
2
  import { ProblemInputPayload } from "../ProblemInputNode";
3
- export interface FormAnswerProps {
3
+ export interface FormSolutionProps {
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 FormSolution(props: FormAnswerProps): import("@emotion/react/jsx-runtime").JSX.Element;
9
+ export declare function FormSolution(props: FormSolutionProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -2,40 +2,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  /** @jsxImportSource @emotion/react */
3
3
  import { css, useTheme } from "@emotion/react";
4
4
  import { Controller } from "react-hook-form";
5
- import Dropdown from "../../../../Dropdown";
6
- import DropdownItem from "../../../../Dropdown/DropdownItem";
7
5
  import Input from "../../../../../components/Input";
8
6
  import { AlertFillIcon, DeleteBinLineIcon, ErrorWarningFillIcon, } from "../../../../../icons";
9
7
  import SquareButton from "../../../../../components/SquareButton";
10
8
  import Tooltip from "../../../../../components/Tooltip";
9
+ import TextTypeDropdown from "./TextTypeDropdown";
11
10
  export function FormSolution(props) {
12
- const { index, control, onDelete, disabled } = props;
11
+ const { index, control, rules, onDelete } = props;
13
12
  const theme = useTheme();
14
- const TextTypeDropdown = (_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: {
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 }) })))] })) })) }));
13
+ return (_jsx(Controller, { name: `solutions.${index}.value`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => {
14
+ const disabled = (error === null || error === void 0 ? void 0 : error.type) === "enabled";
15
+ 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 `
16
+ > div {
17
+ padding: 4px 12px;
18
+ }
19
+ `, startIcon: _jsx(TextTypeDropdown, { index: index, control: control, disabled: disabled }), endIcon: _jsxs("div", Object.assign({ css: css `
20
+ display: flex;
21
+ gap: 4px;
22
+ ` }, { 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 }) })))] })) }));
23
+ } }));
41
24
  }
@@ -4,7 +4,7 @@ import { css, useTheme } from "@emotion/react";
4
4
  import styled from "@emotion/styled";
5
5
  import shadows from "../../../../../foundation/shadows";
6
6
  import { AddFillIcon, AlarmWarningFillIcon, InputMethodLineIcon, QuestionFillIcon, } from "../../../../../icons";
7
- import { useFieldArray, useForm, } from "react-hook-form";
7
+ import { useFieldArray, useForm } from "react-hook-form";
8
8
  import { $isProblemInputNode, } from "../ProblemInputNode";
9
9
  import { FormSolution } from "./FormSolution";
10
10
  import Button from "../../../../../components/Button";
@@ -13,13 +13,11 @@ import { FormCharacterCount } from "./FormCharacterCount";
13
13
  import { FormPlaceholder } from "./FormPlaceholder";
14
14
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
15
15
  import { $getNodeByKey } from "lexical";
16
- import { useState } from "react";
17
16
  export default function SettingForm(props) {
18
17
  const { solutions, showCharacterCount, placeholder, nodeKey, onClose } = props;
19
18
  const [editor] = useLexicalComposerContext();
20
- //fixme: multiAnswerDisabled 삭제
21
- const [multiAnswerDisabled, setMultiAnswerDisabled] = useState(showCharacterCount);
22
- const { control, handleSubmit } = useForm({
19
+ const { control, handleSubmit, watch, trigger } = useForm({
20
+ mode: "all",
23
21
  defaultValues: {
24
22
  solutions,
25
23
  showCharacterCount,
@@ -27,7 +25,7 @@ export default function SettingForm(props) {
27
25
  },
28
26
  });
29
27
  const theme = useTheme();
30
- const { fields, append, remove, update } = useFieldArray({
28
+ const { fields, append, remove } = useFieldArray({
31
29
  control,
32
30
  name: "solutions",
33
31
  keyName: "uid",
@@ -44,25 +42,23 @@ export default function SettingForm(props) {
44
42
  });
45
43
  onClose();
46
44
  };
45
+ const multipleSolutionsDisabled = watch("showCharacterCount");
47
46
  return (_jsxs(Form, Object.assign({ onSubmit: handleSubmit(onSettingSubmit) }, { children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
48
47
  width: 12px;
49
48
  height: 12px;
50
- ` }), "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC815\uB2F5" }), fields
51
- .map((field, index) => ({
52
- field,
53
- index,
54
- }))
55
- .filter(({ field }) => !field.destroyed)
56
- .map(({ field, index }) => (_jsx(FormSolution, { index: index, control: control, disabled: index !== 0 && multiAnswerDisabled, onDelete: index !== 0
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
+ enabled: () => index === 0 ||
53
+ !multipleSolutionsDisabled ||
54
+ "복수 정답이 불가능합니다.",
55
+ required: (value) => value !== "" || "정답을 입력해주세요.",
56
+ },
57
+ }, onDelete: index !== 0
57
58
  ? () => {
58
- if (field.value) {
59
- update(index, Object.assign(Object.assign({}, field), { destroyed: true }));
60
- }
61
- else {
62
- remove(index);
63
- }
59
+ remove(index);
64
60
  }
65
- : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled: multiAnswerDisabled, onClick: () => {
61
+ : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled: multipleSolutionsDisabled, onClick: () => {
66
62
  append({
67
63
  textType: "normal",
68
64
  value: "",
@@ -76,7 +72,7 @@ export default function SettingForm(props) {
76
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 `
77
73
  width: 12px;
78
74
  height: 12px;
79
- ` }) }))] }), _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 `
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 `
80
76
  width: 12px;
81
77
  height: 12px;
82
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" })] })] })));
@@ -0,0 +1,7 @@
1
+ import { Control } from "react-hook-form";
2
+ import { ProblemInputPayload } from "../ProblemInputNode";
3
+ export default function TextTypeDropdown(props: {
4
+ index: number;
5
+ control: Control<ProblemInputPayload, any>;
6
+ disabled: boolean;
7
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Controller } from "react-hook-form";
3
+ import Dropdown from "../../../../Dropdown";
4
+ import { css, useTheme } from "@emotion/react";
5
+ import DropdownItem from "../../../../Dropdown/DropdownItem";
6
+ export default function TextTypeDropdown(props) {
7
+ const theme = useTheme();
8
+ const { index, control, disabled } = props;
9
+ return (_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 `
10
+ ${disabled && `color: ${theme.color.foreground.neutralAlt};`}
11
+ > span {
12
+ font-weight: 700;
13
+ }
14
+ `, menuProps: {
15
+ anchorOrigin: {
16
+ vertical: "bottom",
17
+ horizontal: "center",
18
+ },
19
+ transformOrigin: {
20
+ vertical: "top",
21
+ horizontal: "center",
22
+ },
23
+ } }, { children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? "코드 텍스트" : "일반 텍스트", onClick: () => {
24
+ onChange(value === "normal" ? "code" : "normal");
25
+ } }) }))) }));
26
+ }
@@ -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 { ComponentPickerOption, 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 instanceof ComponentPickerOption &&
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,12 @@ 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
+ constructor(title: string, component: ReactElement);
17
+ }
13
18
  export declare class ComponentPickerOption extends MenuOption {
14
- component?: ReactElement;
15
19
  title: string;
16
20
  icon?: ReactElement;
17
21
  iconContainerClassName?: string;
@@ -24,9 +28,7 @@ export declare class ComponentPickerOption extends MenuOption {
24
28
  keywords?: Array<string>;
25
29
  keyboardShortcut?: string;
26
30
  onSelect: (queryString: string) => void;
27
- } | {
28
- component: ReactElement;
29
31
  });
30
32
  }
31
- export declare function getBaseOptions(editor: LexicalEditor, theme: Theme, setImageOpen: (open: boolean) => void): ComponentPickerOption[];
33
+ export declare function getBaseOptions(editor: LexicalEditor, theme: Theme, setImageOpen: (open: boolean) => void): (ComponentPickerOption | ComponentDrawerOption)[];
32
34
  export declare function ComponentPickerMenuPlugin(): JSX.Element;
@@ -24,20 +24,20 @@ 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) {
33
+ super(title);
34
+ this.title = title;
35
+ this.component = component;
36
+ }
37
+ }
31
38
  export class ComponentPickerOption extends MenuOption {
32
39
  constructor(title, options) {
33
40
  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
41
  this.title = title;
42
42
  this.keywords = options.keywords || [];
43
43
  this.icon = options.icon;
@@ -69,8 +69,8 @@ function getQuizContextOptions(editor, theme) {
69
69
  return [
70
70
  new ComponentPickerOption("주관식 입력 칸", {
71
71
  icon: _jsx(InputMethodLineIcon, { color: theme.color.foreground.primary }),
72
- keywords: ["short answer", "short answer input", "주관식", "주관식 입력"],
73
- onSelect: () => editor.dispatchCommand(INSERT_SHORT_ANSWER_COMMAND, {
72
+ keywords: ["problem input", "주관식 입력"],
73
+ onSelect: () => editor.dispatchCommand(INSERT_PROBLEM_INPUT_COMMAND, {
74
74
  solutions: [
75
75
  {
76
76
  textType: "normal",
@@ -84,24 +84,17 @@ function getQuizContextOptions(editor, theme) {
84
84
  }),
85
85
  new ComponentPickerOption("객관식 입력 칸", {
86
86
  icon: _jsx(ListRadioIcon, { color: theme.color.foreground.primary }),
87
- keywords: [
88
- "multiple select",
89
- "multiple select input",
90
- "객관식",
91
- "객관식 입력",
92
- ],
87
+ keywords: ["problem select", "객관식 입력"],
93
88
  onSelect: () => {
94
89
  // TODO: 객관식 입력 칸 추가
95
90
  },
96
91
  }),
97
- new ComponentPickerOption("메뉴구분선", {
98
- component: (_jsx("div", { css: css `
92
+ new ComponentDrawerOption("메뉴구분선", (_jsx("div", { css: css `
99
93
  width: 100%;
100
94
  height: 1px;
101
95
  background: ${theme.color.background.neutralAltActive};
102
96
  margin: 4px 0;
103
- ` })),
104
- }),
97
+ ` }))),
105
98
  ];
106
99
  }
107
100
  export function getBaseOptions(editor, theme, setImageOpen) {
@@ -211,11 +204,15 @@ export function ComponentPickerMenuPlugin() {
211
204
  const regex = new RegExp(queryString, "i");
212
205
  return [
213
206
  ...getDynamicOptions(editor, queryString),
214
- ...baseOptions.filter((option) => regex.test(option.title) ||
215
- option.keywords.some((keyword) => regex.test(keyword))),
207
+ ...baseOptions.filter((option) => option instanceof ComponentPickerOption &&
208
+ (regex.test(option.title) ||
209
+ option.keywords.some((keyword) => regex.test(keyword)))),
216
210
  ];
217
211
  }, [editor, queryString, setOpen]);
218
212
  const onSelectOption = useCallback((selectedOption, nodeToRemove, closeMenu, matchingString) => {
213
+ if (selectedOption instanceof ComponentDrawerOption) {
214
+ return;
215
+ }
219
216
  editor.update(() => {
220
217
  nodeToRemove === null || nodeToRemove === void 0 ? void 0 : nodeToRemove.remove();
221
218
  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.6",
3
+ "version": "1.8.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,