@team-monolith/cds 1.11.0 → 1.11.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.
@@ -30,6 +30,10 @@ export function InputComponent(props) {
30
30
  const [editor] = useLexicalComposerContext();
31
31
  const [settingOpen, setSettingOpen] = useState(false);
32
32
  const isEditable = useLexicalEditable();
33
+ // SoT를 EditorState로 설정하는 경우 한글 입력시 문제가 생김.
34
+ // ex) "나비" 입력시 -> "ㄴ나납ㅂ비"
35
+ // SoT를 내부 State로 관리하고 EditorState를 따로 설정.
36
+ // 문제를 더 파악하고 추후 개선 필요.
33
37
  const [answerInput, setAnswerInput] = useState(answer);
34
38
  const lexicalCustomConfig = useContext(LexicalCustomConfigContext);
35
39
  // "** ***" 같은 형태입니다.
@@ -37,22 +41,18 @@ export function InputComponent(props) {
37
41
  // 학생 view
38
42
  if (!isEditable) {
39
43
  if (showCharacterCount) {
40
- return (_jsx(SegmentedInput, { readOnly: lexicalCustomConfig.freezeProblemNode, answerFormat: answerFormat, placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => {
41
- setAnswerInput(e.value);
44
+ return (_jsx(SegmentedInput, { readOnly: lexicalCustomConfig.freezeProblemNode, answerFormat: answerFormat, placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (value) => {
45
+ setAnswerInput(value);
42
46
  editor.update(() => {
43
47
  const node = $getNodeByKey(nodeKey);
44
48
  if (!$isProblemInputNode(node)) {
45
49
  return;
46
50
  }
47
- node.setAnswer(e.value);
51
+ node.setAnswer(value);
48
52
  });
49
53
  } }));
50
54
  }
51
55
  return (_jsx(TextInput, { readOnly: lexicalCustomConfig.freezeProblemNode, size: "small", color: "default", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => {
52
- // SoT를 EditorState로 설정하는 경우 한글 입력시 문제가 생김.
53
- // ex) "나비" 입력시 -> "ㄴ나납ㅂ비"
54
- // SoT를 내부 State로 관리하고 EditorState를 따로 설정.
55
- // 문제를 더 파악하고 추후 개선 필요.
56
56
  setAnswerInput(e.target.value);
57
57
  editor.update(() => {
58
58
  const node = $getNodeByKey(nodeKey);
@@ -1,12 +1,12 @@
1
- /// <reference types="react" />
2
1
  export interface SegmentedInputProps {
3
2
  readOnly: boolean;
4
3
  answerFormat: string;
5
4
  placeholder: string;
6
5
  value: string;
7
- onChange: (e: {
8
- value: string;
9
- event: React.ChangeEvent<HTMLInputElement>;
10
- }) => void;
6
+ onChange: (value: string) => void;
11
7
  }
8
+ /**
9
+ * SOT는 value이고, 그 value는 숨겨진 input에 작성됩니다.
10
+ * 그리고 그 value는 split되어 segmented input에 표시됩니다.
11
+ */
12
12
  export default function SegmentedInput(props: SegmentedInputProps): import("@emotion/react/jsx-runtime").JSX.Element;
@@ -4,51 +4,75 @@ import { css } from "@emotion/react";
4
4
  import styled from "@emotion/styled";
5
5
  import { InputBase } from "../../../../components/InputBase";
6
6
  import { useRef, useState } from "react";
7
- /** i번째 값을 value로 바꾸는 setState Wrapper 함수 */
8
- function updateFocus(value, i, setFocus) {
9
- setFocus((prevFocus) => {
10
- const newFocus = prevFocus.slice();
11
- newFocus[i] = value;
12
- return newFocus;
13
- });
7
+ function getDiffIndex(short, long) {
8
+ for (let i = 0; i < short.length; i++) {
9
+ if (short[i] !== long[i]) {
10
+ return i;
11
+ }
12
+ }
13
+ return short.length;
14
14
  }
15
+ /**
16
+ * SOT는 value이고, 그 value는 숨겨진 input에 작성됩니다.
17
+ * 그리고 그 value는 split되어 segmented input에 표시됩니다.
18
+ */
15
19
  export default function SegmentedInput(props) {
16
20
  const { readOnly, answerFormat, placeholder, value, onChange } = props;
17
21
  // "** ***" => ["*", "*", " ", "*", "*", "*"]
18
22
  const splitedFormat = answerFormat.split("");
19
- // "안녕 하세요" => ["",""," ","","",""]
20
- const splitedValues = value.split("");
21
- const [focus, setFocus] = useState([]);
22
- const refs = useRef([]);
23
- return (_jsxs(Container, { children: [_jsxs(InputWrapper, { children: [_jsx("div", Object.assign({ css: css `
23
+ // ["*", "*", " ", "*", "*", "*", " ", "*", "*"] => [2, 5]
24
+ const leftSpacedIndexs = splitedFormat.reduce((prev, curr, i) => (curr === " " ? [...prev, i - prev.length] : prev), []);
25
+ const despacedFormat = splitedFormat.filter((v) => v !== " ");
26
+ // "안녕 하세요" => ["안","녕","하","세","요"]
27
+ const splitedValues = value.split("").filter((v) => v !== " ");
28
+ const [focusedIndex, setFocusedIndex] = useState(null);
29
+ const hiddenRef = useRef(null);
30
+ return (_jsxs(Container, { children: [_jsx("input", { type: "input", ref: hiddenRef, value: splitedValues.join(""), readOnly: readOnly, onKeyDown: (e) => {
31
+ if (e.key === "ArrowLeft") {
32
+ if (focusedIndex === null || focusedIndex <= 0) {
33
+ return;
34
+ }
35
+ else {
36
+ setFocusedIndex(focusedIndex - 1);
37
+ }
38
+ }
39
+ else if (e.key === "ArrowRight") {
40
+ if (focusedIndex === null ||
41
+ focusedIndex >= despacedFormat.length - 1) {
42
+ return;
43
+ }
44
+ else {
45
+ setFocusedIndex(focusedIndex + 1);
46
+ }
47
+ }
48
+ }, onChange: (event) => {
49
+ const latestValue = splitedValues.join("");
50
+ const eventValue = event.target.value;
51
+ const diffIndex = getDiffIndex(latestValue, eventValue);
52
+ onChange(eventValue);
53
+ setFocusedIndex(diffIndex);
54
+ }, onBlur: (e) => {
55
+ setFocusedIndex(null);
56
+ const eventValue = e.target.value;
57
+ if (eventValue.length > despacedFormat.length) {
58
+ onChange(eventValue.slice(0, despacedFormat.length));
59
+ }
60
+ }, css: css `
61
+ opacity: 0;
62
+ height: 0;
63
+ ` }), _jsxs(InputWrapper, { children: [_jsx("div", Object.assign({ css: css `
24
64
  display: flex;
25
65
  align-items: center;
26
66
  gap: 4px;
27
- ` }, { children: splitedFormat.map((format, i) => format === " " ? (_jsx(Space, {}, i)) : (_jsx(SquareInput, { size: "small", color: focus[i] ? "activePrimary" : "default", inputRef: (element) => {
28
- refs.current[i] = element;
29
- }, inputProps: {
67
+ ` }, { children: despacedFormat.map((_format, i) => (_jsx(SquareInput, { leftSpaced: leftSpacedIndexs.includes(i), size: "small", color: focusedIndex === i ? "activePrimary" : "default", inputProps: {
30
68
  readOnly,
31
- onBlur: () => updateFocus(false, i, setFocus),
32
- onFocus: () => updateFocus(true, i, setFocus),
33
- onKeyDown: (e) => {
69
+ onFocus: () => {
34
70
  var _a, _b;
35
- // Backspace 키로 이전 input으로의 포커스 이동
36
- if (e.key === "Backspace" && !splitedValues[i] && i > 0) {
37
- if (splitedFormat[i - 1] === " ") {
38
- (_a = refs.current[i - 2]) === null || _a === void 0 ? void 0 : _a.focus();
39
- return;
40
- }
41
- (_b = refs.current[i - 1]) === null || _b === void 0 ? void 0 : _b.focus();
42
- }
71
+ setFocusedIndex(i);
72
+ (_a = hiddenRef.current) === null || _a === void 0 ? void 0 : _a.focus();
73
+ (_b = hiddenRef.current) === null || _b === void 0 ? void 0 : _b.setSelectionRange(i, i);
43
74
  },
44
- }, value: splitedValues[i] || "", onChange: (event) => {
45
- const eventValue = event.target.value;
46
- // 복붙으로 입력을 입력한 경우
47
- if (eventValue.length > 2) {
48
- onChange({ value: value + eventValue, event });
49
- return;
50
- }
51
- } }, i))) })), _jsx(InputMarker, {})] }), _jsx(Text, { children: placeholder })] }));
75
+ }, value: splitedValues[i] || "", onChange: () => { } }, i))) })), _jsx(InputMarker, {})] }), _jsx(Text, { children: placeholder })] }));
52
76
  }
53
77
  const Container = styled.div `
54
78
  display: flex;
@@ -64,18 +88,19 @@ const InputWrapper = styled.div `
64
88
  gap: 8px;
65
89
  margin: auto;
66
90
  `;
67
- const SquareInput = styled(InputBase) `
68
- width: 36px;
69
- height: 36px;
70
- padding-left: 0;
71
- padding-right: 0;
72
- input {
73
- text-align: center;
74
- }
75
- `;
76
- const Space = styled.div `
77
- width: 4px;
78
- `;
91
+ const SquareInput = styled(InputBase)(({ leftSpaced }) => css `
92
+ width: 36px;
93
+ height: 36px;
94
+ padding-left: 0;
95
+ padding-right: 0;
96
+ input {
97
+ text-align: center;
98
+ }
99
+ ${leftSpaced &&
100
+ css `
101
+ margin-left: 8px;
102
+ `}
103
+ `);
79
104
  const InputMarker = styled.div(({ theme }) => css `
80
105
  height: 8px;
81
106
  border: 1px solid ${theme.color.background.neutralAltActive};
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@team-monolith/cds",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": false,
7
- "dependencies": {
7
+ "dependencies": {
8
8
  "@emotion/css": "^11.11.2",
9
9
  "@emotion/react": "^11.8.2",
10
10
  "@emotion/styled": "^11.8.1",
@@ -16,7 +16,7 @@
16
16
  "hex-to-css-filter": "^5.4.0",
17
17
  "lexical": "^0.12.4",
18
18
  "react": "^18.2.0",
19
- "react-dom": "^18.2.0",
19
+ "react-dom": "^18.2.0",
20
20
  "react-hook-form": "^7.48.2",
21
21
  "remixicon": "^3.4.0",
22
22
  "typescript": "^4.5.5",